blob: 5cefb4500303df504baef2ef2308d0bfc41a6be1 [file] [log] [blame]
/*
* Copyright 2017 NXP
*
* 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/arm-smccc.h>
#include <linux/clk.h>
#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <linux/cpu_cooling.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm_opp.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/syscalls.h>
#include <soc/imx/fsl_sip.h>
#define MAX_CLUSTER_NUM 2
static struct delayed_work cpufreq_governor_daemon;
static DEFINE_SPINLOCK(cpufreq_psci_lock);
struct imx8_cpufreq {
struct clk *cpu_clk;
};
struct imx8_cpufreq cluster_freq[MAX_CLUSTER_NUM];
static struct cpufreq_frequency_table *freq_table[MAX_CLUSTER_NUM];
static unsigned int transition_latency[MAX_CLUSTER_NUM];
struct device *cpu_dev;
static struct thermal_cooling_device *cdev[2];
static void cpufreq_governor_daemon_handler(struct work_struct *work)
{
int fd, i;
unsigned char cluster_governor[MAX_CLUSTER_NUM][54] = {
"/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor",
"",
};
/* generate second cluster's cpufreq governor path */
sprintf(cluster_governor[MAX_CLUSTER_NUM - 1],
"%s%d%s", "/sys/devices/system/cpu/cpu", num_online_cpus() - 1,
"/cpufreq/scaling_governor");
for (i = 0; i < MAX_CLUSTER_NUM; i++) {
fd = sys_open((const char __user __force *)cluster_governor[i],
O_RDWR, 0700);
if (fd >= 0) {
sys_write(fd, "schedutil", strlen("schedutil"));
sys_close(fd);
pr_info("switch cluster %d cpu-freq governor to schedutil\n",
i);
} else {
/* re-schedule if sys write is NOT ready */
schedule_delayed_work(&cpufreq_governor_daemon,
msecs_to_jiffies(3000));
break;
}
}
}
static int imx8_set_target(struct cpufreq_policy *policy, unsigned int index)
{
struct arm_smccc_res res;
unsigned int old_freq, new_freq;
unsigned int cluster_id = topology_physical_package_id(policy->cpu);
new_freq = freq_table[cluster_id][index].frequency;
old_freq = policy->cur;
dev_dbg(cpu_dev, "%u MHz --> %u MHz\n",
old_freq / 1000, new_freq / 1000);
spin_lock(&cpufreq_psci_lock);
arm_smccc_smc(FSL_SIP_CPUFREQ, FSL_SIP_SET_CPUFREQ,
cluster_id, new_freq * 1000, 0, 0, 0, 0, &res);
spin_unlock(&cpufreq_psci_lock);
/*
* As we can only set CPU clock rate in ATF, clock
* framework does NOT know CPU clock rate is changed,
* so here do clk_get_rate once to update CPU clock
* rate, otherwise cat /sys/kernel/debug/clk/xxx/clk_rate
* will return incorrect rate as it does NOT do a
* recalculation.
*/
clk_get_rate(cluster_freq[cluster_id].cpu_clk);
return 0;
}
static int imx8_cpufreq_init(struct cpufreq_policy *policy)
{
int cluster_id = topology_physical_package_id(policy->cpu);
int ret = 0;
policy->clk = cluster_freq[cluster_id].cpu_clk;
policy->cur = clk_get_rate(cluster_freq[cluster_id].cpu_clk) / 1000;
/*
* The driver only supports the SMP configuartion where all processors
* share the clock and voltage and clock.
*/
cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu));
ret = cpufreq_table_validate_and_show(policy, freq_table[cluster_id]);
if (ret) {
pr_err("%s: invalid frequency table: %d\n", __func__, ret);
return ret;
}
policy->cpuinfo.transition_latency = transition_latency[cluster_id];
policy->suspend_freq = policy->max;
pr_info("%s: cluster %d running at freq %d MHz, suspend freq %d MHz\n",
__func__, cluster_id, policy->cur / 1000,
policy->suspend_freq / 1000);
return ret;
}
static void imx8_cpufreq_ready(struct cpufreq_policy *policy)
{
struct device_node *np = of_get_cpu_node(policy->cpu, NULL);
unsigned int cluster_id = topology_physical_package_id(policy->cpu);
if (of_find_property(np, "#cooling-cells", NULL)) {
cdev[cluster_id] = of_cpufreq_cooling_register(np, policy);
if (IS_ERR(cdev[cluster_id]) && PTR_ERR(cdev[cluster_id]) != -ENOSYS) {
pr_err("cpu%d is not running as cooling device: %ld\n",
policy->cpu, PTR_ERR(cdev[cluster_id]));
cdev[cluster_id] = NULL;
}
}
of_node_put(np);
}
static struct cpufreq_driver imx8_cpufreq_driver = {
.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK,
.verify = cpufreq_generic_frequency_table_verify,
.target_index = imx8_set_target,
.get = cpufreq_generic_get,
.init = imx8_cpufreq_init,
.name = "imx8-cpufreq",
.attr = cpufreq_generic_attr,
.ready = imx8_cpufreq_ready,
#ifdef CONFIG_PM
.suspend = cpufreq_generic_suspend,
#endif
};
static int imx8_cpufreq_probe(struct platform_device *pdev)
{
struct device_node *np;
int ret = 0;
int i, cluster_id;
struct device *first_cpu_dev = NULL;
cpu_dev = get_cpu_device(0);
if (!cpu_dev) {
pr_err("failed to get cpu device 0\n");
return -ENODEV;
}
np = of_node_get(cpu_dev->of_node);
if (!np) {
pr_warn("failed to find cpu 0 node\n");
return -ENODEV;
}
ret = dev_pm_opp_of_add_table(cpu_dev);
if (ret < 0) {
dev_err(cpu_dev, "failed to init OPP table: %d\n", ret);
goto put_node;
}
cluster_id = topology_physical_package_id(0);
cluster_freq[cluster_id].cpu_clk = devm_clk_get(cpu_dev, NULL);
if (IS_ERR(cluster_freq[cluster_id].cpu_clk)) {
dev_err(cpu_dev, "failed to get cluster %d clock\n", cluster_id);
ret = -ENOENT;
goto put_node;
}
ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table[cluster_id]);
if (ret) {
dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
goto out_free_opp;
}
if (of_property_read_u32(np, "clock-latency", &transition_latency[cluster_id]))
transition_latency[cluster_id] = CPUFREQ_ETERNAL;
/* init next cluster if there is */
for (i = 1; i < num_online_cpus(); i++) {
if (topology_physical_package_id(i) == topology_physical_package_id(0))
continue;
INIT_DELAYED_WORK(&cpufreq_governor_daemon,
cpufreq_governor_daemon_handler);
schedule_delayed_work(&cpufreq_governor_daemon,
msecs_to_jiffies(3000));
first_cpu_dev = cpu_dev;
cpu_dev = get_cpu_device(i);
if (!cpu_dev) {
pr_err("failed to get cpu device %d\n", i);
return -ENODEV;
}
np = of_node_get(cpu_dev->of_node);
if (!np) {
pr_warn("failed to find cpu %d node\n", i);
ret = -ENODEV;
goto put_node;
}
cluster_id = topology_physical_package_id(i);
cluster_freq[cluster_id].cpu_clk = devm_clk_get(cpu_dev, NULL);
if (IS_ERR(cluster_freq[cluster_id].cpu_clk)) {
dev_err(cpu_dev, "failed to get cluster %d clock\n", cluster_id);
ret = -ENOENT;
goto put_node;
}
ret = dev_pm_opp_of_add_table(cpu_dev);
if (ret < 0) {
dev_err(cpu_dev, "failed to init OPP table: %d\n", ret);
goto put_node;
}
ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table[cluster_id]);
if (ret) {
dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
goto out_free_opp;
}
if (of_property_read_u32(np, "clock-latency", &transition_latency[cluster_id]))
transition_latency[cluster_id] = CPUFREQ_ETERNAL;
break;
}
ret = cpufreq_register_driver(&imx8_cpufreq_driver);
if (ret) {
dev_err(cpu_dev, "failed register driver: %d\n", ret);
if (cluster_id > 0 && first_cpu_dev != NULL) {
dev_pm_opp_free_cpufreq_table(first_cpu_dev, &freq_table[0]);
dev_pm_opp_of_remove_table(first_cpu_dev);
}
goto free_freq_table;
}
of_node_put(np);
return 0;
free_freq_table:
dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table[cluster_id]);
out_free_opp:
dev_pm_opp_of_remove_table(cpu_dev);
put_node:
of_node_put(np);
return ret;
}
static int imx8_cpufreq_remove(struct platform_device *pdev)
{
cpufreq_unregister_driver(&imx8_cpufreq_driver);
return 0;
}
static struct platform_driver imx8_cpufreq_platdrv = {
.driver = {
.name = "imx8-cpufreq",
},
.probe = imx8_cpufreq_probe,
.remove = imx8_cpufreq_remove,
};
module_platform_driver(imx8_cpufreq_platdrv);
MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
MODULE_DESCRIPTION("NXP i.MX8 cpufreq driver");
MODULE_LICENSE("GPL");