1 /* 2 * Tegra 124 cpufreq driver 3 * 4 * This software is licensed under the terms of the GNU General Public 5 * License version 2, as published by the Free Software Foundation, and 6 * may be copied, distributed, and modified under those terms. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 */ 13 14 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 15 16 #include <linux/clk.h> 17 #include <linux/err.h> 18 #include <linux/init.h> 19 #include <linux/kernel.h> 20 #include <linux/module.h> 21 #include <linux/of_device.h> 22 #include <linux/of.h> 23 #include <linux/platform_device.h> 24 #include <linux/pm_opp.h> 25 #include <linux/regulator/consumer.h> 26 #include <linux/types.h> 27 28 struct tegra124_cpufreq_priv { 29 struct regulator *vdd_cpu_reg; 30 struct clk *cpu_clk; 31 struct clk *pllp_clk; 32 struct clk *pllx_clk; 33 struct clk *dfll_clk; 34 struct platform_device *cpufreq_dt_pdev; 35 }; 36 37 static int tegra124_cpu_switch_to_dfll(struct tegra124_cpufreq_priv *priv) 38 { 39 struct clk *orig_parent; 40 int ret; 41 42 ret = clk_set_rate(priv->dfll_clk, clk_get_rate(priv->cpu_clk)); 43 if (ret) 44 return ret; 45 46 orig_parent = clk_get_parent(priv->cpu_clk); 47 clk_set_parent(priv->cpu_clk, priv->pllp_clk); 48 49 ret = clk_prepare_enable(priv->dfll_clk); 50 if (ret) 51 goto out; 52 53 clk_set_parent(priv->cpu_clk, priv->dfll_clk); 54 55 return 0; 56 57 out: 58 clk_set_parent(priv->cpu_clk, orig_parent); 59 60 return ret; 61 } 62 63 static void tegra124_cpu_switch_to_pllx(struct tegra124_cpufreq_priv *priv) 64 { 65 clk_set_parent(priv->cpu_clk, priv->pllp_clk); 66 clk_disable_unprepare(priv->dfll_clk); 67 regulator_sync_voltage(priv->vdd_cpu_reg); 68 clk_set_parent(priv->cpu_clk, priv->pllx_clk); 69 } 70 71 static int tegra124_cpufreq_probe(struct platform_device *pdev) 72 { 73 struct tegra124_cpufreq_priv *priv; 74 struct device_node *np; 75 struct device *cpu_dev; 76 struct platform_device_info cpufreq_dt_devinfo = {}; 77 int ret; 78 79 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 80 if (!priv) 81 return -ENOMEM; 82 83 cpu_dev = get_cpu_device(0); 84 if (!cpu_dev) 85 return -ENODEV; 86 87 np = of_cpu_device_node_get(0); 88 if (!np) 89 return -ENODEV; 90 91 priv->vdd_cpu_reg = regulator_get(cpu_dev, "vdd-cpu"); 92 if (IS_ERR(priv->vdd_cpu_reg)) { 93 ret = PTR_ERR(priv->vdd_cpu_reg); 94 goto out_put_np; 95 } 96 97 priv->cpu_clk = of_clk_get_by_name(np, "cpu_g"); 98 if (IS_ERR(priv->cpu_clk)) { 99 ret = PTR_ERR(priv->cpu_clk); 100 goto out_put_vdd_cpu_reg; 101 } 102 103 priv->dfll_clk = of_clk_get_by_name(np, "dfll"); 104 if (IS_ERR(priv->dfll_clk)) { 105 ret = PTR_ERR(priv->dfll_clk); 106 goto out_put_cpu_clk; 107 } 108 109 priv->pllx_clk = of_clk_get_by_name(np, "pll_x"); 110 if (IS_ERR(priv->pllx_clk)) { 111 ret = PTR_ERR(priv->pllx_clk); 112 goto out_put_dfll_clk; 113 } 114 115 priv->pllp_clk = of_clk_get_by_name(np, "pll_p"); 116 if (IS_ERR(priv->pllp_clk)) { 117 ret = PTR_ERR(priv->pllp_clk); 118 goto out_put_pllx_clk; 119 } 120 121 ret = tegra124_cpu_switch_to_dfll(priv); 122 if (ret) 123 goto out_put_pllp_clk; 124 125 cpufreq_dt_devinfo.name = "cpufreq-dt"; 126 cpufreq_dt_devinfo.parent = &pdev->dev; 127 128 priv->cpufreq_dt_pdev = 129 platform_device_register_full(&cpufreq_dt_devinfo); 130 if (IS_ERR(priv->cpufreq_dt_pdev)) { 131 ret = PTR_ERR(priv->cpufreq_dt_pdev); 132 goto out_switch_to_pllx; 133 } 134 135 platform_set_drvdata(pdev, priv); 136 137 return 0; 138 139 out_switch_to_pllx: 140 tegra124_cpu_switch_to_pllx(priv); 141 out_put_pllp_clk: 142 clk_put(priv->pllp_clk); 143 out_put_pllx_clk: 144 clk_put(priv->pllx_clk); 145 out_put_dfll_clk: 146 clk_put(priv->dfll_clk); 147 out_put_cpu_clk: 148 clk_put(priv->cpu_clk); 149 out_put_vdd_cpu_reg: 150 regulator_put(priv->vdd_cpu_reg); 151 out_put_np: 152 of_node_put(np); 153 154 return ret; 155 } 156 157 static int tegra124_cpufreq_remove(struct platform_device *pdev) 158 { 159 struct tegra124_cpufreq_priv *priv = platform_get_drvdata(pdev); 160 161 platform_device_unregister(priv->cpufreq_dt_pdev); 162 tegra124_cpu_switch_to_pllx(priv); 163 164 clk_put(priv->pllp_clk); 165 clk_put(priv->pllx_clk); 166 clk_put(priv->dfll_clk); 167 clk_put(priv->cpu_clk); 168 regulator_put(priv->vdd_cpu_reg); 169 170 return 0; 171 } 172 173 static struct platform_driver tegra124_cpufreq_platdrv = { 174 .driver.name = "cpufreq-tegra124", 175 .probe = tegra124_cpufreq_probe, 176 .remove = tegra124_cpufreq_remove, 177 }; 178 179 static int __init tegra_cpufreq_init(void) 180 { 181 int ret; 182 struct platform_device *pdev; 183 184 if (!of_machine_is_compatible("nvidia,tegra124")) 185 return -ENODEV; 186 187 /* 188 * Platform driver+device required for handling EPROBE_DEFER with 189 * the regulator and the DFLL clock 190 */ 191 ret = platform_driver_register(&tegra124_cpufreq_platdrv); 192 if (ret) 193 return ret; 194 195 pdev = platform_device_register_simple("cpufreq-tegra124", -1, NULL, 0); 196 if (IS_ERR(pdev)) { 197 platform_driver_unregister(&tegra124_cpufreq_platdrv); 198 return PTR_ERR(pdev); 199 } 200 201 return 0; 202 } 203 module_init(tegra_cpufreq_init); 204 205 MODULE_AUTHOR("Tuomas Tynkkynen <ttynkkynen@nvidia.com>"); 206 MODULE_DESCRIPTION("cpufreq driver for NVIDIA Tegra124"); 207 MODULE_LICENSE("GPL v2"); 208