Blame view

drivers/cpufreq/longrun.c 8.06 KB
4f19048fd   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-only
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
2
3
4
  /*
   * (C) 2002 - 2003  Dominik Brodowski <linux@brodo.de>
   *
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
5
6
7
8
9
10
   *  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
11
  #include <linux/cpufreq.h>
48ee923a6   Dave Jones   [CPUFREQ] checkpa...
12
  #include <linux/timex.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
13
14
15
  
  #include <asm/msr.h>
  #include <asm/processor.h>
fa8031aef   Andi Kleen   cpufreq: Add supp...
16
  #include <asm/cpu_device_id.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
   */
2760984f6   Paul Gortmaker   cpufreq: delete _...
35
  static void 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
  		/* 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.
   */
1e4f63aec   Rafael J. Wysocki   cpufreq: Avoid cr...
125
  static int longrun_verify_policy(struct cpufreq_policy_data *policy)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
126
127
128
129
130
  {
  	if (!policy)
  		return -EINVAL;
  
  	policy->cpu = 0;
be49e3465   Viresh Kumar   cpufreq: add new ...
131
  	cpufreq_verify_within_cpu_limits(policy);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
132

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
133
134
135
136
137
138
139
140
141
142
143
  	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...
144
145
  	pr_debug("cpuid eax is %u
  ", eax);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
146

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