1556 lines
39 KiB
C
1556 lines
39 KiB
C
/*
|
|
* drivers/cpufreq/cpufreq_limit.c
|
|
*
|
|
* Remade according to cpufreq change
|
|
* (refer to commit df0eea4488081e0698b0b58ccd1e8c8823e22841
|
|
* 18c49926c4bf4915e5194d1de3299c0537229f9f)
|
|
*
|
|
* 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/err.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/platform_device.h>
|
|
#ifdef CONFIG_OF
|
|
#include <linux/of.h>
|
|
#endif
|
|
#include <trace/hooks/cpufreq.h>
|
|
|
|
#define MAX_BUF_SIZE 1024
|
|
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
|
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
|
|
|
/* adaptive boost from walt */
|
|
extern int cpufreq_walt_set_adaptive_freq(unsigned int cpu, unsigned int adaptive_low_freq,
|
|
unsigned int adaptive_high_freq);
|
|
extern int cpufreq_walt_get_adaptive_freq(unsigned int cpu, unsigned int *adaptive_low_freq,
|
|
unsigned int *adaptive_high_freq);
|
|
extern int cpufreq_walt_reset_adaptive_freq(unsigned int cpu);
|
|
|
|
static unsigned int __read_mostly lpcharge;
|
|
module_param(lpcharge, uint, 0444);
|
|
|
|
/* voltage based freq table */
|
|
#if IS_ENABLED(CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE)
|
|
extern struct freq_voltage_base cflm_vbf;
|
|
#else
|
|
static struct freq_voltage_base cflm_vbf;
|
|
#endif
|
|
|
|
static DEFINE_MUTEX(cflm_mutex);
|
|
#define LIMIT_RELEASE -1
|
|
|
|
/* boosted state */
|
|
#define BOOSTED 1
|
|
#define NOT_BOOSTED 0
|
|
|
|
#define NUM_CPUS 8
|
|
static unsigned int cflm_req_init[NUM_CPUS];
|
|
static struct freq_qos_request max_req[NUM_CPUS][CFLM_MAX_ITEM];
|
|
static struct freq_qos_request min_req[NUM_CPUS][CFLM_MAX_ITEM];
|
|
static struct kobject *cflm_kobj;
|
|
|
|
struct freq_map {
|
|
unsigned int in;
|
|
unsigned int out;
|
|
};
|
|
|
|
/* adaptive boost threshold - high - low freq table */
|
|
struct aboost_th_table {
|
|
int threshold;
|
|
int high;
|
|
int low;
|
|
};
|
|
|
|
/* input info: freq, time(TBD) */
|
|
struct input_info {
|
|
int boosted;
|
|
int min;
|
|
int max;
|
|
u64 time_in_min_limit;
|
|
u64 time_in_max_limit;
|
|
u64 time_in_over_limit;
|
|
ktime_t last_min_limit_time;
|
|
ktime_t last_max_limit_time;
|
|
ktime_t last_over_limit_time;
|
|
};
|
|
static struct input_info freq_input[CFLM_MAX_ITEM];
|
|
|
|
struct cflm_parameter {
|
|
/* to make virtual freq table */
|
|
struct cpufreq_frequency_table *cpuftbl_L;
|
|
struct cpufreq_frequency_table *cpuftbl_b;
|
|
unsigned int unified_cpuftbl[50];
|
|
unsigned int freq_count;
|
|
bool table_initialized;
|
|
|
|
/* cpu info: silver/gold/prime */
|
|
unsigned int s_first;
|
|
unsigned int s_fmin;
|
|
unsigned int s_fmax;
|
|
unsigned int g_first;
|
|
unsigned int g_fmin;
|
|
unsigned int g_fmax;
|
|
unsigned int p_first;
|
|
unsigned int p_fmin;
|
|
unsigned int p_fmax;
|
|
|
|
/* 4 policy arch: sm8650, titanium, same clock table as gold */
|
|
bool titanium;
|
|
unsigned int t_first;
|
|
unsigned int t_fmin;
|
|
unsigned int t_fmax;
|
|
|
|
/* exceptional case */
|
|
unsigned int g_fmin_up; /* fixed gold clock for performance */
|
|
|
|
/* in virtual table little(silver)/big(gold & prime) */
|
|
unsigned int big_min_freq;
|
|
unsigned int big_max_freq;
|
|
unsigned int ltl_min_freq;
|
|
unsigned int ltl_max_freq;
|
|
|
|
/* pre-defined value */
|
|
struct freq_map *silver_boost_map;
|
|
unsigned int boost_map_size;
|
|
struct freq_map *silver_limit_map;
|
|
unsigned int limit_map_size;
|
|
unsigned int silver_divider;
|
|
|
|
/* current freq in virtual table */
|
|
unsigned int min_limit_val;
|
|
unsigned int max_limit_val;
|
|
|
|
/* sched boost type */
|
|
int sched_boost_type;
|
|
bool sched_boost_cond;
|
|
bool sched_boost_enabled;
|
|
|
|
/* over limit */
|
|
unsigned int over_limit;
|
|
|
|
/* voltage based clock */
|
|
bool vol_based_clk;
|
|
int vbf_offset; /* gold clock offset for perf */
|
|
|
|
/* adaptive boost */
|
|
bool ab_enabled;
|
|
struct aboost_th_table *ab_table;
|
|
};
|
|
|
|
|
|
/* TODO: move to dtsi? */
|
|
static struct cflm_parameter param = {
|
|
.freq_count = 0,
|
|
.table_initialized = false,
|
|
|
|
.s_first = 0,
|
|
.g_first = 2,
|
|
.p_first = 7,
|
|
|
|
.titanium = 0,
|
|
.t_first = 5,
|
|
|
|
.g_fmin_up = 0, /* fixed gold clock for performance */
|
|
|
|
.ltl_min_freq = 0, /* will be auto updated */
|
|
.ltl_max_freq = 0, /* will be auto updated */
|
|
.big_min_freq = 0, /* will be auto updated */
|
|
.big_max_freq = 0, /* will be auto updated */
|
|
|
|
.boost_map_size = 0,
|
|
.limit_map_size = 0,
|
|
.silver_divider = 2,
|
|
|
|
.min_limit_val = -1,
|
|
.max_limit_val = -1,
|
|
|
|
.sched_boost_type = CONSERVATIVE_BOOST,
|
|
.sched_boost_cond = false,
|
|
.sched_boost_enabled = false,
|
|
|
|
.over_limit = 0,
|
|
|
|
.vol_based_clk = false,
|
|
};
|
|
|
|
static bool cflm_make_table(void)
|
|
{
|
|
int i, count = 0;
|
|
int freq_count = 0;
|
|
unsigned int freq;
|
|
bool ret = false;
|
|
|
|
/* big cluster table */
|
|
if (!param.cpuftbl_b)
|
|
goto little;
|
|
|
|
for (i = 0; param.cpuftbl_b[i].frequency != CPUFREQ_TABLE_END; i++)
|
|
count = i;
|
|
|
|
for (i = count; i >= 0; i--) {
|
|
freq = param.cpuftbl_b[i].frequency;
|
|
|
|
if (freq == CPUFREQ_ENTRY_INVALID)
|
|
continue;
|
|
|
|
if (freq < param.big_min_freq ||
|
|
freq > param.big_max_freq)
|
|
continue;
|
|
|
|
param.unified_cpuftbl[freq_count++] = freq;
|
|
}
|
|
|
|
little:
|
|
/* LITTLE cluster table */
|
|
if (!param.cpuftbl_L)
|
|
goto done;
|
|
|
|
for (i = 0; param.cpuftbl_L[i].frequency != CPUFREQ_TABLE_END; i++)
|
|
count = i;
|
|
|
|
for (i = count; i >= 0; i--) {
|
|
freq = param.cpuftbl_L[i].frequency / param.silver_divider;
|
|
|
|
if (freq == CPUFREQ_ENTRY_INVALID)
|
|
continue;
|
|
|
|
if (freq < param.ltl_min_freq ||
|
|
freq > param.ltl_max_freq)
|
|
continue;
|
|
|
|
param.unified_cpuftbl[freq_count++] = freq;
|
|
}
|
|
|
|
done:
|
|
if (freq_count) {
|
|
pr_debug("%s: unified table is made\n", __func__);
|
|
param.freq_count = freq_count;
|
|
ret = true;
|
|
} else {
|
|
pr_err("%s: cannot make unified table\n", __func__);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* cflm_set_table - cpufreq table from dt via qcom-cpufreq
|
|
*/
|
|
static void cflm_set_table(int cpu, struct cpufreq_frequency_table *ftbl)
|
|
{
|
|
int i, count = 0;
|
|
unsigned int max_freq_b = 0, min_freq_b = UINT_MAX;
|
|
unsigned int max_freq_l = 0, min_freq_l = UINT_MAX;
|
|
|
|
if (param.table_initialized)
|
|
return;
|
|
|
|
if (cpu == param.s_first)
|
|
param.cpuftbl_L = ftbl;
|
|
else if (cpu == param.p_first)
|
|
param.cpuftbl_b = ftbl;
|
|
|
|
if (!param.cpuftbl_L)
|
|
return;
|
|
|
|
if (!param.cpuftbl_b)
|
|
return;
|
|
|
|
pr_info("%s: freq table is ready, update config\n", __func__);
|
|
|
|
/* update little config */
|
|
for (i = 0; param.cpuftbl_L[i].frequency != CPUFREQ_TABLE_END; i++)
|
|
count = i;
|
|
|
|
for (i = count; i >= 0; i--) {
|
|
if (param.cpuftbl_L[i].frequency == CPUFREQ_ENTRY_INVALID)
|
|
continue;
|
|
|
|
if (param.cpuftbl_L[i].frequency < min_freq_l)
|
|
min_freq_l = param.cpuftbl_L[i].frequency;
|
|
|
|
if (param.cpuftbl_L[i].frequency > max_freq_l)
|
|
max_freq_l = param.cpuftbl_L[i].frequency;
|
|
}
|
|
|
|
if (!param.ltl_min_freq)
|
|
param.ltl_min_freq = min_freq_l / param.silver_divider;
|
|
if (!param.ltl_max_freq)
|
|
param.ltl_max_freq = max_freq_l / param.silver_divider;
|
|
|
|
/* update big config */
|
|
for (i = 0; param.cpuftbl_b[i].frequency != CPUFREQ_TABLE_END; i++)
|
|
count = i;
|
|
|
|
for (i = count; i >= 0; i--) {
|
|
if (param.cpuftbl_b[i].frequency == CPUFREQ_ENTRY_INVALID)
|
|
continue;
|
|
|
|
if ((param.cpuftbl_b[i].frequency < min_freq_b) &&
|
|
(param.cpuftbl_b[i].frequency > param.ltl_max_freq))
|
|
min_freq_b = param.cpuftbl_b[i].frequency;
|
|
|
|
if (param.cpuftbl_b[i].frequency > max_freq_b)
|
|
max_freq_b = param.cpuftbl_b[i].frequency;
|
|
}
|
|
|
|
if (!param.big_min_freq)
|
|
param.big_min_freq = min_freq_b;
|
|
if (!param.big_max_freq)
|
|
param.big_max_freq = max_freq_b;
|
|
|
|
pr_info("%s: updated: little(%u-%u), big(%u-%u)\n", __func__,
|
|
param.ltl_min_freq, param.ltl_max_freq,
|
|
param.big_min_freq, param.big_max_freq);
|
|
|
|
param.table_initialized = cflm_make_table();
|
|
}
|
|
|
|
/**
|
|
* cflm_get_table - fill the cpufreq table to support HMP
|
|
* @buf a buf that has been requested to fill the cpufreq table
|
|
*/
|
|
static ssize_t cflm_get_table(char *buf)
|
|
{
|
|
ssize_t len = 0;
|
|
int i = 0;
|
|
|
|
if (!param.freq_count)
|
|
return len;
|
|
|
|
for (i = 0; i < param.freq_count; i++)
|
|
len += snprintf(buf + len, MAX_BUF_SIZE, "%u ",
|
|
param.unified_cpuftbl[i]);
|
|
|
|
len--;
|
|
len += snprintf(buf + len, MAX_BUF_SIZE, "\n");
|
|
|
|
pr_info("%s: %s\n", __func__, buf);
|
|
|
|
return len;
|
|
}
|
|
|
|
static void cflm_update_boost(void)
|
|
{
|
|
int i;
|
|
bool boost_condition = false;
|
|
|
|
/* sched boost */
|
|
param.sched_boost_cond = false;
|
|
for (i = 0; i < CFLM_MAX_ITEM; i++) {
|
|
if (freq_input[i].min > (int)param.ltl_max_freq) {
|
|
param.sched_boost_cond = true;
|
|
boost_condition = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (boost_condition) {
|
|
if (!param.sched_boost_enabled) {
|
|
pr_debug("%s: sched boost on, type(%d)\n", __func__, param.sched_boost_type);
|
|
sched_set_boost(param.sched_boost_type);
|
|
param.sched_boost_enabled = true;
|
|
} else {
|
|
pr_debug("%s: sched boost already on, do nothing\n", __func__);
|
|
}
|
|
} else {
|
|
if (param.sched_boost_enabled) {
|
|
pr_debug("%s: sched boost off(%d)\n", __func__, (param.sched_boost_type * -1));
|
|
sched_set_boost(param.sched_boost_type * -1);
|
|
param.sched_boost_enabled = false;
|
|
} else {
|
|
pr_debug("%s: sched boost already off, do nothing\n", __func__);
|
|
}
|
|
}
|
|
}
|
|
|
|
static s32 cflm_freq_qos_read_value(struct freq_constraints *qos,
|
|
enum freq_qos_req_type type)
|
|
{
|
|
s32 ret;
|
|
|
|
switch (type) {
|
|
case FREQ_QOS_MIN:
|
|
ret = IS_ERR_OR_NULL(qos) ?
|
|
FREQ_QOS_MIN_DEFAULT_VALUE :
|
|
READ_ONCE(qos->min_freq.target_value);
|
|
break;
|
|
case FREQ_QOS_MAX:
|
|
ret = IS_ERR_OR_NULL(qos) ?
|
|
FREQ_QOS_MAX_DEFAULT_VALUE :
|
|
READ_ONCE(qos->max_freq.target_value);
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void cflm_current_qos(void)
|
|
{
|
|
struct cpufreq_policy *policy;
|
|
int s_min = 0, s_max = 0;
|
|
int g_min = 0, g_max = 0;
|
|
int p_min = 0, p_max = 0;
|
|
int t_min = 0, t_max = 0;
|
|
unsigned int a_low = 0, a_high = 0;
|
|
|
|
policy = cpufreq_cpu_get(param.s_first);
|
|
if (policy) {
|
|
s_min = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MIN);
|
|
s_max = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MAX);
|
|
cpufreq_cpu_put(policy);
|
|
}
|
|
|
|
if (param.ab_enabled) {
|
|
cpufreq_walt_get_adaptive_freq(param.s_first, &a_low, &a_high);
|
|
pr_cont("%s: s[%d(%d, %d)-%d]", __func__, s_min, a_low, a_high, s_max);
|
|
} else {
|
|
pr_cont("%s: s[%d-%d]", __func__, s_min, s_max);
|
|
}
|
|
|
|
|
|
policy = cpufreq_cpu_get(param.g_first);
|
|
if (policy) {
|
|
g_min = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MIN);
|
|
g_max = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MAX);
|
|
cpufreq_cpu_put(policy);
|
|
}
|
|
|
|
if (param.ab_enabled) {
|
|
cpufreq_walt_get_adaptive_freq(param.g_first, &a_low, &a_high);
|
|
pr_cont(", g[%d(%d, %d)-%d]", g_min, a_low, a_high, g_max);
|
|
} else {
|
|
pr_cont(", g[%d-%d]", g_min, g_max);
|
|
}
|
|
|
|
|
|
/* now, not use adaptive boost for titanium and prime */
|
|
if (param.titanium) {
|
|
policy = cpufreq_cpu_get(param.t_first);
|
|
if (policy) {
|
|
t_min = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MIN);
|
|
t_max = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MAX);
|
|
cpufreq_cpu_put(policy);
|
|
}
|
|
|
|
pr_cont(", t[%d-%d]", t_min, t_max);
|
|
}
|
|
|
|
|
|
policy = cpufreq_cpu_get(param.p_first);
|
|
if (policy) {
|
|
p_min = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MIN);
|
|
p_max = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MAX);
|
|
cpufreq_cpu_put(policy);
|
|
}
|
|
|
|
pr_cont(", p[%d-%d]", p_min, p_max);
|
|
|
|
pr_cont("\n");
|
|
}
|
|
|
|
static bool cflm_max_lock_need_restore(void)
|
|
{
|
|
if ((int)param.over_limit <= 0)
|
|
return false;
|
|
|
|
if (freq_input[CFLM_USERSPACE].min > 0) {
|
|
if (freq_input[CFLM_USERSPACE].min > (int)param.ltl_max_freq) {
|
|
pr_debug("%s: userspace minlock (%d) > ltl max (%d)\n",
|
|
__func__, freq_input[CFLM_USERSPACE].min, param.ltl_max_freq);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (freq_input[CFLM_TOUCH].min > 0) {
|
|
if (freq_input[CFLM_TOUCH].min > (int)param.ltl_max_freq) {
|
|
pr_debug("%s: touch minlock (%d) > ltl max (%d)\n",
|
|
__func__, freq_input[CFLM_TOUCH].min, param.ltl_max_freq);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool cflm_high_pri_min_lock_required(void)
|
|
{
|
|
if ((int)param.over_limit <= 0)
|
|
return false;
|
|
|
|
if (freq_input[CFLM_USERSPACE].min > 0) {
|
|
if (freq_input[CFLM_USERSPACE].min > (int)param.ltl_max_freq) {
|
|
pr_debug("%s: userspace minlock (%d) > ltl max (%d)\n",
|
|
__func__, freq_input[CFLM_USERSPACE].min, param.ltl_max_freq);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (freq_input[CFLM_TOUCH].min > 0) {
|
|
if (freq_input[CFLM_TOUCH].min > (int)param.ltl_max_freq) {
|
|
pr_debug("%s: touch minlock (%d) > ltl max (%d)\n",
|
|
__func__, freq_input[CFLM_TOUCH].min, param.ltl_max_freq);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static unsigned int cflm_get_vol_matched_freq(unsigned int in_freq)
|
|
{
|
|
int i;
|
|
unsigned int out_freq = in_freq;
|
|
|
|
if (param.vbf_offset > cflm_vbf.count || param.vbf_offset < 0) {
|
|
pr_err("%s: bad condition(off(%d), cnt(%d))",
|
|
__func__, param.vbf_offset, cflm_vbf.count);
|
|
return out_freq;
|
|
}
|
|
|
|
/* start from offset */
|
|
for (i = param.vbf_offset; i < cflm_vbf.count; i++) {
|
|
if (cflm_vbf.table[PRIME_CPU][i] <= in_freq) {
|
|
out_freq = cflm_vbf.table[GOLD_CPU][i - param.vbf_offset];
|
|
break;
|
|
}
|
|
}
|
|
|
|
pr_debug("%s: in(%d), out(%d)\n", __func__, in_freq, out_freq);
|
|
|
|
return out_freq;
|
|
}
|
|
|
|
static int cflm_get_silver_boost(int freq)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < param.boost_map_size; i++)
|
|
if (freq >= param.silver_boost_map[i].in)
|
|
return param.silver_boost_map[i].out;
|
|
return freq * param.silver_divider;
|
|
}
|
|
|
|
static int cflm_get_silver_limit(int freq)
|
|
{
|
|
int i;
|
|
|
|
/* prime limit condition */
|
|
for (i = 0; i < param.limit_map_size; i++)
|
|
if (freq >= param.silver_limit_map[i].in)
|
|
return MIN(param.silver_limit_map[i].out, param.s_fmax);
|
|
|
|
/* silver limit condition */
|
|
return freq * param.silver_divider;
|
|
}
|
|
|
|
static int cflm_adaptive_boost(int first_cpu, int min)
|
|
{
|
|
struct cpufreq_policy *policy;
|
|
int cpu = 0;
|
|
int ret = 0;
|
|
int aboost_low;
|
|
|
|
pr_debug("%s: cpu%d: %d\n", __func__, first_cpu, min);
|
|
|
|
if (!param.ab_enabled)
|
|
return -EINVAL;
|
|
|
|
if (!param.ab_table)
|
|
return -EINVAL;
|
|
|
|
if (!param.ab_table[first_cpu].threshold)
|
|
return -EINVAL;
|
|
|
|
policy = cpufreq_cpu_get(first_cpu);
|
|
if (!policy) {
|
|
pr_err("%s: no policy for cpu%d\n", __func__, first_cpu);
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (strcmp((policy->governor->name), "walt")) {
|
|
pr_err("%s: not supported gov(%s)\n", __func__, policy->governor->name);
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (min >= param.ab_table[first_cpu].threshold)
|
|
aboost_low = param.ab_table[first_cpu].high;
|
|
else
|
|
aboost_low = param.ab_table[first_cpu].low;
|
|
|
|
if ((min > 0) && (min < aboost_low)) {
|
|
pr_err("%s: cpu%d boost min(%d) is lower than adaptive low(%d)\n",
|
|
__func__, first_cpu, min, aboost_low);
|
|
aboost_low = min;
|
|
}
|
|
|
|
for_each_cpu(cpu, policy->related_cpus) {
|
|
if (min > 0) {
|
|
pr_debug("%s: set aboost: cpu%d: %d, %d\n", __func__, cpu, aboost_low, min);
|
|
ret = cpufreq_walt_set_adaptive_freq(cpu, aboost_low, min);
|
|
} else {
|
|
pr_debug("%s: clear aboost: cpu%d\n", __func__, cpu);
|
|
ret = cpufreq_walt_reset_adaptive_freq(cpu);
|
|
}
|
|
}
|
|
|
|
cpufreq_cpu_put(policy);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void cflm_freq_decision(int type, int new_min, int new_max)
|
|
{
|
|
int cpu = 0;
|
|
int s_min = param.s_fmin;
|
|
int s_max = param.s_fmax;
|
|
int g_min = param.g_fmin;
|
|
int g_max = param.g_fmax;
|
|
int p_min = param.p_fmin;
|
|
int p_max = param.p_fmax;
|
|
int t_max = param.t_fmax;
|
|
|
|
bool need_update_user_max = false;
|
|
int new_user_max = FREQ_QOS_MAX_DEFAULT_VALUE;
|
|
|
|
pr_info("%s: input: type(%d), min(%d), max(%d)\n",
|
|
__func__, type, new_min, new_max);
|
|
|
|
/* update input freq */
|
|
if (new_min != 0) {
|
|
freq_input[type].min = new_min;
|
|
if ((new_min == LIMIT_RELEASE || new_min == param.ltl_min_freq) &&
|
|
freq_input[type].last_min_limit_time != 0) {
|
|
freq_input[type].time_in_min_limit += ktime_to_ms(ktime_get()-
|
|
freq_input[type].last_min_limit_time);
|
|
freq_input[type].last_min_limit_time = 0;
|
|
freq_input[type].boosted = NOT_BOOSTED;
|
|
pr_debug("%s: type(%d), released(%d)\n", __func__, type, freq_input[type].boosted);
|
|
}
|
|
if (new_min != LIMIT_RELEASE && new_min != param.ltl_min_freq &&
|
|
freq_input[type].last_min_limit_time == 0) {
|
|
freq_input[type].last_min_limit_time = ktime_get();
|
|
freq_input[type].boosted = BOOSTED;
|
|
pr_debug("%s: type(%d), boosted(%d)\n", __func__, type, freq_input[type].boosted);
|
|
}
|
|
}
|
|
|
|
if (new_max != 0) {
|
|
freq_input[type].max = new_max;
|
|
if ((new_max == LIMIT_RELEASE || new_max == param.big_max_freq) &&
|
|
freq_input[type].last_max_limit_time != 0) {
|
|
freq_input[type].time_in_max_limit += ktime_to_ms(ktime_get() -
|
|
freq_input[type].last_max_limit_time);
|
|
freq_input[type].last_max_limit_time = 0;
|
|
}
|
|
if (new_max != LIMIT_RELEASE && new_max != param.big_max_freq &&
|
|
freq_input[type].last_max_limit_time == 0) {
|
|
freq_input[type].last_max_limit_time = ktime_get();
|
|
}
|
|
}
|
|
|
|
if (new_min > 0) {
|
|
if (new_min < param.ltl_min_freq) {
|
|
pr_err("%s: too low freq(%d), set to %d\n",
|
|
__func__, new_min, param.ltl_min_freq);
|
|
new_min = param.ltl_min_freq;
|
|
}
|
|
|
|
pr_debug("%s: new_min=%d, ltl_max=%d, over_limit=%d\n", __func__,
|
|
new_min, param.ltl_max_freq, param.over_limit);
|
|
if ((type == CFLM_USERSPACE || type == CFLM_TOUCH) &&
|
|
cflm_high_pri_min_lock_required()) {
|
|
if (freq_input[CFLM_USERSPACE].max > 0) {
|
|
need_update_user_max = true;
|
|
new_user_max = MAX((int)param.over_limit, freq_input[CFLM_USERSPACE].max);
|
|
pr_debug("%s: override new_max %d => %d, userspace_min=%d, touch_min=%d, ltl_max=%d\n",
|
|
__func__, freq_input[CFLM_USERSPACE].max, new_user_max, freq_input[CFLM_USERSPACE].min,
|
|
freq_input[CFLM_TOUCH].min, param.ltl_max_freq);
|
|
}
|
|
}
|
|
|
|
/* boost @gold/prime */
|
|
s_min = cflm_get_silver_boost(new_min);
|
|
if (new_min > param.ltl_max_freq) {
|
|
g_min = MIN(new_min, param.g_fmax);
|
|
p_min = MIN(new_min, param.p_fmax);
|
|
} else {
|
|
g_min = param.g_fmin;
|
|
p_min = param.p_fmin;
|
|
}
|
|
|
|
if (cflm_adaptive_boost(param.s_first, s_min) < 0)
|
|
freq_qos_update_request(&min_req[param.s_first][type], s_min); /* prevent adaptive boost fail */
|
|
|
|
if (cflm_adaptive_boost(param.g_first, g_min) < 0)
|
|
freq_qos_update_request(&min_req[param.g_first][type], g_min);
|
|
|
|
freq_qos_update_request(&min_req[param.p_first][type], p_min);
|
|
/* TEMP??: no boost for titanium
|
|
*if (param.titanium)
|
|
* freq_qos_update_request(&min_req[param.t_first][type], g_min);
|
|
*/
|
|
} else if (new_min == LIMIT_RELEASE) {
|
|
for_each_possible_cpu(cpu) {
|
|
freq_qos_update_request(&min_req[cpu][type],
|
|
FREQ_QOS_MIN_DEFAULT_VALUE);
|
|
}
|
|
|
|
if (param.ab_enabled) {
|
|
int i;
|
|
int aggr_state = 0;
|
|
|
|
for (i = 0; i < CFLM_MAX_ITEM; i++)
|
|
aggr_state += freq_input[i].boosted;
|
|
|
|
if (aggr_state == 0) {
|
|
cflm_adaptive_boost(param.s_first, 0);
|
|
cflm_adaptive_boost(param.g_first, 0);
|
|
pr_debug("%s: aboost: clear\n", __func__);
|
|
}
|
|
}
|
|
|
|
if ((type == CFLM_USERSPACE || type == CFLM_TOUCH) &&
|
|
cflm_max_lock_need_restore()) { // if there is no high priority min lock and over limit is set
|
|
if (freq_input[CFLM_USERSPACE].max > 0) {
|
|
need_update_user_max = true;
|
|
new_user_max = freq_input[CFLM_USERSPACE].max;
|
|
pr_debug("%s: restore new_max => %d\n",
|
|
__func__, new_user_max);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (new_max > 0) {
|
|
if (new_max > param.big_max_freq) {
|
|
pr_err("%s: too high freq(%d), set to %d\n",
|
|
__func__, new_max, param.big_max_freq);
|
|
new_max = param.big_max_freq;
|
|
}
|
|
|
|
if ((type == CFLM_USERSPACE) && // if userspace maxlock is being set
|
|
cflm_high_pri_min_lock_required()) {
|
|
need_update_user_max = true;
|
|
new_user_max = MAX((int)param.over_limit, freq_input[CFLM_USERSPACE].max);
|
|
pr_debug("%s: force up new_max %d => %d, userspace_min=%d, touch_min=%d, ltl_max=%d\n",
|
|
__func__, new_max, new_user_max, freq_input[CFLM_USERSPACE].min,
|
|
freq_input[CFLM_TOUCH].min, param.ltl_max_freq);
|
|
}
|
|
|
|
s_max = cflm_get_silver_limit(new_max);
|
|
if (new_max < param.big_min_freq) {
|
|
/* if silver clock is limited as fmax,
|
|
* set promised clock for gold cluster
|
|
*/
|
|
if ((new_max == param.s_fmax / param.silver_divider) && (param.g_fmin_up > 0)) {
|
|
g_max = param.g_fmin_up;
|
|
t_max = param.g_fmin_up;
|
|
} else {
|
|
g_max = param.g_fmin;
|
|
t_max = param.t_fmin;
|
|
}
|
|
|
|
p_max = param.p_fmin;
|
|
} else {
|
|
p_max = MIN(new_max, param.p_fmax);
|
|
g_max = MIN(new_max, param.g_fmax);
|
|
if (param.vol_based_clk == true && cflm_vbf.count > 0)
|
|
t_max = MIN(cflm_get_vol_matched_freq(p_max), param.t_fmax);
|
|
else
|
|
t_max = MIN(new_max, param.t_fmax);
|
|
}
|
|
|
|
freq_qos_update_request(&max_req[param.s_first][type], s_max);
|
|
freq_qos_update_request(&max_req[param.g_first][type], g_max);
|
|
freq_qos_update_request(&max_req[param.p_first][type], p_max);
|
|
if (param.titanium)
|
|
freq_qos_update_request(&max_req[param.t_first][type], t_max);
|
|
} else if (new_max == LIMIT_RELEASE) {
|
|
for_each_possible_cpu(cpu)
|
|
freq_qos_update_request(&max_req[cpu][type],
|
|
FREQ_QOS_MAX_DEFAULT_VALUE);
|
|
}
|
|
|
|
if ((freq_input[type].min <= (int)param.ltl_max_freq || new_user_max != (int)param.over_limit) &&
|
|
freq_input[type].last_over_limit_time != 0) {
|
|
freq_input[type].time_in_over_limit += ktime_to_ms(ktime_get() -
|
|
freq_input[type].last_over_limit_time);
|
|
freq_input[type].last_over_limit_time = 0;
|
|
}
|
|
if (freq_input[type].min > (int)param.ltl_max_freq && new_user_max == (int)param.over_limit &&
|
|
freq_input[type].last_over_limit_time == 0) {
|
|
freq_input[type].last_over_limit_time = ktime_get();
|
|
}
|
|
|
|
if (need_update_user_max) {
|
|
pr_debug("%s: update_user_max is true\n", __func__);
|
|
if (new_user_max > param.big_max_freq) {
|
|
pr_debug("%s: too high freq(%d), set to %d\n",
|
|
__func__, new_user_max, param.big_max_freq);
|
|
new_user_max = param.big_max_freq;
|
|
}
|
|
|
|
s_max = cflm_get_silver_limit(new_user_max);
|
|
if (new_user_max < param.big_min_freq) {
|
|
/* if silver clock is limited as fmax,
|
|
* set promised clock for gold cluster
|
|
*/
|
|
if ((new_user_max == param.s_fmax / param.silver_divider) && (param.g_fmin_up > 0)) {
|
|
g_max = param.g_fmin_up;
|
|
t_max = param.g_fmin_up; /* use same freq with gold */
|
|
} else {
|
|
g_max = param.g_fmin;
|
|
t_max = param.t_fmin;
|
|
}
|
|
|
|
p_max = param.p_fmin;
|
|
} else {
|
|
p_max = MIN(new_user_max, param.p_fmax);
|
|
g_max = MIN(new_user_max, param.g_fmax);
|
|
if (param.vol_based_clk == true && cflm_vbf.count > 0)
|
|
t_max = MIN(cflm_get_vol_matched_freq(p_max), param.t_fmax);
|
|
else
|
|
t_max = MIN(new_user_max, param.t_fmax);
|
|
}
|
|
|
|
pr_info("%s: freq_update_request : new userspace max %d %d %d %d\n", __func__, s_max, g_max, t_max, p_max);
|
|
freq_qos_update_request(&max_req[param.s_first][CFLM_USERSPACE], s_max);
|
|
freq_qos_update_request(&max_req[param.g_first][CFLM_USERSPACE], g_max);
|
|
freq_qos_update_request(&max_req[param.p_first][CFLM_USERSPACE], p_max);
|
|
if (param.titanium)
|
|
freq_qos_update_request(&max_req[param.t_first][CFLM_USERSPACE], g_max);
|
|
}
|
|
|
|
cflm_update_boost();
|
|
|
|
cflm_current_qos();
|
|
}
|
|
|
|
static ssize_t cpufreq_table_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
ssize_t len = 0;
|
|
|
|
len = cflm_get_table(buf);
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t cpufreq_max_limit_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return snprintf(buf, MAX_BUF_SIZE, "%d\n", param.max_limit_val);
|
|
}
|
|
|
|
static ssize_t cpufreq_max_limit_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t n)
|
|
{
|
|
int freq;
|
|
int ret = -EINVAL;
|
|
|
|
ret = kstrtoint(buf, 10, &freq);
|
|
if (ret < 0) {
|
|
pr_err("%s: cflm: Invalid cpufreq format\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
mutex_lock(&cflm_mutex);
|
|
|
|
param.max_limit_val = freq;
|
|
cflm_freq_decision(CFLM_USERSPACE, 0, freq);
|
|
|
|
mutex_unlock(&cflm_mutex);
|
|
ret = n;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t cpufreq_min_limit_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return snprintf(buf, MAX_BUF_SIZE, "%d\n", param.min_limit_val);
|
|
}
|
|
|
|
static ssize_t cpufreq_min_limit_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t n)
|
|
{
|
|
int freq;
|
|
int ret = -EINVAL;
|
|
|
|
ret = kstrtoint(buf, 10, &freq);
|
|
if (ret < 0) {
|
|
pr_err("%s: cflm: Invalid cpufreq format\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
mutex_lock(&cflm_mutex);
|
|
|
|
cflm_freq_decision(CFLM_USERSPACE, freq, 0);
|
|
param.min_limit_val = freq;
|
|
|
|
mutex_unlock(&cflm_mutex);
|
|
ret = n;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t over_limit_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return snprintf(buf, MAX_BUF_SIZE, "%d\n", param.over_limit);
|
|
}
|
|
|
|
static ssize_t over_limit_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t n)
|
|
{
|
|
int freq;
|
|
int ret = -EINVAL;
|
|
|
|
ret = kstrtoint(buf, 10, &freq);
|
|
if (ret < 0) {
|
|
pr_err("%s: cflm: Invalid cpufreq format\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
mutex_lock(&cflm_mutex);
|
|
|
|
if (param.over_limit != freq) {
|
|
param.over_limit = freq;
|
|
if ((int)param.max_limit_val > 0)
|
|
cflm_freq_decision(CFLM_USERSPACE, 0, param.max_limit_val);
|
|
}
|
|
|
|
mutex_unlock(&cflm_mutex);
|
|
ret = n;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t limit_stat_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
|
|
ssize_t len = 0;
|
|
int i, j = 0;
|
|
|
|
mutex_lock(&cflm_mutex);
|
|
for (i = 0; i < CFLM_MAX_ITEM; i++) {
|
|
if (freq_input[i].last_min_limit_time != 0) {
|
|
freq_input[i].time_in_min_limit += ktime_to_ms(ktime_get() -
|
|
freq_input[i].last_min_limit_time);
|
|
freq_input[i].last_min_limit_time = ktime_get();
|
|
}
|
|
|
|
if (freq_input[i].last_max_limit_time != 0) {
|
|
freq_input[i].time_in_max_limit += ktime_to_ms(ktime_get() -
|
|
freq_input[i].last_max_limit_time);
|
|
freq_input[i].last_max_limit_time = ktime_get();
|
|
}
|
|
|
|
if (freq_input[i].last_over_limit_time != 0) {
|
|
freq_input[i].time_in_over_limit += ktime_to_ms(ktime_get() -
|
|
freq_input[i].last_over_limit_time);
|
|
freq_input[i].last_over_limit_time = ktime_get();
|
|
}
|
|
}
|
|
|
|
for (j = 0; j < CFLM_MAX_ITEM; j++) {
|
|
len += snprintf(buf + len, MAX_BUF_SIZE - len, "%llu %llu %llu\n",
|
|
freq_input[j].time_in_min_limit, freq_input[j].time_in_max_limit,
|
|
freq_input[j].time_in_over_limit);
|
|
}
|
|
|
|
mutex_unlock(&cflm_mutex);
|
|
return len;
|
|
}
|
|
|
|
static unsigned int cflm_get_table_freq(struct cpufreq_policy *policy,
|
|
unsigned int target_freq, unsigned int relation)
|
|
{
|
|
unsigned int idx;
|
|
|
|
target_freq = clamp_val(target_freq, policy->min, policy->max);
|
|
|
|
if (!policy->freq_table)
|
|
return target_freq;
|
|
|
|
idx = cpufreq_frequency_table_target(policy, target_freq, relation);
|
|
|
|
return policy->freq_table[idx].frequency;
|
|
}
|
|
|
|
static ssize_t vtable_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
ssize_t len = 0;
|
|
int i = 0;
|
|
struct cpufreq_policy *policy = cpufreq_cpu_get(param.g_first);
|
|
unsigned int virt_clk = 0;
|
|
|
|
if (!cflm_vbf.count)
|
|
return len;
|
|
|
|
if (param.vbf_offset > cflm_vbf.count) {
|
|
pr_err("%s: bad condition(off(%d), cnt(%d))",
|
|
__func__, param.vbf_offset, cflm_vbf.count);
|
|
return len;
|
|
}
|
|
|
|
if (param.max_limit_val != LIMIT_RELEASE)
|
|
len += snprintf(buf + len, MAX_BUF_SIZE, "!!!) please, read table again when no limit state\n");
|
|
|
|
len += snprintf(buf + len, MAX_BUF_SIZE, "========================max===============================min================\n");
|
|
len += snprintf(buf + len, MAX_BUF_SIZE, " virt | prime titan gold silver | prime titan gold silver\n");
|
|
for (i = 0; i < param.freq_count; i++) {
|
|
virt_clk = param.unified_cpuftbl[i];
|
|
|
|
if (virt_clk > param.ltl_max_freq) {
|
|
len += snprintf(buf + len, MAX_BUF_SIZE, " %7u | %7u %7u %7u %7u | %7u %7u %7u %7u\n",
|
|
virt_clk,
|
|
|
|
/* max = limit */
|
|
virt_clk,
|
|
cflm_get_vol_matched_freq(virt_clk),
|
|
cflm_get_table_freq(policy, virt_clk, CPUFREQ_RELATION_H),
|
|
cflm_get_silver_limit(virt_clk),
|
|
|
|
/* min = boost */
|
|
virt_clk,
|
|
0,
|
|
cflm_get_table_freq(policy, virt_clk, CPUFREQ_RELATION_L),
|
|
cflm_get_silver_boost(virt_clk));
|
|
} else {
|
|
len += snprintf(buf + len, MAX_BUF_SIZE, " %7u | %7u %7u %7u %7u | %7u %7u %7u %7u\n",
|
|
virt_clk,
|
|
|
|
/* max = limit */
|
|
param.p_fmin,
|
|
param.t_fmin,
|
|
param.g_fmin,
|
|
cflm_get_silver_limit(virt_clk),
|
|
|
|
/* min = boost */
|
|
0,
|
|
0,
|
|
0,
|
|
cflm_get_silver_boost(virt_clk));
|
|
}
|
|
}
|
|
len += snprintf(buf + len, MAX_BUF_SIZE, "=============================================================================\n");
|
|
|
|
cpufreq_cpu_put(policy);
|
|
|
|
pr_info("%s: %s\n", __func__, buf);
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t sched_boost_type_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return snprintf(buf, MAX_BUF_SIZE, "%d\n", param.sched_boost_type);
|
|
}
|
|
|
|
static ssize_t sched_boost_type_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t n)
|
|
{
|
|
int boost_type;
|
|
int ret = -EINVAL;
|
|
|
|
ret = kstrtoint(buf, 10, &boost_type);
|
|
if (ret < 0) {
|
|
pr_err("%s: cflm: Invalid cpufreq format\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
mutex_lock(&cflm_mutex);
|
|
|
|
if ((param.sched_boost_enabled) && (param.sched_boost_type != boost_type)) {
|
|
pr_info("%s: sched boost is enabled(%d), reset(%d)\n", __func__, param.sched_boost_type, boost_type);
|
|
sched_set_boost(param.sched_boost_type * -1);
|
|
sched_set_boost(boost_type);
|
|
}
|
|
|
|
param.sched_boost_type = boost_type;
|
|
pr_info("%s: sched boost type is changed to %d\n", __func__, param.sched_boost_type);
|
|
|
|
mutex_unlock(&cflm_mutex);
|
|
ret = n;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/* sysfs in /sys/power */
|
|
static struct kobj_attribute cpufreq_table = {
|
|
.attr = {
|
|
.name = "cpufreq_table",
|
|
.mode = 0444
|
|
},
|
|
.show = cpufreq_table_show,
|
|
.store = NULL,
|
|
};
|
|
|
|
static struct kobj_attribute cpufreq_min_limit = {
|
|
.attr = {
|
|
.name = "cpufreq_min_limit",
|
|
.mode = 0644
|
|
},
|
|
.show = cpufreq_min_limit_show,
|
|
.store = cpufreq_min_limit_store,
|
|
};
|
|
|
|
static struct kobj_attribute cpufreq_max_limit = {
|
|
.attr = {
|
|
.name = "cpufreq_max_limit",
|
|
.mode = 0644
|
|
},
|
|
.show = cpufreq_max_limit_show,
|
|
.store = cpufreq_max_limit_store,
|
|
};
|
|
|
|
static struct kobj_attribute over_limit = {
|
|
.attr = {
|
|
.name = "over_limit",
|
|
.mode = 0644
|
|
},
|
|
.show = over_limit_show,
|
|
.store = over_limit_store,
|
|
};
|
|
|
|
static struct kobj_attribute limit_stat = {
|
|
.attr = {
|
|
.name = "limit_stat",
|
|
.mode = 0644
|
|
},
|
|
.show = limit_stat_show,
|
|
};
|
|
|
|
static struct kobj_attribute vtable = {
|
|
.attr = {
|
|
.name = "vtable",
|
|
.mode = 0444
|
|
},
|
|
.show = vtable_show,
|
|
.store = NULL,
|
|
};
|
|
|
|
static struct kobj_attribute sched_boost_type = {
|
|
.attr = {
|
|
.name = "sched_boost_type",
|
|
.mode = 0644
|
|
},
|
|
.show = sched_boost_type_show,
|
|
.store = sched_boost_type_store,
|
|
};
|
|
|
|
int set_freq_limit(unsigned int id, unsigned int freq)
|
|
{
|
|
if (lpcharge) {
|
|
pr_err("%s: not allowed in LPM\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&cflm_mutex);
|
|
|
|
pr_info("%s: cflm: id(%d) freq(%d)\n", __func__, (int)id, freq);
|
|
|
|
cflm_freq_decision(id, freq, 0);
|
|
|
|
mutex_unlock(&cflm_mutex);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(set_freq_limit);
|
|
|
|
#define cflm_attr_rw(_name) \
|
|
static struct kobj_attribute _name##_attr = \
|
|
__ATTR(_name, 0644, show_##_name, store_##_name)
|
|
|
|
#define show_one(file_name) \
|
|
static ssize_t show_##file_name \
|
|
(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
|
|
{ \
|
|
return scnprintf(buf, PAGE_SIZE, "%u\n", param.file_name); \
|
|
}
|
|
|
|
#define store_one(file_name) \
|
|
static ssize_t store_##file_name \
|
|
(struct kobject *kobj, struct kobj_attribute *attr, \
|
|
const char *buf, size_t count) \
|
|
{ \
|
|
int ret; \
|
|
\
|
|
ret = sscanf(buf, "%u", ¶m.file_name); \
|
|
if (ret != 1) \
|
|
return -EINVAL; \
|
|
\
|
|
return count; \
|
|
}
|
|
|
|
/* votlage based */
|
|
show_one(vol_based_clk);
|
|
store_one(vol_based_clk);
|
|
cflm_attr_rw(vol_based_clk);
|
|
show_one(vbf_offset);
|
|
store_one(vbf_offset);
|
|
cflm_attr_rw(vbf_offset);
|
|
|
|
/* adaptive boost */
|
|
show_one(ab_enabled);
|
|
store_one(ab_enabled);
|
|
cflm_attr_rw(ab_enabled);
|
|
|
|
static ssize_t show_cflm_info(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
ssize_t len = 0;
|
|
int i = 0;
|
|
|
|
mutex_lock(&cflm_mutex);
|
|
|
|
len += snprintf(buf, MAX_BUF_SIZE, "[basic info]\n");
|
|
len += snprintf(buf + len, MAX_BUF_SIZE - len,
|
|
"real: silver(%d ~ %d), gold(%d ~ %d), prime(%d ~ %d)\n",
|
|
param.s_fmin, param.s_fmax,
|
|
param.g_fmin, param.g_fmax,
|
|
param.p_fmin, param.p_fmax);
|
|
|
|
len += snprintf(buf + len, MAX_BUF_SIZE - len,
|
|
"virt: little(%d ~ %d), big(%d ~ %d)\n",
|
|
param.ltl_min_freq, param.ltl_max_freq,
|
|
param.big_min_freq, param.big_max_freq);
|
|
|
|
len += snprintf(buf + len, MAX_BUF_SIZE - len,
|
|
"param: div(%d), sched boost(%d)\n",
|
|
param.silver_divider, param.sched_boost_type);
|
|
|
|
len += snprintf(buf + len, MAX_BUF_SIZE - len,
|
|
"param: vbf(%d), offset(%d), aboost(%d)\n",
|
|
param.vol_based_clk, param.vbf_offset, param.ab_enabled);
|
|
|
|
len += snprintf(buf + len, MAX_BUF_SIZE - len, "[requested info]\n");
|
|
for (i = 0; i < CFLM_MAX_ITEM; i++) {
|
|
len += snprintf(buf + len, MAX_BUF_SIZE - len,
|
|
"requested: [%d] min(%d), max(%d)\n",
|
|
i, freq_input[i].min, freq_input[i].max);
|
|
}
|
|
|
|
len += snprintf(buf + len, MAX_BUF_SIZE - len, "[aboost table]\n");
|
|
if ((param.ab_enabled) && (param.ab_table)) {
|
|
for (i = 0; i < NUM_CPUS; i++) {
|
|
len += snprintf(buf + len, MAX_BUF_SIZE - len, "cpu%d: %d %d %d\n",
|
|
i, param.ab_table[i].threshold,
|
|
param.ab_table[i].high, param.ab_table[i].low);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&cflm_mutex);
|
|
|
|
return len;
|
|
}
|
|
|
|
static struct kobj_attribute cflm_info =
|
|
__ATTR(info, 0444, show_cflm_info, NULL);
|
|
|
|
static struct attribute *cflm_attributes[] = {
|
|
&cpufreq_table.attr,
|
|
&cpufreq_min_limit.attr,
|
|
&cpufreq_max_limit.attr,
|
|
&over_limit.attr,
|
|
&limit_stat.attr,
|
|
&cflm_info.attr,
|
|
&vtable.attr,
|
|
&sched_boost_type.attr,
|
|
&vol_based_clk_attr.attr,
|
|
&vbf_offset_attr.attr,
|
|
&ab_enabled_attr.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group cflm_attr_group = {
|
|
.attrs = cflm_attributes,
|
|
};
|
|
|
|
#ifdef CONFIG_OF
|
|
static void cflm_parse_dt(struct platform_device *pdev)
|
|
{
|
|
int size = 0;
|
|
|
|
if (!pdev->dev.of_node) {
|
|
pr_info("%s: no device tree\n", __func__);
|
|
return;
|
|
}
|
|
|
|
/* voltage based */
|
|
param.vol_based_clk = of_property_read_bool(pdev->dev.of_node, "limit,vol_based_clk");
|
|
of_property_read_u32(pdev->dev.of_node, "limit,vbf_offset", ¶m.vbf_offset);
|
|
pr_info("%s: param: voltage based clock: %s(offset %d)\n",
|
|
__func__, param.vol_based_clk ? "true" : "false", param.vbf_offset);
|
|
|
|
/* boost table */
|
|
of_get_property(pdev->dev.of_node, "limit,silver_boost_table", &size);
|
|
if (size) {
|
|
param.silver_boost_map = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
|
|
of_property_read_u32_array(pdev->dev.of_node, "limit,silver_boost_table",
|
|
(u32 *)param.silver_boost_map, size / sizeof(u32));
|
|
|
|
param.boost_map_size = size / sizeof(*param.silver_boost_map);
|
|
}
|
|
pr_info("%s: param: boost map size(%d)\n", __func__, param.boost_map_size);
|
|
|
|
/* limit table */
|
|
size = 0;
|
|
of_get_property(pdev->dev.of_node, "limit,silver_limit_table", &size);
|
|
if (size) {
|
|
param.silver_limit_map = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
|
|
of_property_read_u32_array(pdev->dev.of_node, "limit,silver_limit_table",
|
|
(u32 *)param.silver_limit_map, size / sizeof(u32));
|
|
|
|
param.limit_map_size = size / sizeof(*param.silver_limit_map);
|
|
}
|
|
pr_info("%s: param: limit map size(%d)\n", __func__, param.limit_map_size);
|
|
|
|
/* adaptive boost */
|
|
size = 0;
|
|
of_get_property(pdev->dev.of_node, "limit,ab_table", &size);
|
|
if (size) {
|
|
param.ab_table = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
|
|
of_property_read_u32_array(pdev->dev.of_node, "limit,ab_table",
|
|
(u32 *)param.ab_table, size / sizeof(u32));
|
|
|
|
param.ab_enabled = 1;
|
|
pr_info("%s: param: aboost enabled(%d)\n", __func__, size);
|
|
} else {
|
|
param.ab_enabled = 0;
|
|
pr_info("%s: param: no aboost table(%d)\n", __func__, size);
|
|
}
|
|
|
|
/* lowest freq */
|
|
of_property_read_u32(pdev->dev.of_node, "limit,gold_fmin", ¶m.g_fmin);
|
|
of_property_read_u32(pdev->dev.of_node, "limit,titanium_fmin", ¶m.t_fmin);
|
|
of_property_read_u32(pdev->dev.of_node, "limit,prime_fmin", ¶m.p_fmin);
|
|
|
|
/* etc */
|
|
of_property_read_u32(pdev->dev.of_node, "limit,gold_fmin_up", ¶m.g_fmin_up);
|
|
of_property_read_u32(pdev->dev.of_node, "limit,little_max_freq", ¶m.ltl_max_freq);
|
|
of_property_read_u32(pdev->dev.of_node, "limit,big_min_freq", ¶m.big_min_freq);
|
|
|
|
/* sm8650 4 cluster(silver, gold, titanium, prime) */
|
|
param.titanium = of_property_read_bool(pdev->dev.of_node, "limit,support_titanium");
|
|
|
|
of_node_put(pdev->dev.of_node);
|
|
pr_info("%s: param: g_fmin_up(%d), ltl_max_freq(%d), big_min_freq(%d), titanium(%d)\n", __func__,
|
|
param.g_fmin_up, param.ltl_max_freq, param.big_min_freq, param.titanium);
|
|
};
|
|
#endif
|
|
|
|
int cflm_add_qos(void)
|
|
{
|
|
struct cpufreq_policy *policy;
|
|
unsigned int i = 0;
|
|
unsigned int j = 0;
|
|
int ret = 0;
|
|
|
|
for_each_possible_cpu(i) {
|
|
policy = cpufreq_cpu_get(i);
|
|
if (!policy) {
|
|
pr_err("no policy for cpu%d\n", i);
|
|
ret = -EPROBE_DEFER;
|
|
break;
|
|
}
|
|
|
|
for (j = 0; j < CFLM_MAX_ITEM; j++) {
|
|
ret = freq_qos_add_request(&policy->constraints,
|
|
&min_req[i][j],
|
|
FREQ_QOS_MIN, policy->cpuinfo.min_freq);
|
|
if (ret < 0) {
|
|
pr_err("%s: failed to add min req(%d)\n", __func__, ret);
|
|
break;
|
|
}
|
|
cflm_req_init[i] |= BIT(j*2);
|
|
|
|
ret = freq_qos_add_request(&policy->constraints,
|
|
&max_req[i][j],
|
|
FREQ_QOS_MAX, policy->cpuinfo.max_freq);
|
|
if (ret < 0) {
|
|
pr_err("%s: failed to add max req(%d)\n", __func__, ret);
|
|
break;
|
|
}
|
|
cflm_req_init[i] |= BIT(j*2+1);
|
|
}
|
|
if (ret < 0) {
|
|
cpufreq_cpu_put(policy);
|
|
break;
|
|
}
|
|
|
|
if (i == param.s_first) {
|
|
if (!param.s_fmin)
|
|
param.s_fmin = policy->cpuinfo.min_freq;
|
|
param.s_fmax = policy->cpuinfo.max_freq;
|
|
}
|
|
if (i == param.g_first) {
|
|
if (!param.g_fmin)
|
|
param.g_fmin = policy->cpuinfo.min_freq;
|
|
param.g_fmax = policy->cpuinfo.max_freq;
|
|
}
|
|
if (i == param.p_first) {
|
|
if (!param.p_fmin)
|
|
param.p_fmin = policy->cpuinfo.min_freq;
|
|
param.p_fmax = policy->cpuinfo.max_freq;
|
|
}
|
|
if (i == param.t_first) {
|
|
if (!param.t_fmin)
|
|
param.t_fmin = policy->cpuinfo.min_freq;
|
|
param.t_fmax = policy->cpuinfo.max_freq;
|
|
}
|
|
|
|
cflm_set_table(policy->cpu, policy->freq_table);
|
|
|
|
cpufreq_cpu_put(policy);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void cflm_remove_qos(void)
|
|
{
|
|
unsigned int i = 0;
|
|
unsigned int j = 0;
|
|
int ret = 0;
|
|
|
|
pr_info("%s\n", __func__);
|
|
for_each_possible_cpu(i) {
|
|
for (j = 0; j < CFLM_MAX_ITEM; j++) {
|
|
if (cflm_req_init[i] & BIT(j*2)) {
|
|
//pr_info("%s: try to remove min[%d][%d] req\n", __func__, i, j);
|
|
ret = freq_qos_remove_request(&min_req[i][j]);
|
|
if (ret < 0)
|
|
pr_err("%s: failed to remove min_req (%d)\n", __func__, ret);
|
|
}
|
|
if (cflm_req_init[i] & BIT(j*2+1)) {
|
|
//pr_info("%s: try to remove max[%d][%d] req\n", __func__, i, j);
|
|
ret = freq_qos_remove_request(&max_req[i][j]);
|
|
if (ret < 0)
|
|
pr_err("%s: failed to remove max_req (%d)\n", __func__, ret);
|
|
}
|
|
}
|
|
cflm_req_init[i] = 0U;
|
|
}
|
|
}
|
|
|
|
int cflm_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
|
|
pr_info("%s\n", __func__);
|
|
|
|
if (lpcharge) {
|
|
pr_info("%s: dummy for LPM\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
cflm_parse_dt(pdev);
|
|
#endif
|
|
|
|
ret = cflm_add_qos();
|
|
if (ret < 0)
|
|
goto policy_not_ready;
|
|
|
|
cflm_kobj = kobject_create_and_add("cpufreq_limit",
|
|
&cpu_subsys.dev_root->kobj);
|
|
if (!cflm_kobj) {
|
|
pr_err("Unable to cread cflm_kobj\n");
|
|
goto object_create_failed;
|
|
}
|
|
|
|
ret = sysfs_create_group(cflm_kobj, &cflm_attr_group);
|
|
if (ret) {
|
|
pr_err("Unable to create cflm group\n");
|
|
goto group_create_failed;
|
|
}
|
|
|
|
pr_info("%s done\n", __func__);
|
|
return ret;
|
|
|
|
group_create_failed:
|
|
kobject_put(cflm_kobj);
|
|
object_create_failed:
|
|
cflm_kobj = NULL;
|
|
policy_not_ready:
|
|
cflm_remove_qos();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cflm_remove(struct platform_device *pdev)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
|
|
if (!lpcharge && cflm_kobj) {
|
|
cflm_remove_qos();
|
|
sysfs_remove_group(cflm_kobj, &cflm_attr_group);
|
|
kobject_put(cflm_kobj);
|
|
cflm_kobj = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id cflm_match_table[] = {
|
|
{ .compatible = "cpufreq_limit" },
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver cflm_driver = {
|
|
.driver = {
|
|
.name = "cpufreq_limit",
|
|
.of_match_table = cflm_match_table,
|
|
},
|
|
.probe = cflm_probe,
|
|
.remove = cflm_remove,
|
|
};
|
|
|
|
static int __init cflm_init(void)
|
|
{
|
|
return platform_driver_register(&cflm_driver);
|
|
}
|
|
|
|
static void __exit cflm_exit(void)
|
|
{
|
|
platform_driver_unregister(&cflm_driver);
|
|
}
|
|
|
|
MODULE_AUTHOR("Sangyoung Son <hello.son@samsung.com");
|
|
MODULE_DESCRIPTION("'cpufreq_limit' - A driver to limit cpu frequency");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
late_initcall(cflm_init);
|
|
module_exit(cflm_exit);
|