1 /* 2 * Copyright (C) 2010 Google, Inc. 3 * 4 * Author: 5 * Colin Cross <ccross@google.com> 6 * Based on arch/arm/plat-omap/cpu-omap.c, (C) 2005 Nokia Corporation 7 * 8 * This software is licensed under the terms of the GNU General Public 9 * License version 2, as published by the Free Software Foundation, and 10 * may be copied, distributed, and modified under those terms. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 */ 18 19 #include <linux/clk.h> 20 #include <linux/cpufreq.h> 21 #include <linux/err.h> 22 #include <linux/init.h> 23 #include <linux/module.h> 24 #include <linux/platform_device.h> 25 #include <linux/types.h> 26 27 static struct cpufreq_frequency_table freq_table[] = { 28 { .frequency = 216000 }, 29 { .frequency = 312000 }, 30 { .frequency = 456000 }, 31 { .frequency = 608000 }, 32 { .frequency = 760000 }, 33 { .frequency = 816000 }, 34 { .frequency = 912000 }, 35 { .frequency = 1000000 }, 36 { .frequency = CPUFREQ_TABLE_END }, 37 }; 38 39 struct tegra20_cpufreq { 40 struct device *dev; 41 struct cpufreq_driver driver; 42 struct clk *cpu_clk; 43 struct clk *pll_x_clk; 44 struct clk *pll_p_clk; 45 bool pll_x_prepared; 46 }; 47 48 static unsigned int tegra_get_intermediate(struct cpufreq_policy *policy, 49 unsigned int index) 50 { 51 struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data(); 52 unsigned int ifreq = clk_get_rate(cpufreq->pll_p_clk) / 1000; 53 54 /* 55 * Don't switch to intermediate freq if: 56 * - we are already at it, i.e. policy->cur == ifreq 57 * - index corresponds to ifreq 58 */ 59 if (freq_table[index].frequency == ifreq || policy->cur == ifreq) 60 return 0; 61 62 return ifreq; 63 } 64 65 static int tegra_target_intermediate(struct cpufreq_policy *policy, 66 unsigned int index) 67 { 68 struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data(); 69 int ret; 70 71 /* 72 * Take an extra reference to the main pll so it doesn't turn 73 * off when we move the cpu off of it as enabling it again while we 74 * switch to it from tegra_target() would take additional time. 75 * 76 * When target-freq is equal to intermediate freq we don't need to 77 * switch to an intermediate freq and so this routine isn't called. 78 * Also, we wouldn't be using pll_x anymore and must not take extra 79 * reference to it, as it can be disabled now to save some power. 80 */ 81 clk_prepare_enable(cpufreq->pll_x_clk); 82 83 ret = clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_p_clk); 84 if (ret) 85 clk_disable_unprepare(cpufreq->pll_x_clk); 86 else 87 cpufreq->pll_x_prepared = true; 88 89 return ret; 90 } 91 92 static int tegra_target(struct cpufreq_policy *policy, unsigned int index) 93 { 94 struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data(); 95 unsigned long rate = freq_table[index].frequency; 96 unsigned int ifreq = clk_get_rate(cpufreq->pll_p_clk) / 1000; 97 int ret; 98 99 /* 100 * target freq == pll_p, don't need to take extra reference to pll_x_clk 101 * as it isn't used anymore. 102 */ 103 if (rate == ifreq) 104 return clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_p_clk); 105 106 ret = clk_set_rate(cpufreq->pll_x_clk, rate * 1000); 107 /* Restore to earlier frequency on error, i.e. pll_x */ 108 if (ret) 109 dev_err(cpufreq->dev, "Failed to change pll_x to %lu\n", rate); 110 111 ret = clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_x_clk); 112 /* This shouldn't fail while changing or restoring */ 113 WARN_ON(ret); 114 115 /* 116 * Drop count to pll_x clock only if we switched to intermediate freq 117 * earlier while transitioning to a target frequency. 118 */ 119 if (cpufreq->pll_x_prepared) { 120 clk_disable_unprepare(cpufreq->pll_x_clk); 121 cpufreq->pll_x_prepared = false; 122 } 123 124 return ret; 125 } 126 127 static int tegra_cpu_init(struct cpufreq_policy *policy) 128 { 129 struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data(); 130 int ret; 131 132 clk_prepare_enable(cpufreq->cpu_clk); 133 134 /* FIXME: what's the actual transition time? */ 135 ret = cpufreq_generic_init(policy, freq_table, 300 * 1000); 136 if (ret) { 137 clk_disable_unprepare(cpufreq->cpu_clk); 138 return ret; 139 } 140 141 policy->clk = cpufreq->cpu_clk; 142 policy->suspend_freq = freq_table[0].frequency; 143 return 0; 144 } 145 146 static int tegra_cpu_exit(struct cpufreq_policy *policy) 147 { 148 struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data(); 149 150 clk_disable_unprepare(cpufreq->cpu_clk); 151 return 0; 152 } 153 154 static int tegra20_cpufreq_probe(struct platform_device *pdev) 155 { 156 struct tegra20_cpufreq *cpufreq; 157 int err; 158 159 cpufreq = devm_kzalloc(&pdev->dev, sizeof(*cpufreq), GFP_KERNEL); 160 if (!cpufreq) 161 return -ENOMEM; 162 163 cpufreq->cpu_clk = clk_get_sys(NULL, "cclk"); 164 if (IS_ERR(cpufreq->cpu_clk)) 165 return PTR_ERR(cpufreq->cpu_clk); 166 167 cpufreq->pll_x_clk = clk_get_sys(NULL, "pll_x"); 168 if (IS_ERR(cpufreq->pll_x_clk)) { 169 err = PTR_ERR(cpufreq->pll_x_clk); 170 goto put_cpu; 171 } 172 173 cpufreq->pll_p_clk = clk_get_sys(NULL, "pll_p"); 174 if (IS_ERR(cpufreq->pll_p_clk)) { 175 err = PTR_ERR(cpufreq->pll_p_clk); 176 goto put_pll_x; 177 } 178 179 cpufreq->dev = &pdev->dev; 180 cpufreq->driver.get = cpufreq_generic_get; 181 cpufreq->driver.attr = cpufreq_generic_attr; 182 cpufreq->driver.init = tegra_cpu_init; 183 cpufreq->driver.exit = tegra_cpu_exit; 184 cpufreq->driver.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK; 185 cpufreq->driver.verify = cpufreq_generic_frequency_table_verify; 186 cpufreq->driver.suspend = cpufreq_generic_suspend; 187 cpufreq->driver.driver_data = cpufreq; 188 cpufreq->driver.target_index = tegra_target; 189 cpufreq->driver.get_intermediate = tegra_get_intermediate; 190 cpufreq->driver.target_intermediate = tegra_target_intermediate; 191 snprintf(cpufreq->driver.name, CPUFREQ_NAME_LEN, "tegra"); 192 193 err = cpufreq_register_driver(&cpufreq->driver); 194 if (err) 195 goto put_pll_p; 196 197 platform_set_drvdata(pdev, cpufreq); 198 199 return 0; 200 201 put_pll_p: 202 clk_put(cpufreq->pll_p_clk); 203 put_pll_x: 204 clk_put(cpufreq->pll_x_clk); 205 put_cpu: 206 clk_put(cpufreq->cpu_clk); 207 208 return err; 209 } 210 211 static int tegra20_cpufreq_remove(struct platform_device *pdev) 212 { 213 struct tegra20_cpufreq *cpufreq = platform_get_drvdata(pdev); 214 215 cpufreq_unregister_driver(&cpufreq->driver); 216 217 clk_put(cpufreq->pll_p_clk); 218 clk_put(cpufreq->pll_x_clk); 219 clk_put(cpufreq->cpu_clk); 220 221 return 0; 222 } 223 224 static struct platform_driver tegra20_cpufreq_driver = { 225 .probe = tegra20_cpufreq_probe, 226 .remove = tegra20_cpufreq_remove, 227 .driver = { 228 .name = "tegra20-cpufreq", 229 }, 230 }; 231 module_platform_driver(tegra20_cpufreq_driver); 232 233 MODULE_ALIAS("platform:tegra20-cpufreq"); 234 MODULE_AUTHOR("Colin Cross <ccross@android.com>"); 235 MODULE_DESCRIPTION("NVIDIA Tegra20 cpufreq driver"); 236 MODULE_LICENSE("GPL"); 237