From 97e757069da7101a54ab3ea6ad7c14656098e575 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Thu, 8 Aug 2019 08:06:09 +0800 Subject: [PATCH] cpufreq: Add i.MX7ULP cpufreq support Add i.MX7ULP cpufreq driver support. Signed-off-by: Anson Huang --- drivers/cpufreq/Kconfig.arm | 8 + drivers/cpufreq/Makefile | 1 + drivers/cpufreq/imx7ulp-cpufreq.c | 260 ++++++++++++++++++++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 drivers/cpufreq/imx7ulp-cpufreq.c diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index a905796f7f85..211a4fce638b 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -114,6 +114,14 @@ config ARM_IMX_CPUFREQ_DT If in doubt, say N. +config ARM_IMX7ULP_CPUFREQ + tristate "NXP i.MX7ULP cpufreq support" + depends on ARCH_MXC + help + This adds cpufreq driver support for NXP i.MX7ULP series SoCs. + + If in doubt, say N. + config ARM_KIRKWOOD_CPUFREQ def_bool MACH_KIRKWOOD help diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 9a9f5ccd13d9..f6fdc4e4014c 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_ARCH_DAVINCI) += davinci-cpufreq.o obj-$(CONFIG_ARM_HIGHBANK_CPUFREQ) += highbank-cpufreq.o obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o obj-$(CONFIG_ARM_IMX_CPUFREQ_DT) += imx-cpufreq-dt.o +obj-$(CONFIG_ARM_IMX7ULP_CPUFREQ) += imx7ulp-cpufreq.o obj-$(CONFIG_ARM_KIRKWOOD_CPUFREQ) += kirkwood-cpufreq.o obj-$(CONFIG_ARM_MEDIATEK_CPUFREQ) += mediatek-cpufreq.o obj-$(CONFIG_MACH_MVEBU_V7) += mvebu-cpufreq.o diff --git a/drivers/cpufreq/imx7ulp-cpufreq.c b/drivers/cpufreq/imx7ulp-cpufreq.c new file mode 100644 index 000000000000..43a9b9520ae9 --- /dev/null +++ b/drivers/cpufreq/imx7ulp-cpufreq.c @@ -0,0 +1,260 @@ + /* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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)) { + dev_err(cpu_dev, "failed to get regulator\n"); + ret = -ENOENT; + goto put_reg; + } + + 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"); +