diff options
Diffstat (limited to 'drivers/cpufreq/imx7ulp-cpufreq.c')
-rw-r--r-- | drivers/cpufreq/imx7ulp-cpufreq.c | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/drivers/cpufreq/imx7ulp-cpufreq.c b/drivers/cpufreq/imx7ulp-cpufreq.c new file mode 100644 index 000000000000..04b0a27c49bf --- /dev/null +++ b/drivers/cpufreq/imx7ulp-cpufreq.c @@ -0,0 +1,261 @@ + /* + * 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/clk.h> +#include <linux/cpu.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/pm_opp.h> +#include <linux/pm_qos.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/suspend.h> + +#define MAX_RUN_FREQ 528000 + +static struct clk *arm_clk; +static struct clk *core_div; +static struct clk *sys_sel; +static struct clk *hsrun_sys_sel; +static struct clk *hsrun_core; +static struct clk *spll_pfd0; +static struct clk *spll_sel; +static struct clk *firc_clk; +static struct clk *spll; + +static struct pm_qos_request pm_qos_hsrun; +static struct regulator *arm_reg; +static struct device *cpu_dev; +static struct cpufreq_frequency_table *freq_table; +static unsigned int transition_latency; + +static int imx7ulp_set_target(struct cpufreq_policy *policy, unsigned int index) +{ + struct dev_pm_opp *opp; + unsigned long freq_hz, volt, volt_old; + unsigned int old_freq, new_freq; + int ret; + + new_freq = freq_table[index].frequency; + freq_hz = new_freq * 1000; + old_freq = clk_get_rate(arm_clk) / 1000; + if (new_freq == 0 || old_freq == 0) + return -EINVAL; + + opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz); + if (IS_ERR(opp)) { + dev_err(cpu_dev, "failed to find OPP for %ld\n", freq_hz); + return PTR_ERR(opp); + } + volt = dev_pm_opp_get_voltage(opp); + dev_pm_opp_put(opp); + + volt_old = regulator_get_voltage(arm_reg); + + dev_dbg(cpu_dev, "%u MHz, %ld mV --> %u MHz, %ld mV\n", + old_freq / 1000, volt_old / 1000, + new_freq / 1000, volt / 1000); + + /* Scaling up? scale voltage before frequency */ + if (new_freq > old_freq) { + ret = regulator_set_voltage_tol(arm_reg, volt, 0); + if (ret) { + dev_err(cpu_dev, "failed to scale vddarm up: %d\n", ret); + return ret; + } + } + + /* before changing pll_arm rate, change the arm_src's soure + * to firc clk first. + */ + if (new_freq > MAX_RUN_FREQ) { + pm_qos_add_request(&pm_qos_hsrun, PM_QOS_CPU_DMA_LATENCY, 0); + /* change the RUN clock to firc */ + clk_set_parent(sys_sel, firc_clk); + /* change the clock rate in HSRUN */ + clk_set_rate(spll, 480000000); + clk_set_rate(spll_pfd0, new_freq * 1000); + clk_set_parent(hsrun_sys_sel, spll_sel); + clk_set_parent(arm_clk, hsrun_core); + } else { + /* change the HSRUN clock to firc */ + clk_set_parent(hsrun_sys_sel, firc_clk); + /* change the clock rate in RUN */ + clk_set_rate(spll, 528000000); + clk_set_rate(spll_pfd0, new_freq * 1000); + clk_set_parent(sys_sel, spll_sel); + clk_set_parent(arm_clk, core_div); + if (old_freq > MAX_RUN_FREQ) + pm_qos_remove_request(&pm_qos_hsrun); + } + + /* scaling down? scaling voltage after frequency */ + if (new_freq < old_freq) { + ret = regulator_set_voltage_tol(arm_reg, volt, 0); + if (ret) { + dev_warn(cpu_dev, "failed to scale vddarm down: %d\n", ret); + ret = 0; + } + } + + return 0; +} + +static int imx7ulp_cpufreq_init(struct cpufreq_policy *policy) +{ + policy->clk = arm_clk; + policy->cur = clk_get_rate(arm_clk) / 1000; + policy->suspend_freq = freq_table[0].frequency; + + cpufreq_generic_init(policy, freq_table, transition_latency); + + return 0; +} + +static struct cpufreq_driver imx7ulp_cpufreq_driver = { + .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK, + .verify = cpufreq_generic_frequency_table_verify, + .target_index = imx7ulp_set_target, + .get = cpufreq_generic_get, + .init = imx7ulp_cpufreq_init, + .name = "imx7ulp-cpufreq", + .attr = cpufreq_generic_attr, +#ifdef CONFIG_PM + .suspend = cpufreq_generic_suspend, +#endif +}; + +static int imx7ulp_cpufreq_probe(struct platform_device *pdev) +{ + struct device_node *np; + int ret; + + cpu_dev = get_cpu_device(0); + if (!cpu_dev) { + pr_err("failed to get cpu0 device\n"); + return -ENOENT; + } + + np = of_node_get(cpu_dev->of_node); + if (!np) { + dev_err(cpu_dev, "failed to find the cpu0 node\n"); + return -ENOENT; + } + + arm_clk = clk_get(cpu_dev, "arm"); + sys_sel = clk_get(cpu_dev, "sys_sel"); + core_div = clk_get(cpu_dev, "core_div"); + hsrun_sys_sel = clk_get(cpu_dev, "hsrun_sys_sel"); + hsrun_core = clk_get(cpu_dev, "hsrun_core"); + spll_pfd0 = clk_get(cpu_dev, "spll_pfd0"); + spll_sel = clk_get(cpu_dev, "spll_sel"); + firc_clk = clk_get(cpu_dev, "firc"); + spll = clk_get(cpu_dev, "spll"); + + if (IS_ERR(arm_clk) || IS_ERR(sys_sel) || IS_ERR(spll_sel) || + IS_ERR(spll_sel) || IS_ERR(firc_clk) || IS_ERR(hsrun_sys_sel) || + IS_ERR(hsrun_core) || IS_ERR(spll)) { + dev_err(cpu_dev, "failed to get cpu clock\n"); + ret = -ENOENT; + goto put_clk; + } + + arm_reg = regulator_get(cpu_dev, "arm"); + if (IS_ERR(arm_reg)) { + if (PTR_ERR(arm_reg) != -EPROBE_DEFER) + dev_err(cpu_dev, "failed to get regulator\n"); + ret = PTR_ERR(arm_reg); + goto put_clk; + } + + 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_reg; + } + + ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table); + if (ret) { + dev_err(cpu_dev, "failed to init cpufreq table\n"); + goto put_reg; + } + + if (of_property_read_u32(np, "clock-latency", &transition_latency)) + transition_latency = CPUFREQ_ETERNAL; + + ret = cpufreq_register_driver(&imx7ulp_cpufreq_driver); + if (ret) { + dev_err(cpu_dev, "failed to register driver\n"); + goto free_opp_table; + } + + return 0; + +free_opp_table: + dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); +put_reg: + regulator_put(arm_reg); +put_clk: + if (!IS_ERR(arm_clk)) + clk_put(arm_clk); + if (!IS_ERR(sys_sel)) + clk_put(sys_sel); + if (!IS_ERR(core_div)) + clk_put(core_div); + if (!IS_ERR(hsrun_sys_sel)) + clk_put(hsrun_sys_sel); + if (!IS_ERR(hsrun_core)) + clk_put(hsrun_core); + if (!IS_ERR(spll_pfd0)) + clk_put(spll_pfd0); + if (!IS_ERR(spll_sel)) + clk_put(spll_sel); + if (!IS_ERR(firc_clk)) + clk_put(firc_clk); + if (!IS_ERR(spll)) + clk_put(spll); + + return ret; +} + +static int imx7ulp_cpufreq_remove(struct platform_device *pdev) +{ + cpufreq_unregister_driver(&imx7ulp_cpufreq_driver); + dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); + + regulator_put(arm_reg); + clk_put(arm_clk); + clk_put(sys_sel); + clk_put(core_div); + clk_put(hsrun_sys_sel); + clk_put(hsrun_core); + clk_put(spll_pfd0); + clk_put(spll_sel); + clk_put(firc_clk); + clk_put(spll); + + return 0; +} + +static struct platform_driver imx7ulp_cpufreq_platdrv = { + .driver = { + .name = "imx7ulp-cpufreq", + .owner = THIS_MODULE, + }, + .probe = imx7ulp_cpufreq_probe, + .remove = imx7ulp_cpufreq_remove, +}; + +module_platform_driver(imx7ulp_cpufreq_platdrv); + +MODULE_DESCRIPTION("NXP i.MX7ULP cpufreq driver"); +MODULE_LICENSE("GPL v2"); + |