Blame view

drivers/cpufreq/longrun.c 8.27 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>
fa8031aef   Andi Kleen   cpufreq: Add supp...
17
  #include <asm/cpu_device_id.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
18

221dee285   Linus Torvalds   Revert "[CPUFREQ]...
19
  static struct cpufreq_driver	longrun_driver;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  
  /**
   * 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
   */
2760984f6   Paul Gortmaker   cpufreq: delete _...
36
  static void longrun_get_policy(struct cpufreq_policy *policy)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
37
38
39
40
  {
  	u32 msr_lo, msr_hi;
  
  	rdmsr(MSR_TMTA_LONGRUN_FLAGS, msr_lo, msr_hi);
2d06d8c49   Dominik Brodowski   [CPUFREQ] use dyn...
41
42
  	pr_debug("longrun flags are %x - %x
  ", msr_lo, msr_hi);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
43
44
45
46
47
48
  	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...
49
50
  	pr_debug("longrun ctrl is %x - %x
  ", msr_lo, msr_hi);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
51
52
  	msr_lo &= 0x0000007F;
  	msr_hi &= 0x0000007F;
48ee923a6   Dave Jones   [CPUFREQ] checkpa...
53
  	if (longrun_high_freq <= longrun_low_freq) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
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
79
  		/* 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...
80
  	if (longrun_high_freq <= longrun_low_freq) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
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
  		/* 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;
be49e3465   Viresh Kumar   cpufreq: add new ...
132
  	cpufreq_verify_within_cpu_limits(policy);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
  
  	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...
149
150
  	pr_debug("cpuid eax is %u
  ", eax);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
151

48ee923a6   Dave Jones   [CPUFREQ] checkpa...
152
  	return eax * 1000;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
153
154
155
156
157
158
159
160
161
162
163
164
  }
  
  /**
   * 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)
   */
2760984f6   Paul Gortmaker   cpufreq: delete _...
165
  static int longrun_determine_freqs(unsigned int *low_freq,
7e2d81122   Holger Freyther   [CPUFREQ] Fix sec...
166
  						      unsigned int *high_freq)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
167
168
169
170
171
  {
  	u32 msr_lo, msr_hi;
  	u32 save_lo, save_hi;
  	u32 eax, ebx, ecx, edx;
  	u32 try_hi;
92cb7612a   Mike Travis   x86: convert cpui...
172
  	struct cpuinfo_x86 *c = &cpu_data(0);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
  
  	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...
195
196
  		pr_debug("longrun table interface told %u - %u kHz
  ",
48ee923a6   Dave Jones   [CPUFREQ] checkpa...
197
  				*low_freq, *high_freq);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
198
199
200
201
202
203
204
205
206
  
  		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...
207
208
  	pr_debug("high frequency is %u kHz
  ", *high_freq);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
209
210
211
212
213
214
215
216
217
218
219
220
  
  	/* 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...
221
  	for (try_hi = 80; try_hi > 0 && ecx > 90; try_hi -= 10) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
222
223
224
  		/* set to 0 to try_hi perf_pctg */
  		msr_lo &= 0xFFFFFF80;
  		msr_hi &= 0xFFFFFF80;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
225
226
227
228
229
230
231
232
233
  		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...
234
235
  	pr_debug("percentage is %u %%, freq is %u MHz
  ", ecx, eax);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
236
237
238
  
  	/* performance_pctg = (current_freq - low_freq)/(high_freq - low_freq)
  	 * eqals
48ee923a6   Dave Jones   [CPUFREQ] checkpa...
239
  	 * low_freq * (1 - perf_pctg) = (cur_freq - high_freq * perf_pctg)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
240
241
242
243
244
245
246
  	 *
  	 * 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:...
247
  	edx = ((eax - ebx) * 100) / (100 - ecx);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
248
  	*low_freq = edx * 1000; /* back to kHz */
2d06d8c49   Dominik Brodowski   [CPUFREQ] use dyn...
249
250
  	pr_debug("low frequency is %u kHz
  ", *low_freq);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
251
252
253
254
255
256
  
  	if (*low_freq > *high_freq)
  		*low_freq = *high_freq;
  
  	return 0;
  }
2760984f6   Paul Gortmaker   cpufreq: delete _...
257
  static int longrun_cpu_init(struct cpufreq_policy *policy)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
  {
  	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]...
278
  static struct cpufreq_driver longrun_driver = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
279
280
281
282
283
284
  	.flags		= CPUFREQ_CONST_LOOPS,
  	.verify		= longrun_verify_policy,
  	.setpolicy	= longrun_set_policy,
  	.get		= longrun_get,
  	.init		= longrun_cpu_init,
  	.name		= "longrun",
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
285
  };
fa8031aef   Andi Kleen   cpufreq: Add supp...
286
287
288
289
290
291
  static const struct x86_cpu_id longrun_ids[] = {
  	{ X86_VENDOR_TRANSMETA, X86_FAMILY_ANY, X86_MODEL_ANY,
  	  X86_FEATURE_LONGRUN },
  	{}
  };
  MODULE_DEVICE_TABLE(x86cpu, longrun_ids);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
292
293
294
295
296
297
298
299
  
  /**
   * longrun_init - initializes the Transmeta Crusoe LongRun CPUFreq driver
   *
   * Initializes the LongRun support.
   */
  static int __init longrun_init(void)
  {
fa8031aef   Andi Kleen   cpufreq: Add supp...
300
  	if (!x86_match_cpu(longrun_ids))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
301
  		return -ENODEV;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
302
303
304
305
306
307
308
309
310
311
312
  	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...
313
314
315
316
  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
317
318
319
  
  module_init(longrun_init);
  module_exit(longrun_exit);