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