Commit | Line | Data |
---|---|---|
e6c91b64 | 1 | /* |
96f1050d | 2 | * Blackfin core clock scaling |
e6c91b64 | 3 | * |
8944b5a2 | 4 | * Copyright 2008-2011 Analog Devices Inc. |
e6c91b64 | 5 | * |
96f1050d | 6 | * Licensed under the GPL-2 or later. |
e6c91b64 MH |
7 | */ |
8 | ||
9 | #include <linux/kernel.h> | |
6a550b99 | 10 | #include <linux/module.h> |
e6c91b64 MH |
11 | #include <linux/types.h> |
12 | #include <linux/init.h> | |
96900315 | 13 | #include <linux/clk.h> |
e6c91b64 MH |
14 | #include <linux/cpufreq.h> |
15 | #include <linux/fs.h> | |
7998a878 | 16 | #include <linux/delay.h> |
e6c91b64 MH |
17 | #include <asm/blackfin.h> |
18 | #include <asm/time.h> | |
761ec44a | 19 | #include <asm/dpmc.h> |
e6c91b64 | 20 | |
96900315 | 21 | |
e6c91b64 | 22 | /* this is the table of CCLK frequencies, in Hz */ |
50701588 | 23 | /* .driver_data is the entry in the auxiliary dpm_state_table[] */ |
e6c91b64 MH |
24 | static struct cpufreq_frequency_table bfin_freq_table[] = { |
25 | { | |
26 | .frequency = CPUFREQ_TABLE_END, | |
50701588 | 27 | .driver_data = 0, |
e6c91b64 MH |
28 | }, |
29 | { | |
30 | .frequency = CPUFREQ_TABLE_END, | |
50701588 | 31 | .driver_data = 1, |
e6c91b64 MH |
32 | }, |
33 | { | |
34 | .frequency = CPUFREQ_TABLE_END, | |
50701588 | 35 | .driver_data = 2, |
e6c91b64 MH |
36 | }, |
37 | { | |
38 | .frequency = CPUFREQ_TABLE_END, | |
50701588 | 39 | .driver_data = 0, |
e6c91b64 MH |
40 | }, |
41 | }; | |
42 | ||
43 | static struct bfin_dpm_state { | |
44 | unsigned int csel; /* system clock divider */ | |
45 | unsigned int tscale; /* change the divider on the core timer interrupt */ | |
46 | } dpm_state_table[3]; | |
47 | ||
6c2b7072 | 48 | #if defined(CONFIG_CYCLES_CLOCKSOURCE) |
1bfb4b21 | 49 | /* |
8944b5a2 | 50 | * normalized to maximum frequency offset for CYCLES, |
6c2b7072 GY |
51 | * used in time-ts cycles clock source, but could be used |
52 | * somewhere also. | |
1bfb4b21 VM |
53 | */ |
54 | unsigned long long __bfin_cycles_off; | |
55 | unsigned int __bfin_cycles_mod; | |
6c2b7072 | 56 | #endif |
1bfb4b21 | 57 | |
e6c91b64 | 58 | /**************************************************************************/ |
6c2b7072 GY |
59 | static void __init bfin_init_tables(unsigned long cclk, unsigned long sclk) |
60 | { | |
e6c91b64 | 61 | |
6c2b7072 GY |
62 | unsigned long csel, min_cclk; |
63 | int index; | |
64 | ||
65 | /* Anomaly 273 seems to still exist on non-BF54x w/dcache turned on */ | |
66 | #if ANOMALY_05000273 || ANOMALY_05000274 || \ | |
7c7d0277 SZ |
67 | (!(defined(CONFIG_BF54x) || defined(CONFIG_BF60x)) \ |
68 | && defined(CONFIG_BFIN_EXTMEM_DCACHEABLE)) | |
6c2b7072 GY |
69 | min_cclk = sclk * 2; |
70 | #else | |
71 | min_cclk = sclk; | |
72 | #endif | |
96900315 SM |
73 | |
74 | #ifndef CONFIG_BF60x | |
6c2b7072 | 75 | csel = ((bfin_read_PLL_DIV() & CSEL) >> 4); |
96900315 SM |
76 | #else |
77 | csel = bfin_read32(CGU0_DIV) & 0x1F; | |
78 | #endif | |
6c2b7072 | 79 | |
810f1512 | 80 | for (index = 0; (cclk >> index) >= min_cclk && csel <= 3 && index < 3; index++, csel++) { |
6c2b7072 | 81 | bfin_freq_table[index].frequency = cclk >> index; |
96900315 | 82 | #ifndef CONFIG_BF60x |
6c2b7072 | 83 | dpm_state_table[index].csel = csel << 4; /* Shift now into PLL_DIV bitpos */ |
96900315 SM |
84 | #else |
85 | dpm_state_table[index].csel = csel; | |
96900315 | 86 | #endif |
810f1512 | 87 | dpm_state_table[index].tscale = (TIME_SCALE >> index) - 1; |
6c2b7072 GY |
88 | |
89 | pr_debug("cpufreq: freq:%d csel:0x%x tscale:%d\n", | |
90 | bfin_freq_table[index].frequency, | |
91 | dpm_state_table[index].csel, | |
92 | dpm_state_table[index].tscale); | |
93 | } | |
94 | return; | |
95 | } | |
96 | ||
97 | static void bfin_adjust_core_timer(void *info) | |
e6c91b64 | 98 | { |
6c2b7072 GY |
99 | unsigned int tscale; |
100 | unsigned int index = *(unsigned int *)info; | |
101 | ||
102 | /* we have to adjust the core timer, because it is using cclk */ | |
103 | tscale = dpm_state_table[index].tscale; | |
104 | bfin_write_TSCALE(tscale); | |
105 | return; | |
106 | } | |
e6c91b64 | 107 | |
6c2b7072 GY |
108 | static unsigned int bfin_getfreq_khz(unsigned int cpu) |
109 | { | |
110 | /* Both CoreA/B have the same core clock */ | |
a10101d5 | 111 | return get_cclk() / 1000; |
e6c91b64 MH |
112 | } |
113 | ||
3a3cf0d7 | 114 | #ifdef CONFIG_BF60x |
96900315 SM |
115 | unsigned long cpu_set_cclk(int cpu, unsigned long new) |
116 | { | |
117 | struct clk *clk; | |
118 | int ret; | |
119 | ||
120 | clk = clk_get(NULL, "CCLK"); | |
121 | if (IS_ERR(clk)) | |
122 | return -ENODEV; | |
123 | ||
124 | ret = clk_set_rate(clk, new); | |
125 | clk_put(clk); | |
126 | return ret; | |
127 | } | |
3a3cf0d7 | 128 | #endif |
96900315 | 129 | |
9c0ebcf7 | 130 | static int bfin_target(struct cpufreq_policy *policy, unsigned int index) |
e6c91b64 | 131 | { |
3a3cf0d7 BL |
132 | #ifndef CONFIG_BF60x |
133 | unsigned int plldiv; | |
134 | #endif | |
7998a878 GY |
135 | static unsigned long lpj_ref; |
136 | static unsigned int lpj_ref_freq; | |
d4019f0a | 137 | unsigned int old_freq, new_freq; |
96900315 | 138 | int ret = 0; |
7998a878 | 139 | |
6c2b7072 | 140 | #if defined(CONFIG_CYCLES_CLOCKSOURCE) |
1bfb4b21 | 141 | cycles_t cycles; |
6c2b7072 | 142 | #endif |
e6c91b64 | 143 | |
d4019f0a VK |
144 | old_freq = bfin_getfreq_khz(0); |
145 | new_freq = bfin_freq_table[index].frequency; | |
6c2b7072 | 146 | |
96900315 | 147 | #ifndef CONFIG_BF60x |
b43a7ffb VK |
148 | plldiv = (bfin_read_PLL_DIV() & SSEL) | dpm_state_table[index].csel; |
149 | bfin_write_PLL_DIV(plldiv); | |
96900315 | 150 | #else |
d4019f0a | 151 | ret = cpu_set_cclk(policy->cpu, new_freq * 1000); |
b43a7ffb VK |
152 | if (ret != 0) { |
153 | WARN_ONCE(ret, "cpufreq set freq failed %d\n", ret); | |
154 | return ret; | |
155 | } | |
96900315 | 156 | #endif |
b43a7ffb | 157 | on_each_cpu(bfin_adjust_core_timer, &index, 1); |
6c2b7072 | 158 | #if defined(CONFIG_CYCLES_CLOCKSOURCE) |
b43a7ffb VK |
159 | cycles = get_cycles(); |
160 | SSYNC(); | |
161 | cycles += 10; /* ~10 cycles we lose after get_cycles() */ | |
162 | __bfin_cycles_off += (cycles << __bfin_cycles_mod) - (cycles << index); | |
163 | __bfin_cycles_mod = index; | |
6c2b7072 | 164 | #endif |
b43a7ffb VK |
165 | if (!lpj_ref_freq) { |
166 | lpj_ref = loops_per_jiffy; | |
d4019f0a | 167 | lpj_ref_freq = old_freq; |
6c2b7072 | 168 | } |
d4019f0a | 169 | if (new_freq != old_freq) { |
b43a7ffb | 170 | loops_per_jiffy = cpufreq_scale(lpj_ref, |
d4019f0a | 171 | lpj_ref_freq, new_freq); |
b43a7ffb VK |
172 | } |
173 | ||
96900315 | 174 | return ret; |
e6c91b64 MH |
175 | } |
176 | ||
96900315 | 177 | static int __bfin_cpu_init(struct cpufreq_policy *policy) |
e6c91b64 MH |
178 | { |
179 | ||
6c2b7072 | 180 | unsigned long cclk, sclk; |
e6c91b64 | 181 | |
a10101d5 MH |
182 | cclk = get_cclk() / 1000; |
183 | sclk = get_sclk() / 1000; | |
e6c91b64 | 184 | |
6c2b7072 GY |
185 | if (policy->cpu == CPUFREQ_CPU) |
186 | bfin_init_tables(cclk, sclk); | |
e6c91b64 | 187 | |
d887a1ce MH |
188 | policy->cpuinfo.transition_latency = 50000; /* 50us assumed */ |
189 | ||
e2889e2c | 190 | return cpufreq_table_validate_and_show(policy, bfin_freq_table); |
e6c91b64 MH |
191 | } |
192 | ||
e6c91b64 | 193 | static struct cpufreq_driver bfin_driver = { |
00ff424c | 194 | .verify = cpufreq_generic_frequency_table_verify, |
9c0ebcf7 | 195 | .target_index = bfin_target, |
a10101d5 | 196 | .get = bfin_getfreq_khz, |
e6c91b64 MH |
197 | .init = __bfin_cpu_init, |
198 | .name = "bfin cpufreq", | |
00ff424c | 199 | .attr = cpufreq_generic_attr, |
e6c91b64 MH |
200 | }; |
201 | ||
202 | static int __init bfin_cpu_init(void) | |
203 | { | |
204 | return cpufreq_register_driver(&bfin_driver); | |
205 | } | |
206 | ||
207 | static void __exit bfin_cpu_exit(void) | |
208 | { | |
209 | cpufreq_unregister_driver(&bfin_driver); | |
210 | } | |
211 | ||
212 | MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); | |
213 | MODULE_DESCRIPTION("cpufreq driver for Blackfin"); | |
214 | MODULE_LICENSE("GPL"); | |
215 | ||
216 | module_init(bfin_cpu_init); | |
217 | module_exit(bfin_cpu_exit); |