383 lines
9.6 KiB
C
Executable file
383 lines
9.6 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* power budget driver
|
|
* updated: 2021
|
|
*/
|
|
|
|
#include <linux/of.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/device.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/thermal.h>
|
|
#include <soc/samsung/freq-qos-tracer.h>
|
|
#include "../../../kernel/sched/sched.h"
|
|
#include "../../../kernel/sched/ems/ems.h"
|
|
|
|
#include "xperf.h"
|
|
|
|
static const char *prefix = "pago";
|
|
|
|
static uint debug;
|
|
|
|
static uint gov;
|
|
static uint gov_ms;
|
|
static uint gov_run;
|
|
static uint gov_done = 1;
|
|
static uint power_limits[NUM_OF_SYSBUSY_STATE];
|
|
|
|
static uint multi_volt[MAX_CLUSTER];
|
|
static uint batt_temp_thd;
|
|
|
|
/******************************************************************************
|
|
* power budget governor *
|
|
******************************************************************************/
|
|
static uint power_limit;
|
|
static struct freq_qos_request gqos[MAX_CLUSTER];
|
|
|
|
static int gov_thread(void *data)
|
|
{
|
|
uint cpu;
|
|
uint cpu_cur[MAX_CLUSTER];
|
|
uint cpu_max[MAX_CLUSTER];
|
|
int cluster;
|
|
int cpu_power;
|
|
int power_total;
|
|
int cl_idx;
|
|
int f_idx;
|
|
int freq;
|
|
int next_mgn;
|
|
int util_ratio;
|
|
unsigned long power;
|
|
|
|
if (gov)
|
|
return 0;
|
|
|
|
gov = 1;
|
|
while (gov) {
|
|
for_each_cluster(cluster) {
|
|
cpu = cls[cluster].first_cpu;
|
|
cpu_max[cluster] = cpufreq_quick_get_max(cpu) / 1000;
|
|
cpu_cur[cluster] = cpufreq_quick_get(cpu) / 1000;
|
|
}
|
|
power_total = 0;
|
|
for_each_possible_cpu(cpu) {
|
|
util_ratio = (cpu_util_avgs[cpu] * 100) / 1024;
|
|
cl_idx = get_cl_idx(cpu);
|
|
freq = cpu_cur[cl_idx] * 1000;
|
|
f_idx = get_f_idx(cl_idx, freq);
|
|
power = cls[cl_idx].freqs[f_idx].dyn_power + et_freq_to_spower(cpu, freq);
|
|
cpu_power = util_ratio * power / 100;
|
|
power_total += cpu_power;
|
|
}
|
|
next_mgn = (power_total > power_limit) ? -1 : 1;
|
|
for (cl_idx = 1; cl_idx < cl_cnt; cl_idx++) {
|
|
freq = cpu_max[cl_idx] * 1000;
|
|
f_idx = get_f_idx(cl_idx, freq);
|
|
f_idx += next_mgn;
|
|
f_idx = (f_idx < 0) ? 0 : f_idx;
|
|
f_idx = (f_idx < cls[cl_idx].freq_size) ? f_idx : cls[cl_idx].freq_size - 1;
|
|
freq = cls[cl_idx].freqs[f_idx].freq;
|
|
f_idx = (freq == 0) ? f_idx - 1 : f_idx;
|
|
freq_qos_update_request(&gqos[cl_idx], cls[cl_idx].freqs[f_idx].freq);
|
|
}
|
|
msleep(gov_ms);
|
|
}
|
|
|
|
for_each_cluster(cluster)
|
|
freq_qos_update_request(&gqos[cluster], INT_MAX);
|
|
|
|
gov_done = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool is_gov_enable(void)
|
|
{
|
|
return gov_run && gov_ms;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* sysbusy notifier *
|
|
******************************************************************************/
|
|
static struct task_struct *gov_task;
|
|
static struct freq_qos_request sqos[MAX_CLUSTER];
|
|
static const char *batt_tz_names;
|
|
|
|
static void set_multi_idx(void)
|
|
{
|
|
int cluster;
|
|
unsigned int idx;
|
|
|
|
for_each_cluster(cluster) {
|
|
idx = cls[cluster].multi_idx;
|
|
if (idx != cls[cluster].freq_size - 1)
|
|
freq_qos_update_request(&sqos[cluster], cls[cluster].freqs[idx].freq);
|
|
}
|
|
}
|
|
|
|
static void reset_multi_idx(void)
|
|
{
|
|
int cluster;
|
|
|
|
for_each_cluster(cluster)
|
|
freq_qos_update_request(&sqos[cluster], FREQ_QOS_MAX_DEFAULT_VALUE);
|
|
}
|
|
|
|
static bool is_batt_hot(void)
|
|
{
|
|
int temp;
|
|
|
|
thermal_zone_get_temp(thermal_zone_get_zone_by_name(batt_tz_names), &temp);
|
|
|
|
if (debug)
|
|
pr_info("[%s] battery temp=%d\n", prefix, temp);
|
|
|
|
return (temp > batt_temp_thd);
|
|
}
|
|
|
|
static int gov_notifier_call(struct notifier_block *nb, unsigned long val, void *v)
|
|
{
|
|
enum sysbusy_state state = *(enum sysbusy_state *)v;
|
|
|
|
if (val != SYSBUSY_STATE_CHANGE)
|
|
return NOTIFY_OK;
|
|
|
|
if (state < SYSBUSY_STATE2) {
|
|
gov = 0;
|
|
reset_multi_idx();
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
/* gov thread */
|
|
if (is_gov_enable()) {
|
|
power_limit = power_limits[state];
|
|
if (gov_done) {
|
|
gov_done = 0;
|
|
gov_task = kthread_create(gov_thread, NULL, "xperf_gov%u", 0);
|
|
kthread_bind_mask(gov_task, cls[0].siblings);
|
|
wake_up_process(gov_task);
|
|
}
|
|
}
|
|
|
|
/* multi volt */
|
|
if (is_batt_hot())
|
|
set_multi_idx();
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block sysbusy_notifier;
|
|
|
|
/******************************************************************************
|
|
* helper function *
|
|
******************************************************************************/
|
|
static void find_multi_idx(void)
|
|
{
|
|
int cluster, i;
|
|
unsigned int size;
|
|
|
|
for_each_cluster(cluster) {
|
|
if (multi_volt[cluster] < 0) {
|
|
cls[cluster].multi_idx = 0;
|
|
continue;
|
|
}
|
|
|
|
size = cls[cluster].freq_size;
|
|
|
|
for (i = size - 1; i >= 0; i--) {
|
|
if (multi_volt[cluster] > cls[cluster].freqs[i].volt) {
|
|
cls[cluster].multi_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i < 0)
|
|
cls[cluster].multi_idx = 0;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
* sysfs interface *
|
|
******************************************************************************/
|
|
#define DEF_NODE_RW(name) \
|
|
static ssize_t name##_show(struct kobject *k, struct kobj_attribute *attr, char *buf) \
|
|
{ \
|
|
int ret = 0; \
|
|
ret += sprintf(buf + ret, "%d\n", name); \
|
|
return ret; } \
|
|
static ssize_t name##_store(struct kobject *k, struct kobj_attribute *attr, const char *buf, size_t count) \
|
|
{ \
|
|
if (kstrtouint(buf, 10, &name)) \
|
|
return -EINVAL; \
|
|
return count; } \
|
|
static struct kobj_attribute name##_attr = __ATTR_RW(name)
|
|
|
|
DEF_NODE_RW(debug);
|
|
DEF_NODE_RW(gov_run);
|
|
DEF_NODE_RW(gov_ms);
|
|
DEF_NODE_RW(batt_temp_thd);
|
|
|
|
static ssize_t power_limits_show(struct kobject *k, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
int ret = 0, i;
|
|
|
|
for (i = 0; i < NUM_OF_SYSBUSY_STATE; i++)
|
|
ret += sprintf(buf + ret, "%d:%d\n", i, power_limits[i]);
|
|
return ret;
|
|
}
|
|
static ssize_t power_limits_store(struct kobject *k, struct kobj_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
if (sscanf(buf, "%d %d %d %d", &power_limits[0], &power_limits[1], &power_limits[2], &power_limits[3]) != 4)
|
|
return -EINVAL;
|
|
|
|
return count;
|
|
}
|
|
static struct kobj_attribute power_limits_attr = __ATTR_RW(power_limits);
|
|
|
|
static ssize_t multi_volt_show(struct kobject *k, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
int cluster, ret = 0;
|
|
unsigned int idx;
|
|
|
|
for_each_cluster(cluster) {
|
|
idx = cls[cluster].multi_idx;
|
|
ret += sprintf(buf + ret, "cluster%d multi-volt=%-5u multi-freq=%-8u\n",
|
|
cluster, multi_volt[cluster], cls[cluster].freqs[idx].freq);
|
|
}
|
|
ret += sprintf(buf + ret, "# echo <cluster> <volt> > multi_volt\n");
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t multi_volt_store(struct kobject *k, struct kobj_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
int cluster, val;
|
|
|
|
if (sscanf(buf, "%d %d", &cluster, &val) != 2)
|
|
return -EINVAL;
|
|
if (cluster < 0 || cluster >= MAX_CLUSTER)
|
|
return -EINVAL;
|
|
multi_volt[cluster] = val;
|
|
find_multi_idx();
|
|
return count;
|
|
}
|
|
static struct kobj_attribute multi_volt_attr = __ATTR_RW(multi_volt);
|
|
|
|
static struct attribute *pago_attrs[] = {
|
|
&debug_attr.attr,
|
|
&gov_run_attr.attr,
|
|
&gov_ms_attr.attr,
|
|
&power_limits_attr.attr,
|
|
&multi_volt_attr.attr,
|
|
&batt_temp_thd_attr.attr,
|
|
NULL
|
|
};
|
|
static struct attribute_group pago_group = {
|
|
.attrs = pago_attrs,
|
|
};
|
|
|
|
/******************************************************************************
|
|
* initialization *
|
|
******************************************************************************/
|
|
static struct kobject *pago_kobj;
|
|
|
|
static int pago_dt_init(struct device_node *dn)
|
|
{
|
|
struct device_node *pago_node;
|
|
|
|
pago_node = of_get_child_by_name(dn, "pago");
|
|
if (!pago_node)
|
|
return -ENODATA;
|
|
|
|
if (of_property_read_u32(pago_node, "gov-ms", &gov_ms))
|
|
gov_run = 0;
|
|
if (of_property_read_u32(pago_node, "gov-run", &gov_run))
|
|
gov_run = 0;
|
|
if (of_property_read_u32_array(pago_node, "power-limit", power_limits, NUM_OF_SYSBUSY_STATE) < 0)
|
|
gov_run = 0;
|
|
|
|
of_property_read_u32_array(pago_node, "multi-volt", multi_volt, cl_cnt);
|
|
|
|
of_property_read_u32(pago_node, "batt-temp-thd", &batt_temp_thd);
|
|
|
|
if (of_property_read_string(pago_node, "batt-tz-names", &batt_tz_names))
|
|
return -ENODATA;
|
|
|
|
find_multi_idx();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pago_sysfs_init(struct kobject *kobj)
|
|
{
|
|
pago_kobj = kobject_create_and_add("pago", kobj);
|
|
|
|
if (!pago_kobj) {
|
|
pr_info("[%s] create node failed: %s\n", prefix, __FILE__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (sysfs_create_group(pago_kobj, &pago_group)) {
|
|
pr_info("[%s] create group failed: %s\n", prefix, __FILE__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pago_fqos_init(void)
|
|
{
|
|
int cluster;
|
|
struct cpufreq_policy *policy;
|
|
|
|
for_each_cluster(cluster) {
|
|
policy = cpufreq_cpu_get(cls[cluster].first_cpu);
|
|
if (!policy) {
|
|
for_each_cluster(cluster) {
|
|
freq_qos_tracer_remove_request(&gqos[cluster]);
|
|
freq_qos_tracer_remove_request(&sqos[cluster]);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
freq_qos_tracer_add_request(&policy->constraints,
|
|
&gqos[cluster],
|
|
FREQ_QOS_MAX,
|
|
FREQ_QOS_MAX_DEFAULT_VALUE);
|
|
freq_qos_tracer_add_request(&policy->constraints,
|
|
&sqos[cluster],
|
|
FREQ_QOS_MAX,
|
|
FREQ_QOS_MAX_DEFAULT_VALUE);
|
|
|
|
cpufreq_cpu_put(policy);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int xperf_pago_init(struct platform_device *pdev, struct kobject *kobj)
|
|
{
|
|
if (pago_dt_init(pdev->dev.of_node)) {
|
|
pr_info("[%s] failed to parse device tree\n", prefix);
|
|
return -ENODATA;
|
|
}
|
|
|
|
if (pago_sysfs_init(kobj)) {
|
|
pr_info("[%s] failed to initialize sysfs\n", prefix);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (pago_fqos_init()) {
|
|
pr_info("[%s] failed to initialize freq-qos\n", prefix);
|
|
return -EINVAL;
|
|
}
|
|
|
|
sysbusy_notifier.notifier_call = gov_notifier_call;
|
|
sysbusy_register_notifier(&sysbusy_notifier);
|
|
|
|
return 0;
|
|
}
|