xref: /openbmc/linux/drivers/cpufreq/powernow-k6.c (revision ead5d1f4d877e92c051e1a1ade623d0d30e71619)
14f19048fSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2bb0a56ecSDave Jones /*
3bb0a56ecSDave Jones  *  This file was based upon code in Powertweak Linux (http://powertweak.sf.net)
4bb0a56ecSDave Jones  *  (C) 2000-2003  Dave Jones, Arjan van de Ven, Janne Pänkälä,
5bb0a56ecSDave Jones  *                 Dominik Brodowski.
6bb0a56ecSDave Jones  *
7bb0a56ecSDave Jones  *  BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous*
8bb0a56ecSDave Jones  */
9bb0a56ecSDave Jones 
101c5864e2SJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
111c5864e2SJoe Perches 
12bb0a56ecSDave Jones #include <linux/kernel.h>
13bb0a56ecSDave Jones #include <linux/module.h>
14bb0a56ecSDave Jones #include <linux/init.h>
15bb0a56ecSDave Jones #include <linux/cpufreq.h>
16bb0a56ecSDave Jones #include <linux/ioport.h>
17bb0a56ecSDave Jones #include <linux/timex.h>
18bb0a56ecSDave Jones #include <linux/io.h>
19bb0a56ecSDave Jones 
20fa8031aeSAndi Kleen #include <asm/cpu_device_id.h>
21bb0a56ecSDave Jones #include <asm/msr.h>
22bb0a56ecSDave Jones 
23bb0a56ecSDave Jones #define POWERNOW_IOPORT 0xfff0          /* it doesn't matter where, as long
24bb0a56ecSDave Jones 					   as it is unused */
25bb0a56ecSDave Jones 
26bb0a56ecSDave Jones static unsigned int                     busfreq;   /* FSB, in 10 kHz */
27bb0a56ecSDave Jones static unsigned int                     max_multiplier;
28bb0a56ecSDave Jones 
29d82b922aSMikulas Patocka static unsigned int			param_busfreq = 0;
30d82b922aSMikulas Patocka static unsigned int			param_max_multiplier = 0;
31d82b922aSMikulas Patocka 
32d82b922aSMikulas Patocka module_param_named(max_multiplier, param_max_multiplier, uint, S_IRUGO);
33d82b922aSMikulas Patocka MODULE_PARM_DESC(max_multiplier, "Maximum multiplier (allowed values: 20 30 35 40 45 50 55 60)");
34d82b922aSMikulas Patocka 
35d82b922aSMikulas Patocka module_param_named(bus_frequency, param_busfreq, uint, S_IRUGO);
36d82b922aSMikulas Patocka MODULE_PARM_DESC(bus_frequency, "Bus frequency in kHz");
37bb0a56ecSDave Jones 
38bb0a56ecSDave Jones /* Clock ratio multiplied by 10 - see table 27 in AMD#23446 */
39bb0a56ecSDave Jones static struct cpufreq_frequency_table clock_ratio[] = {
407f4b0461SViresh Kumar 	{0, 60,  /* 110 -> 6.0x */ 0},
417f4b0461SViresh Kumar 	{0, 55,  /* 011 -> 5.5x */ 0},
427f4b0461SViresh Kumar 	{0, 50,  /* 001 -> 5.0x */ 0},
437f4b0461SViresh Kumar 	{0, 45,  /* 000 -> 4.5x */ 0},
447f4b0461SViresh Kumar 	{0, 40,  /* 010 -> 4.0x */ 0},
457f4b0461SViresh Kumar 	{0, 35,  /* 111 -> 3.5x */ 0},
467f4b0461SViresh Kumar 	{0, 30,  /* 101 -> 3.0x */ 0},
477f4b0461SViresh Kumar 	{0, 20,  /* 100 -> 2.0x */ 0},
487f4b0461SViresh Kumar 	{0, 0, CPUFREQ_TABLE_END}
49bb0a56ecSDave Jones };
50bb0a56ecSDave Jones 
5122c73795SMikulas Patocka static const u8 index_to_register[8] = { 6, 3, 1, 0, 2, 7, 5, 4 };
5222c73795SMikulas Patocka static const u8 register_to_index[8] = { 3, 2, 4, 1, 7, 6, 0, 5 };
5322c73795SMikulas Patocka 
54d82b922aSMikulas Patocka static const struct {
55d82b922aSMikulas Patocka 	unsigned freq;
56d82b922aSMikulas Patocka 	unsigned mult;
57d82b922aSMikulas Patocka } usual_frequency_table[] = {
58853aa05aSMikulas Patocka 	{ 350000, 35 },	// 100   * 3.5
59d82b922aSMikulas Patocka 	{ 400000, 40 },	// 100   * 4
60d82b922aSMikulas Patocka 	{ 450000, 45 }, // 100   * 4.5
61d82b922aSMikulas Patocka 	{ 475000, 50 }, //  95   * 5
62d82b922aSMikulas Patocka 	{ 500000, 50 }, // 100   * 5
63d82b922aSMikulas Patocka 	{ 506250, 45 }, // 112.5 * 4.5
64d82b922aSMikulas Patocka 	{ 533500, 55 }, //  97   * 5.5
65d82b922aSMikulas Patocka 	{ 550000, 55 }, // 100   * 5.5
66d82b922aSMikulas Patocka 	{ 562500, 50 }, // 112.5 * 5
67d82b922aSMikulas Patocka 	{ 570000, 60 }, //  95   * 6
68d82b922aSMikulas Patocka 	{ 600000, 60 }, // 100   * 6
69d82b922aSMikulas Patocka 	{ 618750, 55 }, // 112.5 * 5.5
70d82b922aSMikulas Patocka 	{ 660000, 55 }, // 120   * 5.5
71d82b922aSMikulas Patocka 	{ 675000, 60 }, // 112.5 * 6
72d82b922aSMikulas Patocka 	{ 720000, 60 }, // 120   * 6
73d82b922aSMikulas Patocka };
74d82b922aSMikulas Patocka 
75d82b922aSMikulas Patocka #define FREQ_RANGE		3000
76bb0a56ecSDave Jones 
77bb0a56ecSDave Jones /**
78bb0a56ecSDave Jones  * powernow_k6_get_cpu_multiplier - returns the current FSB multiplier
79bb0a56ecSDave Jones  *
80bb0a56ecSDave Jones  * Returns the current setting of the frequency multiplier. Core clock
81bb0a56ecSDave Jones  * speed is frequency of the Front-Side Bus multiplied with this value.
82bb0a56ecSDave Jones  */
powernow_k6_get_cpu_multiplier(void)83bb0a56ecSDave Jones static int powernow_k6_get_cpu_multiplier(void)
84bb0a56ecSDave Jones {
85e20e1d0aSMikulas Patocka 	unsigned long invalue = 0;
86bb0a56ecSDave Jones 	u32 msrval;
87bb0a56ecSDave Jones 
88e20e1d0aSMikulas Patocka 	local_irq_disable();
89e20e1d0aSMikulas Patocka 
90bb0a56ecSDave Jones 	msrval = POWERNOW_IOPORT + 0x1;
91bb0a56ecSDave Jones 	wrmsr(MSR_K6_EPMR, msrval, 0); /* enable the PowerNow port */
92bb0a56ecSDave Jones 	invalue = inl(POWERNOW_IOPORT + 0x8);
93bb0a56ecSDave Jones 	msrval = POWERNOW_IOPORT + 0x0;
94bb0a56ecSDave Jones 	wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */
95bb0a56ecSDave Jones 
96e20e1d0aSMikulas Patocka 	local_irq_enable();
97e20e1d0aSMikulas Patocka 
9822c73795SMikulas Patocka 	return clock_ratio[register_to_index[(invalue >> 5)&7]].driver_data;
99bb0a56ecSDave Jones }
100bb0a56ecSDave Jones 
powernow_k6_set_cpu_multiplier(unsigned int best_i)101e20e1d0aSMikulas Patocka static void powernow_k6_set_cpu_multiplier(unsigned int best_i)
102e20e1d0aSMikulas Patocka {
103e20e1d0aSMikulas Patocka 	unsigned long outvalue, invalue;
104e20e1d0aSMikulas Patocka 	unsigned long msrval;
105e20e1d0aSMikulas Patocka 	unsigned long cr0;
106e20e1d0aSMikulas Patocka 
107e20e1d0aSMikulas Patocka 	/* we now need to transform best_i to the BVC format, see AMD#23446 */
108e20e1d0aSMikulas Patocka 
109e20e1d0aSMikulas Patocka 	/*
110e20e1d0aSMikulas Patocka 	 * The processor doesn't respond to inquiry cycles while changing the
111e20e1d0aSMikulas Patocka 	 * frequency, so we must disable cache.
112e20e1d0aSMikulas Patocka 	 */
113e20e1d0aSMikulas Patocka 	local_irq_disable();
114e20e1d0aSMikulas Patocka 	cr0 = read_cr0();
115e20e1d0aSMikulas Patocka 	write_cr0(cr0 | X86_CR0_CD);
116e20e1d0aSMikulas Patocka 	wbinvd();
117e20e1d0aSMikulas Patocka 
11822c73795SMikulas Patocka 	outvalue = (1<<12) | (1<<10) | (1<<9) | (index_to_register[best_i]<<5);
119e20e1d0aSMikulas Patocka 
120e20e1d0aSMikulas Patocka 	msrval = POWERNOW_IOPORT + 0x1;
121e20e1d0aSMikulas Patocka 	wrmsr(MSR_K6_EPMR, msrval, 0); /* enable the PowerNow port */
122e20e1d0aSMikulas Patocka 	invalue = inl(POWERNOW_IOPORT + 0x8);
123e20e1d0aSMikulas Patocka 	invalue = invalue & 0x1f;
124e20e1d0aSMikulas Patocka 	outvalue = outvalue | invalue;
125e20e1d0aSMikulas Patocka 	outl(outvalue, (POWERNOW_IOPORT + 0x8));
126e20e1d0aSMikulas Patocka 	msrval = POWERNOW_IOPORT + 0x0;
127e20e1d0aSMikulas Patocka 	wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */
128e20e1d0aSMikulas Patocka 
129e20e1d0aSMikulas Patocka 	write_cr0(cr0);
130e20e1d0aSMikulas Patocka 	local_irq_enable();
131e20e1d0aSMikulas Patocka }
132bb0a56ecSDave Jones 
133bb0a56ecSDave Jones /**
1349c0ebcf7SViresh Kumar  * powernow_k6_target - set the PowerNow! multiplier
135bb0a56ecSDave Jones  * @best_i: clock_ratio[best_i] is the target multiplier
136bb0a56ecSDave Jones  *
137bb0a56ecSDave Jones  *   Tries to change the PowerNow! multiplier
138bb0a56ecSDave Jones  */
powernow_k6_target(struct cpufreq_policy * policy,unsigned int best_i)1399c0ebcf7SViresh Kumar static int powernow_k6_target(struct cpufreq_policy *policy,
140b43a7ffbSViresh Kumar 		unsigned int best_i)
141bb0a56ecSDave Jones {
142bb0a56ecSDave Jones 
14350701588SViresh Kumar 	if (clock_ratio[best_i].driver_data > max_multiplier) {
1441c5864e2SJoe Perches 		pr_err("invalid target frequency\n");
1459c0ebcf7SViresh Kumar 		return -EINVAL;
146bb0a56ecSDave Jones 	}
147bb0a56ecSDave Jones 
148e20e1d0aSMikulas Patocka 	powernow_k6_set_cpu_multiplier(best_i);
149bb0a56ecSDave Jones 
150bb0a56ecSDave Jones 	return 0;
151bb0a56ecSDave Jones }
152bb0a56ecSDave Jones 
powernow_k6_cpu_init(struct cpufreq_policy * policy)153bb0a56ecSDave Jones static int powernow_k6_cpu_init(struct cpufreq_policy *policy)
154bb0a56ecSDave Jones {
155041526f9SStratos Karafotis 	struct cpufreq_frequency_table *pos;
156bb0a56ecSDave Jones 	unsigned int i, f;
157d82b922aSMikulas Patocka 	unsigned khz;
158bb0a56ecSDave Jones 
159bb0a56ecSDave Jones 	if (policy->cpu != 0)
160bb0a56ecSDave Jones 		return -ENODEV;
161bb0a56ecSDave Jones 
162d82b922aSMikulas Patocka 	max_multiplier = 0;
163d82b922aSMikulas Patocka 	khz = cpu_khz;
164d82b922aSMikulas Patocka 	for (i = 0; i < ARRAY_SIZE(usual_frequency_table); i++) {
165d82b922aSMikulas Patocka 		if (khz >= usual_frequency_table[i].freq - FREQ_RANGE &&
166d82b922aSMikulas Patocka 		    khz <= usual_frequency_table[i].freq + FREQ_RANGE) {
167d82b922aSMikulas Patocka 			khz = usual_frequency_table[i].freq;
168d82b922aSMikulas Patocka 			max_multiplier = usual_frequency_table[i].mult;
169d82b922aSMikulas Patocka 			break;
170d82b922aSMikulas Patocka 		}
171d82b922aSMikulas Patocka 	}
172d82b922aSMikulas Patocka 	if (param_max_multiplier) {
173041526f9SStratos Karafotis 		cpufreq_for_each_entry(pos, clock_ratio)
174041526f9SStratos Karafotis 			if (pos->driver_data == param_max_multiplier) {
175d82b922aSMikulas Patocka 				max_multiplier = param_max_multiplier;
176d82b922aSMikulas Patocka 				goto have_max_multiplier;
177d82b922aSMikulas Patocka 			}
1781c5864e2SJoe Perches 		pr_err("invalid max_multiplier parameter, valid parameters 20, 30, 35, 40, 45, 50, 55, 60\n");
179d82b922aSMikulas Patocka 		return -EINVAL;
180d82b922aSMikulas Patocka 	}
181d82b922aSMikulas Patocka 
182d82b922aSMikulas Patocka 	if (!max_multiplier) {
1831c5864e2SJoe Perches 		pr_warn("unknown frequency %u, cannot determine current multiplier\n",
184b49c22a6SJoe Perches 			khz);
1851c5864e2SJoe Perches 		pr_warn("use module parameters max_multiplier and bus_frequency\n");
186d82b922aSMikulas Patocka 		return -EOPNOTSUPP;
187d82b922aSMikulas Patocka 	}
188d82b922aSMikulas Patocka 
189d82b922aSMikulas Patocka have_max_multiplier:
190d82b922aSMikulas Patocka 	param_max_multiplier = max_multiplier;
191d82b922aSMikulas Patocka 
192d82b922aSMikulas Patocka 	if (param_busfreq) {
193d82b922aSMikulas Patocka 		if (param_busfreq >= 50000 && param_busfreq <= 150000) {
194d82b922aSMikulas Patocka 			busfreq = param_busfreq / 10;
195d82b922aSMikulas Patocka 			goto have_busfreq;
196d82b922aSMikulas Patocka 		}
1971c5864e2SJoe Perches 		pr_err("invalid bus_frequency parameter, allowed range 50000 - 150000 kHz\n");
198d82b922aSMikulas Patocka 		return -EINVAL;
199d82b922aSMikulas Patocka 	}
200d82b922aSMikulas Patocka 
201d82b922aSMikulas Patocka 	busfreq = khz / max_multiplier;
202d82b922aSMikulas Patocka have_busfreq:
203d82b922aSMikulas Patocka 	param_busfreq = busfreq * 10;
204bb0a56ecSDave Jones 
205bb0a56ecSDave Jones 	/* table init */
206041526f9SStratos Karafotis 	cpufreq_for_each_entry(pos, clock_ratio) {
207041526f9SStratos Karafotis 		f = pos->driver_data;
208bb0a56ecSDave Jones 		if (f > max_multiplier)
209041526f9SStratos Karafotis 			pos->frequency = CPUFREQ_ENTRY_INVALID;
210bb0a56ecSDave Jones 		else
211041526f9SStratos Karafotis 			pos->frequency = busfreq * f;
212bb0a56ecSDave Jones 	}
213bb0a56ecSDave Jones 
214bb0a56ecSDave Jones 	/* cpuinfo and default policy values */
215e20e1d0aSMikulas Patocka 	policy->cpuinfo.transition_latency = 500000;
216e2376d1fSViresh Kumar 	policy->freq_table = clock_ratio;
217bb0a56ecSDave Jones 
218e2376d1fSViresh Kumar 	return 0;
219bb0a56ecSDave Jones }
220bb0a56ecSDave Jones 
221bb0a56ecSDave Jones 
powernow_k6_cpu_exit(struct cpufreq_policy * policy)222bb0a56ecSDave Jones static int powernow_k6_cpu_exit(struct cpufreq_policy *policy)
223bb0a56ecSDave Jones {
224bb0a56ecSDave Jones 	unsigned int i;
225237ede16SSrivatsa S. Bhat 
226237ede16SSrivatsa S. Bhat 	for (i = 0; (clock_ratio[i].frequency != CPUFREQ_TABLE_END); i++) {
2273221e55bSSrivatsa S. Bhat 		if (clock_ratio[i].driver_data == max_multiplier) {
2283221e55bSSrivatsa S. Bhat 			struct cpufreq_freqs freqs;
2293221e55bSSrivatsa S. Bhat 
2303221e55bSSrivatsa S. Bhat 			freqs.old = policy->cur;
2313221e55bSSrivatsa S. Bhat 			freqs.new = clock_ratio[i].frequency;
2323221e55bSSrivatsa S. Bhat 			freqs.flags = 0;
2333221e55bSSrivatsa S. Bhat 
2343221e55bSSrivatsa S. Bhat 			cpufreq_freq_transition_begin(policy, &freqs);
2359c0ebcf7SViresh Kumar 			powernow_k6_target(policy, i);
2363221e55bSSrivatsa S. Bhat 			cpufreq_freq_transition_end(policy, &freqs, 0);
2373221e55bSSrivatsa S. Bhat 			break;
2383221e55bSSrivatsa S. Bhat 		}
239bb0a56ecSDave Jones 	}
240bb0a56ecSDave Jones 	return 0;
241bb0a56ecSDave Jones }
242bb0a56ecSDave Jones 
powernow_k6_get(unsigned int cpu)243bb0a56ecSDave Jones static unsigned int powernow_k6_get(unsigned int cpu)
244bb0a56ecSDave Jones {
245bb0a56ecSDave Jones 	unsigned int ret;
246bb0a56ecSDave Jones 	ret = (busfreq * powernow_k6_get_cpu_multiplier());
247bb0a56ecSDave Jones 	return ret;
248bb0a56ecSDave Jones }
249bb0a56ecSDave Jones 
250bb0a56ecSDave Jones static struct cpufreq_driver powernow_k6_driver = {
251d63bd27fSViresh Kumar 	.verify		= cpufreq_generic_frequency_table_verify,
2529c0ebcf7SViresh Kumar 	.target_index	= powernow_k6_target,
253bb0a56ecSDave Jones 	.init		= powernow_k6_cpu_init,
254bb0a56ecSDave Jones 	.exit		= powernow_k6_cpu_exit,
255bb0a56ecSDave Jones 	.get		= powernow_k6_get,
256bb0a56ecSDave Jones 	.name		= "powernow-k6",
257d63bd27fSViresh Kumar 	.attr		= cpufreq_generic_attr,
258bb0a56ecSDave Jones };
259bb0a56ecSDave Jones 
260fa8031aeSAndi Kleen static const struct x86_cpu_id powernow_k6_ids[] = {
261*b11d77faSThomas Gleixner 	X86_MATCH_VENDOR_FAM_MODEL(AMD, 5, 12, NULL),
262*b11d77faSThomas Gleixner 	X86_MATCH_VENDOR_FAM_MODEL(AMD, 5, 13, NULL),
263fa8031aeSAndi Kleen 	{}
264fa8031aeSAndi Kleen };
265b4d2d231SBen Hutchings MODULE_DEVICE_TABLE(x86cpu, powernow_k6_ids);
266bb0a56ecSDave Jones 
267bb0a56ecSDave Jones /**
268bb0a56ecSDave Jones  * powernow_k6_init - initializes the k6 PowerNow! CPUFreq driver
269bb0a56ecSDave Jones  *
270bb0a56ecSDave Jones  *   Initializes the K6 PowerNow! support. Returns -ENODEV on unsupported
271bb0a56ecSDave Jones  * devices, -EINVAL or -ENOMEM on problems during initiatization, and zero
272bb0a56ecSDave Jones  * on success.
273bb0a56ecSDave Jones  */
powernow_k6_init(void)274bb0a56ecSDave Jones static int __init powernow_k6_init(void)
275bb0a56ecSDave Jones {
276fa8031aeSAndi Kleen 	if (!x86_match_cpu(powernow_k6_ids))
277bb0a56ecSDave Jones 		return -ENODEV;
278bb0a56ecSDave Jones 
279bb0a56ecSDave Jones 	if (!request_region(POWERNOW_IOPORT, 16, "PowerNow!")) {
2801c5864e2SJoe Perches 		pr_info("PowerNow IOPORT region already used\n");
281bb0a56ecSDave Jones 		return -EIO;
282bb0a56ecSDave Jones 	}
283bb0a56ecSDave Jones 
284bb0a56ecSDave Jones 	if (cpufreq_register_driver(&powernow_k6_driver)) {
285bb0a56ecSDave Jones 		release_region(POWERNOW_IOPORT, 16);
286bb0a56ecSDave Jones 		return -EINVAL;
287bb0a56ecSDave Jones 	}
288bb0a56ecSDave Jones 
289bb0a56ecSDave Jones 	return 0;
290bb0a56ecSDave Jones }
291bb0a56ecSDave Jones 
292bb0a56ecSDave Jones 
293bb0a56ecSDave Jones /**
294bb0a56ecSDave Jones  * powernow_k6_exit - unregisters AMD K6-2+/3+ PowerNow! support
295bb0a56ecSDave Jones  *
296bb0a56ecSDave Jones  *   Unregisters AMD K6-2+ / K6-3+ PowerNow! support.
297bb0a56ecSDave Jones  */
powernow_k6_exit(void)298bb0a56ecSDave Jones static void __exit powernow_k6_exit(void)
299bb0a56ecSDave Jones {
300bb0a56ecSDave Jones 	cpufreq_unregister_driver(&powernow_k6_driver);
301bb0a56ecSDave Jones 	release_region(POWERNOW_IOPORT, 16);
302bb0a56ecSDave Jones }
303bb0a56ecSDave Jones 
304bb0a56ecSDave Jones 
305d5e80b4bSDave Jones MODULE_AUTHOR("Arjan van de Ven, Dave Jones, "
306bb0a56ecSDave Jones 		"Dominik Brodowski <linux@brodo.de>");
307bb0a56ecSDave Jones MODULE_DESCRIPTION("PowerNow! driver for AMD K6-2+ / K6-3+ processors.");
308bb0a56ecSDave Jones MODULE_LICENSE("GPL");
309bb0a56ecSDave Jones 
310bb0a56ecSDave Jones module_init(powernow_k6_init);
311bb0a56ecSDave Jones module_exit(powernow_k6_exit);
312