1124961Ssobomax/*-
2124961Ssobomax * Copyright (c) 2001 Tamotsu Hattori.
3138722Snjl * Copyright (c) 2001 Mitsuru IWASAKI.
4124961Ssobomax * All rights reserved.
5124961Ssobomax *
6124961Ssobomax * Redistribution and use in source and binary forms, with or without
7124961Ssobomax * modification, are permitted provided that the following conditions
8124961Ssobomax * are met:
9124961Ssobomax * 1. Redistributions of source code must retain the above copyright
10124961Ssobomax *    notice, this list of conditions and the following disclaimer.
11124961Ssobomax * 2. Redistributions in binary form must reproduce the above copyright
12124961Ssobomax *    notice, this list of conditions and the following disclaimer in the
13124961Ssobomax *    documentation and/or other materials provided with the distribution.
14124961Ssobomax * 3. All advertising materials mentioning features or use of this software
15124961Ssobomax *    must display the following acknowledgement:
16124961Ssobomax *	This product includes software developed by the University of
17124961Ssobomax *	California, Berkeley and its contributors.
18124961Ssobomax * 4. Neither the name of the University nor the names of its contributors
19124961Ssobomax *    may be used to endorse or promote products derived from this software
20124961Ssobomax *    without specific prior written permission.
21124961Ssobomax *
22124961Ssobomax * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23124961Ssobomax * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24124961Ssobomax * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25124961Ssobomax * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26124961Ssobomax * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27124961Ssobomax * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28124961Ssobomax * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29124961Ssobomax * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30124961Ssobomax * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31124961Ssobomax * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32124961Ssobomax * SUCH DAMAGE.
33124961Ssobomax *
34124961Ssobomax */
35124961Ssobomax
36124961Ssobomax#include <sys/cdefs.h>
37124961Ssobomax__FBSDID("$FreeBSD$");
38124961Ssobomax
39124961Ssobomax#include "opt_cpu.h"
40124961Ssobomax
41124961Ssobomax#include <sys/param.h>
42124961Ssobomax#include <sys/systm.h>
43124961Ssobomax#include <sys/kernel.h>
44124961Ssobomax#include <sys/conf.h>
45124961Ssobomax#include <sys/power.h>
46124961Ssobomax#include <sys/sysctl.h>
47124961Ssobomax#include <sys/types.h>
48124961Ssobomax
49185341Sjkim#include <machine/cputypes.h>
50124961Ssobomax#include <machine/md_var.h>
51124961Ssobomax#include <machine/specialreg.h>
52124961Ssobomax
53124961Ssobomax/*
54124961Ssobomax * Transmeta Crusoe LongRun Support by Tamotsu Hattori.
55124961Ssobomax */
56124961Ssobomax
57124961Ssobomax#define MSR_TMx86_LONGRUN		0x80868010
58124961Ssobomax#define MSR_TMx86_LONGRUN_FLAGS		0x80868011
59124961Ssobomax
60124961Ssobomax#define LONGRUN_MODE_MASK(x)		((x) & 0x000000007f)
61124961Ssobomax#define LONGRUN_MODE_RESERVED(x)	((x) & 0xffffff80)
62124961Ssobomax#define LONGRUN_MODE_WRITE(x, y)	(LONGRUN_MODE_RESERVED(x) | LONGRUN_MODE_MASK(y))
63124961Ssobomax
64124961Ssobomax#define LONGRUN_MODE_MINFREQUENCY	0x00
65124961Ssobomax#define LONGRUN_MODE_ECONOMY		0x01
66124961Ssobomax#define LONGRUN_MODE_PERFORMANCE	0x02
67124961Ssobomax#define LONGRUN_MODE_MAXFREQUENCY	0x03
68124961Ssobomax#define LONGRUN_MODE_UNKNOWN		0x04
69124961Ssobomax#define LONGRUN_MODE_MAX		0x04
70124961Ssobomax
71124961Ssobomaxunion msrinfo {
72124961Ssobomax	u_int64_t	msr;
73124961Ssobomax	u_int32_t	regs[2];
74124961Ssobomax};
75124961Ssobomax
76124961Ssobomaxstatic u_int32_t longrun_modes[LONGRUN_MODE_MAX][3] = {
77124961Ssobomax	/*  MSR low, MSR high, flags bit0 */
78124961Ssobomax	{	  0,	  0,		0},	/* LONGRUN_MODE_MINFREQUENCY */
79124961Ssobomax	{	  0,	100,		0},	/* LONGRUN_MODE_ECONOMY */
80124961Ssobomax	{	  0,	100,		1},	/* LONGRUN_MODE_PERFORMANCE */
81124961Ssobomax	{	100,	100,		1},	/* LONGRUN_MODE_MAXFREQUENCY */
82124961Ssobomax};
83124961Ssobomax
84124961Ssobomaxstatic u_int
85124961Ssobomaxtmx86_get_longrun_mode(void)
86124961Ssobomax{
87214346Sjhb	register_t	saveintr;
88124961Ssobomax	union msrinfo	msrinfo;
89124961Ssobomax	u_int		low, high, flags, mode;
90124961Ssobomax
91214346Sjhb	saveintr = intr_disable();
92124961Ssobomax
93124961Ssobomax	msrinfo.msr = rdmsr(MSR_TMx86_LONGRUN);
94124961Ssobomax	low = LONGRUN_MODE_MASK(msrinfo.regs[0]);
95124961Ssobomax	high = LONGRUN_MODE_MASK(msrinfo.regs[1]);
96124961Ssobomax	flags = rdmsr(MSR_TMx86_LONGRUN_FLAGS) & 0x01;
97124961Ssobomax
98124961Ssobomax	for (mode = 0; mode < LONGRUN_MODE_MAX; mode++) {
99124961Ssobomax		if (low   == longrun_modes[mode][0] &&
100124961Ssobomax		    high  == longrun_modes[mode][1] &&
101124961Ssobomax		    flags == longrun_modes[mode][2]) {
102124961Ssobomax			goto out;
103124961Ssobomax		}
104124961Ssobomax	}
105124961Ssobomax	mode = LONGRUN_MODE_UNKNOWN;
106124961Ssobomaxout:
107214346Sjhb	intr_restore(saveintr);
108124961Ssobomax	return (mode);
109124961Ssobomax}
110124961Ssobomax
111124961Ssobomaxstatic u_int
112124961Ssobomaxtmx86_get_longrun_status(u_int * frequency, u_int * voltage, u_int * percentage)
113124961Ssobomax{
114214346Sjhb	register_t	saveintr;
115124961Ssobomax	u_int		regs[4];
116124961Ssobomax
117214346Sjhb	saveintr = intr_disable();
118124961Ssobomax
119124961Ssobomax	do_cpuid(0x80860007, regs);
120124961Ssobomax	*frequency = regs[0];
121124961Ssobomax	*voltage = regs[1];
122124961Ssobomax	*percentage = regs[2];
123124961Ssobomax
124214346Sjhb	intr_restore(saveintr);
125124961Ssobomax	return (1);
126124961Ssobomax}
127124961Ssobomax
128124961Ssobomaxstatic u_int
129124961Ssobomaxtmx86_set_longrun_mode(u_int mode)
130124961Ssobomax{
131214346Sjhb	register_t	saveintr;
132124961Ssobomax	union msrinfo	msrinfo;
133124961Ssobomax
134124961Ssobomax	if (mode >= LONGRUN_MODE_UNKNOWN) {
135124961Ssobomax		return (0);
136124961Ssobomax	}
137124961Ssobomax
138214346Sjhb	saveintr = intr_disable();
139124961Ssobomax
140124961Ssobomax	/* Write LongRun mode values to Model Specific Register. */
141124961Ssobomax	msrinfo.msr = rdmsr(MSR_TMx86_LONGRUN);
142124961Ssobomax	msrinfo.regs[0] = LONGRUN_MODE_WRITE(msrinfo.regs[0],
143124961Ssobomax					     longrun_modes[mode][0]);
144124961Ssobomax	msrinfo.regs[1] = LONGRUN_MODE_WRITE(msrinfo.regs[1],
145124961Ssobomax					     longrun_modes[mode][1]);
146124961Ssobomax	wrmsr(MSR_TMx86_LONGRUN, msrinfo.msr);
147124961Ssobomax
148124961Ssobomax	/* Write LongRun mode flags to Model Specific Register. */
149124961Ssobomax	msrinfo.msr = rdmsr(MSR_TMx86_LONGRUN_FLAGS);
150124961Ssobomax	msrinfo.regs[0] = (msrinfo.regs[0] & ~0x01) | longrun_modes[mode][2];
151124961Ssobomax	wrmsr(MSR_TMx86_LONGRUN_FLAGS, msrinfo.msr);
152124961Ssobomax
153214346Sjhb	intr_restore(saveintr);
154124961Ssobomax	return (1);
155124961Ssobomax}
156124961Ssobomax
157124961Ssobomaxstatic u_int			 crusoe_longrun;
158124961Ssobomaxstatic u_int			 crusoe_frequency;
159124961Ssobomaxstatic u_int	 		 crusoe_voltage;
160124961Ssobomaxstatic u_int	 		 crusoe_percentage;
161124961Ssobomaxstatic u_int	 		 crusoe_performance_longrun = LONGRUN_MODE_PERFORMANCE;
162124961Ssobomaxstatic u_int	 		 crusoe_economy_longrun = LONGRUN_MODE_ECONOMY;
163124961Ssobomaxstatic struct sysctl_ctx_list	 crusoe_sysctl_ctx;
164124961Ssobomaxstatic struct sysctl_oid	*crusoe_sysctl_tree;
165124961Ssobomax
166124961Ssobomaxstatic void
167124961Ssobomaxtmx86_longrun_power_profile(void *arg)
168124961Ssobomax{
169124961Ssobomax	int	state;
170124961Ssobomax	u_int	new;
171124961Ssobomax
172124961Ssobomax	state = power_profile_get_state();
173124961Ssobomax	if (state != POWER_PROFILE_PERFORMANCE &&
174124961Ssobomax	    state != POWER_PROFILE_ECONOMY) {
175124961Ssobomax		return;
176124961Ssobomax	}
177124961Ssobomax
178124961Ssobomax	switch (state) {
179124961Ssobomax	case POWER_PROFILE_PERFORMANCE:
180124961Ssobomax		new =crusoe_performance_longrun;
181124961Ssobomax		break;
182124961Ssobomax	case POWER_PROFILE_ECONOMY:
183124961Ssobomax		new = crusoe_economy_longrun;
184124961Ssobomax		break;
185124961Ssobomax	default:
186124961Ssobomax		new = tmx86_get_longrun_mode();
187124961Ssobomax		break;
188124961Ssobomax	}
189124961Ssobomax
190124961Ssobomax	if (tmx86_get_longrun_mode() != new) {
191124961Ssobomax		tmx86_set_longrun_mode(new);
192124961Ssobomax	}
193124961Ssobomax}
194124961Ssobomax
195124961Ssobomaxstatic int
196124961Ssobomaxtmx86_longrun_sysctl(SYSCTL_HANDLER_ARGS)
197124961Ssobomax{
198124961Ssobomax	u_int	mode;
199124961Ssobomax	int	error;
200124961Ssobomax
201124961Ssobomax	crusoe_longrun = tmx86_get_longrun_mode();
202124961Ssobomax	mode = crusoe_longrun;
203124961Ssobomax	error = sysctl_handle_int(oidp, &mode, 0, req);
204124961Ssobomax	if (error || !req->newptr) {
205124961Ssobomax		return (error);
206124961Ssobomax	}
207124961Ssobomax	if (mode >= LONGRUN_MODE_UNKNOWN) {
208124961Ssobomax		error = EINVAL;
209124961Ssobomax		return (error);
210124961Ssobomax	}
211124961Ssobomax	if (crusoe_longrun != mode) {
212124961Ssobomax		crusoe_longrun = mode;
213124961Ssobomax		tmx86_set_longrun_mode(crusoe_longrun);
214124961Ssobomax	}
215124961Ssobomax
216124961Ssobomax	return (error);
217124961Ssobomax}
218124961Ssobomax
219124961Ssobomaxstatic int
220124961Ssobomaxtmx86_status_sysctl(SYSCTL_HANDLER_ARGS)
221124961Ssobomax{
222124961Ssobomax	u_int	val;
223124961Ssobomax	int	error;
224124961Ssobomax
225124961Ssobomax	tmx86_get_longrun_status(&crusoe_frequency,
226124961Ssobomax				 &crusoe_voltage, &crusoe_percentage);
227124961Ssobomax	val = *(u_int *)oidp->oid_arg1;
228124961Ssobomax	error = sysctl_handle_int(oidp, &val, 0, req);
229124961Ssobomax	return (error);
230124961Ssobomax}
231124961Ssobomax
232124961Ssobomaxstatic int
233124961Ssobomaxtmx86_longrun_profile_sysctl(SYSCTL_HANDLER_ARGS)
234124961Ssobomax{
235124961Ssobomax	u_int32_t *argp;
236124961Ssobomax	u_int32_t arg;
237124961Ssobomax	int	error;
238124961Ssobomax
239124961Ssobomax	argp = (u_int32_t *)oidp->oid_arg1;
240124961Ssobomax	arg = *argp;
241124961Ssobomax	error = sysctl_handle_int(oidp, &arg, 0, req);
242124961Ssobomax
243124961Ssobomax	/* error or no new value */
244124961Ssobomax	if ((error != 0) || (req->newptr == NULL))
245124961Ssobomax		return (error);
246124961Ssobomax
247124961Ssobomax	/* range check */
248124961Ssobomax	if (arg >= LONGRUN_MODE_UNKNOWN)
249124961Ssobomax		return (EINVAL);
250124961Ssobomax
251124961Ssobomax	/* set new value and possibly switch */
252124961Ssobomax	*argp = arg;
253124961Ssobomax
254124961Ssobomax	tmx86_longrun_power_profile(NULL);
255124961Ssobomax
256124961Ssobomax	return (0);
257124961Ssobomax
258124961Ssobomax}
259124961Ssobomax
260124961Ssobomaxstatic void
261124961Ssobomaxsetup_tmx86_longrun(void *dummy __unused)
262124961Ssobomax{
263185341Sjkim
264185341Sjkim	if (cpu_vendor_id != CPU_VENDOR_TRANSMETA)
265124961Ssobomax		return;
266124961Ssobomax
267124961Ssobomax	crusoe_longrun = tmx86_get_longrun_mode();
268124961Ssobomax	tmx86_get_longrun_status(&crusoe_frequency,
269124961Ssobomax				 &crusoe_voltage, &crusoe_percentage);
270124961Ssobomax	printf("Crusoe LongRun support enabled, current mode: %d "
271124961Ssobomax	       "<%dMHz %dmV %d%%>\n", crusoe_longrun, crusoe_frequency,
272124961Ssobomax	       crusoe_voltage, crusoe_percentage);
273124961Ssobomax
274124961Ssobomax	sysctl_ctx_init(&crusoe_sysctl_ctx);
275124961Ssobomax	crusoe_sysctl_tree = SYSCTL_ADD_NODE(&crusoe_sysctl_ctx,
276124961Ssobomax				SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO,
277124961Ssobomax				"crusoe", CTLFLAG_RD, 0,
278124961Ssobomax				"Transmeta Crusoe LongRun support");
279124961Ssobomax	SYSCTL_ADD_PROC(&crusoe_sysctl_ctx, SYSCTL_CHILDREN(crusoe_sysctl_tree),
280124961Ssobomax		OID_AUTO, "longrun", CTLTYPE_INT | CTLFLAG_RW,
281124961Ssobomax		&crusoe_longrun, 0, tmx86_longrun_sysctl, "I",
282124961Ssobomax		"LongRun mode [0-3]");
283124961Ssobomax	SYSCTL_ADD_PROC(&crusoe_sysctl_ctx, SYSCTL_CHILDREN(crusoe_sysctl_tree),
284124961Ssobomax		OID_AUTO, "frequency", CTLTYPE_INT | CTLFLAG_RD,
285124961Ssobomax		&crusoe_frequency, 0, tmx86_status_sysctl, "I",
286124961Ssobomax		"Current frequency (MHz)");
287124961Ssobomax	SYSCTL_ADD_PROC(&crusoe_sysctl_ctx, SYSCTL_CHILDREN(crusoe_sysctl_tree),
288124961Ssobomax		OID_AUTO, "voltage", CTLTYPE_INT | CTLFLAG_RD,
289124961Ssobomax		&crusoe_voltage, 0, tmx86_status_sysctl, "I",
290124961Ssobomax		"Current voltage (mV)");
291124961Ssobomax	SYSCTL_ADD_PROC(&crusoe_sysctl_ctx, SYSCTL_CHILDREN(crusoe_sysctl_tree),
292124961Ssobomax		OID_AUTO, "percentage", CTLTYPE_INT | CTLFLAG_RD,
293124961Ssobomax		&crusoe_percentage, 0, tmx86_status_sysctl, "I",
294124961Ssobomax		"Processing performance (%)");
295124961Ssobomax	SYSCTL_ADD_PROC(&crusoe_sysctl_ctx, SYSCTL_CHILDREN(crusoe_sysctl_tree),
296124961Ssobomax		OID_AUTO, "performance_longrun", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_RW,
297124961Ssobomax		&crusoe_performance_longrun, 0, tmx86_longrun_profile_sysctl, "I", "");
298124961Ssobomax	SYSCTL_ADD_PROC(&crusoe_sysctl_ctx, SYSCTL_CHILDREN(crusoe_sysctl_tree),
299124961Ssobomax		OID_AUTO, "economy_longrun", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_RW,
300124961Ssobomax		&crusoe_economy_longrun, 0, tmx86_longrun_profile_sysctl, "I", "");
301124961Ssobomax
302124961Ssobomax	/* register performance profile change handler */
303124961Ssobomax	EVENTHANDLER_REGISTER(power_profile_change, tmx86_longrun_power_profile, NULL, 0);
304124961Ssobomax}
305124961SsobomaxSYSINIT(setup_tmx86_longrun, SI_SUB_CPU, SI_ORDER_ANY, setup_tmx86_longrun,
306124961Ssobomax    NULL);
307