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