e97196c4e20a77361e1667b3b03443cc7831a23c
[dragonfly.git] / sys / platform / pc32 / i386 / p4tcc.c
1 /*      $OpenBSD: p4tcc.c,v 1.1 2003/12/20 18:23:18 tedu Exp $ */
2 /*
3  * Copyright (c) 2003 Ted Unangst
4  * Copyright (c) 2004 Maxim Sobolev <sobomax@FreeBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 /*
29  * Restrict power consumption by using thermal control circuit.
30  * This operates independently of speedstep.
31  * Found on Pentium 4 and later models (feature TM).
32  *
33  * References:
34  * Intel Developer's manual v.3 #245472-012
35  *
36  * On some models, the cpu can hang if it's running at a slow speed.
37  * Workarounds included below.
38  *
39  * $FreeBSD: /repoman/r/ncvs/src/sys/i386/i386/p4tcc.c,v 1.3.2.1 2004/03/03 15:24:15 sobomax Exp $
40  * $DragonFly: src/sys/platform/pc32/i386/p4tcc.c,v 1.3 2007/04/30 07:18:55 dillon Exp $
41  */
42
43 #include <sys/cdefs.h>
44
45 #include "opt_cpu.h"
46 #include <sys/param.h>
47 #include <sys/systm.h>
48 #include <sys/kernel.h>
49 #include <sys/conf.h>
50 #include <sys/power.h>
51 #include <sys/sysctl.h>
52 #include <sys/types.h>
53
54 #include <machine/md_var.h>
55 #include <machine/specialreg.h>
56
57 static u_int                    p4tcc_percentage;
58 static u_int                    p4tcc_economy;
59 static u_int                    p4tcc_performance;
60 static struct sysctl_ctx_list   p4tcc_sysctl_ctx;
61 static struct sysctl_oid        *p4tcc_sysctl_tree;
62
63 static struct {
64         u_short level;
65         u_short rlevel;
66         u_short reg;
67 } tcc[] = {
68         { 88, 100, 0 },
69         { 75, 88,  7 },
70         { 63, 75,  6 },
71         { 50, 63,  5 },
72         { 38, 50,  4 },
73         { 25, 38,  3 },
74         { 13, 25,  2 },
75         { 0,  13,  1 }
76 };
77
78 #define TCC_LEVELS      sizeof(tcc) / sizeof(tcc[0])
79
80 static u_short
81 p4tcc_getperf(void)
82 {
83         u_int64_t msreg;
84         int i;
85
86         msreg = rdmsr(MSR_THERM_CONTROL);
87         msreg = (msreg >> 1) & 0x07;
88         for (i = 0; i < TCC_LEVELS; i++) {
89                 if (msreg == tcc[i].reg)
90                         break;
91         }
92
93         return (tcc[i].rlevel);
94 }
95
96 static void
97 p4tcc_setperf(u_int percentage)
98 {
99         int i;
100         u_int64_t msreg;
101
102         if (percentage > tcc[0].rlevel)
103                 percentage = tcc[0].rlevel;
104         for (i = 0; i < TCC_LEVELS - 1; i++) {
105                 if (percentage > tcc[i].level)
106                         break;
107         }
108
109         msreg = rdmsr(MSR_THERM_CONTROL);
110         msreg &= ~0x1e; /* bit 0 reserved */
111         if (tcc[i].reg != 0)
112                 msreg |= tcc[i].reg << 1 | 1 << 4;
113         wrmsr(MSR_THERM_CONTROL, msreg);
114 }
115
116 static int
117 p4tcc_perf_sysctl(SYSCTL_HANDLER_ARGS)
118 {
119         u_int percentage;
120         int error;
121
122         p4tcc_percentage = p4tcc_getperf();
123         percentage = p4tcc_percentage;
124         error = sysctl_handle_int(oidp, &percentage, 0, req);
125         if (error || !req->newptr) {
126                 return (error);
127         }
128         if (p4tcc_percentage != percentage) {
129                 p4tcc_setperf(percentage);
130         }
131
132         return (error);
133 }
134
135 static void
136 p4tcc_power_profile(void *arg)
137 {
138         int state;
139         u_int new;
140
141         state = power_profile_get_state();
142         if (state != POWER_PROFILE_PERFORMANCE &&
143             state != POWER_PROFILE_ECONOMY) {
144                 return;
145         }
146
147         switch (state) {
148         case POWER_PROFILE_PERFORMANCE:
149                 new = p4tcc_performance;
150                 break;
151         case POWER_PROFILE_ECONOMY:
152                 new = p4tcc_economy;
153                 break;
154         default:
155                 new = p4tcc_getperf();
156                 break;
157         }
158
159         if (p4tcc_getperf() != new) {
160                 p4tcc_setperf(new);
161         }
162 }
163
164 static int
165 p4tcc_profile_sysctl(SYSCTL_HANDLER_ARGS)
166 {
167         u_int32_t *argp;
168         u_int32_t arg;
169         int error;
170
171         argp = (u_int32_t *)oidp->oid_arg1;
172         arg = *argp;
173         error = sysctl_handle_int(oidp, &arg, 0, req);
174
175         /* error or no new value */
176         if ((error != 0) || (req->newptr == NULL))
177                 return (error);
178
179         /* range check */
180         if (arg > tcc[0].rlevel)
181                 arg = tcc[0].rlevel;
182
183         /* set new value and possibly switch */
184         *argp = arg;
185
186         p4tcc_power_profile(NULL);
187
188         *argp = p4tcc_getperf();
189
190         return (0);
191 }
192
193 static void
194 setup_p4tcc(void *dummy __unused)
195 {
196
197         if ((cpu_feature & (CPUID_ACPI | CPUID_TM)) !=
198             (CPUID_ACPI | CPUID_TM))
199                 return;
200
201         switch (cpu_id & 0xf) {
202         case 0x22:      /* errata O50 P44 and Z21 */
203         case 0x24:
204         case 0x25:
205         case 0x27:
206         case 0x29:
207                 /* hang with 12.5 */
208                 tcc[TCC_LEVELS - 1] = tcc[TCC_LEVELS - 2];
209                 break;
210         case 0x07:      /* errata N44 and P18 */
211         case 0x0a:
212         case 0x12:
213         case 0x13:
214                 /* hang at 12.5 and 25 */
215                 tcc[TCC_LEVELS - 1] = tcc[TCC_LEVELS - 2] = tcc[TCC_LEVELS - 3];
216                 break;
217         default:
218                 break;
219         }
220
221         p4tcc_economy = tcc[TCC_LEVELS - 1].rlevel;
222         p4tcc_performance = tcc[0].rlevel;
223
224         p4tcc_percentage = p4tcc_getperf();
225         kprintf("Pentium 4 TCC support enabled, current performance %u%%\n",
226             p4tcc_percentage);
227
228         sysctl_ctx_init(&p4tcc_sysctl_ctx);
229         p4tcc_sysctl_tree = SYSCTL_ADD_NODE(&p4tcc_sysctl_ctx,
230             SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "p4tcc", CTLFLAG_RD, 0,
231             "Pentium 4 Thermal Control Circuitry support");
232         SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx,
233             SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO,
234             "cpuperf", CTLTYPE_INT | CTLFLAG_RW,
235             &p4tcc_percentage, 0, p4tcc_perf_sysctl, "I",
236             "CPU performance in % of maximum");
237         SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx,
238             SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO,
239             "cpuperf_performance", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_RW,
240             &p4tcc_performance, 0, p4tcc_profile_sysctl, "I",
241             "CPU performance in % of maximum in Performance mode");
242         SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx,
243             SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO,
244             "cpuperf_economy", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_RW,
245             &p4tcc_economy, 0, p4tcc_profile_sysctl, "I",
246             "CPU performance in % of maximum in Economy mode");
247
248         /* register performance profile change handler */
249         EVENTHANDLER_REGISTER(power_profile_change, p4tcc_power_profile, NULL, 0);
250 }
251
252 /*
253  * Set this is pre-smp to give us a chance to play nice in case
254  * SMP startup locks the system up.
255  */
256 SYSINIT(setup_p4tcc, SI_BOOT2_PRESMP, SI_ORDER_ANY, setup_p4tcc, NULL);
257