Rev 600 | Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
582 | mauro | 1 | /* |
2 | * This file was based upon code in Powertweak Linux (http://powertweak.sf.net) |
||
3 | * (C) 2000-2003 Dave Jones, Arjan van de Ven, Janne P�k�� Dominik Brodowski. |
||
4 | * |
||
5 | * Licensed under the terms of the GNU GPL License version 2. |
||
6 | * |
||
7 | * BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous* |
||
8 | */ |
||
9 | |||
10 | #include <linuxcomp.h> |
||
11 | |||
12 | #include <linux/kernel.h> |
||
13 | #include <linux/module.h> |
||
14 | #include <linux/init.h> |
||
15 | #include <linux/cpufreq.h> |
||
16 | #include <linux/ioport.h> |
||
17 | #include <linux/slab.h> |
||
18 | |||
19 | #include <asm/msr.h> |
||
20 | #include <asm/timex.h> |
||
21 | #include <asm/io.h> |
||
22 | |||
23 | extern struct cpuinfo_x86 new_cpu_data; |
||
24 | extern unsigned long cpu_khz; |
||
25 | |||
26 | #define POWERNOW_IOPORT 0xfff0 /* it doesn't matter where, as long |
||
27 | as it is unused */ |
||
28 | |||
29 | static unsigned int busfreq; /* FSB, in 10 kHz */ |
||
30 | static unsigned int max_multiplier; |
||
31 | |||
32 | |||
33 | /* Clock ratio multiplied by 10 - see table 27 in AMD#23446 */ |
||
34 | static struct cpufreq_frequency_table clock_ratio[] = { |
||
35 | {45, /* 000 -> 4.5x */ 0}, |
||
36 | {50, /* 001 -> 5.0x */ 0}, |
||
37 | {40, /* 010 -> 4.0x */ 0}, |
||
38 | {55, /* 011 -> 5.5x */ 0}, |
||
39 | {20, /* 100 -> 2.0x */ 0}, |
||
40 | {30, /* 101 -> 3.0x */ 0}, |
||
41 | {60, /* 110 -> 6.0x */ 0}, |
||
42 | {35, /* 111 -> 3.5x */ 0}, |
||
43 | {0, CPUFREQ_TABLE_END} |
||
44 | }; |
||
45 | |||
46 | |||
47 | /** |
||
48 | * powernow_k6_get_cpu_multiplier - returns the current FSB multiplier |
||
49 | * |
||
50 | * Returns the current setting of the frequency multiplier. Core clock |
||
51 | * speed is frequency of the Front-Side Bus multiplied with this value. |
||
52 | */ |
||
53 | static int powernow_k6_get_cpu_multiplier(void) |
||
54 | { |
||
55 | u64 invalue = 0; |
||
56 | u32 msrval; |
||
57 | |||
58 | msrval = POWERNOW_IOPORT + 0x1; |
||
59 | wrmsr(MSR_K6_EPMR, msrval, 0); /* enable the PowerNow port */ |
||
60 | invalue=inl(POWERNOW_IOPORT + 0x8); |
||
61 | msrval = POWERNOW_IOPORT + 0x0; |
||
62 | wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */ |
||
63 | |||
64 | return clock_ratio[(invalue >> 5)&7].index; |
||
65 | } |
||
66 | |||
67 | |||
68 | /** |
||
69 | * powernow_k6_set_state - set the PowerNow! multiplier |
||
70 | * @best_i: clock_ratio[best_i] is the target multiplier |
||
71 | * |
||
72 | * Tries to change the PowerNow! multiplier |
||
73 | */ |
||
74 | static void powernow_k6_set_state (unsigned int best_i) |
||
75 | { |
||
76 | unsigned long outvalue=0, invalue=0; |
||
77 | unsigned long msrval; |
||
78 | struct cpufreq_freqs freqs; |
||
79 | |||
80 | if (clock_ratio[best_i].index > max_multiplier) { |
||
81 | printk(KERN_ERR "cpufreq: invalid target frequency\n"); |
||
82 | return; |
||
83 | } |
||
84 | |||
85 | freqs.old = busfreq * powernow_k6_get_cpu_multiplier(); |
||
86 | freqs.new = busfreq * clock_ratio[best_i].index; |
||
87 | freqs.cpu = 0; // powernow-k6.c is UP only driver |
||
88 | |||
89 | //!!!cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); |
||
90 | |||
91 | /* we now need to transform best_i to the BVC format, see AMD#23446 */ |
||
92 | |||
93 | outvalue = (1<<12) | (1<<10) | (1<<9) | (best_i<<5); |
||
94 | |||
95 | msrval = POWERNOW_IOPORT + 0x1; |
||
96 | wrmsr(MSR_K6_EPMR, msrval, 0); /* enable the PowerNow port */ |
||
97 | invalue=inl(POWERNOW_IOPORT + 0x8); |
||
98 | invalue = invalue & 0xf; |
||
99 | outvalue = outvalue | invalue; |
||
100 | outl(outvalue ,(POWERNOW_IOPORT + 0x8)); |
||
101 | msrval = POWERNOW_IOPORT + 0x0; |
||
102 | wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */ |
||
103 | |||
104 | //!!!cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); |
||
105 | |||
106 | return; |
||
107 | } |
||
108 | |||
109 | |||
110 | /** |
||
111 | * powernow_k6_verify - verifies a new CPUfreq policy |
||
112 | * @policy: new policy |
||
113 | * |
||
114 | * Policy must be within lowest and highest possible CPU Frequency, |
||
115 | * and at least one possible state must be within min and max. |
||
116 | */ |
||
117 | static int powernow_k6_verify(struct cpufreq_policy *policy) |
||
118 | { |
||
119 | return cpufreq_frequency_table_verify(policy, &clock_ratio[0]); |
||
120 | } |
||
121 | |||
122 | |||
123 | /** |
||
124 | * powernow_k6_setpolicy - sets a new CPUFreq policy |
||
125 | * @policy - new policy |
||
126 | * |
||
127 | * sets a new CPUFreq policy |
||
128 | */ |
||
129 | static int powernow_k6_target (struct cpufreq_policy *policy, |
||
130 | unsigned int target_freq, |
||
131 | unsigned int relation) |
||
132 | { |
||
133 | unsigned int newstate = 0; |
||
134 | |||
135 | if (cpufreq_frequency_table_target(policy, &clock_ratio[0], target_freq, relation, &newstate)) |
||
136 | return -EINVAL; |
||
137 | |||
138 | powernow_k6_set_state(newstate); |
||
139 | |||
140 | return 0; |
||
141 | } |
||
142 | |||
143 | |||
144 | static int powernow_k6_cpu_init(struct cpufreq_policy *policy) |
||
145 | { |
||
146 | unsigned int i; |
||
147 | |||
148 | if (policy->cpu != 0) |
||
149 | return -ENODEV; |
||
150 | |||
151 | /* get frequencies */ |
||
152 | max_multiplier = powernow_k6_get_cpu_multiplier(); |
||
153 | busfreq = cpu_khz / max_multiplier; |
||
154 | |||
155 | /* table init */ |
||
156 | for (i=0; (clock_ratio[i].frequency != CPUFREQ_TABLE_END); i++) { |
||
157 | if (clock_ratio[i].index > max_multiplier) |
||
158 | clock_ratio[i].frequency = CPUFREQ_ENTRY_INVALID; |
||
159 | else |
||
160 | clock_ratio[i].frequency = busfreq * clock_ratio[i].index; |
||
161 | } |
||
162 | |||
163 | /* cpuinfo and default policy values */ |
||
164 | policy->governor = 0; //!!!CPUFREQ_DEFAULT_GOVERNOR; |
||
165 | policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; |
||
166 | policy->cur = busfreq * max_multiplier; |
||
167 | |||
168 | return cpufreq_frequency_table_cpuinfo(policy, &clock_ratio[0]); |
||
169 | } |
||
170 | |||
171 | |||
172 | static int powernow_k6_cpu_exit(struct cpufreq_policy *policy) |
||
173 | { |
||
174 | unsigned int i; |
||
175 | for (i=0; i<8; i++) { |
||
176 | if (i==max_multiplier) |
||
177 | powernow_k6_set_state(i); |
||
178 | } |
||
179 | return 0; |
||
180 | } |
||
181 | |||
182 | |||
183 | static struct cpufreq_driver powernow_k6_driver = { |
||
184 | .verify = powernow_k6_verify, |
||
185 | .target = powernow_k6_target, |
||
186 | .init = powernow_k6_cpu_init, |
||
187 | .exit = powernow_k6_cpu_exit, |
||
188 | .name = "powernow-k6", |
||
189 | .owner = THIS_MODULE, |
||
190 | }; |
||
191 | |||
192 | |||
193 | /** |
||
194 | * powernow_k6_init - initializes the k6 PowerNow! CPUFreq driver |
||
195 | * |
||
196 | * Initializes the K6 PowerNow! support. Returns -ENODEV on unsupported |
||
197 | * devices, -EINVAL or -ENOMEM on problems during initiatization, and zero |
||
198 | * on success. |
||
199 | */ |
||
200 | /*static*/ int __init powernow_k6_init(void) |
||
201 | { |
||
202 | struct cpuinfo_x86 *c = &new_cpu_data; |
||
203 | |||
204 | if ((c->x86_vendor != X86_VENDOR_AMD) || (c->x86 != 5) || |
||
205 | ((c->x86_model != 12) && (c->x86_model != 13))) |
||
206 | return -ENODEV; |
||
207 | |||
208 | if (!request_region(POWERNOW_IOPORT, 16, "PowerNow!")) { |
||
209 | printk("cpufreq: PowerNow IOPORT region already used.\n"); |
||
210 | return -EIO; |
||
211 | } |
||
212 | |||
213 | if (cpufreq_register_driver(&powernow_k6_driver)) { |
||
214 | release_region (POWERNOW_IOPORT, 16); |
||
215 | return -EINVAL; |
||
216 | } |
||
217 | |||
218 | return 0; |
||
219 | } |
||
220 | |||
221 | |||
222 | /** |
||
223 | * powernow_k6_exit - unregisters AMD K6-2+/3+ PowerNow! support |
||
224 | * |
||
225 | * Unregisters AMD K6-2+ / K6-3+ PowerNow! support. |
||
226 | */ |
||
227 | /*static*/ void __exit powernow_k6_exit(void) |
||
228 | { |
||
229 | cpufreq_unregister_driver(&powernow_k6_driver); |
||
230 | release_region (POWERNOW_IOPORT, 16); |
||
231 | } |
||
232 | |||
233 | |||
234 | MODULE_AUTHOR ("Arjan van de Ven <arjanv@redhat.com>, Dave Jones <davej@codemonkey.org.uk>, Dominik Brodowski <linux@brodo.de>"); |
||
235 | MODULE_DESCRIPTION ("PowerNow! driver for AMD K6-2+ / K6-3+ processors."); |
||
236 | MODULE_LICENSE ("GPL"); |
||
237 | |||
238 | module_init(powernow_k6_init); |
||
239 | module_exit(powernow_k6_exit); |