blob: ba283b90e72a32bee7306a15e618ce8c85efecbf [file]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/pm_opp.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/cpumask.h>
#include <linux/mutex.h>
#include <linux/of_platform.h>
#include <linux/topology.h>
#include <linux/regulator/consumer.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/regulator/driver.h>
#include "opp.h"
#include "aml_cpufreq.h"
#include <linux/energy_model.h>
#include <linux/cpu_cooling.h>
#include <linux/thermal.h>
#include <linux/arm-smccc.h>
#include <linux/proc_fs.h>
#include <linux/amlogic/gki_module.h>
static LIST_HEAD(cluster_list);
static DEFINE_MUTEX(cluster_list_lock);
static DEFINE_MUTEX(cpufreq_target_lock);
static unsigned int freqmax[CLUSTER_MAX];
static int freqmax0_param_v2(char *buff)
{
if (!buff)
return -EINVAL;
/*Example: freqmax0=1800000*/
if (kstrtoint(buff, 0, &freqmax[0]))
return -EINVAL;
return 0;
}
static int freqmax1_param_v2(char *buff)
{
if (!buff)
return -EINVAL;
/*Example: freqmax1=1800000*/
if (kstrtoint(buff, 0, &freqmax[1]))
return -EINVAL;
return 0;
}
__setup("freqmax0=", freqmax0_param_v2);
__setup("freqmax1=", freqmax1_param_v2);
static struct cluster_data *find_cluster_data_by_cpu(int cpu)
{
struct cluster_data *pos, *ret = NULL;
mutex_lock(&cluster_list_lock);
list_for_each_entry(pos, &cluster_list, node) {
if (cpumask_test_cpu(cpu, pos->cpus)) {
ret = pos;
break;
}
}
mutex_unlock(&cluster_list_lock);
return ret;
}
static int aml_cpufreq_verify(struct cpufreq_policy_data *pd)
{
int ret;
unsigned int maxfreq = pd->cpu ? freqmax[1] : freqmax[0];
ret = cpufreq_generic_frequency_table_verify(pd);
if (ret)
return ret;
if (maxfreq && maxfreq >= pd->min && maxfreq < pd->max) {
pr_info("policy%d: set policy->max to %u from bootargs\n", pd->cpu, maxfreq);
pd->max = maxfreq;
}
return ret;
}
/* find the dsu opp state:
* i=-1: follow cpu freq volt
* i=0: use dsu_opp_table[0]; i=1: use dsu_opp_table[1]; ......
*/
static void get_suggested_dsuopp_by_cpuopp(struct cluster_data *data,
struct opp_node *dsuopp, struct opp_node *cpuopp)
{
int cnt = data->dsu_opp_cnt, i;
struct cpudsu_threshold_node *table = data->dsu_opp_table;
if (!data->dsuclk)
return;
for (i = cnt; i > 0; i--)
if (cpuopp->rate >= table[i - 1].cpurate)
break;
i--;
if (i < 0) {
dsuopp->rate = cpuopp->rate;
dsuopp->volt = cpuopp->volt;
} else {
dsuopp->rate = table[i].dsurate;
dsuopp->volt = table[i].dsuvolt;
}
}
static void aml_dsu_freq_volt_set(struct cluster_data *cdata,
struct opp_node *dsuopp, enum cpufreq_scaling_mode mode)
{
if (!cdata->dsuclk)
return;
if (cdata->dsureg && mode == SCALING_UP)
regulator_set_voltage(cdata->dsureg, dsuopp->volt, MAX_VOLTAGE);
if (cdata->dsuclk_shared)
clk_set_min_rate(cdata->dsuclk, dsuopp->rate * 1000);
clk_set_rate(cdata->dsuclk, dsuopp->rate * 1000);
if (cdata->dsureg && mode == SCALING_DOWN)
regulator_set_voltage(cdata->dsureg, dsuopp->volt, MAX_VOLTAGE);
}
static int aml_dvfs_algorithm_default(struct cluster_data *data,
unsigned int index)
{
struct opp_node oppold, *oppnew;
struct opp_node dsuopp;
int ret = 0;
oppold.rate = clk_get_rate(data->cpuclk) / 1000;
oppold.volt = data->cpureg ? regulator_get_voltage(data->cpureg) : 0;
oppnew = &data->opp_table[index];
pr_debug("[cluster%d]scaling from[%lu %d] to [%lu %d]\n", data->clusterid, oppold.rate,
oppold.volt, oppnew->rate, oppnew->volt);
get_suggested_dsuopp_by_cpuopp(data, &dsuopp, oppnew);
if (oppnew->rate > oppold.rate) {
if (data->cpureg && regulator_set_voltage(data->cpureg, oppnew->volt,
MAX_VOLTAGE)) {
pr_err("scale voltage up to %duv failed. ret=%d\n", oppnew->volt, ret);
goto out;
}
aml_dsu_freq_volt_set(data, &dsuopp, SCALING_UP);
}
ret = clk_set_rate(data->cpuclk, oppnew->rate * 1000);
if (ret) {
if (oppnew->rate > oppold.rate) {
get_suggested_dsuopp_by_cpuopp(data, &dsuopp, &oppold);
aml_dsu_freq_volt_set(data, &dsuopp, SCALING_DOWN);
if (data->cpureg)
regulator_set_voltage(data->cpureg, oppold.volt, MAX_VOLTAGE);
}
goto out;
}
if (oppnew->rate < oppold.rate) {
aml_dsu_freq_volt_set(data, &dsuopp, SCALING_DOWN);
if (data->cpureg && regulator_set_voltage(data->cpureg, oppnew->volt, MAX_VOLTAGE))
goto out;
}
pr_debug("[cluster%d]cpuget[%lu %d],dsuget[%lu %d]\n",
data->clusterid, clk_get_rate(data->cpuclk) / 1000,
data->cpureg ? regulator_get_voltage(data->cpureg) : 0,
clk_get_rate(data->dsuclk) / 1000, data->dsureg ?
regulator_get_voltage(data->dsureg) : 0);
out:
return ret;
}
static int aml_cpufreq_set_target(struct cpufreq_policy *policy,
unsigned int index)
{
struct cluster_data *data = policy->driver_data;
int ret;
mutex_lock(&cpufreq_target_lock);
ret = aml_dvfs_algorithm_default(data, index);
mutex_unlock(&cpufreq_target_lock);
return ret;
}
static unsigned int aml_cpufreq_get_rate(unsigned int cpu)
{
struct cpufreq_policy policy;
struct cluster_data *data;
u32 rate = 0;
if (!cpufreq_get_policy(&policy, cpu)) {
data = policy.driver_data;
rate = clk_get_rate(data->cpuclk) / 1000;
pr_debug("%s: cpu: %d, cluster: %d, freq: %u\n",
__func__, cpu, data->clusterid, rate);
}
return rate;
}
static void free_clock(struct clk *clock)
{
if (!IS_ERR_OR_NULL(clock))
clk_put(clock);
}
static void free_regulator(struct regulator *reg)
{
if (!IS_ERR_OR_NULL(reg))
devm_regulator_put(reg);
}
static bool aml_setup_clock(struct cluster_data *cdata)
{
bool ret = false;
cdata->cpuclk = of_clk_get_by_name(cdata->np, CPUCLK);
if (PTR_ERR_OR_ZERO(cdata->cpuclk))
goto out;
cdata->dsuclk = of_clk_get_by_name(cdata->np, DSUCLK);
if (PTR_ERR_OR_ZERO(cdata->dsuclk))
cdata->dsuclk = NULL;
if (!cdata->dsuclk && of_property_count_strings(cdata->np, "clock-names") == 2)
ret = false;
else
ret = true;
if (cdata->dsuclk)
cdata->dsuclk_shared = of_property_read_bool(cdata->np, "dsuclk_shared");
out:
return ret;
}
static bool aml_setup_regulator(struct cluster_data *cdata)
{
bool ret = false;
char regname[25] = {0};
cdata->skip_volt_scaling = of_property_read_bool(cdata->np, "skip_volt_scaling");
if (cdata->skip_volt_scaling) {
cdata->cpureg = NULL;
cdata->dsureg = NULL;
ret = true;
goto out;
}
sprintf(regname, "cluster%d-cpu", cdata->clusterid);
cdata->cpureg = devm_regulator_get_optional(cdata->dev, regname);
if (PTR_ERR_OR_ZERO(cdata->cpureg)) {
pr_debug("%s:failed to get %s regulator, ret=%ld\n", __func__, regname,
PTR_ERR(cdata->cpureg));
cdata->cpureg = NULL;
goto out;
}
sprintf(regname, "cluster%d-dsu", cdata->clusterid);
cdata->dsureg = devm_regulator_get_optional(cdata->dev, regname);
if (PTR_ERR_OR_ZERO(cdata->dsureg)) {
pr_debug("%s:failed to get %s regulator, ret=%ld\n", __func__, regname,
PTR_ERR(cdata->dsureg));
cdata->dsureg = NULL;
sprintf(regname, "cluster%d-dsu-supply", cdata->clusterid);
if (!cdata->dsureg && of_find_property(cdata->dev->of_node, regname, NULL))
goto out;
}
ret = true;
out:
return ret;
}
static unsigned int get_cpufreq_table_index(u64 function_id,
u64 arg0, u64 arg1, u64 arg2)
{
struct arm_smccc_res res;
arm_smccc_smc((unsigned long)function_id,
(unsigned long)arg0,
(unsigned long)arg1,
(unsigned long)arg2,
0, 0, 0, 0, &res);
return res.a0;
}
static int get_opp_index_of_policy_exit(struct cluster_data *data)
{
struct opp_node *opp_table = data->opp_table;
int i;
for (i = 0; i < data->opp_cnt; i++) {
if (opp_table[i].volt != opp_table[0].volt)
break;
}
return i > 0 ? i - 1 : i;
}
static bool aml_setup_opptable(struct cluster_data *data)
{
struct device_node *np;
struct device *cpudev;
struct opp_table *opptable;
struct dev_pm_opp *opp;
bool ret = false;
int table_index = 0, i = 0, cpu;
u32 rate, rate1, volt;
char dsutable_name[20] = {0};
int dsuopp_len;
np = data->np;
data->pdvfs_enabled = of_property_read_bool(np, "pdvfs_enabled");
if (data->pdvfs_enabled)
table_index = get_cpufreq_table_index(GET_DVFS_TABLE_INDEX,
data->clusterid, 0, 0);
data->table_index = table_index;
/*one of the cluster cpus may be removed from device tree for isolation,
*so we need to get opp-table from other cpus of the cluster
*/
for_each_cpu(cpu, data->cpus) {
cpudev = get_cpu_device(cpu);
if (dev_pm_opp_of_add_table_indexed(cpudev, table_index))
continue;
else
break;
}
if (dev_pm_opp_init_cpufreq_table(cpudev, &data->freq_table)) {
pr_err("%s:failed to init cpufreq table,cluster:%d\n", __func__, data->clusterid);
goto out;
}
data->opp_cnt = dev_pm_opp_get_opp_count(cpudev);
data->opp_table = kmalloc_array(data->opp_cnt, sizeof(*data->opp_table), GFP_KERNEL);
opptable = dev_pm_opp_get_opp_table(cpudev);
if (!data->opp_table || !opptable)
goto out;
mutex_lock(&opptable->lock);
list_for_each_entry(opp, &opptable->opp_list, node) {
if (!opp->available)
continue;
pr_debug("%lu %lu\n", opp->rates[0], opp->supplies[0].u_volt);
data->opp_table[i].rate = opp->rates[0] / 1000;
data->opp_table[i].volt = opp->supplies[0].u_volt;
i++;
}
dev_pm_opp_put_opp_table(opptable);
mutex_unlock(&opptable->lock);
data->exit_index = get_opp_index_of_policy_exit(data);
if (!data->dsuclk)
return true;
if (data->dsureg) {
dsuopp_len = 3;
sprintf(dsutable_name, "dsu-opp-table%d", data->table_index);
} else {
dsuopp_len = 2;
sprintf(dsutable_name, "dsu-opp-table");
}
data->dsu_opp_cnt = of_property_count_u32_elems(np, dsutable_name) / dsuopp_len;
if (data->dsu_opp_cnt <= 0)
goto out;
data->dsu_opp_table = kmalloc_array(data->dsu_opp_cnt,
sizeof(*data->dsu_opp_table), GFP_KERNEL);
if (IS_ERR(data->dsu_opp_table)) {
pr_err("failed to alloc dsu_opp_table.\n");
data->dsu_opp_table = NULL;
goto out;
}
for (i = 0; i < data->dsu_opp_cnt; i++) {
of_property_read_u32_index(np, dsutable_name, dsuopp_len * i, &rate);
of_property_read_u32_index(np, dsutable_name, dsuopp_len * i + 1, &rate1);
data->dsu_opp_table[i].cpurate = rate;
data->dsu_opp_table[i].dsurate = rate1;
if (data->dsureg) {
of_property_read_u32_index(np, dsutable_name, dsuopp_len * i + 2, &volt);
data->dsu_opp_table[i].dsuvolt = volt;
}
}
return true;
out:
return ret;
}
static int opptable_show(struct seq_file *m, void *v)
{
struct cluster_data *data = m->private;
int i;
seq_printf(m, "pdvfs_en: %d\n", data->pdvfs_enabled);
seq_printf(m, "table_index: %d\n", data->table_index);
seq_puts(m, "opp table:\n");
for (i = 0; i < data->opp_cnt; i++)
seq_printf(m, "%lu %d\n", data->opp_table[i].rate, data->opp_table[i].volt);
if (!data->dsuclk)
return 0;
seq_puts(m, "dsu_opp table:\n");
for (i = 0; i < data->dsu_opp_cnt; i++) {
if (data->dsureg)
seq_printf(m, "%lu %lu %d\n", data->dsu_opp_table[i].cpurate,
data->dsu_opp_table[i].dsurate, data->dsu_opp_table[i].dsuvolt);
else
seq_printf(m, "%lu %lu\n", data->dsu_opp_table[i].cpurate,
data->dsu_opp_table[i].dsurate);
}
return 0;
}
static int aml_opptable_open(struct inode *inode, struct file *file)
{
return single_open(file, opptable_show, pde_data(inode));
}
static const struct proc_ops aml_opptable_fops = {
.proc_open = aml_opptable_open,
.proc_read = seq_read,
.proc_release = single_release,
};
static void aml_create_proc_files(struct cluster_data *data)
{
char policy_name[10] = {0};
snprintf(policy_name, sizeof(policy_name), "policy%u", cpumask_first(data->cpus));
data->proccur = proc_mkdir(policy_name, data->procroot);
if (data->proccur)
proc_create_data("opp-table", 0444, data->proccur,
&aml_opptable_fops, data);
}
static void aml_validate_cpu_voltage(struct cluster_data *data)
{
int cnt = data->opp_cnt;
int dsu_cnt = data->dsu_opp_cnt;
unsigned long max = data->opp_table[cnt - 1].volt;
unsigned long min = data->opp_table[0].volt;
int cpuvolt_old, dsuvolt_old;
if (data->skip_volt_scaling)
return;
cpuvolt_old = regulator_get_voltage(data->cpureg);
if (cpuvolt_old > max)
cpuvolt_old = max;
else if (cpuvolt_old < min)
cpuvolt_old = min;
regulator_set_voltage(data->cpureg, cpuvolt_old, MAX_VOLTAGE);
if (!data->dsureg)
return;
dsuvolt_old = regulator_get_voltage(data->dsureg);
if (dsuvolt_old > data->dsu_opp_table[dsu_cnt - 1].dsuvolt)
dsuvolt_old = data->dsu_opp_table[dsu_cnt - 1].dsuvolt;
else if (dsuvolt_old < min)
dsuvolt_old = min;
regulator_set_voltage(data->dsureg, dsuvolt_old, MAX_VOLTAGE);
}
static void aml_setup_policy_rest(struct cluster_data *data)
{
struct device_node *np = data->np;
struct cpufreq_policy *policy = data->policy;
if (of_property_read_u32(np, "clock-latency", &policy->cpuinfo.transition_latency))
policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
if (of_property_read_u32(np, "suspend-freq", &policy->suspend_freq))
policy->suspend_freq = data->opp_table[data->opp_cnt - 1].rate;
policy->cur = clk_get_rate(data->cpuclk) / 1000;
cpumask_copy(policy->cpus, data->cpus);
}
static int aml_cpufreq_init(struct cpufreq_policy *policy)
{
struct cluster_data *data = find_cluster_data_by_cpu(policy->cpu);
data->policy = policy;
policy->driver_data = data;
policy->freq_table = data->freq_table;
aml_create_proc_files(data);
aml_setup_policy_rest(data);
pr_info("cpufreq v2 policy%d init succeed.\n", cpumask_first(data->cpus));
return 0;
}
static void destroy_aml_cpufreq_proc_files(struct cpufreq_policy *policy)
{
char policy_name[10] = {0};
struct cluster_data *data = policy->driver_data;
snprintf(policy_name, sizeof(policy_name), "policy%u", cpumask_first(data->cpus));
remove_proc_entry("opp-table", data->proccur);
remove_proc_entry(policy_name, data->procroot);
}
static int aml_cpufreq_exit(struct cpufreq_policy *policy)
{
struct cluster_data *data = policy->driver_data;
if (!data->suspended)
aml_cpufreq_set_target(policy, data->exit_index);
destroy_aml_cpufreq_proc_files(policy);
return 0;
}
static int aml_cpufreq_suspend(struct cpufreq_policy *policy)
{
struct cluster_data *data = policy->driver_data;
data->suspended = true;
if (policy->cdev)
dev_set_uevent_suppress(&policy->cdev->device, true);
return cpufreq_generic_suspend(policy);
}
static int aml_cpufreq_resume(struct cpufreq_policy *policy)
{
struct cluster_data *data = policy->driver_data;
data->suspended = false;
if (policy->cdev)
dev_set_uevent_suppress(&policy->cdev->device, false);
return cpufreq_generic_suspend(policy);
}
static struct cpufreq_driver aml_cpufreq_driver = {
.name = "aml-cpufreq",
.flags = CPUFREQ_IS_COOLING_DEV |
CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
CPUFREQ_NEED_INITIAL_FREQ_CHECK,
.verify = aml_cpufreq_verify,
.target_index = aml_cpufreq_set_target,
.get = aml_cpufreq_get_rate,
.init = aml_cpufreq_init,
.exit = aml_cpufreq_exit,
.attr = cpufreq_generic_attr,
.suspend = aml_cpufreq_suspend,
.resume = aml_cpufreq_resume,
.register_em = cpufreq_register_em_with_opp,
};
static int aml_cpuclk_ready(struct device_node *np)
{
int ret;
struct clk *cpuclk;
cpuclk = of_clk_get_by_name(np, CPUCLK);
ret = PTR_ERR_OR_ZERO(cpuclk);
if (ret == -EPROBE_DEFER)
pr_err("cpu clock not ready, retry!\n");
else if (ret)
pr_err("failed to get clock:%d!\n", ret);
else
clk_put(cpuclk);
return ret;
}
static int aml_cpureg_ready(struct device *dev, struct device_node *np, int clusterid)
{
int ret;
struct regulator *cpureg;
char cpureg_name[15] = {0};
if (of_property_read_bool(np, "skip_volt_scaling"))
return 0;
sprintf(cpureg_name, "cluster%d-cpu", clusterid);
cpureg = devm_regulator_get_optional(dev, cpureg_name);
ret = PTR_ERR_OR_ZERO(cpureg);
if (ret == -EPROBE_DEFER)
pr_err("cpu regulator not ready, retry!\n");
else if (ret)
pr_err("failed to get cpu regulator:%d!\n", ret);
else
devm_regulator_put(cpureg);
return ret;
}
static int aml_cpufreq_probe(struct platform_device *pdev)
{
struct proc_dir_entry *root = NULL;
struct device_node *cluster_np;
struct cluster_data *clusterdata;
char cluster_name[10] = {0};
int ret = -1, i = 0, j, count;
struct property *prop;
const __be32 *cur;
while (1) {
sprintf(cluster_name, "cluster%d", i);
cluster_np = of_get_child_by_name(pdev->dev.of_node, cluster_name);
if (!cluster_np)
break;
ret = aml_cpuclk_ready(cluster_np);
if (ret)
return ret;
ret = aml_cpureg_ready(&pdev->dev, cluster_np, i);
if (ret)
return ret;
i++;
}
count = i;
if (count == 0)
goto out;
clusterdata = devm_kmalloc(&pdev->dev, sizeof(*clusterdata) * count, GFP_KERNEL);
if (!clusterdata)
goto out;
root = proc_mkdir("aml_cpufreq", NULL);
if (!root)
goto out;
for (i = 0; i < count; i++) {
sprintf(cluster_name, "cluster%d", i);
cluster_np = of_get_child_by_name(pdev->dev.of_node, cluster_name);
clusterdata[i].clusterid = i;
clusterdata[i].np = cluster_np;
clusterdata[i].dev = &pdev->dev;
clusterdata[i].procroot = root;
/*setup cluster related cpus*/
of_property_for_each_u32(cluster_np, "cluster_cores", prop, cur, j) {
cpumask_set_cpu(j, clusterdata[i].cpus);
pr_info("cpu%d->cluster%d\n", j, clusterdata[i].clusterid);
}
if (!aml_setup_clock(&clusterdata[i]))
goto free;
if (!aml_setup_regulator(&clusterdata[i]))
goto free;
if (!aml_setup_opptable(&clusterdata[i]))
goto free;
aml_validate_cpu_voltage(&clusterdata[i]);
mutex_lock(&cluster_list_lock);
list_add_tail(&clusterdata[i].node, &cluster_list);
mutex_unlock(&cluster_list_lock);
}
ret = cpufreq_register_driver(&aml_cpufreq_driver);
if (ret)
pr_err("%s: Failed to register platform driver, err: %d\n", __func__, ret);
else
goto out;
free:
for (i = 0; i < count; i++) {
kfree(clusterdata[i].opp_table);
kfree(clusterdata[i].dsu_opp_table);
free_regulator(clusterdata[i].cpureg);
free_regulator(clusterdata[i].dsureg);
free_clock(clusterdata[i].cpuclk);
free_clock(clusterdata[i].dsuclk);
}
kfree(clusterdata);
proc_remove(root);
out:
return ret;
}
static void aml_cpufreq_remove(struct platform_device *pdev)
{
cpufreq_unregister_driver(&aml_cpufreq_driver);
}
static const struct of_device_id aml_cpufreq_dt_match[] = {
{ .compatible = "amlogic, aml-cpufreq",
},
{},
};
static struct platform_driver aml_cpufreq_platdrv = {
.driver = {
.name = "cpufreq-aml",
.owner = THIS_MODULE,
.of_match_table = aml_cpufreq_dt_match,
},
.probe = aml_cpufreq_probe,
.remove_new = aml_cpufreq_remove,
};
int __init aml_cpufreq_v2_init(void)
{
return platform_driver_register(&aml_cpufreq_platdrv);
}
void __exit aml_cpufreq_v2_exit(void)
{
platform_driver_unregister(&aml_cpufreq_platdrv);
}