p4tcc.c revision 126412
1141296Sdas/*	$OpenBSD: p4tcc.c,v 1.1 2003/12/20 18:23:18 tedu Exp $ */
2141296Sdas/*
32116Sjkh * Copyright (c) 2003 Ted Unangst
42116Sjkh * Copyright (c) 2004 Maxim Sobolev <sobomax@FreeBSD.org>
52116Sjkh * All rights reserved.
62116Sjkh *
7141296Sdas * Redistribution and use in source and binary forms, with or without
82116Sjkh * modification, are permitted provided that the following conditions
9141296Sdas * are met:
102116Sjkh * 1. Redistributions of source code must retain the above copyright
112116Sjkh *    notice, this list of conditions and the following disclaimer.
12141296Sdas * 2. Redistributions in binary form must reproduce the above copyright
132116Sjkh *    notice, this list of conditions and the following disclaimer in the
142116Sjkh *    documentation and/or other materials provided with the distribution.
15176385Sbde *
16176385Sbde * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
172116Sjkh * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
182116Sjkh * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19141296Sdas * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20141296Sdas * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
212116Sjkh * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
222116Sjkh * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
232116Sjkh * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24176465Sbde * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25176465Sbde * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
262116Sjkh * SUCH DAMAGE.
272116Sjkh */
282116Sjkh/*
292116Sjkh * Restrict power consumption by using thermal control circuit.
302116Sjkh * This operates independently of speedstep.
312116Sjkh * Found on Pentium 4 and later models (feature TM).
322116Sjkh *
332116Sjkh * References:
342116Sjkh * Intel Developer's manual v.3 #245472-012
352116Sjkh *
362116Sjkh * On some models, the cpu can hang if it's running at a slow speed.
372116Sjkh * Workarounds included below.
382116Sjkh */
398870Srgrimes
402116Sjkh#include <sys/cdefs.h>
412116Sjkh__FBSDID("$FreeBSD: head/sys/i386/cpufreq/p4tcc.c 126412 2004-02-29 18:30:35Z maxim $");
422116Sjkh
432116Sjkh#include "opt_cpu.h"
442116Sjkh#include <sys/param.h>
452116Sjkh#include <sys/systm.h>
462116Sjkh#include <sys/kernel.h>
472116Sjkh#include <sys/conf.h>
482116Sjkh#include <sys/power.h>
492116Sjkh#include <sys/sysctl.h>
502116Sjkh#include <sys/types.h>
51176385Sbde
52176385Sbde#include <machine/md_var.h>
53176385Sbde#include <machine/specialreg.h>
54176385Sbde
55176385Sbdestatic u_int			p4tcc_percentage;
562116Sjkhstatic u_int			p4tcc_economy;
572116Sjkhstatic u_int			p4tcc_performance;
582116Sjkhstatic struct sysctl_ctx_list	p4tcc_sysctl_ctx;
592116Sjkhstatic struct sysctl_oid	*p4tcc_sysctl_tree;
602116Sjkh
612116Sjkhstatic struct {
622116Sjkh	u_short level;
632116Sjkh	u_short rlevel;
64176409Sbde	u_short reg;
652116Sjkh} tcc[] = {
662116Sjkh	{ 88, 100, 0 },
67176409Sbde	{ 75, 88,  7 },
68176409Sbde	{ 63, 75,  6 },
69176409Sbde	{ 50, 63,  5 },
70176409Sbde	{ 38, 50,  4 },
71176409Sbde	{ 25, 38,  3 },
72176409Sbde	{ 13, 25,  2 },
73176409Sbde	{ 0,  13,  1 }
747659Sbde};
757659Sbde
76176409Sbde#define TCC_LEVELS	sizeof(tcc) / sizeof(tcc[0])
77176409Sbde
78176409Sbdestatic u_short
797659Sbdep4tcc_getperf(void)
807659Sbde{
81176409Sbde	u_int64_t msreg;
827659Sbde	int i;
83176409Sbde
84176409Sbde	msreg = rdmsr(MSR_THERM_CONTROL);
85176409Sbde	msreg = (msreg >> 1) & 0x07;
86176409Sbde	for (i = 0; i < TCC_LEVELS; i++) {
87176409Sbde		if (msreg == tcc[i].reg)
88176409Sbde			break;
89176409Sbde	}
90176409Sbde
91176409Sbde	return (tcc[i].rlevel);
92176409Sbde}
93176409Sbde
94176409Sbdestatic void
957659Sbdep4tcc_setperf(u_int percentage)
967659Sbde{
97176409Sbde	int i;
98176409Sbde	u_int64_t msreg;
99176409Sbde
100176409Sbde	if (percentage > tcc[0].rlevel)
101176409Sbde		percentage = tcc[0].rlevel;
102176409Sbde	for (i = 0; i < TCC_LEVELS - 1; i++) {
103176409Sbde		if (percentage > tcc[i].level)
104176409Sbde			break;
105176409Sbde	}
106176409Sbde
107176409Sbde	msreg = rdmsr(MSR_THERM_CONTROL);
108176409Sbde	msreg &= ~0x1e;	/* bit 0 reserved */
109176409Sbde	if (tcc[i].reg != 0)
110176409Sbde		msreg |= tcc[i].reg << 1 | 1 << 4;
111176409Sbde	wrmsr(MSR_THERM_CONTROL, msreg);
112176409Sbde}
113176409Sbde
114176409Sbdestatic int
115176409Sbdep4tcc_perf_sysctl(SYSCTL_HANDLER_ARGS)
116176409Sbde{
117176409Sbde	u_int percentage;
118176409Sbde	int error;
119176409Sbde
120176409Sbde	p4tcc_percentage = p4tcc_getperf();
121176409Sbde	percentage = p4tcc_percentage;
122176409Sbde	error = sysctl_handle_int(oidp, &percentage, 0, req);
123176409Sbde	if (error || !req->newptr) {
124176409Sbde		return (error);
125176409Sbde	}
126176409Sbde	if (p4tcc_percentage != percentage) {
127176409Sbde		p4tcc_setperf(percentage);
1282116Sjkh	}
129176409Sbde
1302116Sjkh	return (error);
131176465Sbde}
132176465Sbde
133176465Sbdestatic void
134176465Sbdep4tcc_power_profile(void *arg)
135176465Sbde{
136176465Sbde	int state;
1372116Sjkh	u_int new;
1382116Sjkh
139176465Sbde	state = power_profile_get_state();
1402116Sjkh	if (state != POWER_PROFILE_PERFORMANCE &&
1412116Sjkh	    state != POWER_PROFILE_ECONOMY) {
142176466Sbde		return;
1432116Sjkh	}
1442116Sjkh
145141296Sdas	switch (state) {
1462116Sjkh	case POWER_PROFILE_PERFORMANCE:
1472116Sjkh		new = p4tcc_performance;
1482116Sjkh		break;
1492116Sjkh	case POWER_PROFILE_ECONOMY:
150141296Sdas		new = p4tcc_economy;
1512116Sjkh		break;
152141296Sdas	default:
1532116Sjkh		new = p4tcc_getperf();
1542116Sjkh		break;
1552116Sjkh	}
1562116Sjkh
1572116Sjkh	if (p4tcc_getperf() != new) {
158141296Sdas		p4tcc_setperf(new);
1592116Sjkh	}
160141296Sdas}
1612116Sjkh
1622116Sjkhstatic int
1632116Sjkhp4tcc_profile_sysctl(SYSCTL_HANDLER_ARGS)
1642116Sjkh{
1652116Sjkh	u_int32_t *argp;
1662116Sjkh	u_int32_t arg;
1672116Sjkh	int error;
1682116Sjkh
169141296Sdas	argp = (u_int32_t *)oidp->oid_arg1;
1702116Sjkh	arg = *argp;
1712116Sjkh	error = sysctl_handle_int(oidp, &arg, 0, req);
1722116Sjkh
1732116Sjkh	/* error or no new value */
1742116Sjkh	if ((error != 0) || (req->newptr == NULL))
1752116Sjkh		return (error);
1762116Sjkh
1772116Sjkh	/* range check */
1782116Sjkh	if (arg > tcc[0].rlevel)
1792116Sjkh		arg = tcc[0].rlevel;
1802116Sjkh
1812116Sjkh	/* set new value and possibly switch */
1822116Sjkh	*argp = arg;
1832116Sjkh
1842116Sjkh	p4tcc_power_profile(NULL);
1852116Sjkh
1862116Sjkh	*argp = p4tcc_getperf();
187176356Sdas
1882116Sjkh	return (0);
1892116Sjkh}
1902116Sjkh
191static void
192setup_p4tcc(void *dummy __unused)
193{
194
195	if ((cpu_feature & (CPUID_ACPI | CPUID_TM)) !=
196	    (CPUID_ACPI | CPUID_TM))
197		return;
198
199	switch (cpu_id & 0xf) {
200	case 0x22:	/* errata O50 P44 and Z21 */
201	case 0x24:
202	case 0x25:
203	case 0x27:
204	case 0x29:
205		/* hang with 12.5 */
206		tcc[TCC_LEVELS - 1] = tcc[TCC_LEVELS - 2];
207		break;
208	case 0x07:	/* errata N44 and P18 */
209	case 0x0a:
210	case 0x12:
211	case 0x13:
212		/* hang at 12.5 and 25 */
213		tcc[TCC_LEVELS - 1] = tcc[TCC_LEVELS - 2] = tcc[TCC_LEVELS - 3];
214		break;
215	default:
216		break;
217	}
218
219	p4tcc_economy = tcc[TCC_LEVELS - 1].rlevel;
220	p4tcc_performance = tcc[0].rlevel;
221
222	p4tcc_percentage = p4tcc_getperf();
223	printf("Pentium 4 TCC support enabled, current performance %u%%\n",
224	    p4tcc_percentage);
225
226	sysctl_ctx_init(&p4tcc_sysctl_ctx);
227	p4tcc_sysctl_tree = SYSCTL_ADD_NODE(&p4tcc_sysctl_ctx,
228	    SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "p4tcc", CTLFLAG_RD, 0,
229	    "Pentium 4 Thermal Control Circuitry support");
230	SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx,
231	    SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO,
232	    "cpuperf", CTLTYPE_INT | CTLFLAG_RW,
233	    &p4tcc_percentage, 0, p4tcc_perf_sysctl, "I",
234	    "CPU performance in % of maximum");
235	SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx,
236	    SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO,
237	    "cpuperf_performance", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_RW,
238	    &p4tcc_performance, 0, p4tcc_profile_sysctl, "I",
239	    "CPU performance in % of maximum in Performance mode");
240	SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx,
241	    SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO,
242	    "cpuperf_economy", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_RW,
243	    &p4tcc_economy, 0, p4tcc_profile_sysctl, "I",
244	    "CPU performance in % of maximum in Economy mode");
245
246	/* register performance profile change handler */
247	EVENTHANDLER_REGISTER(power_profile_change, p4tcc_power_profile, NULL, 0);
248}
249SYSINIT(setup_p4tcc, SI_SUB_CPU, SI_ORDER_ANY, setup_p4tcc, NULL);
250