/* * 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) { int ret; policy->clk = arm_clk; policy->cur = clk_get_rate(arm_clk) / 1000; policy->suspend_freq = freq_table[0].frequency; ret = cpufreq_generic_init(policy, freq_table, transition_latency); if (ret) { dev_err(cpu_dev, "imx7ulp cpufreq init failed\n"); return ret; } 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");