1144194Snjl/*-
2144194Snjl * Copyright (c) 2004-2005 Bruno Ducrot
3144194Snjl * Copyright (c) 2004 FUKUDA Nobuhiko <nfukuda@spa.is.uec.ac.jp>
4144194Snjl *
5144194Snjl * Redistribution and use in source and binary forms, with or without
6144194Snjl * modification, are permitted provided that the following conditions
7144194Snjl * are met:
8144194Snjl * 1. Redistributions of source code must retain the above copyright
9144194Snjl *    notice, this list of conditions and the following disclaimer.
10144194Snjl * 2. Redistributions in binary form must reproduce the above copyright
11144194Snjl *    notice, this list of conditions and the following disclaimer in the
12144194Snjl *    documentation and/or other materials provided with the distribution.
13144194Snjl *
14144194Snjl * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15144194Snjl * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16144194Snjl * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17144194Snjl * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18144194Snjl * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19144194Snjl * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20144194Snjl * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21144194Snjl * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22144194Snjl * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23144194Snjl * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24144194Snjl */
25144194Snjl
26144194Snjl/*
27144194Snjl * Many thanks to Nate Lawson for his helpful comments on this driver and
28144194Snjl * to Jung-uk Kim for testing.
29144194Snjl */
30144194Snjl
31144194Snjl#include <sys/cdefs.h>
32144194Snjl__FBSDID("$FreeBSD$");
33144194Snjl
34144194Snjl#include <sys/param.h>
35144194Snjl#include <sys/bus.h>
36144194Snjl#include <sys/cpu.h>
37144194Snjl#include <sys/kernel.h>
38144194Snjl#include <sys/malloc.h>
39144194Snjl#include <sys/module.h>
40144194Snjl#include <sys/pcpu.h>
41144194Snjl#include <sys/systm.h>
42144194Snjl
43144194Snjl#include <machine/pc/bios.h>
44144194Snjl#include <machine/md_var.h>
45144194Snjl#include <machine/specialreg.h>
46144194Snjl#include <machine/cputypes.h>
47144194Snjl#include <machine/vmparam.h>
48144194Snjl#include <sys/rman.h>
49144194Snjl
50144194Snjl#include <vm/vm.h>
51144194Snjl#include <vm/pmap.h>
52144194Snjl
53144194Snjl#include "cpufreq_if.h"
54144194Snjl
55144194Snjl#define PN7_TYPE	0
56144194Snjl#define PN8_TYPE	1
57144194Snjl
58166197Sbruno/* Flags for some hardware bugs. */
59166197Sbruno#define A0_ERRATA	0x1	/* Bugs for the rev. A0 of Athlon (K7):
60166197Sbruno				 * Interrupts must be disabled and no half
61166197Sbruno				 * multipliers are allowed */
62166197Sbruno#define PENDING_STUCK	0x2	/* With some buggy chipset and some newer AMD64
63166197Sbruno				 * processor (Rev. G?):
64166197Sbruno				 * the pending bit from the msr FIDVID_STATUS
65166197Sbruno				 * is set forever.  No workaround :( */
66166197Sbruno
67144194Snjl/* Legacy configuration via BIOS table PSB. */
68144194Snjl#define PSB_START	0
69144194Snjl#define PSB_STEP	0x10
70144194Snjl#define PSB_SIG		"AMDK7PNOW!"
71144194Snjl#define PSB_LEN		10
72144194Snjl#define PSB_OFF		0
73144194Snjl
74144194Snjlstruct psb_header {
75144194Snjl	char		 signature[10];
76144194Snjl	uint8_t		 version;
77144194Snjl	uint8_t		 flags;
78144194Snjl	uint16_t	 settlingtime;
79144194Snjl	uint8_t		 res1;
80144194Snjl	uint8_t		 numpst;
81144194Snjl} __packed;
82144194Snjl
83144194Snjlstruct pst_header {
84144194Snjl	uint32_t	 cpuid;
85144194Snjl	uint8_t		 fsb;
86144194Snjl	uint8_t		 maxfid;
87144194Snjl	uint8_t		 startvid;
88144194Snjl	uint8_t		 numpstates;
89144194Snjl} __packed;
90144194Snjl
91144194Snjl/*
92144194Snjl * MSRs and bits used by Powernow technology
93144194Snjl */
94144194Snjl#define MSR_AMDK7_FIDVID_CTL		0xc0010041
95144194Snjl#define MSR_AMDK7_FIDVID_STATUS		0xc0010042
96144194Snjl
97144194Snjl/* Bitfields used by K7 */
98144194Snjl
99144194Snjl#define PN7_CTR_FID(x)			((x) & 0x1f)
100144194Snjl#define PN7_CTR_VID(x)			(((x) & 0x1f) << 8)
101144194Snjl#define PN7_CTR_FIDC			0x00010000
102144194Snjl#define PN7_CTR_VIDC			0x00020000
103144194Snjl#define PN7_CTR_FIDCHRATIO		0x00100000
104144194Snjl#define PN7_CTR_SGTC(x)			(((uint64_t)(x) & 0x000fffff) << 32)
105144194Snjl
106144194Snjl#define PN7_STA_CFID(x)			((x) & 0x1f)
107144194Snjl#define PN7_STA_SFID(x)			(((x) >> 8) & 0x1f)
108144194Snjl#define PN7_STA_MFID(x)			(((x) >> 16) & 0x1f)
109144194Snjl#define PN7_STA_CVID(x)			(((x) >> 32) & 0x1f)
110144194Snjl#define PN7_STA_SVID(x)			(((x) >> 40) & 0x1f)
111144194Snjl#define PN7_STA_MVID(x)			(((x) >> 48) & 0x1f)
112144194Snjl
113144194Snjl/* ACPI ctr_val status register to powernow k7 configuration */
114144194Snjl#define ACPI_PN7_CTRL_TO_FID(x)		((x) & 0x1f)
115144194Snjl#define ACPI_PN7_CTRL_TO_VID(x)		(((x) >> 5) & 0x1f)
116144194Snjl#define ACPI_PN7_CTRL_TO_SGTC(x)	(((x) >> 10) & 0xffff)
117144194Snjl
118144194Snjl/* Bitfields used by K8 */
119144194Snjl
120144194Snjl#define PN8_CTR_FID(x)			((x) & 0x3f)
121144194Snjl#define PN8_CTR_VID(x)			(((x) & 0x1f) << 8)
122144194Snjl#define PN8_CTR_PENDING(x)		(((x) & 1) << 32)
123144194Snjl
124144194Snjl#define PN8_STA_CFID(x)			((x) & 0x3f)
125144194Snjl#define PN8_STA_SFID(x)			(((x) >> 8) & 0x3f)
126144194Snjl#define PN8_STA_MFID(x)			(((x) >> 16) & 0x3f)
127144194Snjl#define PN8_STA_PENDING(x)		(((x) >> 31) & 0x01)
128144194Snjl#define PN8_STA_CVID(x)			(((x) >> 32) & 0x1f)
129144194Snjl#define PN8_STA_SVID(x)			(((x) >> 40) & 0x1f)
130144194Snjl#define PN8_STA_MVID(x)			(((x) >> 48) & 0x1f)
131144194Snjl
132144194Snjl/* Reserved1 to powernow k8 configuration */
133144194Snjl#define PN8_PSB_TO_RVO(x)		((x) & 0x03)
134144194Snjl#define PN8_PSB_TO_IRT(x)		(((x) >> 2) & 0x03)
135144194Snjl#define PN8_PSB_TO_MVS(x)		(((x) >> 4) & 0x03)
136144194Snjl#define PN8_PSB_TO_BATT(x)		(((x) >> 6) & 0x03)
137144194Snjl
138144194Snjl/* ACPI ctr_val status register to powernow k8 configuration */
139144194Snjl#define ACPI_PN8_CTRL_TO_FID(x)		((x) & 0x3f)
140144194Snjl#define ACPI_PN8_CTRL_TO_VID(x)		(((x) >> 6) & 0x1f)
141144194Snjl#define ACPI_PN8_CTRL_TO_VST(x)		(((x) >> 11) & 0x1f)
142144194Snjl#define ACPI_PN8_CTRL_TO_MVS(x)		(((x) >> 18) & 0x03)
143144194Snjl#define ACPI_PN8_CTRL_TO_PLL(x)		(((x) >> 20) & 0x7f)
144144194Snjl#define ACPI_PN8_CTRL_TO_RVO(x)		(((x) >> 28) & 0x03)
145144194Snjl#define ACPI_PN8_CTRL_TO_IRT(x)		(((x) >> 30) & 0x03)
146144194Snjl
147144194Snjl
148144194Snjl#define WRITE_FIDVID(fid, vid, ctrl)	\
149144194Snjl	wrmsr(MSR_AMDK7_FIDVID_CTL,	\
150144194Snjl	    (((ctrl) << 32) | (1ULL << 16) | ((vid) << 8) | (fid)))
151144194Snjl
152144194Snjl#define COUNT_OFF_IRT(irt)	DELAY(10 * (1 << (irt)))
153144194Snjl#define COUNT_OFF_VST(vst)	DELAY(20 * (vst))
154144194Snjl
155144194Snjl#define FID_TO_VCO_FID(fid)	\
156144194Snjl	(((fid) < 8) ? (8 + ((fid) << 1)) : (fid))
157144194Snjl
158144194Snjl/*
159144194Snjl * Divide each value by 10 to get the processor multiplier.
160144194Snjl * Some of those tables are the same as the Linux powernow-k7
161144194Snjl * implementation by Dave Jones.
162144194Snjl */
163144194Snjlstatic int pn7_fid_to_mult[32] = {
164144194Snjl	110, 115, 120, 125, 50, 55, 60, 65,
165144194Snjl	70, 75, 80, 85, 90, 95, 100, 105,
166144194Snjl	30, 190, 40, 200, 130, 135, 140, 210,
167144194Snjl	150, 225, 160, 165, 170, 180, 0, 0,
168144194Snjl};
169144194Snjl
170144194Snjl
171166197Sbrunostatic int pn8_fid_to_mult[64] = {
172166197Sbruno	40, 45, 50, 55, 60, 65, 70, 75,
173166197Sbruno	80, 85, 90, 95, 100, 105, 110, 115,
174166197Sbruno	120, 125, 130, 135, 140, 145, 150, 155,
175166197Sbruno	160, 165, 170, 175, 180, 185, 190, 195,
176166197Sbruno	200, 205, 210, 215, 220, 225, 230, 235,
177166197Sbruno	240, 245, 250, 255, 260, 265, 270, 275,
178166197Sbruno	280, 285, 290, 295, 300, 305, 310, 315,
179166197Sbruno	320, 325, 330, 335, 340, 345, 350, 355,
180144194Snjl};
181144194Snjl
182144194Snjl/*
183144194Snjl * Units are in mV.
184144194Snjl */
185144194Snjl/* Mobile VRM (K7) */
186144194Snjlstatic int pn7_mobile_vid_to_volts[] = {
187144194Snjl	2000, 1950, 1900, 1850, 1800, 1750, 1700, 1650,
188144194Snjl	1600, 1550, 1500, 1450, 1400, 1350, 1300, 0,
189144194Snjl	1275, 1250, 1225, 1200, 1175, 1150, 1125, 1100,
190144194Snjl	1075, 1050, 1025, 1000, 975, 950, 925, 0,
191144194Snjl};
192144194Snjl/* Desktop VRM (K7) */
193144194Snjlstatic int pn7_desktop_vid_to_volts[] = {
194144194Snjl	2000, 1950, 1900, 1850, 1800, 1750, 1700, 1650,
195144194Snjl	1600, 1550, 1500, 1450, 1400, 1350, 1300, 0,
196144194Snjl	1275, 1250, 1225, 1200, 1175, 1150, 1125, 1100,
197144194Snjl	1075, 1050, 1025, 1000, 975, 950, 925, 0,
198144194Snjl};
199144194Snjl/* Desktop and Mobile VRM (K8) */
200144194Snjlstatic int pn8_vid_to_volts[] = {
201144194Snjl	1550, 1525, 1500, 1475, 1450, 1425, 1400, 1375,
202144194Snjl	1350, 1325, 1300, 1275, 1250, 1225, 1200, 1175,
203144194Snjl	1150, 1125, 1100, 1075, 1050, 1025, 1000, 975,
204144194Snjl	950, 925, 900, 875, 850, 825, 800, 0,
205144194Snjl};
206144194Snjl
207144194Snjl#define POWERNOW_MAX_STATES		16
208144194Snjl
209144194Snjlstruct powernow_state {
210144194Snjl	int freq;
211144194Snjl	int power;
212144194Snjl	int fid;
213144194Snjl	int vid;
214144194Snjl};
215144194Snjl
216144194Snjlstruct pn_softc {
217144194Snjl	device_t		 dev;
218144194Snjl	int			 pn_type;
219144194Snjl	struct powernow_state	 powernow_states[POWERNOW_MAX_STATES];
220144194Snjl	u_int			 fsb;
221144194Snjl	u_int			 sgtc;
222144194Snjl	u_int			 vst;
223144194Snjl	u_int			 mvs;
224144194Snjl	u_int			 pll;
225144194Snjl	u_int			 rvo;
226144194Snjl	u_int			 irt;
227144194Snjl	int			 low;
228144194Snjl	int			 powernow_max_states;
229144194Snjl	u_int			 powernow_state;
230166197Sbruno	u_int			 errata;
231144194Snjl	int			*vid_to_volts;
232144194Snjl};
233144194Snjl
234144194Snjl/*
235144194Snjl * Offsets in struct cf_setting array for private values given by
236144194Snjl * acpi_perf driver.
237144194Snjl */
238144194Snjl#define PX_SPEC_CONTROL		0
239144194Snjl#define PX_SPEC_STATUS		1
240144194Snjl
241144194Snjlstatic void	pn_identify(driver_t *driver, device_t parent);
242144194Snjlstatic int	pn_probe(device_t dev);
243144194Snjlstatic int	pn_attach(device_t dev);
244144194Snjlstatic int	pn_detach(device_t dev);
245144194Snjlstatic int	pn_set(device_t dev, const struct cf_setting *cf);
246144194Snjlstatic int	pn_get(device_t dev, struct cf_setting *cf);
247144194Snjlstatic int	pn_settings(device_t dev, struct cf_setting *sets,
248144194Snjl		    int *count);
249144194Snjlstatic int	pn_type(device_t dev, int *type);
250144194Snjl
251144194Snjlstatic device_method_t pn_methods[] = {
252144194Snjl	/* Device interface */
253144194Snjl	DEVMETHOD(device_identify, pn_identify),
254144194Snjl	DEVMETHOD(device_probe, pn_probe),
255144194Snjl	DEVMETHOD(device_attach, pn_attach),
256144194Snjl	DEVMETHOD(device_detach, pn_detach),
257144194Snjl
258144194Snjl	/* cpufreq interface */
259144194Snjl	DEVMETHOD(cpufreq_drv_set, pn_set),
260144194Snjl	DEVMETHOD(cpufreq_drv_get, pn_get),
261144194Snjl	DEVMETHOD(cpufreq_drv_settings, pn_settings),
262144194Snjl	DEVMETHOD(cpufreq_drv_type, pn_type),
263144194Snjl
264144194Snjl	{0, 0}
265144194Snjl};
266144194Snjl
267144194Snjlstatic devclass_t pn_devclass;
268144194Snjlstatic driver_t pn_driver = {
269144194Snjl	"powernow",
270144194Snjl	pn_methods,
271144194Snjl	sizeof(struct pn_softc),
272144194Snjl};
273144194Snjl
274144194SnjlDRIVER_MODULE(powernow, cpu, pn_driver, pn_devclass, 0, 0);
275144194Snjl
276144194Snjlstatic int
277144194Snjlpn7_setfidvid(struct pn_softc *sc, int fid, int vid)
278144194Snjl{
279144194Snjl	int cfid, cvid;
280144194Snjl	uint64_t status, ctl;
281144194Snjl
282144194Snjl	status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
283144194Snjl	cfid = PN7_STA_CFID(status);
284144194Snjl	cvid = PN7_STA_CVID(status);
285144194Snjl
286144194Snjl	/* We're already at the requested level. */
287144194Snjl	if (fid == cfid && vid == cvid)
288144194Snjl		return (0);
289144194Snjl
290144194Snjl	ctl = rdmsr(MSR_AMDK7_FIDVID_CTL) & PN7_CTR_FIDCHRATIO;
291144194Snjl
292144194Snjl	ctl |= PN7_CTR_FID(fid);
293144194Snjl	ctl |= PN7_CTR_VID(vid);
294144194Snjl	ctl |= PN7_CTR_SGTC(sc->sgtc);
295144194Snjl
296166197Sbruno	if (sc->errata & A0_ERRATA)
297144194Snjl		disable_intr();
298144194Snjl
299144194Snjl	if (pn7_fid_to_mult[fid] < pn7_fid_to_mult[cfid]) {
300144194Snjl		wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_FIDC);
301144194Snjl		if (vid != cvid)
302144194Snjl			wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_VIDC);
303144194Snjl	} else {
304144194Snjl		wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_VIDC);
305144194Snjl		if (fid != cfid)
306144194Snjl			wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_FIDC);
307144194Snjl	}
308144194Snjl
309166197Sbruno	if (sc->errata & A0_ERRATA)
310144194Snjl		enable_intr();
311144194Snjl
312144194Snjl	return (0);
313144194Snjl}
314144194Snjl
315144194Snjlstatic int
316166197Sbrunopn8_read_pending_wait(uint64_t *status)
317166197Sbruno{
318166197Sbruno	int i = 10000;
319166197Sbruno
320166197Sbruno	do
321166197Sbruno		*status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
322166197Sbruno	while (PN8_STA_PENDING(*status) && --i);
323166197Sbruno
324166197Sbruno	return (i == 0 ? ENXIO : 0);
325166197Sbruno}
326166197Sbruno
327166197Sbrunostatic int
328166197Sbrunopn8_write_fidvid(u_int fid, u_int vid, uint64_t ctrl, uint64_t *status)
329166197Sbruno{
330166197Sbruno	int i = 100;
331166197Sbruno
332166197Sbruno	do
333166197Sbruno		WRITE_FIDVID(fid, vid, ctrl);
334166197Sbruno	while (pn8_read_pending_wait(status) && --i);
335166197Sbruno
336166197Sbruno	return (i == 0 ? ENXIO : 0);
337166197Sbruno}
338166197Sbruno
339166197Sbrunostatic int
340144194Snjlpn8_setfidvid(struct pn_softc *sc, int fid, int vid)
341144194Snjl{
342144194Snjl	uint64_t status;
343144194Snjl	int cfid, cvid;
344144194Snjl	int rvo;
345166197Sbruno	int rv;
346144194Snjl	u_int val;
347144194Snjl
348166197Sbruno	rv = pn8_read_pending_wait(&status);
349166197Sbruno	if (rv)
350166197Sbruno		return (rv);
351166197Sbruno
352144194Snjl	cfid = PN8_STA_CFID(status);
353144194Snjl	cvid = PN8_STA_CVID(status);
354144194Snjl
355144194Snjl	if (fid == cfid && vid == cvid)
356144194Snjl		return (0);
357144194Snjl
358144194Snjl	/*
359144194Snjl	 * Phase 1: Raise core voltage to requested VID if frequency is
360144194Snjl	 * going up.
361144194Snjl	 */
362144194Snjl	while (cvid > vid) {
363144194Snjl		val = cvid - (1 << sc->mvs);
364166197Sbruno		rv = pn8_write_fidvid(cfid, (val > 0) ? val : 0, 1ULL, &status);
365166197Sbruno		if (rv) {
366166197Sbruno			sc->errata |= PENDING_STUCK;
367166197Sbruno			return (rv);
368166197Sbruno		}
369144194Snjl		cvid = PN8_STA_CVID(status);
370144194Snjl		COUNT_OFF_VST(sc->vst);
371144194Snjl	}
372144194Snjl
373144194Snjl	/* ... then raise to voltage + RVO (if required) */
374144194Snjl	for (rvo = sc->rvo; rvo > 0 && cvid > 0; --rvo) {
375144194Snjl		/* XXX It's not clear from spec if we have to do that
376144194Snjl		 * in 0.25 step or in MVS.  Therefore do it as it's done
377144194Snjl		 * under Linux */
378166197Sbruno		rv = pn8_write_fidvid(cfid, cvid - 1, 1ULL, &status);
379166197Sbruno		if (rv) {
380166197Sbruno			sc->errata |= PENDING_STUCK;
381166197Sbruno			return (rv);
382166197Sbruno		}
383144194Snjl		cvid = PN8_STA_CVID(status);
384144194Snjl		COUNT_OFF_VST(sc->vst);
385144194Snjl	}
386144194Snjl
387144194Snjl	/* Phase 2: change to requested core frequency */
388144194Snjl	if (cfid != fid) {
389166197Sbruno		u_int vco_fid, vco_cfid, fid_delta;
390144194Snjl
391144194Snjl		vco_fid = FID_TO_VCO_FID(fid);
392144194Snjl		vco_cfid = FID_TO_VCO_FID(cfid);
393144194Snjl
394144194Snjl		while (abs(vco_fid - vco_cfid) > 2) {
395166197Sbruno			fid_delta = (vco_cfid & 1) ? 1 : 2;
396144194Snjl			if (fid > cfid) {
397166197Sbruno				if (cfid > 7)
398166197Sbruno					val = cfid + fid_delta;
399144194Snjl				else
400166197Sbruno					val = FID_TO_VCO_FID(cfid) + fid_delta;
401144194Snjl			} else
402166197Sbruno				val = cfid - fid_delta;
403166197Sbruno			rv = pn8_write_fidvid(val, cvid,
404166197Sbruno			    sc->pll * (uint64_t) sc->fsb,
405166197Sbruno			    &status);
406166197Sbruno			if (rv) {
407166197Sbruno				sc->errata |= PENDING_STUCK;
408166197Sbruno				return (rv);
409166197Sbruno			}
410144194Snjl			cfid = PN8_STA_CFID(status);
411144194Snjl			COUNT_OFF_IRT(sc->irt);
412144194Snjl
413144194Snjl			vco_cfid = FID_TO_VCO_FID(cfid);
414144194Snjl		}
415144194Snjl
416166197Sbruno		rv = pn8_write_fidvid(fid, cvid,
417166197Sbruno		    sc->pll * (uint64_t) sc->fsb,
418166197Sbruno		    &status);
419166197Sbruno		if (rv) {
420166197Sbruno			sc->errata |= PENDING_STUCK;
421166197Sbruno			return (rv);
422166197Sbruno		}
423144194Snjl		cfid = PN8_STA_CFID(status);
424144194Snjl		COUNT_OFF_IRT(sc->irt);
425144194Snjl	}
426144194Snjl
427144194Snjl	/* Phase 3: change to requested voltage */
428144194Snjl	if (cvid != vid) {
429166197Sbruno		rv = pn8_write_fidvid(cfid, vid, 1ULL, &status);
430144194Snjl		cvid = PN8_STA_CVID(status);
431144194Snjl		COUNT_OFF_VST(sc->vst);
432144194Snjl	}
433144194Snjl
434144194Snjl	/* Check if transition failed. */
435144194Snjl	if (cfid != fid || cvid != vid)
436166197Sbruno		rv = ENXIO;
437144194Snjl
438166197Sbruno	return (rv);
439144194Snjl}
440144194Snjl
441144194Snjlstatic int
442144194Snjlpn_set(device_t dev, const struct cf_setting *cf)
443144194Snjl{
444144194Snjl	struct pn_softc *sc;
445144194Snjl	int fid, vid;
446144194Snjl	int i;
447144194Snjl	int rv;
448144194Snjl
449144194Snjl	if (cf == NULL)
450144194Snjl		return (EINVAL);
451144194Snjl	sc = device_get_softc(dev);
452144194Snjl
453166197Sbruno	if (sc->errata & PENDING_STUCK)
454166197Sbruno		return (ENXIO);
455166197Sbruno
456144194Snjl	for (i = 0; i < sc->powernow_max_states; ++i)
457144194Snjl		if (CPUFREQ_CMP(sc->powernow_states[i].freq / 1000, cf->freq))
458144194Snjl			break;
459144194Snjl
460144194Snjl	fid = sc->powernow_states[i].fid;
461144194Snjl	vid = sc->powernow_states[i].vid;
462144194Snjl
463144194Snjl	rv = ENODEV;
464144194Snjl
465144194Snjl	switch (sc->pn_type) {
466144194Snjl	case PN7_TYPE:
467144194Snjl		rv = pn7_setfidvid(sc, fid, vid);
468144194Snjl		break;
469144194Snjl	case PN8_TYPE:
470144194Snjl		rv = pn8_setfidvid(sc, fid, vid);
471144194Snjl		break;
472144194Snjl	}
473144194Snjl
474144194Snjl	return (rv);
475144194Snjl}
476144194Snjl
477144194Snjlstatic int
478144194Snjlpn_get(device_t dev, struct cf_setting *cf)
479144194Snjl{
480144194Snjl	struct pn_softc *sc;
481144194Snjl	u_int cfid = 0, cvid = 0;
482144194Snjl	int i;
483144194Snjl	uint64_t status;
484144194Snjl
485144194Snjl	if (cf == NULL)
486144194Snjl		return (EINVAL);
487144194Snjl	sc = device_get_softc(dev);
488166197Sbruno	if (sc->errata & PENDING_STUCK)
489166197Sbruno		return (ENXIO);
490144194Snjl
491144194Snjl	status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
492144194Snjl
493144194Snjl	switch (sc->pn_type) {
494144194Snjl	case PN7_TYPE:
495144194Snjl		cfid = PN7_STA_CFID(status);
496144194Snjl		cvid = PN7_STA_CVID(status);
497144194Snjl		break;
498144194Snjl	case PN8_TYPE:
499144194Snjl		cfid = PN8_STA_CFID(status);
500144194Snjl		cvid = PN8_STA_CVID(status);
501144194Snjl		break;
502144194Snjl	}
503144194Snjl	for (i = 0; i < sc->powernow_max_states; ++i)
504144194Snjl		if (cfid == sc->powernow_states[i].fid &&
505144194Snjl		    cvid == sc->powernow_states[i].vid)
506144194Snjl			break;
507144194Snjl
508144194Snjl	if (i < sc->powernow_max_states) {
509144194Snjl		cf->freq = sc->powernow_states[i].freq / 1000;
510144194Snjl		cf->power = sc->powernow_states[i].power;
511144194Snjl		cf->lat = 200;
512144194Snjl		cf->volts = sc->vid_to_volts[cvid];
513144194Snjl		cf->dev = dev;
514144194Snjl	} else {
515144194Snjl		memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf));
516144194Snjl		cf->dev = NULL;
517144194Snjl	}
518144194Snjl
519144194Snjl	return (0);
520144194Snjl}
521144194Snjl
522144194Snjlstatic int
523144194Snjlpn_settings(device_t dev, struct cf_setting *sets, int *count)
524144194Snjl{
525144194Snjl	struct pn_softc *sc;
526144194Snjl	int i;
527144194Snjl
528144194Snjl	if (sets == NULL|| count == NULL)
529144194Snjl		return (EINVAL);
530144194Snjl	sc = device_get_softc(dev);
531144194Snjl	if (*count < sc->powernow_max_states)
532144194Snjl		return (E2BIG);
533144194Snjl	for (i = 0; i < sc->powernow_max_states; ++i) {
534144194Snjl		sets[i].freq = sc->powernow_states[i].freq / 1000;
535144194Snjl		sets[i].power = sc->powernow_states[i].power;
536144194Snjl		sets[i].lat = 200;
537144194Snjl		sets[i].volts = sc->vid_to_volts[sc->powernow_states[i].vid];
538144194Snjl		sets[i].dev = dev;
539144194Snjl	}
540144194Snjl	*count = sc->powernow_max_states;
541144194Snjl
542144194Snjl	return (0);
543144194Snjl}
544144194Snjl
545144194Snjlstatic int
546144194Snjlpn_type(device_t dev, int *type)
547144194Snjl{
548144194Snjl	if (type == NULL)
549144194Snjl		return (EINVAL);
550144194Snjl
551144194Snjl	*type = CPUFREQ_TYPE_ABSOLUTE;
552144194Snjl
553144194Snjl	return (0);
554144194Snjl}
555144194Snjl
556144194Snjl/*
557144194Snjl * Given a set of pair of fid/vid, and number of performance states,
558144194Snjl * compute powernow_states via an insertion sort.
559144194Snjl */
560144194Snjlstatic int
561144194Snjldecode_pst(struct pn_softc *sc, uint8_t *p, int npstates)
562144194Snjl{
563144194Snjl	int i, j, n;
564144194Snjl	struct powernow_state state;
565144194Snjl
566144194Snjl	for (i = 0; i < POWERNOW_MAX_STATES; ++i)
567144194Snjl		sc->powernow_states[i].freq = CPUFREQ_VAL_UNKNOWN;
568144194Snjl
569144194Snjl	for (n = 0, i = 0; i < npstates; ++i) {
570144194Snjl		state.fid = *p++;
571144194Snjl		state.vid = *p++;
572144194Snjl		state.power = CPUFREQ_VAL_UNKNOWN;
573144194Snjl
574144194Snjl		switch (sc->pn_type) {
575144194Snjl		case PN7_TYPE:
576144194Snjl			state.freq = 100 * pn7_fid_to_mult[state.fid] * sc->fsb;
577166197Sbruno			if ((sc->errata & A0_ERRATA) &&
578144194Snjl			    (pn7_fid_to_mult[state.fid] % 10) == 5)
579144194Snjl				continue;
580144194Snjl			break;
581144194Snjl		case PN8_TYPE:
582166197Sbruno			state.freq = 100 * pn8_fid_to_mult[state.fid] * sc->fsb;
583144194Snjl			break;
584144194Snjl		}
585144194Snjl
586144194Snjl		j = n;
587144194Snjl		while (j > 0 && sc->powernow_states[j - 1].freq < state.freq) {
588144194Snjl			memcpy(&sc->powernow_states[j],
589144194Snjl			    &sc->powernow_states[j - 1],
590144194Snjl			    sizeof(struct powernow_state));
591144194Snjl			--j;
592144194Snjl		}
593144194Snjl		memcpy(&sc->powernow_states[j], &state,
594144194Snjl		    sizeof(struct powernow_state));
595144194Snjl		++n;
596144194Snjl	}
597144194Snjl
598144194Snjl	/*
599166197Sbruno	 * Fix powernow_max_states, if errata a0 give us less states
600144194Snjl	 * than expected.
601144194Snjl	 */
602144194Snjl	sc->powernow_max_states = n;
603144194Snjl
604144194Snjl	if (bootverbose)
605144194Snjl		for (i = 0; i < sc->powernow_max_states; ++i) {
606144194Snjl			int fid = sc->powernow_states[i].fid;
607144194Snjl			int vid = sc->powernow_states[i].vid;
608144194Snjl
609144194Snjl			printf("powernow: %2i %8dkHz FID %02x VID %02x\n",
610144194Snjl			    i,
611144194Snjl			    sc->powernow_states[i].freq,
612144194Snjl			    fid,
613144194Snjl			    vid);
614144194Snjl		}
615144194Snjl
616144194Snjl	return (0);
617144194Snjl}
618144194Snjl
619144194Snjlstatic int
620144194Snjlcpuid_is_k7(u_int cpuid)
621144194Snjl{
622144194Snjl
623144194Snjl	switch (cpuid) {
624144194Snjl	case 0x760:
625144194Snjl	case 0x761:
626144194Snjl	case 0x762:
627144194Snjl	case 0x770:
628144194Snjl	case 0x771:
629144194Snjl	case 0x780:
630144194Snjl	case 0x781:
631144194Snjl	case 0x7a0:
632144194Snjl		return (TRUE);
633144194Snjl	}
634144194Snjl	return (FALSE);
635144194Snjl}
636144194Snjl
637144194Snjlstatic int
638144194Snjlpn_decode_pst(device_t dev)
639144194Snjl{
640144194Snjl	int maxpst;
641144194Snjl	struct pn_softc *sc;
642144194Snjl	u_int cpuid, maxfid, startvid;
643144194Snjl	u_long sig;
644144194Snjl	struct psb_header *psb;
645144194Snjl	uint8_t *p;
646144194Snjl	u_int regs[4];
647144194Snjl	uint64_t status;
648144194Snjl
649144194Snjl	sc = device_get_softc(dev);
650144194Snjl
651144194Snjl	do_cpuid(0x80000001, regs);
652144194Snjl	cpuid = regs[0];
653144194Snjl
654144194Snjl	if ((cpuid & 0xfff) == 0x760)
655166197Sbruno		sc->errata |= A0_ERRATA;
656144194Snjl
657144194Snjl	status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
658144194Snjl
659144194Snjl	switch (sc->pn_type) {
660144194Snjl	case PN7_TYPE:
661144194Snjl		maxfid = PN7_STA_MFID(status);
662144194Snjl		startvid = PN7_STA_SVID(status);
663144194Snjl		break;
664144194Snjl	case PN8_TYPE:
665144194Snjl		maxfid = PN8_STA_MFID(status);
666144194Snjl		/*
667144194Snjl		 * we should actually use a variable named 'maxvid' if K8,
668144194Snjl		 * but why introducing a new variable for that?
669144194Snjl		 */
670144194Snjl		startvid = PN8_STA_MVID(status);
671144194Snjl		break;
672144194Snjl	default:
673144194Snjl		return (ENODEV);
674144194Snjl	}
675144194Snjl
676144194Snjl	if (bootverbose) {
677144194Snjl		device_printf(dev, "STATUS: 0x%jx\n", status);
678144194Snjl		device_printf(dev, "STATUS: maxfid: 0x%02x\n", maxfid);
679144194Snjl		device_printf(dev, "STATUS: %s: 0x%02x\n",
680144194Snjl		    sc->pn_type == PN7_TYPE ? "startvid" : "maxvid",
681144194Snjl		    startvid);
682144194Snjl	}
683144194Snjl
684144194Snjl	sig = bios_sigsearch(PSB_START, PSB_SIG, PSB_LEN, PSB_STEP, PSB_OFF);
685144194Snjl	if (sig) {
686144194Snjl		struct pst_header *pst;
687144194Snjl
688144194Snjl		psb = (struct psb_header*)(uintptr_t)BIOS_PADDRTOVADDR(sig);
689144194Snjl
690144194Snjl		switch (psb->version) {
691144194Snjl		default:
692144194Snjl			return (ENODEV);
693144194Snjl		case 0x14:
694144380Snjl			/*
695144380Snjl			 * We can't be picky about numpst since at least
696144380Snjl			 * some systems have a value of 1 and some have 2.
697144380Snjl			 * We trust that cpuid_is_k7() will be better at
698144380Snjl			 * catching that we're on a K8 anyway.
699144380Snjl			 */
700144380Snjl			if (sc->pn_type != PN8_TYPE)
701144194Snjl				return (EINVAL);
702144194Snjl			sc->vst = psb->settlingtime;
703144194Snjl			sc->rvo = PN8_PSB_TO_RVO(psb->res1),
704144194Snjl			sc->irt = PN8_PSB_TO_IRT(psb->res1),
705144194Snjl			sc->mvs = PN8_PSB_TO_MVS(psb->res1),
706144194Snjl			sc->low = PN8_PSB_TO_BATT(psb->res1);
707144194Snjl			if (bootverbose) {
708144194Snjl				device_printf(dev, "PSB: VST: %d\n",
709144194Snjl				    psb->settlingtime);
710144194Snjl				device_printf(dev, "PSB: RVO %x IRT %d "
711144194Snjl				    "MVS %d BATT %d\n",
712144194Snjl				    sc->rvo,
713144194Snjl				    sc->irt,
714144194Snjl				    sc->mvs,
715144194Snjl				    sc->low);
716144194Snjl			}
717144194Snjl			break;
718144194Snjl		case 0x12:
719144194Snjl			if (sc->pn_type != PN7_TYPE)
720144194Snjl				return (EINVAL);
721144194Snjl			sc->sgtc = psb->settlingtime * sc->fsb;
722144194Snjl			if (sc->sgtc < 100 * sc->fsb)
723144194Snjl				sc->sgtc = 100 * sc->fsb;
724144194Snjl			break;
725144194Snjl		}
726144194Snjl
727144194Snjl		p = ((uint8_t *) psb) + sizeof(struct psb_header);
728144194Snjl		pst = (struct pst_header*) p;
729144194Snjl
730144194Snjl		maxpst = 200;
731144194Snjl
732144194Snjl		do {
733144194Snjl			struct pst_header *pst = (struct pst_header*) p;
734144194Snjl
735144194Snjl			if (cpuid == pst->cpuid &&
736144194Snjl			    maxfid == pst->maxfid &&
737144194Snjl			    startvid == pst->startvid) {
738144194Snjl				sc->powernow_max_states = pst->numpstates;
739144194Snjl				switch (sc->pn_type) {
740144194Snjl				case PN7_TYPE:
741144194Snjl					if (abs(sc->fsb - pst->fsb) > 5)
742144194Snjl						continue;
743144194Snjl					break;
744144194Snjl				case PN8_TYPE:
745144194Snjl					break;
746144194Snjl				}
747144194Snjl				return (decode_pst(sc,
748144194Snjl				    p + sizeof(struct pst_header),
749144194Snjl				    sc->powernow_max_states));
750144194Snjl			}
751144194Snjl
752144194Snjl			p += sizeof(struct pst_header) + (2 * pst->numpstates);
753144194Snjl		} while (cpuid_is_k7(pst->cpuid) && maxpst--);
754144194Snjl
755144194Snjl		device_printf(dev, "no match for extended cpuid %.3x\n", cpuid);
756144194Snjl	}
757144194Snjl
758144194Snjl	return (ENODEV);
759144194Snjl}
760144194Snjl
761144194Snjlstatic int
762144194Snjlpn_decode_acpi(device_t dev, device_t perf_dev)
763144194Snjl{
764144194Snjl	int i, j, n;
765144194Snjl	uint64_t status;
766144194Snjl	uint32_t ctrl;
767144194Snjl	u_int cpuid;
768144194Snjl	u_int regs[4];
769144194Snjl	struct pn_softc *sc;
770144194Snjl	struct powernow_state state;
771144194Snjl	struct cf_setting sets[POWERNOW_MAX_STATES];
772144194Snjl	int count = POWERNOW_MAX_STATES;
773144194Snjl	int type;
774144194Snjl	int rv;
775144194Snjl
776144194Snjl	if (perf_dev == NULL)
777144194Snjl		return (ENXIO);
778144194Snjl
779144194Snjl	rv = CPUFREQ_DRV_SETTINGS(perf_dev, sets, &count);
780144194Snjl	if (rv)
781144194Snjl		return (ENXIO);
782144194Snjl	rv = CPUFREQ_DRV_TYPE(perf_dev, &type);
783144194Snjl	if (rv || (type & CPUFREQ_FLAG_INFO_ONLY) == 0)
784144194Snjl		return (ENXIO);
785144194Snjl
786144194Snjl	sc = device_get_softc(dev);
787144194Snjl
788144194Snjl	do_cpuid(0x80000001, regs);
789144194Snjl	cpuid = regs[0];
790144194Snjl	if ((cpuid & 0xfff) == 0x760)
791166197Sbruno		sc->errata |= A0_ERRATA;
792144194Snjl
793144194Snjl	ctrl = 0;
794144194Snjl	sc->sgtc = 0;
795144194Snjl	for (n = 0, i = 0; i < count; ++i) {
796144194Snjl		ctrl = sets[i].spec[PX_SPEC_CONTROL];
797144194Snjl		switch (sc->pn_type) {
798144194Snjl		case PN7_TYPE:
799144194Snjl			state.fid = ACPI_PN7_CTRL_TO_FID(ctrl);
800144194Snjl			state.vid = ACPI_PN7_CTRL_TO_VID(ctrl);
801166197Sbruno			if ((sc->errata & A0_ERRATA) &&
802144194Snjl			    (pn7_fid_to_mult[state.fid] % 10) == 5)
803144194Snjl				continue;
804144194Snjl			break;
805144194Snjl		case PN8_TYPE:
806144194Snjl			state.fid = ACPI_PN8_CTRL_TO_FID(ctrl);
807144194Snjl			state.vid = ACPI_PN8_CTRL_TO_VID(ctrl);
808144194Snjl			break;
809144194Snjl		}
810221102Sjkim		state.freq = sets[i].freq * 1000;
811144194Snjl		state.power = sets[i].power;
812144194Snjl
813144194Snjl		j = n;
814144194Snjl		while (j > 0 && sc->powernow_states[j - 1].freq < state.freq) {
815144194Snjl			memcpy(&sc->powernow_states[j],
816144194Snjl			    &sc->powernow_states[j - 1],
817144194Snjl			    sizeof(struct powernow_state));
818144194Snjl			--j;
819144194Snjl		}
820144194Snjl		memcpy(&sc->powernow_states[j], &state,
821144194Snjl		    sizeof(struct powernow_state));
822144194Snjl		++n;
823144194Snjl	}
824144194Snjl
825144194Snjl	sc->powernow_max_states = n;
826144194Snjl	state = sc->powernow_states[0];
827144194Snjl	status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
828144194Snjl
829144194Snjl	switch (sc->pn_type) {
830144194Snjl	case PN7_TYPE:
831144194Snjl		sc->sgtc = ACPI_PN7_CTRL_TO_SGTC(ctrl);
832144194Snjl		/*
833144194Snjl		 * XXX Some bios forget the max frequency!
834144194Snjl		 * This maybe indicates we have the wrong tables.  Therefore,
835144194Snjl		 * don't implement a quirk, but fallback to BIOS legacy
836144194Snjl		 * tables instead.
837144194Snjl		 */
838144194Snjl		if (PN7_STA_MFID(status) != state.fid) {
839144194Snjl			device_printf(dev, "ACPI MAX frequency not found\n");
840144194Snjl			return (EINVAL);
841144194Snjl		}
842221102Sjkim		sc->fsb = state.freq / 100 / pn7_fid_to_mult[state.fid];
843144194Snjl		break;
844144194Snjl	case PN8_TYPE:
845144194Snjl		sc->vst = ACPI_PN8_CTRL_TO_VST(ctrl),
846144194Snjl		sc->mvs = ACPI_PN8_CTRL_TO_MVS(ctrl),
847144194Snjl		sc->pll = ACPI_PN8_CTRL_TO_PLL(ctrl),
848144194Snjl		sc->rvo = ACPI_PN8_CTRL_TO_RVO(ctrl),
849144194Snjl		sc->irt = ACPI_PN8_CTRL_TO_IRT(ctrl);
850144194Snjl		sc->low = 0; /* XXX */
851144194Snjl
852144194Snjl		/*
853144194Snjl		 * powernow k8 supports only one low frequency.
854144194Snjl		 */
855144194Snjl		if (sc->powernow_max_states >= 2 &&
856144194Snjl		    (sc->powernow_states[sc->powernow_max_states - 2].fid < 8))
857144194Snjl			return (EINVAL);
858221102Sjkim		sc->fsb = state.freq / 100 / pn8_fid_to_mult[state.fid];
859144194Snjl		break;
860144194Snjl	}
861144194Snjl
862144194Snjl	return (0);
863144194Snjl}
864144194Snjl
865144194Snjlstatic void
866144194Snjlpn_identify(driver_t *driver, device_t parent)
867144194Snjl{
868144194Snjl
869184104Sjkim	if ((amd_pminfo & AMDPM_FID) == 0 || (amd_pminfo & AMDPM_VID) == 0)
870144194Snjl		return;
871144194Snjl	switch (cpu_id & 0xf00) {
872144194Snjl	case 0x600:
873144194Snjl	case 0xf00:
874144194Snjl		break;
875144194Snjl	default:
876144194Snjl		return;
877144194Snjl	}
878144194Snjl	if (device_find_child(parent, "powernow", -1) != NULL)
879144194Snjl		return;
880185349Sjkim	if (BUS_ADD_CHILD(parent, 10, "powernow", -1) == NULL)
881144194Snjl		device_printf(parent, "powernow: add child failed\n");
882144194Snjl}
883144194Snjl
884144194Snjlstatic int
885144194Snjlpn_probe(device_t dev)
886144194Snjl{
887144194Snjl	struct pn_softc *sc;
888144194Snjl	uint64_t status;
889144194Snjl	uint64_t rate;
890144194Snjl	struct pcpu *pc;
891144194Snjl	u_int sfid, mfid, cfid;
892144194Snjl
893144194Snjl	sc = device_get_softc(dev);
894166197Sbruno	sc->errata = 0;
895144194Snjl	status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
896144194Snjl
897144194Snjl	pc = cpu_get_pcpu(dev);
898144194Snjl	if (pc == NULL)
899144194Snjl		return (ENODEV);
900144194Snjl
901144194Snjl	cpu_est_clockrate(pc->pc_cpuid, &rate);
902144194Snjl
903144194Snjl	switch (cpu_id & 0xf00) {
904144194Snjl	case 0x600:
905144194Snjl		sfid = PN7_STA_SFID(status);
906144194Snjl		mfid = PN7_STA_MFID(status);
907144194Snjl		cfid = PN7_STA_CFID(status);
908144194Snjl		sc->pn_type = PN7_TYPE;
909144194Snjl		sc->fsb = rate / 100000 / pn7_fid_to_mult[cfid];
910144194Snjl
911144194Snjl		/*
912144194Snjl		 * If start FID is different to max FID, then it is a
913144194Snjl		 * mobile processor.  If not, it is a low powered desktop
914144194Snjl		 * processor.
915144194Snjl		 */
916144194Snjl		if (PN7_STA_SFID(status) != PN7_STA_MFID(status)) {
917144194Snjl			sc->vid_to_volts = pn7_mobile_vid_to_volts;
918144194Snjl			device_set_desc(dev, "PowerNow! K7");
919144194Snjl		} else {
920144194Snjl			sc->vid_to_volts = pn7_desktop_vid_to_volts;
921144194Snjl			device_set_desc(dev, "Cool`n'Quiet K7");
922144194Snjl		}
923144194Snjl		break;
924144194Snjl
925144194Snjl	case 0xf00:
926144194Snjl		sfid = PN8_STA_SFID(status);
927144194Snjl		mfid = PN8_STA_MFID(status);
928144194Snjl		cfid = PN8_STA_CFID(status);
929144194Snjl		sc->pn_type = PN8_TYPE;
930144194Snjl		sc->vid_to_volts = pn8_vid_to_volts;
931166197Sbruno		sc->fsb = rate / 100000 / pn8_fid_to_mult[cfid];
932144194Snjl
933144194Snjl		if (PN8_STA_SFID(status) != PN8_STA_MFID(status))
934144194Snjl			device_set_desc(dev, "PowerNow! K8");
935144194Snjl		else
936144194Snjl			device_set_desc(dev, "Cool`n'Quiet K8");
937144194Snjl		break;
938144194Snjl	default:
939144194Snjl		return (ENODEV);
940144194Snjl	}
941144194Snjl
942144194Snjl	return (0);
943144194Snjl}
944144194Snjl
945144194Snjlstatic int
946144194Snjlpn_attach(device_t dev)
947144194Snjl{
948144194Snjl	int rv;
949144194Snjl	device_t child;
950144194Snjl
951144194Snjl	child = device_find_child(device_get_parent(dev), "acpi_perf", -1);
952144194Snjl	if (child) {
953144194Snjl		rv = pn_decode_acpi(dev, child);
954144194Snjl		if (rv)
955144194Snjl			rv = pn_decode_pst(dev);
956144194Snjl	} else
957144194Snjl		rv = pn_decode_pst(dev);
958144194Snjl
959144194Snjl	if (rv != 0)
960144194Snjl		return (ENXIO);
961144194Snjl	cpufreq_register(dev);
962144194Snjl	return (0);
963144194Snjl}
964144194Snjl
965144194Snjlstatic int
966144194Snjlpn_detach(device_t dev)
967144194Snjl{
968144194Snjl
969182401Sjhb	return (cpufreq_unregister(dev));
970144194Snjl}
971