summaryrefslogtreecommitdiffstats
path: root/src/cpu/intel/speedstep/speedstep.c
blob: fba5df2f863fd809e2c6cefa138f1f7d82bf6f38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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
79
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/* SPDX-License-Identifier: GPL-2.0-only */

#include <types.h>
#include <string.h>
#include <arch/cpu.h>
#include <cpu/x86/msr.h>
#include <console/console.h>
#include <cpu/intel/speedstep.h>

/**
 * @brief Gather speedstep limits for current processor
 *
 * At least power limits are processor type specific. Penryn introduced half
 * steps in bus ratios. Don't know about Atom processors.
 */
static void speedstep_get_limits(sst_params_t *const params)
{
	msr_t msr;

	const uint16_t cpu_id = (cpuid_eax(1) >> 4) & 0xffff;
	const uint32_t state_mask =
		/* Penryn supports non integer (i.e. half) ratios. */
		((cpu_id == 0x1067) ? SPEEDSTEP_RATIO_NONINT : 0)
		| SPEEDSTEP_RATIO_VALUE_MASK | SPEEDSTEP_VID_MASK;

	/* Initialize params to zero. */
	memset(params, '\0', sizeof(*params));

	/* Read Super-LFM parameters. */
	if (((rdmsr(MSR_EXTENDED_CONFIG).lo >> 27) & 3) == 3) {/*supported and
								 enabled bits */
		msr = rdmsr(MSR_FSB_CLOCK_VCC);
		params->slfm = SPEEDSTEP_STATE_FROM_MSR(msr.lo, state_mask);
		params->slfm.dynfsb	= 1;
		params->slfm.is_slfm	= 1;
	}

	/* Read normal minimum parameters. */
	msr = rdmsr(MSR_THERM2_CTL);
	params->min = SPEEDSTEP_STATE_FROM_MSR(msr.lo, state_mask);

	/* Read normal maximum parameters. */
	/* Newer CPUs provide the normal maximum settings in
	   IA32_PLATFORM_ID. The values in IA32_PERF_STATUS change
	   when using turbo mode. */
	msr = rdmsr(IA32_PLATFORM_ID);
	params->max = SPEEDSTEP_STATE_FROM_MSR(msr.lo, state_mask);
	if (cpu_id == 0x006e) {
		/* Looks like Yonah CPUs don't have the frequency ratio in
		   IA32_PLATFORM_ID. Use IA32_PERF_STATUS instead, the reading
		   should be reliable as those CPUs don't have turbo mode. */
		msr = rdmsr(IA32_PERF_STATUS);
		params->max.ratio = (msr.hi & SPEEDSTEP_RATIO_VALUE_MASK)
						>> SPEEDSTEP_RATIO_SHIFT;
	}

	/* Read turbo parameters. */
	msr = rdmsr(MSR_FSB_CLOCK_VCC);
	if ((msr.hi & (1 << (63 - 32))) &&
		/* supported and */
			!(rdmsr(IA32_MISC_ENABLE).hi & (1 << (38 - 32)))) {
			/* not disabled */
		params->turbo = SPEEDSTEP_STATE_FROM_MSR(msr.hi, state_mask);
		params->turbo.is_turbo = 1;
	}

	/* Set power limits by processor type. */
	/* Defined values match the normal voltage versions only. But
	   they are only a hint for OSPM, so this should not hurt much. */
	switch (cpu_id) {
	case 0x006e:
		/* Yonah */
		params->min.power	= SPEEDSTEP_MIN_POWER_YONAH;
		params->max.power	= SPEEDSTEP_MAX_POWER_YONAH;
		break;
	case 0x1067:
		/* Penryn */
		params->slfm.power	= SPEEDSTEP_SLFM_POWER_PENRYN;
		params->min.power	= SPEEDSTEP_MIN_POWER_PENRYN;
		params->max.power	= SPEEDSTEP_MAX_POWER_PENRYN;
		params->turbo.power	= SPEEDSTEP_MAX_POWER_PENRYN;
		break;
	case 0x006f:
		/* Merom */
	default:
		/* Use Merom values by default (as before). */
		params->slfm.power	= SPEEDSTEP_SLFM_POWER_MEROM;
		params->min.power	= SPEEDSTEP_MIN_POWER_MEROM;
		params->max.power	= SPEEDSTEP_MAX_POWER_MEROM;
		params->turbo.power	= SPEEDSTEP_MAX_POWER_MEROM;
		break;
	}
}

/**
 * @brief Generate full p-states table from processor parameters
 *
 * This is generic code and should work at least for Merom and Penryn
 * processors. It is used to generate ACPI tables and configure EMTTM.
 */
void speedstep_gen_pstates(sst_table_t *const table)
{
	sst_params_t params;
	/* Gather speedstep limits. */
	speedstep_get_limits(&params);


	/*\ First, find the number of normal states: \*/

	/* Calculate with doubled values to work
	   around non-integer (.5) bus ratios. */
	const int power_diff2	= (params.max.power - params.min.power) * 2;
	const int vid_diff2	= (params.max.vid - params.min.vid) * 2;
	const int max_ratio2	= SPEEDSTEP_DOUBLE_RATIO(params.max);
	const int min_ratio2	= SPEEDSTEP_DOUBLE_RATIO(params.min);
	const int ratio_diff2	= max_ratio2 - min_ratio2;
	/* Calculate number of normal states (LFM to HFM, min to max). */
	/* Increase step size, until all states fit into the table.
	   (Note: First try should always work, if
	    SPEEDSTEP_MAX_NORMAL_STATES is set correctly.) */
	int states, step2 = 0;
	do {
		step2 += 2 * 2; /* Must be a multiple of 2 (doubled). */
		states = ratio_diff2 / step2 + 1;
	} while (states > SPEEDSTEP_MAX_NORMAL_STATES);
	if (step2 > 4)
		printk(BIOS_INFO, "Enhanced Speedstep processor with "
				  "more than %d possible p-states.\n",
			SPEEDSTEP_MAX_NORMAL_STATES);
	if (states < 2) /* Report at least two normal states. */
		states = 2;


	/*\ Now, fill the table: \*/

	table->num_states = 0;

	/* Add turbo state if supported. */
	if (params.turbo.is_turbo)
		table->states[table->num_states++] = params.turbo;

	/* Add HFM first. */
	table->states[table->num_states] = params.max;
	/* Work around HFM and LFM having the same bus ratio. */
	if ((params.max.dynfsb == params.min.dynfsb) &&
			(params.max.nonint == params.min.nonint) &&
			(params.max.ratio == params.min.ratio))
		table->states[table->num_states].vid = params.min.vid;
	++table->num_states;
	--states;

	/* Now, add all other normal states based on LFM (min). */
	const int power_step	= (power_diff2 / states) / 2;
	const int vid_step	= (vid_diff2 / states) / 2;
	const int ratio_step    = step2 / 2;
	int power   = params.min.power + (states - 1) * power_step;
	int vid	    = params.min.vid   + (states - 1) * vid_step;
	int ratio   = params.min.ratio + (states - 1) * ratio_step;
	for (; states > 0; --states) {
		table->states[table->num_states++] =
			(sst_state_t){ 0, 0, ratio, vid, 0, 0, power };
		power	-= power_step;
		vid	-= vid_step;
		ratio	-= ratio_step;
	}

	/* At last, add Super-LFM state if supported. */
	if (params.slfm.is_slfm)
		table->states[table->num_states++] = params.slfm;
}