tegra124-cpufreq.c (5023B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Tegra 124 cpufreq driver 4 */ 5 6#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 7 8#include <linux/clk.h> 9#include <linux/cpufreq.h> 10#include <linux/err.h> 11#include <linux/init.h> 12#include <linux/kernel.h> 13#include <linux/module.h> 14#include <linux/of_device.h> 15#include <linux/of.h> 16#include <linux/platform_device.h> 17#include <linux/pm_opp.h> 18#include <linux/types.h> 19 20struct tegra124_cpufreq_priv { 21 struct clk *cpu_clk; 22 struct clk *pllp_clk; 23 struct clk *pllx_clk; 24 struct clk *dfll_clk; 25 struct platform_device *cpufreq_dt_pdev; 26}; 27 28static int tegra124_cpu_switch_to_dfll(struct tegra124_cpufreq_priv *priv) 29{ 30 struct clk *orig_parent; 31 int ret; 32 33 ret = clk_set_rate(priv->dfll_clk, clk_get_rate(priv->cpu_clk)); 34 if (ret) 35 return ret; 36 37 orig_parent = clk_get_parent(priv->cpu_clk); 38 clk_set_parent(priv->cpu_clk, priv->pllp_clk); 39 40 ret = clk_prepare_enable(priv->dfll_clk); 41 if (ret) 42 goto out; 43 44 clk_set_parent(priv->cpu_clk, priv->dfll_clk); 45 46 return 0; 47 48out: 49 clk_set_parent(priv->cpu_clk, orig_parent); 50 51 return ret; 52} 53 54static int tegra124_cpufreq_probe(struct platform_device *pdev) 55{ 56 struct tegra124_cpufreq_priv *priv; 57 struct device_node *np; 58 struct device *cpu_dev; 59 struct platform_device_info cpufreq_dt_devinfo = {}; 60 int ret; 61 62 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 63 if (!priv) 64 return -ENOMEM; 65 66 cpu_dev = get_cpu_device(0); 67 if (!cpu_dev) 68 return -ENODEV; 69 70 np = of_cpu_device_node_get(0); 71 if (!np) 72 return -ENODEV; 73 74 priv->cpu_clk = of_clk_get_by_name(np, "cpu_g"); 75 if (IS_ERR(priv->cpu_clk)) { 76 ret = PTR_ERR(priv->cpu_clk); 77 goto out_put_np; 78 } 79 80 priv->dfll_clk = of_clk_get_by_name(np, "dfll"); 81 if (IS_ERR(priv->dfll_clk)) { 82 ret = PTR_ERR(priv->dfll_clk); 83 goto out_put_cpu_clk; 84 } 85 86 priv->pllx_clk = of_clk_get_by_name(np, "pll_x"); 87 if (IS_ERR(priv->pllx_clk)) { 88 ret = PTR_ERR(priv->pllx_clk); 89 goto out_put_dfll_clk; 90 } 91 92 priv->pllp_clk = of_clk_get_by_name(np, "pll_p"); 93 if (IS_ERR(priv->pllp_clk)) { 94 ret = PTR_ERR(priv->pllp_clk); 95 goto out_put_pllx_clk; 96 } 97 98 ret = tegra124_cpu_switch_to_dfll(priv); 99 if (ret) 100 goto out_put_pllp_clk; 101 102 cpufreq_dt_devinfo.name = "cpufreq-dt"; 103 cpufreq_dt_devinfo.parent = &pdev->dev; 104 105 priv->cpufreq_dt_pdev = 106 platform_device_register_full(&cpufreq_dt_devinfo); 107 if (IS_ERR(priv->cpufreq_dt_pdev)) { 108 ret = PTR_ERR(priv->cpufreq_dt_pdev); 109 goto out_put_pllp_clk; 110 } 111 112 platform_set_drvdata(pdev, priv); 113 114 of_node_put(np); 115 116 return 0; 117 118out_put_pllp_clk: 119 clk_put(priv->pllp_clk); 120out_put_pllx_clk: 121 clk_put(priv->pllx_clk); 122out_put_dfll_clk: 123 clk_put(priv->dfll_clk); 124out_put_cpu_clk: 125 clk_put(priv->cpu_clk); 126out_put_np: 127 of_node_put(np); 128 129 return ret; 130} 131 132static int __maybe_unused tegra124_cpufreq_suspend(struct device *dev) 133{ 134 struct tegra124_cpufreq_priv *priv = dev_get_drvdata(dev); 135 int err; 136 137 /* 138 * PLLP rate 408Mhz is below the CPU Fmax at Vmin and is safe to 139 * use during suspend and resume. So, switch the CPU clock source 140 * to PLLP and disable DFLL. 141 */ 142 err = clk_set_parent(priv->cpu_clk, priv->pllp_clk); 143 if (err < 0) { 144 dev_err(dev, "failed to reparent to PLLP: %d\n", err); 145 return err; 146 } 147 148 clk_disable_unprepare(priv->dfll_clk); 149 150 return 0; 151} 152 153static int __maybe_unused tegra124_cpufreq_resume(struct device *dev) 154{ 155 struct tegra124_cpufreq_priv *priv = dev_get_drvdata(dev); 156 int err; 157 158 /* 159 * Warmboot code powers up the CPU with PLLP clock source. 160 * Enable DFLL clock and switch CPU clock source back to DFLL. 161 */ 162 err = clk_prepare_enable(priv->dfll_clk); 163 if (err < 0) { 164 dev_err(dev, "failed to enable DFLL clock for CPU: %d\n", err); 165 goto disable_cpufreq; 166 } 167 168 err = clk_set_parent(priv->cpu_clk, priv->dfll_clk); 169 if (err < 0) { 170 dev_err(dev, "failed to reparent to DFLL clock: %d\n", err); 171 goto disable_dfll; 172 } 173 174 return 0; 175 176disable_dfll: 177 clk_disable_unprepare(priv->dfll_clk); 178disable_cpufreq: 179 disable_cpufreq(); 180 181 return err; 182} 183 184static const struct dev_pm_ops tegra124_cpufreq_pm_ops = { 185 SET_SYSTEM_SLEEP_PM_OPS(tegra124_cpufreq_suspend, 186 tegra124_cpufreq_resume) 187}; 188 189static struct platform_driver tegra124_cpufreq_platdrv = { 190 .driver.name = "cpufreq-tegra124", 191 .driver.pm = &tegra124_cpufreq_pm_ops, 192 .probe = tegra124_cpufreq_probe, 193}; 194 195static int __init tegra_cpufreq_init(void) 196{ 197 int ret; 198 struct platform_device *pdev; 199 200 if (!(of_machine_is_compatible("nvidia,tegra124") || 201 of_machine_is_compatible("nvidia,tegra210"))) 202 return -ENODEV; 203 204 /* 205 * Platform driver+device required for handling EPROBE_DEFER with 206 * the regulator and the DFLL clock 207 */ 208 ret = platform_driver_register(&tegra124_cpufreq_platdrv); 209 if (ret) 210 return ret; 211 212 pdev = platform_device_register_simple("cpufreq-tegra124", -1, NULL, 0); 213 if (IS_ERR(pdev)) { 214 platform_driver_unregister(&tegra124_cpufreq_platdrv); 215 return PTR_ERR(pdev); 216 } 217 218 return 0; 219} 220module_init(tegra_cpufreq_init); 221 222MODULE_AUTHOR("Tuomas Tynkkynen <ttynkkynen@nvidia.com>"); 223MODULE_DESCRIPTION("cpufreq driver for NVIDIA Tegra124"); 224MODULE_LICENSE("GPL v2");