p4tcc.c revision 124930
179543Sru/*	$OpenBSD: p4tcc.c,v 1.1 2003/12/20 18:23:18 tedu Exp $ */
279543Sru/*
369626Sru * Copyright (c) 2003 Ted Unangst
469626Sru * Copyright (c) 2004 Maxim Sobolev <sobomax@FreeBSD.org>
569626Sru * All rights reserved.
669626Sru *
769626Sru * Redistribution and use in source and binary forms, with or without
869626Sru * modification, are permitted provided that the following conditions
969626Sru * are met:
1069626Sru * 1. Redistributions of source code must retain the above copyright
1169626Sru *    notice, this list of conditions and the following disclaimer.
1269626Sru * 2. Redistributions in binary form must reproduce the above copyright
1369626Sru *    notice, this list of conditions and the following disclaimer in the
1469626Sru *    documentation and/or other materials provided with the distribution.
1569626Sru *
1669626Sru * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1769626Sru * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1869626Sru * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1969626Sru * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2069626Sru * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
2169626Sru * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
2269626Sru * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
2369626Sru * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2455839Sasmodai * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2555839Sasmodai * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2655839Sasmodai * SUCH DAMAGE.
2755839Sasmodai */
2869626Sru/*
2969626Sru * Restrict power consumption by using thermal control circuit.
3055839Sasmodai * This operates independently of speedstep.
3169626Sru * Found on Pentium 4 and later models (feature TM).
3255839Sasmodai *
3375584Sru * References:
3455839Sasmodai * Intel Developer's manual v.3 #245472-012
3555839Sasmodai *
3655839Sasmodai * On some models, the cpu can hang if it's running at a slow speed.
3755839Sasmodai * Workarounds included below.
3855839Sasmodai */
3955839Sasmodai
4075584Sru#include <sys/cdefs.h>
4175584Sru__FBSDID("$FreeBSD: head/sys/i386/cpufreq/p4tcc.c 124930 2004-01-24 21:13:13Z sobomax $");
4275584Sru
4375584Sru#include "opt_cpu.h"
4475584Sru#include <sys/param.h>
4575584Sru#include <sys/systm.h>
4675584Sru#include <sys/kernel.h>
4775584Sru#include <sys/conf.h>
4875584Sru#include <sys/power.h>
4955839Sasmodai#include <sys/sysctl.h>
5055839Sasmodai#include <sys/types.h>
5155839Sasmodai
5275584Sru#include <machine/md_var.h>
5375584Sru#include <machine/specialreg.h>
5475584Sru
5555839Sasmodaistatic u_int			p4tcc_percentage;
5675584Srustatic u_int			p4tcc_economy;
5755839Sasmodaistatic u_int			p4tcc_performance;
5855839Sasmodaistatic struct sysctl_ctx_list	p4tcc_sysctl_ctx;
5969626Srustatic struct sysctl_oid	*p4tcc_sysctl_tree;
6069626Sru
6169626Srustatic struct {
6269626Sru	u_short level;
6369626Sru	u_short rlevel;
6469626Sru	u_short reg;
6569626Sru} tcc[] = {
6655839Sasmodai	{ 88, 100, 0 },
6775584Sru	{ 75, 88,  7 },
6875584Sru	{ 63, 75,  6 },
6975584Sru	{ 50, 63,  5 },
7075584Sru	{ 38, 50,  4 },
7175584Sru	{ 25, 38,  3 },
7275584Sru	{ 13, 25,  2 },
7375584Sru	{ 0,  13,  1 }
7469626Sru};
7569626Sru
7669626Sru#define TCC_LEVELS	sizeof(tcc) / sizeof(tcc[0])
7769626Sru
7869626Srustatic u_short
7969626Srup4tcc_getperf(void)
8069626Sru{
8169626Sru	u_int64_t msreg;
8269626Sru	int i;
8369626Sru
8469626Sru	msreg = rdmsr(MSR_THERM_CONTROL);
8569626Sru	msreg = (msreg >> 1) & 0x07;
8669626Sru	for (i = 0; i < TCC_LEVELS; i++) {
8769626Sru		if (msreg == tcc[i].reg)
8869626Sru			break;
8969626Sru	}
9069626Sru
9169626Sru	return (tcc[i].rlevel);
9269626Sru}
9369626Sru
9469626Srustatic void
9569626Srup4tcc_setperf(u_int percentage)
9669626Sru{
9769626Sru	int i;
9869626Sru	u_int64_t msreg;
9969626Sru
10069626Sru	if (percentage > tcc[0].rlevel)
10169626Sru		percentage = tcc[0].rlevel;
10269626Sru	for (i = 0; i < TCC_LEVELS - 1; i++) {
10369626Sru		if (percentage > tcc[i].level)
10469626Sru			break;
10569626Sru	}
10669626Sru
10769626Sru	msreg = rdmsr(MSR_THERM_CONTROL);
10869626Sru	msreg &= ~0x1e;	/* bit 0 reserved */
10969626Sru	if (tcc[i].reg != 0)
11075584Sru		msreg |= tcc[i].reg << 1 | 1 << 4;
11175584Sru	wrmsr(MSR_THERM_CONTROL, msreg);
11275584Sru}
11369626Sru
11469626Srustatic int
11575584Srup4tcc_perf_sysctl(SYSCTL_HANDLER_ARGS)
11655839Sasmodai{
11755839Sasmodai	u_int percentage;
11855839Sasmodai	int error;
11955839Sasmodai
12055839Sasmodai	p4tcc_percentage = p4tcc_getperf();
12155839Sasmodai	percentage = p4tcc_percentage;
12255839Sasmodai	error = sysctl_handle_int(oidp, &percentage, 0, req);
12355839Sasmodai	if (error || !req->newptr) {
12469626Sru		return (error);
12569626Sru	}
12669626Sru	if (p4tcc_percentage != percentage) {
12769626Sru		p4tcc_setperf(percentage);
12869626Sru	}
12969626Sru
13069626Sru	return (error);
13169626Sru}
13269626Sru
13369626Srustatic void
13469626Srup4tcc_power_profile(void *arg)
13569626Sru{
13669626Sru	int state;
13769626Sru	u_int new;
13869626Sru
13969626Sru	state = power_profile_get_state();
14069626Sru	if (state != POWER_PROFILE_PERFORMANCE &&
14169626Sru	    state != POWER_PROFILE_ECONOMY) {
14269626Sru		return;
14369626Sru	}
14469626Sru
14569626Sru	switch (state) {
14669626Sru	case POWER_PROFILE_PERFORMANCE:
14769626Sru		new = p4tcc_performance;
14869626Sru		break;
14955839Sasmodai	case POWER_PROFILE_ECONOMY:
15069626Sru		new = p4tcc_economy;
15169626Sru		break;
15269626Sru	default:
15369626Sru		new = p4tcc_getperf();
15469626Sru		break;
15569626Sru	}
15669626Sru
15769626Sru	if (p4tcc_getperf() != new) {
15869626Sru		p4tcc_setperf(new);
15969626Sru	}
16055839Sasmodai}
16169626Sru
16269626Srustatic int
16369626Srup4tcc_profile_sysctl(SYSCTL_HANDLER_ARGS)
16469626Sru{
16569626Sru	u_int32_t *argp;
16669626Sru	u_int32_t arg;
16769626Sru	int error;
16869626Sru
16969626Sru	argp = (u_int32_t *)oidp->oid_arg1;
17069626Sru	arg = *argp;
17155839Sasmodai	error = sysctl_handle_int(oidp, &arg, 0, req);
17255839Sasmodai
17369626Sru	/* error or no new value */
17475584Sru	if ((error != 0) || (req->newptr == NULL))
17555839Sasmodai		return (error);
17669626Sru
17769626Sru	/* range check */
17855839Sasmodai	if (arg > tcc[0].rlevel)
17955839Sasmodai		arg = tcc[0].rlevel;
18055839Sasmodai
18155839Sasmodai	/* set new value and possibly switch */
18255839Sasmodai	*argp = arg;
18355839Sasmodai
18455839Sasmodai	p4tcc_power_profile(NULL);
18555839Sasmodai
18655839Sasmodai	*argp = p4tcc_getperf();
18769626Sru
18869626Sru	return (0);
18969626Sru}
19069626Sru
19169626Srustatic void
19269626Srusetup_p4tcc(void *dummy __unused)
19355839Sasmodai{
19469626Sru
19569626Sru	if ((cpu_feature & (CPUID_ACPI | CPUID_TM)) !=
19669626Sru	    (CPUID_ACPI | CPUID_TM))
19769626Sru		return;
19869626Sru
19955839Sasmodai	switch (cpu_id & 0xf) {
20055839Sasmodai	case 0x22:	/* errata O50 P44 and Z21 */
20169626Sru	case 0x24:
20269626Sru	case 0x25:
20369626Sru	case 0x27:
20469626Sru	case 0x29:
20569626Sru		/* hang with 12.5 */
20655839Sasmodai		tcc[TCC_LEVELS - 1] = tcc[TCC_LEVELS - 2];
20755839Sasmodai		break;
20875584Sru	case 0x07:	/* errata N44 and P18 */
20975584Sru	case 0x0a:
21055839Sasmodai	case 0x12:
21169626Sru	case 0x13:
21255839Sasmodai		/* hang at 12.5 and 25 */
21369626Sru		tcc[TCC_LEVELS - 1] = tcc[TCC_LEVELS - 2] = tcc[TCC_LEVELS - 3];
21469626Sru		break;
21569626Sru	default:
21669626Sru		break;
21769626Sru	}
21869626Sru
21955839Sasmodai	p4tcc_economy = tcc[TCC_LEVELS - 1].rlevel;
22055839Sasmodai	p4tcc_performance = tcc[0].rlevel;
22169626Sru
22269626Sru	p4tcc_percentage = p4tcc_getperf();
22355839Sasmodai	printf("Pentium 4 TCC support enabled, current performance %u%%\n",
22469626Sru	    p4tcc_percentage);
22575584Sru
22675584Sru	sysctl_ctx_init(&p4tcc_sysctl_ctx);
22775584Sru	p4tcc_sysctl_tree = SYSCTL_ADD_NODE(&p4tcc_sysctl_ctx,
22855839Sasmodai	    SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "p4tcc", CTLFLAG_RD, 0,
22969626Sru	    "Pentium 4 Ternal Control Circuitry support");
23069626Sru	SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx,
23155839Sasmodai	    SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO,
23255839Sasmodai	    "cpuperf", CTLTYPE_INT | CTLFLAG_RW,
23369626Sru	    &p4tcc_percentage, 0, p4tcc_perf_sysctl, "I",
23469626Sru	    "CPU performance in % of maximum");
23569626Sru	SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx,
23669626Sru	    SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO,
23755839Sasmodai	    "cpuperf_performance", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_RW,
23855839Sasmodai	    &p4tcc_performance, 0, p4tcc_profile_sysctl, "I",
23969626Sru	    "CPU performance in % of maximum in Performance mode");
24055839Sasmodai	SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx,
24155839Sasmodai	    SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO,
24269626Sru	    "cpuperf_economy", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_RW,
24369626Sru	    &p4tcc_economy, 0, p4tcc_profile_sysctl, "I",
24469626Sru	    "CPU performance in % of maximum in Economy mode");
24569626Sru
24669626Sru	/* register performance profile change handler */
24769626Sru	EVENTHANDLER_REGISTER(power_profile_change, p4tcc_power_profile, NULL, 0);
24869626Sru}
24969626SruSYSINIT(setup_p4tcc, SI_SUB_CPU, SI_ORDER_ANY, setup_p4tcc, NULL);
25069626Sru