244 lines · c
1/*2 * drivers/cpufreq/spear-cpufreq.c3 *4 * CPU Frequency Scaling for SPEAr platform5 *6 * Copyright (C) 2012 ST Microelectronics7 * Deepak Sikri <deepak.sikri@st.com>8 *9 * This file is licensed under the terms of the GNU General Public10 * License version 2. This program is licensed "as is" without any11 * warranty of any kind, whether express or implied.12 */13 14#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt15 16#include <linux/clk.h>17#include <linux/cpufreq.h>18#include <linux/err.h>19#include <linux/init.h>20#include <linux/module.h>21#include <linux/of.h>22#include <linux/platform_device.h>23#include <linux/slab.h>24#include <linux/types.h>25 26/* SPEAr CPUFreq driver data structure */27static struct {28 struct clk *clk;29 unsigned int transition_latency;30 struct cpufreq_frequency_table *freq_tbl;31 u32 cnt;32} spear_cpufreq;33 34static struct clk *spear1340_cpu_get_possible_parent(unsigned long newfreq)35{36 struct clk *sys_pclk;37 int pclk;38 /*39 * In SPEAr1340, cpu clk's parent sys clk can take input from40 * following sources41 */42 static const char * const sys_clk_src[] = {43 "sys_syn_clk",44 "pll1_clk",45 "pll2_clk",46 "pll3_clk",47 };48 49 /*50 * As sys clk can have multiple source with their own range51 * limitation so we choose possible sources accordingly52 */53 if (newfreq <= 300000000)54 pclk = 0; /* src is sys_syn_clk */55 else if (newfreq > 300000000 && newfreq <= 500000000)56 pclk = 3; /* src is pll3_clk */57 else if (newfreq == 600000000)58 pclk = 1; /* src is pll1_clk */59 else60 return ERR_PTR(-EINVAL);61 62 /* Get parent to sys clock */63 sys_pclk = clk_get(NULL, sys_clk_src[pclk]);64 if (IS_ERR(sys_pclk))65 pr_err("Failed to get %s clock\n", sys_clk_src[pclk]);66 67 return sys_pclk;68}69 70/*71 * In SPEAr1340, we cannot use newfreq directly because we need to actually72 * access a source clock (clk) which might not be ancestor of cpu at present.73 * Hence in SPEAr1340 we would operate on source clock directly before switching74 * cpu clock to it.75 */76static int spear1340_set_cpu_rate(struct clk *sys_pclk, unsigned long newfreq)77{78 struct clk *sys_clk;79 int ret = 0;80 81 sys_clk = clk_get_parent(spear_cpufreq.clk);82 if (IS_ERR(sys_clk)) {83 pr_err("failed to get cpu's parent (sys) clock\n");84 return PTR_ERR(sys_clk);85 }86 87 /* Set the rate of the source clock before changing the parent */88 ret = clk_set_rate(sys_pclk, newfreq);89 if (ret) {90 pr_err("Failed to set sys clk rate to %lu\n", newfreq);91 return ret;92 }93 94 ret = clk_set_parent(sys_clk, sys_pclk);95 if (ret) {96 pr_err("Failed to set sys clk parent\n");97 return ret;98 }99 100 return 0;101}102 103static int spear_cpufreq_target(struct cpufreq_policy *policy,104 unsigned int index)105{106 long newfreq;107 struct clk *srcclk;108 int ret, mult = 1;109 110 newfreq = spear_cpufreq.freq_tbl[index].frequency * 1000;111 112 if (of_machine_is_compatible("st,spear1340")) {113 /*114 * SPEAr1340 is special in the sense that due to the possibility115 * of multiple clock sources for cpu clk's parent we can have116 * different clock source for different frequency of cpu clk.117 * Hence we need to choose one from amongst these possible clock118 * sources.119 */120 srcclk = spear1340_cpu_get_possible_parent(newfreq);121 if (IS_ERR(srcclk)) {122 pr_err("Failed to get src clk\n");123 return PTR_ERR(srcclk);124 }125 126 /* SPEAr1340: src clk is always 2 * intended cpu clk */127 mult = 2;128 } else {129 /*130 * src clock to be altered is ancestor of cpu clock. Hence we131 * can directly work on cpu clk132 */133 srcclk = spear_cpufreq.clk;134 }135 136 newfreq = clk_round_rate(srcclk, newfreq * mult);137 if (newfreq <= 0) {138 pr_err("clk_round_rate failed for cpu src clock\n");139 return newfreq;140 }141 142 if (mult == 2)143 ret = spear1340_set_cpu_rate(srcclk, newfreq);144 else145 ret = clk_set_rate(spear_cpufreq.clk, newfreq);146 147 if (ret)148 pr_err("CPU Freq: cpu clk_set_rate failed: %d\n", ret);149 150 return ret;151}152 153static int spear_cpufreq_init(struct cpufreq_policy *policy)154{155 policy->clk = spear_cpufreq.clk;156 cpufreq_generic_init(policy, spear_cpufreq.freq_tbl,157 spear_cpufreq.transition_latency);158 return 0;159}160 161static struct cpufreq_driver spear_cpufreq_driver = {162 .name = "cpufreq-spear",163 .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK,164 .verify = cpufreq_generic_frequency_table_verify,165 .target_index = spear_cpufreq_target,166 .get = cpufreq_generic_get,167 .init = spear_cpufreq_init,168 .attr = cpufreq_generic_attr,169};170 171static int spear_cpufreq_probe(struct platform_device *pdev)172{173 struct device_node *np;174 struct cpufreq_frequency_table *freq_tbl;175 u32 val;176 int cnt, ret, i = 0;177 178 np = of_cpu_device_node_get(0);179 if (!np) {180 pr_err("No cpu node found\n");181 return -ENODEV;182 }183 184 if (of_property_read_u32(np, "clock-latency",185 &spear_cpufreq.transition_latency))186 spear_cpufreq.transition_latency = CPUFREQ_ETERNAL;187 188 cnt = of_property_count_u32_elems(np, "cpufreq_tbl");189 if (cnt <= 0) {190 pr_err("Invalid cpufreq_tbl\n");191 ret = -ENODEV;192 goto out_put_node;193 }194 195 freq_tbl = kcalloc(cnt + 1, sizeof(*freq_tbl), GFP_KERNEL);196 if (!freq_tbl) {197 ret = -ENOMEM;198 goto out_put_node;199 }200 201 of_property_for_each_u32(np, "cpufreq_tbl", val)202 freq_tbl[i++].frequency = val;203 204 freq_tbl[cnt].frequency = CPUFREQ_TABLE_END;205 206 spear_cpufreq.freq_tbl = freq_tbl;207 208 of_node_put(np);209 210 spear_cpufreq.clk = clk_get(NULL, "cpu_clk");211 if (IS_ERR(spear_cpufreq.clk)) {212 pr_err("Unable to get CPU clock\n");213 ret = PTR_ERR(spear_cpufreq.clk);214 goto out_put_mem;215 }216 217 ret = cpufreq_register_driver(&spear_cpufreq_driver);218 if (!ret)219 return 0;220 221 pr_err("failed register driver: %d\n", ret);222 clk_put(spear_cpufreq.clk);223 224out_put_mem:225 kfree(freq_tbl);226 return ret;227 228out_put_node:229 of_node_put(np);230 return ret;231}232 233static struct platform_driver spear_cpufreq_platdrv = {234 .driver = {235 .name = "spear-cpufreq",236 },237 .probe = spear_cpufreq_probe,238};239module_platform_driver(spear_cpufreq_platdrv);240 241MODULE_AUTHOR("Deepak Sikri <deepak.sikri@st.com>");242MODULE_DESCRIPTION("SPEAr CPUFreq driver");243MODULE_LICENSE("GPL");244