Blame view

drivers/cpufreq/longrun.c 8.26 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
2
3
4
5
6
7
8
9
10
11
  /*
   * (C) 2002 - 2003  Dominik Brodowski <linux@brodo.de>
   *
   *  Licensed under the terms of the GNU GPL License version 2.
   *
   *  BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous*
   */
  
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/init.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
12
  #include <linux/cpufreq.h>
48ee923a6   Dave Jones   [CPUFREQ] checkpa...
13
  #include <linux/timex.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
14
15
16
  
  #include <asm/msr.h>
  #include <asm/processor.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
17

221dee285   Linus Torvalds   Revert "[CPUFREQ]...
18
  static struct cpufreq_driver	longrun_driver;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  
  /**
   * longrun_{low,high}_freq is needed for the conversion of cpufreq kHz
   * values into per cent values. In TMTA microcode, the following is valid:
   * performance_pctg = (current_freq - low_freq)/(high_freq - low_freq)
   */
  static unsigned int longrun_low_freq, longrun_high_freq;
  
  
  /**
   * longrun_get_policy - get the current LongRun policy
   * @policy: struct cpufreq_policy where current policy is written into
   *
   * Reads the current LongRun policy by access to MSR_TMTA_LONGRUN_FLAGS
   * and MSR_TMTA_LONGRUN_CTRL
   */
a69a0612c   Rakib Mullick   [CPUFREQ]: x86, c...
35
  static void __cpuinit longrun_get_policy(struct cpufreq_policy *policy)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
36
37
38
39
  {
  	u32 msr_lo, msr_hi;
  
  	rdmsr(MSR_TMTA_LONGRUN_FLAGS, msr_lo, msr_hi);
2d06d8c49   Dominik Brodowski   [CPUFREQ] use dyn...
40
41
  	pr_debug("longrun flags are %x - %x
  ", msr_lo, msr_hi);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
42
43
44
45
46
47
  	if (msr_lo & 0x01)
  		policy->policy = CPUFREQ_POLICY_PERFORMANCE;
  	else
  		policy->policy = CPUFREQ_POLICY_POWERSAVE;
  
  	rdmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi);
2d06d8c49   Dominik Brodowski   [CPUFREQ] use dyn...
48
49
  	pr_debug("longrun ctrl is %x - %x
  ", msr_lo, msr_hi);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
50
51
  	msr_lo &= 0x0000007F;
  	msr_hi &= 0x0000007F;
48ee923a6   Dave Jones   [CPUFREQ] checkpa...
52
  	if (longrun_high_freq <= longrun_low_freq) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
  		/* Assume degenerate Longrun table */
  		policy->min = policy->max = longrun_high_freq;
  	} else {
  		policy->min = longrun_low_freq + msr_lo *
  			((longrun_high_freq - longrun_low_freq) / 100);
  		policy->max = longrun_low_freq + msr_hi *
  			((longrun_high_freq - longrun_low_freq) / 100);
  	}
  	policy->cpu = 0;
  }
  
  
  /**
   * longrun_set_policy - sets a new CPUFreq policy
   * @policy: new policy
   *
   * Sets a new CPUFreq policy on LongRun-capable processors. This function
   * has to be called with cpufreq_driver locked.
   */
  static int longrun_set_policy(struct cpufreq_policy *policy)
  {
  	u32 msr_lo, msr_hi;
  	u32 pctg_lo, pctg_hi;
  
  	if (!policy)
  		return -EINVAL;
48ee923a6   Dave Jones   [CPUFREQ] checkpa...
79
  	if (longrun_high_freq <= longrun_low_freq) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
  		/* Assume degenerate Longrun table */
  		pctg_lo = pctg_hi = 100;
  	} else {
  		pctg_lo = (policy->min - longrun_low_freq) /
  			((longrun_high_freq - longrun_low_freq) / 100);
  		pctg_hi = (policy->max - longrun_low_freq) /
  			((longrun_high_freq - longrun_low_freq) / 100);
  	}
  
  	if (pctg_hi > 100)
  		pctg_hi = 100;
  	if (pctg_lo > pctg_hi)
  		pctg_lo = pctg_hi;
  
  	/* performance or economy mode */
  	rdmsr(MSR_TMTA_LONGRUN_FLAGS, msr_lo, msr_hi);
  	msr_lo &= 0xFFFFFFFE;
  	switch (policy->policy) {
  	case CPUFREQ_POLICY_PERFORMANCE:
  		msr_lo |= 0x00000001;
  		break;
  	case CPUFREQ_POLICY_POWERSAVE:
  		break;
  	}
  	wrmsr(MSR_TMTA_LONGRUN_FLAGS, msr_lo, msr_hi);
  
  	/* lower and upper boundary */
  	rdmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi);
  	msr_lo &= 0xFFFFFF80;
  	msr_hi &= 0xFFFFFF80;
  	msr_lo |= pctg_lo;
  	msr_hi |= pctg_hi;
  	wrmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi);
  
  	return 0;
  }
  
  
  /**
   * longrun_verify_poliy - verifies a new CPUFreq policy
   * @policy: the policy to verify
   *
   * Validates a new CPUFreq policy. This function has to be called with
   * cpufreq_driver locked.
   */
  static int longrun_verify_policy(struct cpufreq_policy *policy)
  {
  	if (!policy)
  		return -EINVAL;
  
  	policy->cpu = 0;
  	cpufreq_verify_within_limits(policy,
  		policy->cpuinfo.min_freq,
  		policy->cpuinfo.max_freq);
  
  	if ((policy->policy != CPUFREQ_POLICY_POWERSAVE) &&
  	    (policy->policy != CPUFREQ_POLICY_PERFORMANCE))
  		return -EINVAL;
  
  	return 0;
  }
  
  static unsigned int longrun_get(unsigned int cpu)
  {
  	u32 eax, ebx, ecx, edx;
  
  	if (cpu)
  		return 0;
  
  	cpuid(0x80860007, &eax, &ebx, &ecx, &edx);
2d06d8c49   Dominik Brodowski   [CPUFREQ] use dyn...
150
151
  	pr_debug("cpuid eax is %u
  ", eax);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
152

48ee923a6   Dave Jones   [CPUFREQ] checkpa...
153
  	return eax * 1000;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
154
155
156
157
158
159
160
161
162
163
164
165
  }
  
  /**
   * longrun_determine_freqs - determines the lowest and highest possible core frequency
   * @low_freq: an int to put the lowest frequency into
   * @high_freq: an int to put the highest frequency into
   *
   * Determines the lowest and highest possible core frequencies on this CPU.
   * This is necessary to calculate the performance percentage according to
   * TMTA rules:
   * performance_pctg = (target_freq - low_freq)/(high_freq - low_freq)
   */
b2a33c172   Julia Lawall   [CPUFREQ] arch/x8...
166
  static int __cpuinit longrun_determine_freqs(unsigned int *low_freq,
7e2d81122   Holger Freyther   [CPUFREQ] Fix sec...
167
  						      unsigned int *high_freq)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
168
169
170
171
172
  {
  	u32 msr_lo, msr_hi;
  	u32 save_lo, save_hi;
  	u32 eax, ebx, ecx, edx;
  	u32 try_hi;
92cb7612a   Mike Travis   x86: convert cpui...
173
  	struct cpuinfo_x86 *c = &cpu_data(0);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
  
  	if (!low_freq || !high_freq)
  		return -EINVAL;
  
  	if (cpu_has(c, X86_FEATURE_LRTI)) {
  		/* if the LongRun Table Interface is present, the
  		 * detection is a bit easier:
  		 * For minimum frequency, read out the maximum
  		 * level (msr_hi), write that into "currently
  		 * selected level", and read out the frequency.
  		 * For maximum frequency, read out level zero.
  		 */
  		/* minimum */
  		rdmsr(MSR_TMTA_LRTI_READOUT, msr_lo, msr_hi);
  		wrmsr(MSR_TMTA_LRTI_READOUT, msr_hi, msr_hi);
  		rdmsr(MSR_TMTA_LRTI_VOLT_MHZ, msr_lo, msr_hi);
  		*low_freq = msr_lo * 1000; /* to kHz */
  
  		/* maximum */
  		wrmsr(MSR_TMTA_LRTI_READOUT, 0, msr_hi);
  		rdmsr(MSR_TMTA_LRTI_VOLT_MHZ, msr_lo, msr_hi);
  		*high_freq = msr_lo * 1000; /* to kHz */
2d06d8c49   Dominik Brodowski   [CPUFREQ] use dyn...
196
197
  		pr_debug("longrun table interface told %u - %u kHz
  ",
48ee923a6   Dave Jones   [CPUFREQ] checkpa...
198
  				*low_freq, *high_freq);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
199
200
201
202
203
204
205
206
207
  
  		if (*low_freq > *high_freq)
  			*low_freq = *high_freq;
  		return 0;
  	}
  
  	/* set the upper border to the value determined during TSC init */
  	*high_freq = (cpu_khz / 1000);
  	*high_freq = *high_freq * 1000;
2d06d8c49   Dominik Brodowski   [CPUFREQ] use dyn...
208
209
  	pr_debug("high frequency is %u kHz
  ", *high_freq);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
210
211
212
213
214
215
216
217
218
219
220
221
  
  	/* get current borders */
  	rdmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi);
  	save_lo = msr_lo & 0x0000007F;
  	save_hi = msr_hi & 0x0000007F;
  
  	/* if current perf_pctg is larger than 90%, we need to decrease the
  	 * upper limit to make the calculation more accurate.
  	 */
  	cpuid(0x80860007, &eax, &ebx, &ecx, &edx);
  	/* try decreasing in 10% steps, some processors react only
  	 * on some barrier values */
48ee923a6   Dave Jones   [CPUFREQ] checkpa...
222
  	for (try_hi = 80; try_hi > 0 && ecx > 90; try_hi -= 10) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
223
224
225
  		/* set to 0 to try_hi perf_pctg */
  		msr_lo &= 0xFFFFFF80;
  		msr_hi &= 0xFFFFFF80;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
226
227
228
229
230
231
232
233
234
  		msr_hi |= try_hi;
  		wrmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi);
  
  		/* read out current core MHz and current perf_pctg */
  		cpuid(0x80860007, &eax, &ebx, &ecx, &edx);
  
  		/* restore values */
  		wrmsr(MSR_TMTA_LONGRUN_CTRL, save_lo, save_hi);
  	}
2d06d8c49   Dominik Brodowski   [CPUFREQ] use dyn...
235
236
  	pr_debug("percentage is %u %%, freq is %u MHz
  ", ecx, eax);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
237
238
239
  
  	/* performance_pctg = (current_freq - low_freq)/(high_freq - low_freq)
  	 * eqals
48ee923a6   Dave Jones   [CPUFREQ] checkpa...
240
  	 * low_freq * (1 - perf_pctg) = (cur_freq - high_freq * perf_pctg)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
241
242
243
244
245
246
247
  	 *
  	 * high_freq * perf_pctg is stored tempoarily into "ebx".
  	 */
  	ebx = (((cpu_khz / 1000) * ecx) / 100); /* to MHz */
  
  	if ((ecx > 95) || (ecx == 0) || (eax < ebx))
  		return -EIO;
667ad4f70   maximilian attems   [CPUFREQ] Crusoe:...
248
  	edx = ((eax - ebx) * 100) / (100 - ecx);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
249
  	*low_freq = edx * 1000; /* back to kHz */
2d06d8c49   Dominik Brodowski   [CPUFREQ] use dyn...
250
251
  	pr_debug("low frequency is %u kHz
  ", *low_freq);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
252
253
254
255
256
257
  
  	if (*low_freq > *high_freq)
  		*low_freq = *high_freq;
  
  	return 0;
  }
7e2d81122   Holger Freyther   [CPUFREQ] Fix sec...
258
  static int __cpuinit longrun_cpu_init(struct cpufreq_policy *policy)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
  {
  	int result = 0;
  
  	/* capability check */
  	if (policy->cpu != 0)
  		return -ENODEV;
  
  	/* detect low and high frequency */
  	result = longrun_determine_freqs(&longrun_low_freq, &longrun_high_freq);
  	if (result)
  		return result;
  
  	/* cpuinfo and default policy values */
  	policy->cpuinfo.min_freq = longrun_low_freq;
  	policy->cpuinfo.max_freq = longrun_high_freq;
  	policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
  	longrun_get_policy(policy);
  
  	return 0;
  }
221dee285   Linus Torvalds   Revert "[CPUFREQ]...
279
  static struct cpufreq_driver longrun_driver = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
  	.flags		= CPUFREQ_CONST_LOOPS,
  	.verify		= longrun_verify_policy,
  	.setpolicy	= longrun_set_policy,
  	.get		= longrun_get,
  	.init		= longrun_cpu_init,
  	.name		= "longrun",
  	.owner		= THIS_MODULE,
  };
  
  
  /**
   * longrun_init - initializes the Transmeta Crusoe LongRun CPUFreq driver
   *
   * Initializes the LongRun support.
   */
  static int __init longrun_init(void)
  {
92cb7612a   Mike Travis   x86: convert cpui...
297
  	struct cpuinfo_x86 *c = &cpu_data(0);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
  
  	if (c->x86_vendor != X86_VENDOR_TRANSMETA ||
  	    !cpu_has(c, X86_FEATURE_LONGRUN))
  		return -ENODEV;
  
  	return cpufreq_register_driver(&longrun_driver);
  }
  
  
  /**
   * longrun_exit - unregisters LongRun support
   */
  static void __exit longrun_exit(void)
  {
  	cpufreq_unregister_driver(&longrun_driver);
  }
48ee923a6   Dave Jones   [CPUFREQ] checkpa...
314
315
316
317
  MODULE_AUTHOR("Dominik Brodowski <linux@brodo.de>");
  MODULE_DESCRIPTION("LongRun driver for Transmeta Crusoe and "
  		"Efficeon processors.");
  MODULE_LICENSE("GPL");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
318
319
320
  
  module_init(longrun_init);
  module_exit(longrun_exit);