1/* us3_cpufreq.c: UltraSPARC-III cpu frequency support
2 *
3 * Copyright (C) 2003 David S. Miller (davem@redhat.com)
4 *
5 * Many thanks to Dominik Brodowski for fixing up the cpufreq
6 * infrastructure in order to make this driver easier to implement.
7 */
8
9#include <linux/kernel.h>
10#include <linux/module.h>
11#include <linux/sched.h>
12#include <linux/smp.h>
13#include <linux/cpufreq.h>
14#include <linux/threads.h>
15#include <linux/slab.h>
16#include <linux/init.h>
17
18#include <asm/head.h>
19#include <asm/timer.h>
20
21static struct cpufreq_driver *cpufreq_us3_driver;
22
23struct us3_freq_percpu_info {
24	struct cpufreq_frequency_table table[4];
25};
26
27/* Indexed by cpu number. */
28static struct us3_freq_percpu_info *us3_freq_table;
29
30/* UltraSPARC-III has three dividers: 1, 2, and 32.  These are controlled
31 * in the Safari config register.
32 */
33#define SAFARI_CFG_DIV_1	0x0000000000000000UL
34#define SAFARI_CFG_DIV_2	0x0000000040000000UL
35#define SAFARI_CFG_DIV_32	0x0000000080000000UL
36#define SAFARI_CFG_DIV_MASK	0x00000000C0000000UL
37
38static unsigned long read_safari_cfg(void)
39{
40	unsigned long ret;
41
42	__asm__ __volatile__("ldxa	[%%g0] %1, %0"
43			     : "=&r" (ret)
44			     : "i" (ASI_SAFARI_CONFIG));
45	return ret;
46}
47
48static void write_safari_cfg(unsigned long val)
49{
50	__asm__ __volatile__("stxa	%0, [%%g0] %1\n\t"
51			     "membar	#Sync"
52			     : /* no outputs */
53			     : "r" (val), "i" (ASI_SAFARI_CONFIG)
54			     : "memory");
55}
56
57static unsigned long get_current_freq(unsigned int cpu, unsigned long safari_cfg)
58{
59	unsigned long clock_tick = sparc64_get_clock_tick(cpu) / 1000;
60	unsigned long ret;
61
62	switch (safari_cfg & SAFARI_CFG_DIV_MASK) {
63	case SAFARI_CFG_DIV_1:
64		ret = clock_tick / 1;
65		break;
66	case SAFARI_CFG_DIV_2:
67		ret = clock_tick / 2;
68		break;
69	case SAFARI_CFG_DIV_32:
70		ret = clock_tick / 32;
71		break;
72	default:
73		BUG();
74	};
75
76	return ret;
77}
78
79static unsigned int us3_freq_get(unsigned int cpu)
80{
81	cpumask_t cpus_allowed;
82	unsigned long reg;
83	unsigned int ret;
84
85	if (!cpu_online(cpu))
86		return 0;
87
88	cpus_allowed = current->cpus_allowed;
89	set_cpus_allowed(current, cpumask_of_cpu(cpu));
90
91	reg = read_safari_cfg();
92	ret = get_current_freq(cpu, reg);
93
94	set_cpus_allowed(current, cpus_allowed);
95
96	return ret;
97}
98
99static void us3_set_cpu_divider_index(unsigned int cpu, unsigned int index)
100{
101	unsigned long new_bits, new_freq, reg;
102	cpumask_t cpus_allowed;
103	struct cpufreq_freqs freqs;
104
105	if (!cpu_online(cpu))
106		return;
107
108	cpus_allowed = current->cpus_allowed;
109	set_cpus_allowed(current, cpumask_of_cpu(cpu));
110
111	new_freq = sparc64_get_clock_tick(cpu) / 1000;
112	switch (index) {
113	case 0:
114		new_bits = SAFARI_CFG_DIV_1;
115		new_freq /= 1;
116		break;
117	case 1:
118		new_bits = SAFARI_CFG_DIV_2;
119		new_freq /= 2;
120		break;
121	case 2:
122		new_bits = SAFARI_CFG_DIV_32;
123		new_freq /= 32;
124		break;
125
126	default:
127		BUG();
128	};
129
130	reg = read_safari_cfg();
131
132	freqs.old = get_current_freq(cpu, reg);
133	freqs.new = new_freq;
134	freqs.cpu = cpu;
135	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
136
137	reg &= ~SAFARI_CFG_DIV_MASK;
138	reg |= new_bits;
139	write_safari_cfg(reg);
140
141	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
142
143	set_cpus_allowed(current, cpus_allowed);
144}
145
146static int us3_freq_target(struct cpufreq_policy *policy,
147			  unsigned int target_freq,
148			  unsigned int relation)
149{
150	unsigned int new_index = 0;
151
152	if (cpufreq_frequency_table_target(policy,
153					   &us3_freq_table[policy->cpu].table[0],
154					   target_freq,
155					   relation,
156					   &new_index))
157		return -EINVAL;
158
159	us3_set_cpu_divider_index(policy->cpu, new_index);
160
161	return 0;
162}
163
164static int us3_freq_verify(struct cpufreq_policy *policy)
165{
166	return cpufreq_frequency_table_verify(policy,
167					      &us3_freq_table[policy->cpu].table[0]);
168}
169
170static int __init us3_freq_cpu_init(struct cpufreq_policy *policy)
171{
172	unsigned int cpu = policy->cpu;
173	unsigned long clock_tick = sparc64_get_clock_tick(cpu) / 1000;
174	struct cpufreq_frequency_table *table =
175		&us3_freq_table[cpu].table[0];
176
177	table[0].index = 0;
178	table[0].frequency = clock_tick / 1;
179	table[1].index = 1;
180	table[1].frequency = clock_tick / 2;
181	table[2].index = 2;
182	table[2].frequency = clock_tick / 32;
183	table[3].index = 0;
184	table[3].frequency = CPUFREQ_TABLE_END;
185
186	policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
187	policy->cpuinfo.transition_latency = 0;
188	policy->cur = clock_tick;
189
190	return cpufreq_frequency_table_cpuinfo(policy, table);
191}
192
193static int us3_freq_cpu_exit(struct cpufreq_policy *policy)
194{
195	if (cpufreq_us3_driver)
196		us3_set_cpu_divider_index(policy->cpu, 0);
197
198	return 0;
199}
200
201static int __init us3_freq_init(void)
202{
203	unsigned long manuf, impl, ver;
204	int ret;
205
206	if (tlb_type != cheetah && tlb_type != cheetah_plus)
207		return -ENODEV;
208
209	__asm__("rdpr %%ver, %0" : "=r" (ver));
210	manuf = ((ver >> 48) & 0xffff);
211	impl  = ((ver >> 32) & 0xffff);
212
213	if (manuf == CHEETAH_MANUF &&
214	    (impl == CHEETAH_IMPL ||
215	     impl == CHEETAH_PLUS_IMPL ||
216	     impl == JAGUAR_IMPL ||
217	     impl == PANTHER_IMPL)) {
218		struct cpufreq_driver *driver;
219
220		ret = -ENOMEM;
221		driver = kzalloc(sizeof(struct cpufreq_driver), GFP_KERNEL);
222		if (!driver)
223			goto err_out;
224
225		us3_freq_table = kzalloc(
226			(NR_CPUS * sizeof(struct us3_freq_percpu_info)),
227			GFP_KERNEL);
228		if (!us3_freq_table)
229			goto err_out;
230
231		driver->init = us3_freq_cpu_init;
232		driver->verify = us3_freq_verify;
233		driver->target = us3_freq_target;
234		driver->get = us3_freq_get;
235		driver->exit = us3_freq_cpu_exit;
236		driver->owner = THIS_MODULE,
237		strcpy(driver->name, "UltraSPARC-III");
238
239		cpufreq_us3_driver = driver;
240		ret = cpufreq_register_driver(driver);
241		if (ret)
242			goto err_out;
243
244		return 0;
245
246err_out:
247		if (driver) {
248			kfree(driver);
249			cpufreq_us3_driver = NULL;
250		}
251		kfree(us3_freq_table);
252		us3_freq_table = NULL;
253		return ret;
254	}
255
256	return -ENODEV;
257}
258
259static void __exit us3_freq_exit(void)
260{
261	if (cpufreq_us3_driver) {
262		cpufreq_unregister_driver(cpufreq_us3_driver);
263		kfree(cpufreq_us3_driver);
264		cpufreq_us3_driver = NULL;
265		kfree(us3_freq_table);
266		us3_freq_table = NULL;
267	}
268}
269
270MODULE_AUTHOR("David S. Miller <davem@redhat.com>");
271MODULE_DESCRIPTION("cpufreq driver for UltraSPARC-III");
272MODULE_LICENSE("GPL");
273
274module_init(us3_freq_init);
275module_exit(us3_freq_exit);
276