1/*
2 * Blackfin core clock scaling
3 *
4 * Copyright 2008-2011 Analog Devices Inc.
5 *
6 * Licensed under the GPL-2 or later.
7 */
8
9#include <linux/kernel.h>
10#include <linux/module.h>
11#include <linux/types.h>
12#include <linux/init.h>
13#include <linux/clk.h>
14#include <linux/cpufreq.h>
15#include <linux/fs.h>
16#include <linux/delay.h>
17#include <asm/blackfin.h>
18#include <asm/time.h>
19#include <asm/dpmc.h>
20
21
22/* this is the table of CCLK frequencies, in Hz */
23/* .driver_data is the entry in the auxiliary dpm_state_table[] */
24static struct cpufreq_frequency_table bfin_freq_table[] = {
25	{
26		.frequency = CPUFREQ_TABLE_END,
27		.driver_data = 0,
28	},
29	{
30		.frequency = CPUFREQ_TABLE_END,
31		.driver_data = 1,
32	},
33	{
34		.frequency = CPUFREQ_TABLE_END,
35		.driver_data = 2,
36	},
37	{
38		.frequency = CPUFREQ_TABLE_END,
39		.driver_data = 0,
40	},
41};
42
43static 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
48#if defined(CONFIG_CYCLES_CLOCKSOURCE)
49/*
50 * normalized to maximum frequency offset for CYCLES,
51 * used in time-ts cycles clock source, but could be used
52 * somewhere also.
53 */
54unsigned long long __bfin_cycles_off;
55unsigned int __bfin_cycles_mod;
56#endif
57
58/**************************************************************************/
59static void __init bfin_init_tables(unsigned long cclk, unsigned long sclk)
60{
61
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 || \
67	(!(defined(CONFIG_BF54x) || defined(CONFIG_BF60x)) \
68	&& defined(CONFIG_BFIN_EXTMEM_DCACHEABLE))
69	min_cclk = sclk * 2;
70#else
71	min_cclk = sclk;
72#endif
73
74#ifndef CONFIG_BF60x
75	csel = ((bfin_read_PLL_DIV() & CSEL) >> 4);
76#else
77	csel = bfin_read32(CGU0_DIV) & 0x1F;
78#endif
79
80	for (index = 0;  (cclk >> index) >= min_cclk && csel <= 3 && index < 3; index++, csel++) {
81		bfin_freq_table[index].frequency = cclk >> index;
82#ifndef CONFIG_BF60x
83		dpm_state_table[index].csel = csel << 4; /* Shift now into PLL_DIV bitpos */
84#else
85		dpm_state_table[index].csel = csel;
86#endif
87		dpm_state_table[index].tscale =  (TIME_SCALE >> index) - 1;
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
97static void bfin_adjust_core_timer(void *info)
98{
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}
107
108static unsigned int bfin_getfreq_khz(unsigned int cpu)
109{
110	/* Both CoreA/B have the same core clock */
111	return get_cclk() / 1000;
112}
113
114#ifdef CONFIG_BF60x
115unsigned 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}
128#endif
129
130static int bfin_target(struct cpufreq_policy *policy, unsigned int index)
131{
132#ifndef CONFIG_BF60x
133	unsigned int plldiv;
134#endif
135	static unsigned long lpj_ref;
136	static unsigned int  lpj_ref_freq;
137	unsigned int old_freq, new_freq;
138	int ret = 0;
139
140#if defined(CONFIG_CYCLES_CLOCKSOURCE)
141	cycles_t cycles;
142#endif
143
144	old_freq = bfin_getfreq_khz(0);
145	new_freq = bfin_freq_table[index].frequency;
146
147#ifndef CONFIG_BF60x
148	plldiv = (bfin_read_PLL_DIV() & SSEL) | dpm_state_table[index].csel;
149	bfin_write_PLL_DIV(plldiv);
150#else
151	ret = cpu_set_cclk(policy->cpu, new_freq * 1000);
152	if (ret != 0) {
153		WARN_ONCE(ret, "cpufreq set freq failed %d\n", ret);
154		return ret;
155	}
156#endif
157	on_each_cpu(bfin_adjust_core_timer, &index, 1);
158#if defined(CONFIG_CYCLES_CLOCKSOURCE)
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;
164#endif
165	if (!lpj_ref_freq) {
166		lpj_ref = loops_per_jiffy;
167		lpj_ref_freq = old_freq;
168	}
169	if (new_freq != old_freq) {
170		loops_per_jiffy = cpufreq_scale(lpj_ref,
171				lpj_ref_freq, new_freq);
172	}
173
174	return ret;
175}
176
177static int __bfin_cpu_init(struct cpufreq_policy *policy)
178{
179
180	unsigned long cclk, sclk;
181
182	cclk = get_cclk() / 1000;
183	sclk = get_sclk() / 1000;
184
185	if (policy->cpu == CPUFREQ_CPU)
186		bfin_init_tables(cclk, sclk);
187
188	policy->cpuinfo.transition_latency = 50000; /* 50us assumed */
189
190	return cpufreq_table_validate_and_show(policy, bfin_freq_table);
191}
192
193static struct cpufreq_driver bfin_driver = {
194	.verify = cpufreq_generic_frequency_table_verify,
195	.target_index = bfin_target,
196	.get = bfin_getfreq_khz,
197	.init = __bfin_cpu_init,
198	.name = "bfin cpufreq",
199	.attr = cpufreq_generic_attr,
200};
201
202static int __init bfin_cpu_init(void)
203{
204	return cpufreq_register_driver(&bfin_driver);
205}
206
207static void __exit bfin_cpu_exit(void)
208{
209	cpufreq_unregister_driver(&bfin_driver);
210}
211
212MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
213MODULE_DESCRIPTION("cpufreq driver for Blackfin");
214MODULE_LICENSE("GPL");
215
216module_init(bfin_cpu_init);
217module_exit(bfin_cpu_exit);
218