07b68f9266
checks, including cpuid_is_k7(), will catch CPUs that really don't support this method. Submitted by: Bruno Ducrot Tested by: Jari Kirma (kirma cs.hut.fi)
935 lines
22 KiB
C
935 lines
22 KiB
C
/*-
|
|
* Copyright (c) 2004-2005 Bruno Ducrot
|
|
* Copyright (c) 2004 FUKUDA Nobuhiko <nfukuda@spa.is.uec.ac.jp>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* Many thanks to Nate Lawson for his helpful comments on this driver and
|
|
* to Jung-uk Kim for testing.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/cpu.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/module.h>
|
|
#include <sys/pcpu.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include <machine/pc/bios.h>
|
|
#include <machine/md_var.h>
|
|
#include <machine/specialreg.h>
|
|
#include <machine/cputypes.h>
|
|
#include <machine/clock.h>
|
|
#include <machine/vmparam.h>
|
|
#include <sys/rman.h>
|
|
|
|
#include <vm/vm.h>
|
|
#include <vm/pmap.h>
|
|
|
|
#include "cpufreq_if.h"
|
|
|
|
#define PN7_TYPE 0
|
|
#define PN8_TYPE 1
|
|
|
|
/* Legacy configuration via BIOS table PSB. */
|
|
#define PSB_START 0
|
|
#define PSB_STEP 0x10
|
|
#define PSB_SIG "AMDK7PNOW!"
|
|
#define PSB_LEN 10
|
|
#define PSB_OFF 0
|
|
|
|
struct psb_header {
|
|
char signature[10];
|
|
uint8_t version;
|
|
uint8_t flags;
|
|
uint16_t settlingtime;
|
|
uint8_t res1;
|
|
uint8_t numpst;
|
|
} __packed;
|
|
|
|
struct pst_header {
|
|
uint32_t cpuid;
|
|
uint8_t fsb;
|
|
uint8_t maxfid;
|
|
uint8_t startvid;
|
|
uint8_t numpstates;
|
|
} __packed;
|
|
|
|
/*
|
|
* MSRs and bits used by Powernow technology
|
|
*/
|
|
#define MSR_AMDK7_FIDVID_CTL 0xc0010041
|
|
#define MSR_AMDK7_FIDVID_STATUS 0xc0010042
|
|
|
|
/* Bitfields used by K7 */
|
|
|
|
#define PN7_CTR_FID(x) ((x) & 0x1f)
|
|
#define PN7_CTR_VID(x) (((x) & 0x1f) << 8)
|
|
#define PN7_CTR_FIDC 0x00010000
|
|
#define PN7_CTR_VIDC 0x00020000
|
|
#define PN7_CTR_FIDCHRATIO 0x00100000
|
|
#define PN7_CTR_SGTC(x) (((uint64_t)(x) & 0x000fffff) << 32)
|
|
|
|
#define PN7_STA_CFID(x) ((x) & 0x1f)
|
|
#define PN7_STA_SFID(x) (((x) >> 8) & 0x1f)
|
|
#define PN7_STA_MFID(x) (((x) >> 16) & 0x1f)
|
|
#define PN7_STA_CVID(x) (((x) >> 32) & 0x1f)
|
|
#define PN7_STA_SVID(x) (((x) >> 40) & 0x1f)
|
|
#define PN7_STA_MVID(x) (((x) >> 48) & 0x1f)
|
|
|
|
/* ACPI ctr_val status register to powernow k7 configuration */
|
|
#define ACPI_PN7_CTRL_TO_FID(x) ((x) & 0x1f)
|
|
#define ACPI_PN7_CTRL_TO_VID(x) (((x) >> 5) & 0x1f)
|
|
#define ACPI_PN7_CTRL_TO_SGTC(x) (((x) >> 10) & 0xffff)
|
|
|
|
/* Bitfields used by K8 */
|
|
|
|
#define PN8_CTR_FID(x) ((x) & 0x3f)
|
|
#define PN8_CTR_VID(x) (((x) & 0x1f) << 8)
|
|
#define PN8_CTR_PENDING(x) (((x) & 1) << 32)
|
|
|
|
#define PN8_STA_CFID(x) ((x) & 0x3f)
|
|
#define PN8_STA_SFID(x) (((x) >> 8) & 0x3f)
|
|
#define PN8_STA_MFID(x) (((x) >> 16) & 0x3f)
|
|
#define PN8_STA_PENDING(x) (((x) >> 31) & 0x01)
|
|
#define PN8_STA_CVID(x) (((x) >> 32) & 0x1f)
|
|
#define PN8_STA_SVID(x) (((x) >> 40) & 0x1f)
|
|
#define PN8_STA_MVID(x) (((x) >> 48) & 0x1f)
|
|
|
|
/* Reserved1 to powernow k8 configuration */
|
|
#define PN8_PSB_TO_RVO(x) ((x) & 0x03)
|
|
#define PN8_PSB_TO_IRT(x) (((x) >> 2) & 0x03)
|
|
#define PN8_PSB_TO_MVS(x) (((x) >> 4) & 0x03)
|
|
#define PN8_PSB_TO_BATT(x) (((x) >> 6) & 0x03)
|
|
|
|
/* ACPI ctr_val status register to powernow k8 configuration */
|
|
#define ACPI_PN8_CTRL_TO_FID(x) ((x) & 0x3f)
|
|
#define ACPI_PN8_CTRL_TO_VID(x) (((x) >> 6) & 0x1f)
|
|
#define ACPI_PN8_CTRL_TO_VST(x) (((x) >> 11) & 0x1f)
|
|
#define ACPI_PN8_CTRL_TO_MVS(x) (((x) >> 18) & 0x03)
|
|
#define ACPI_PN8_CTRL_TO_PLL(x) (((x) >> 20) & 0x7f)
|
|
#define ACPI_PN8_CTRL_TO_RVO(x) (((x) >> 28) & 0x03)
|
|
#define ACPI_PN8_CTRL_TO_IRT(x) (((x) >> 30) & 0x03)
|
|
|
|
|
|
#define WRITE_FIDVID(fid, vid, ctrl) \
|
|
wrmsr(MSR_AMDK7_FIDVID_CTL, \
|
|
(((ctrl) << 32) | (1ULL << 16) | ((vid) << 8) | (fid)))
|
|
|
|
#define READ_PENDING_WAIT(status) \
|
|
do { \
|
|
(status) = rdmsr(MSR_AMDK7_FIDVID_STATUS); \
|
|
} while (PN8_STA_PENDING(status))
|
|
|
|
#define COUNT_OFF_IRT(irt) DELAY(10 * (1 << (irt)))
|
|
#define COUNT_OFF_VST(vst) DELAY(20 * (vst))
|
|
|
|
#define FID_TO_VCO_FID(fid) \
|
|
(((fid) < 8) ? (8 + ((fid) << 1)) : (fid))
|
|
|
|
/*
|
|
* Divide each value by 10 to get the processor multiplier.
|
|
* Some of those tables are the same as the Linux powernow-k7
|
|
* implementation by Dave Jones.
|
|
*/
|
|
static int pn7_fid_to_mult[32] = {
|
|
110, 115, 120, 125, 50, 55, 60, 65,
|
|
70, 75, 80, 85, 90, 95, 100, 105,
|
|
30, 190, 40, 200, 130, 135, 140, 210,
|
|
150, 225, 160, 165, 170, 180, 0, 0,
|
|
};
|
|
|
|
|
|
static int pn8_fid_to_mult[32] = {
|
|
40, 50, 60, 70, 80, 90, 100, 110,
|
|
120, 130, 140, 150, 160, 170, 180, 190,
|
|
220, 230, 240, 250, 260, 270, 280, 290,
|
|
300, 310, 320, 330, 340, 350,
|
|
};
|
|
|
|
/*
|
|
* Units are in mV.
|
|
*/
|
|
/* Mobile VRM (K7) */
|
|
static int pn7_mobile_vid_to_volts[] = {
|
|
2000, 1950, 1900, 1850, 1800, 1750, 1700, 1650,
|
|
1600, 1550, 1500, 1450, 1400, 1350, 1300, 0,
|
|
1275, 1250, 1225, 1200, 1175, 1150, 1125, 1100,
|
|
1075, 1050, 1025, 1000, 975, 950, 925, 0,
|
|
};
|
|
/* Desktop VRM (K7) */
|
|
static int pn7_desktop_vid_to_volts[] = {
|
|
2000, 1950, 1900, 1850, 1800, 1750, 1700, 1650,
|
|
1600, 1550, 1500, 1450, 1400, 1350, 1300, 0,
|
|
1275, 1250, 1225, 1200, 1175, 1150, 1125, 1100,
|
|
1075, 1050, 1025, 1000, 975, 950, 925, 0,
|
|
};
|
|
/* Desktop and Mobile VRM (K8) */
|
|
static int pn8_vid_to_volts[] = {
|
|
1550, 1525, 1500, 1475, 1450, 1425, 1400, 1375,
|
|
1350, 1325, 1300, 1275, 1250, 1225, 1200, 1175,
|
|
1150, 1125, 1100, 1075, 1050, 1025, 1000, 975,
|
|
950, 925, 900, 875, 850, 825, 800, 0,
|
|
};
|
|
|
|
#define POWERNOW_MAX_STATES 16
|
|
|
|
struct powernow_state {
|
|
int freq;
|
|
int power;
|
|
int fid;
|
|
int vid;
|
|
};
|
|
|
|
struct pn_softc {
|
|
device_t dev;
|
|
int pn_type;
|
|
struct powernow_state powernow_states[POWERNOW_MAX_STATES];
|
|
u_int fsb;
|
|
u_int sgtc;
|
|
u_int vst;
|
|
u_int mvs;
|
|
u_int pll;
|
|
u_int rvo;
|
|
u_int irt;
|
|
int low;
|
|
int powernow_max_states;
|
|
u_int powernow_state;
|
|
int errata_a0;
|
|
int *vid_to_volts;
|
|
};
|
|
|
|
/*
|
|
* Offsets in struct cf_setting array for private values given by
|
|
* acpi_perf driver.
|
|
*/
|
|
#define PX_SPEC_CONTROL 0
|
|
#define PX_SPEC_STATUS 1
|
|
|
|
static void pn_identify(driver_t *driver, device_t parent);
|
|
static int pn_probe(device_t dev);
|
|
static int pn_attach(device_t dev);
|
|
static int pn_detach(device_t dev);
|
|
static int pn_set(device_t dev, const struct cf_setting *cf);
|
|
static int pn_get(device_t dev, struct cf_setting *cf);
|
|
static int pn_settings(device_t dev, struct cf_setting *sets,
|
|
int *count);
|
|
static int pn_type(device_t dev, int *type);
|
|
|
|
static device_method_t pn_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_identify, pn_identify),
|
|
DEVMETHOD(device_probe, pn_probe),
|
|
DEVMETHOD(device_attach, pn_attach),
|
|
DEVMETHOD(device_detach, pn_detach),
|
|
|
|
/* cpufreq interface */
|
|
DEVMETHOD(cpufreq_drv_set, pn_set),
|
|
DEVMETHOD(cpufreq_drv_get, pn_get),
|
|
DEVMETHOD(cpufreq_drv_settings, pn_settings),
|
|
DEVMETHOD(cpufreq_drv_type, pn_type),
|
|
|
|
{0, 0}
|
|
};
|
|
|
|
static devclass_t pn_devclass;
|
|
static driver_t pn_driver = {
|
|
"powernow",
|
|
pn_methods,
|
|
sizeof(struct pn_softc),
|
|
};
|
|
|
|
DRIVER_MODULE(powernow, cpu, pn_driver, pn_devclass, 0, 0);
|
|
|
|
static int
|
|
pn7_setfidvid(struct pn_softc *sc, int fid, int vid)
|
|
{
|
|
int cfid, cvid;
|
|
uint64_t status, ctl;
|
|
|
|
status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
|
|
cfid = PN7_STA_CFID(status);
|
|
cvid = PN7_STA_CVID(status);
|
|
|
|
/* We're already at the requested level. */
|
|
if (fid == cfid && vid == cvid)
|
|
return (0);
|
|
|
|
ctl = rdmsr(MSR_AMDK7_FIDVID_CTL) & PN7_CTR_FIDCHRATIO;
|
|
|
|
ctl |= PN7_CTR_FID(fid);
|
|
ctl |= PN7_CTR_VID(vid);
|
|
ctl |= PN7_CTR_SGTC(sc->sgtc);
|
|
|
|
if (sc->errata_a0)
|
|
disable_intr();
|
|
|
|
if (pn7_fid_to_mult[fid] < pn7_fid_to_mult[cfid]) {
|
|
wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_FIDC);
|
|
if (vid != cvid)
|
|
wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_VIDC);
|
|
} else {
|
|
wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_VIDC);
|
|
if (fid != cfid)
|
|
wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_FIDC);
|
|
}
|
|
|
|
if (sc->errata_a0)
|
|
enable_intr();
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
pn8_setfidvid(struct pn_softc *sc, int fid, int vid)
|
|
{
|
|
uint64_t status;
|
|
int cfid, cvid;
|
|
int rvo;
|
|
u_int val;
|
|
|
|
READ_PENDING_WAIT(status);
|
|
cfid = PN8_STA_CFID(status);
|
|
cvid = PN8_STA_CVID(status);
|
|
|
|
if (fid == cfid && vid == cvid)
|
|
return (0);
|
|
|
|
/*
|
|
* Phase 1: Raise core voltage to requested VID if frequency is
|
|
* going up.
|
|
*/
|
|
while (cvid > vid) {
|
|
val = cvid - (1 << sc->mvs);
|
|
WRITE_FIDVID(cfid, (val > 0) ? val : 0, 1ULL);
|
|
READ_PENDING_WAIT(status);
|
|
cvid = PN8_STA_CVID(status);
|
|
COUNT_OFF_VST(sc->vst);
|
|
}
|
|
|
|
/* ... then raise to voltage + RVO (if required) */
|
|
for (rvo = sc->rvo; rvo > 0 && cvid > 0; --rvo) {
|
|
/* XXX It's not clear from spec if we have to do that
|
|
* in 0.25 step or in MVS. Therefore do it as it's done
|
|
* under Linux */
|
|
WRITE_FIDVID(cfid, cvid - 1, 1ULL);
|
|
READ_PENDING_WAIT(status);
|
|
cvid = PN8_STA_CVID(status);
|
|
COUNT_OFF_VST(sc->vst);
|
|
}
|
|
|
|
/* Phase 2: change to requested core frequency */
|
|
if (cfid != fid) {
|
|
u_int vco_fid, vco_cfid;
|
|
|
|
vco_fid = FID_TO_VCO_FID(fid);
|
|
vco_cfid = FID_TO_VCO_FID(cfid);
|
|
|
|
while (abs(vco_fid - vco_cfid) > 2) {
|
|
if (fid > cfid) {
|
|
if (cfid > 6)
|
|
val = cfid + 2;
|
|
else
|
|
val = FID_TO_VCO_FID(cfid) + 2;
|
|
} else
|
|
val = cfid - 2;
|
|
WRITE_FIDVID(val, cvid, sc->pll * (uint64_t) sc->fsb);
|
|
READ_PENDING_WAIT(status);
|
|
cfid = PN8_STA_CFID(status);
|
|
COUNT_OFF_IRT(sc->irt);
|
|
|
|
vco_cfid = FID_TO_VCO_FID(cfid);
|
|
}
|
|
|
|
WRITE_FIDVID(fid, cvid, sc->pll * (uint64_t) sc->fsb);
|
|
READ_PENDING_WAIT(status);
|
|
cfid = PN8_STA_CFID(status);
|
|
COUNT_OFF_IRT(sc->irt);
|
|
}
|
|
|
|
/* Phase 3: change to requested voltage */
|
|
if (cvid != vid) {
|
|
WRITE_FIDVID(cfid, vid, 1ULL);
|
|
READ_PENDING_WAIT(status);
|
|
cvid = PN8_STA_CVID(status);
|
|
COUNT_OFF_VST(sc->vst);
|
|
}
|
|
|
|
/* Check if transition failed. */
|
|
if (cfid != fid || cvid != vid)
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
pn_set(device_t dev, const struct cf_setting *cf)
|
|
{
|
|
struct pn_softc *sc;
|
|
int fid, vid;
|
|
int i;
|
|
int rv;
|
|
|
|
if (cf == NULL)
|
|
return (EINVAL);
|
|
sc = device_get_softc(dev);
|
|
|
|
for (i = 0; i < sc->powernow_max_states; ++i)
|
|
if (CPUFREQ_CMP(sc->powernow_states[i].freq / 1000, cf->freq))
|
|
break;
|
|
|
|
fid = sc->powernow_states[i].fid;
|
|
vid = sc->powernow_states[i].vid;
|
|
|
|
rv = ENODEV;
|
|
|
|
switch (sc->pn_type) {
|
|
case PN7_TYPE:
|
|
rv = pn7_setfidvid(sc, fid, vid);
|
|
break;
|
|
case PN8_TYPE:
|
|
rv = pn8_setfidvid(sc, fid, vid);
|
|
break;
|
|
}
|
|
|
|
return (rv);
|
|
}
|
|
|
|
static int
|
|
pn_get(device_t dev, struct cf_setting *cf)
|
|
{
|
|
struct pn_softc *sc;
|
|
u_int cfid = 0, cvid = 0;
|
|
int i;
|
|
uint64_t status;
|
|
|
|
if (cf == NULL)
|
|
return (EINVAL);
|
|
sc = device_get_softc(dev);
|
|
|
|
status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
|
|
|
|
switch (sc->pn_type) {
|
|
case PN7_TYPE:
|
|
cfid = PN7_STA_CFID(status);
|
|
cvid = PN7_STA_CVID(status);
|
|
break;
|
|
case PN8_TYPE:
|
|
cfid = PN8_STA_CFID(status);
|
|
cvid = PN8_STA_CVID(status);
|
|
break;
|
|
}
|
|
for (i = 0; i < sc->powernow_max_states; ++i)
|
|
if (cfid == sc->powernow_states[i].fid &&
|
|
cvid == sc->powernow_states[i].vid)
|
|
break;
|
|
|
|
if (i < sc->powernow_max_states) {
|
|
cf->freq = sc->powernow_states[i].freq / 1000;
|
|
cf->power = sc->powernow_states[i].power;
|
|
cf->lat = 200;
|
|
cf->volts = sc->vid_to_volts[cvid];
|
|
cf->dev = dev;
|
|
} else {
|
|
memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf));
|
|
cf->dev = NULL;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
pn_settings(device_t dev, struct cf_setting *sets, int *count)
|
|
{
|
|
struct pn_softc *sc;
|
|
int i;
|
|
|
|
if (sets == NULL|| count == NULL)
|
|
return (EINVAL);
|
|
sc = device_get_softc(dev);
|
|
if (*count < sc->powernow_max_states)
|
|
return (E2BIG);
|
|
for (i = 0; i < sc->powernow_max_states; ++i) {
|
|
sets[i].freq = sc->powernow_states[i].freq / 1000;
|
|
sets[i].power = sc->powernow_states[i].power;
|
|
sets[i].lat = 200;
|
|
sets[i].volts = sc->vid_to_volts[sc->powernow_states[i].vid];
|
|
sets[i].dev = dev;
|
|
}
|
|
*count = sc->powernow_max_states;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
pn_type(device_t dev, int *type)
|
|
{
|
|
if (type == NULL)
|
|
return (EINVAL);
|
|
|
|
*type = CPUFREQ_TYPE_ABSOLUTE;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Given a set of pair of fid/vid, and number of performance states,
|
|
* compute powernow_states via an insertion sort.
|
|
*/
|
|
static int
|
|
decode_pst(struct pn_softc *sc, uint8_t *p, int npstates)
|
|
{
|
|
int i, j, n;
|
|
struct powernow_state state;
|
|
|
|
for (i = 0; i < POWERNOW_MAX_STATES; ++i)
|
|
sc->powernow_states[i].freq = CPUFREQ_VAL_UNKNOWN;
|
|
|
|
for (n = 0, i = 0; i < npstates; ++i) {
|
|
state.fid = *p++;
|
|
state.vid = *p++;
|
|
state.power = CPUFREQ_VAL_UNKNOWN;
|
|
|
|
switch (sc->pn_type) {
|
|
case PN7_TYPE:
|
|
state.freq = 100 * pn7_fid_to_mult[state.fid] * sc->fsb;
|
|
if (sc->errata_a0 &&
|
|
(pn7_fid_to_mult[state.fid] % 10) == 5)
|
|
continue;
|
|
break;
|
|
case PN8_TYPE:
|
|
state.freq = 100 * pn8_fid_to_mult[state.fid >> 1] *
|
|
sc->fsb;
|
|
break;
|
|
}
|
|
|
|
j = n;
|
|
while (j > 0 && sc->powernow_states[j - 1].freq < state.freq) {
|
|
memcpy(&sc->powernow_states[j],
|
|
&sc->powernow_states[j - 1],
|
|
sizeof(struct powernow_state));
|
|
--j;
|
|
}
|
|
memcpy(&sc->powernow_states[j], &state,
|
|
sizeof(struct powernow_state));
|
|
++n;
|
|
}
|
|
|
|
/*
|
|
* Fix powernow_max_states, if errata_a0 give us less states
|
|
* than expected.
|
|
*/
|
|
sc->powernow_max_states = n;
|
|
|
|
if (bootverbose)
|
|
for (i = 0; i < sc->powernow_max_states; ++i) {
|
|
int fid = sc->powernow_states[i].fid;
|
|
int vid = sc->powernow_states[i].vid;
|
|
|
|
printf("powernow: %2i %8dkHz FID %02x VID %02x\n",
|
|
i,
|
|
sc->powernow_states[i].freq,
|
|
fid,
|
|
vid);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
cpuid_is_k7(u_int cpuid)
|
|
{
|
|
|
|
switch (cpuid) {
|
|
case 0x760:
|
|
case 0x761:
|
|
case 0x762:
|
|
case 0x770:
|
|
case 0x771:
|
|
case 0x780:
|
|
case 0x781:
|
|
case 0x7a0:
|
|
return (TRUE);
|
|
}
|
|
return (FALSE);
|
|
}
|
|
|
|
static int
|
|
pn_decode_pst(device_t dev)
|
|
{
|
|
int maxpst;
|
|
struct pn_softc *sc;
|
|
u_int cpuid, maxfid, startvid;
|
|
u_long sig;
|
|
struct psb_header *psb;
|
|
uint8_t *p;
|
|
u_int regs[4];
|
|
uint64_t status;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
do_cpuid(0x80000001, regs);
|
|
cpuid = regs[0];
|
|
|
|
if ((cpuid & 0xfff) == 0x760)
|
|
sc->errata_a0 = TRUE;
|
|
|
|
status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
|
|
|
|
switch (sc->pn_type) {
|
|
case PN7_TYPE:
|
|
maxfid = PN7_STA_MFID(status);
|
|
startvid = PN7_STA_SVID(status);
|
|
break;
|
|
case PN8_TYPE:
|
|
maxfid = PN8_STA_MFID(status);
|
|
/*
|
|
* we should actually use a variable named 'maxvid' if K8,
|
|
* but why introducing a new variable for that?
|
|
*/
|
|
startvid = PN8_STA_MVID(status);
|
|
break;
|
|
default:
|
|
return (ENODEV);
|
|
}
|
|
|
|
if (bootverbose) {
|
|
device_printf(dev, "STATUS: 0x%jx\n", status);
|
|
device_printf(dev, "STATUS: maxfid: 0x%02x\n", maxfid);
|
|
device_printf(dev, "STATUS: %s: 0x%02x\n",
|
|
sc->pn_type == PN7_TYPE ? "startvid" : "maxvid",
|
|
startvid);
|
|
}
|
|
|
|
sig = bios_sigsearch(PSB_START, PSB_SIG, PSB_LEN, PSB_STEP, PSB_OFF);
|
|
if (sig) {
|
|
struct pst_header *pst;
|
|
|
|
psb = (struct psb_header*)(uintptr_t)BIOS_PADDRTOVADDR(sig);
|
|
|
|
switch (psb->version) {
|
|
default:
|
|
return (ENODEV);
|
|
case 0x14:
|
|
/*
|
|
* We can't be picky about numpst since at least
|
|
* some systems have a value of 1 and some have 2.
|
|
* We trust that cpuid_is_k7() will be better at
|
|
* catching that we're on a K8 anyway.
|
|
*/
|
|
if (sc->pn_type != PN8_TYPE)
|
|
return (EINVAL);
|
|
sc->vst = psb->settlingtime;
|
|
sc->rvo = PN8_PSB_TO_RVO(psb->res1),
|
|
sc->irt = PN8_PSB_TO_IRT(psb->res1),
|
|
sc->mvs = PN8_PSB_TO_MVS(psb->res1),
|
|
sc->low = PN8_PSB_TO_BATT(psb->res1);
|
|
if (bootverbose) {
|
|
device_printf(dev, "PSB: VST: %d\n",
|
|
psb->settlingtime);
|
|
device_printf(dev, "PSB: RVO %x IRT %d "
|
|
"MVS %d BATT %d\n",
|
|
sc->rvo,
|
|
sc->irt,
|
|
sc->mvs,
|
|
sc->low);
|
|
}
|
|
break;
|
|
case 0x12:
|
|
if (sc->pn_type != PN7_TYPE)
|
|
return (EINVAL);
|
|
sc->sgtc = psb->settlingtime * sc->fsb;
|
|
if (sc->sgtc < 100 * sc->fsb)
|
|
sc->sgtc = 100 * sc->fsb;
|
|
break;
|
|
}
|
|
|
|
p = ((uint8_t *) psb) + sizeof(struct psb_header);
|
|
pst = (struct pst_header*) p;
|
|
|
|
maxpst = 200;
|
|
|
|
do {
|
|
struct pst_header *pst = (struct pst_header*) p;
|
|
|
|
if (cpuid == pst->cpuid &&
|
|
maxfid == pst->maxfid &&
|
|
startvid == pst->startvid) {
|
|
sc->powernow_max_states = pst->numpstates;
|
|
switch (sc->pn_type) {
|
|
case PN7_TYPE:
|
|
if (abs(sc->fsb - pst->fsb) > 5)
|
|
continue;
|
|
break;
|
|
case PN8_TYPE:
|
|
break;
|
|
}
|
|
return (decode_pst(sc,
|
|
p + sizeof(struct pst_header),
|
|
sc->powernow_max_states));
|
|
}
|
|
|
|
p += sizeof(struct pst_header) + (2 * pst->numpstates);
|
|
} while (cpuid_is_k7(pst->cpuid) && maxpst--);
|
|
|
|
device_printf(dev, "no match for extended cpuid %.3x\n", cpuid);
|
|
}
|
|
|
|
return (ENODEV);
|
|
}
|
|
|
|
/*
|
|
* TODO: this should be done in sys/ARCH/ARCH/identcpu.c
|
|
*/
|
|
static int
|
|
cpu_is_powernow_capable(void)
|
|
{
|
|
u_int regs[4];
|
|
|
|
if (strcmp(cpu_vendor, "AuthenticAMD") != 0 ||
|
|
cpu_exthigh < 0x80000007)
|
|
return (FALSE);
|
|
|
|
do_cpuid(0x80000007, regs);
|
|
return (regs[3] & 0x6);
|
|
}
|
|
|
|
static int
|
|
pn_decode_acpi(device_t dev, device_t perf_dev)
|
|
{
|
|
int i, j, n;
|
|
uint64_t status;
|
|
uint32_t ctrl;
|
|
u_int cpuid;
|
|
u_int regs[4];
|
|
struct pn_softc *sc;
|
|
struct powernow_state state;
|
|
struct cf_setting sets[POWERNOW_MAX_STATES];
|
|
int count = POWERNOW_MAX_STATES;
|
|
int type;
|
|
int rv;
|
|
|
|
if (perf_dev == NULL)
|
|
return (ENXIO);
|
|
|
|
rv = CPUFREQ_DRV_SETTINGS(perf_dev, sets, &count);
|
|
if (rv)
|
|
return (ENXIO);
|
|
rv = CPUFREQ_DRV_TYPE(perf_dev, &type);
|
|
if (rv || (type & CPUFREQ_FLAG_INFO_ONLY) == 0)
|
|
return (ENXIO);
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
do_cpuid(0x80000001, regs);
|
|
cpuid = regs[0];
|
|
if ((cpuid & 0xfff) == 0x760)
|
|
sc->errata_a0 = TRUE;
|
|
|
|
ctrl = 0;
|
|
sc->sgtc = 0;
|
|
for (n = 0, i = 0; i < count; ++i) {
|
|
ctrl = sets[i].spec[PX_SPEC_CONTROL];
|
|
switch (sc->pn_type) {
|
|
case PN7_TYPE:
|
|
state.fid = ACPI_PN7_CTRL_TO_FID(ctrl);
|
|
state.vid = ACPI_PN7_CTRL_TO_VID(ctrl);
|
|
if (sc->errata_a0 &&
|
|
(pn7_fid_to_mult[state.fid] % 10) == 5)
|
|
continue;
|
|
state.freq = 100 * pn7_fid_to_mult[state.fid] * sc->fsb;
|
|
break;
|
|
case PN8_TYPE:
|
|
state.fid = ACPI_PN8_CTRL_TO_FID(ctrl);
|
|
state.vid = ACPI_PN8_CTRL_TO_VID(ctrl);
|
|
state.freq = 100 * pn8_fid_to_mult[state.fid >> 1] *
|
|
sc->fsb;
|
|
break;
|
|
}
|
|
|
|
state.power = sets[i].power;
|
|
|
|
j = n;
|
|
while (j > 0 && sc->powernow_states[j - 1].freq < state.freq) {
|
|
memcpy(&sc->powernow_states[j],
|
|
&sc->powernow_states[j - 1],
|
|
sizeof(struct powernow_state));
|
|
--j;
|
|
}
|
|
memcpy(&sc->powernow_states[j], &state,
|
|
sizeof(struct powernow_state));
|
|
++n;
|
|
}
|
|
|
|
sc->powernow_max_states = n;
|
|
state = sc->powernow_states[0];
|
|
status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
|
|
|
|
switch (sc->pn_type) {
|
|
case PN7_TYPE:
|
|
sc->sgtc = ACPI_PN7_CTRL_TO_SGTC(ctrl);
|
|
/*
|
|
* XXX Some bios forget the max frequency!
|
|
* This maybe indicates we have the wrong tables. Therefore,
|
|
* don't implement a quirk, but fallback to BIOS legacy
|
|
* tables instead.
|
|
*/
|
|
if (PN7_STA_MFID(status) != state.fid) {
|
|
device_printf(dev, "ACPI MAX frequency not found\n");
|
|
return (EINVAL);
|
|
}
|
|
break;
|
|
case PN8_TYPE:
|
|
sc->vst = ACPI_PN8_CTRL_TO_VST(ctrl),
|
|
sc->mvs = ACPI_PN8_CTRL_TO_MVS(ctrl),
|
|
sc->pll = ACPI_PN8_CTRL_TO_PLL(ctrl),
|
|
sc->rvo = ACPI_PN8_CTRL_TO_RVO(ctrl),
|
|
sc->irt = ACPI_PN8_CTRL_TO_IRT(ctrl);
|
|
sc->low = 0; /* XXX */
|
|
|
|
/*
|
|
* powernow k8 supports only one low frequency.
|
|
*/
|
|
if (sc->powernow_max_states >= 2 &&
|
|
(sc->powernow_states[sc->powernow_max_states - 2].fid < 8))
|
|
return (EINVAL);
|
|
break;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
pn_identify(driver_t *driver, device_t parent)
|
|
{
|
|
device_t child;
|
|
|
|
if (cpu_is_powernow_capable() == 0)
|
|
return;
|
|
switch (cpu_id & 0xf00) {
|
|
case 0x600:
|
|
case 0xf00:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
if (device_find_child(parent, "powernow", -1) != NULL)
|
|
return;
|
|
if ((child = BUS_ADD_CHILD(parent, 0, "powernow", -1)) == NULL)
|
|
device_printf(parent, "powernow: add child failed\n");
|
|
}
|
|
|
|
static int
|
|
pn_probe(device_t dev)
|
|
{
|
|
struct pn_softc *sc;
|
|
uint64_t status;
|
|
uint64_t rate;
|
|
struct pcpu *pc;
|
|
u_int sfid, mfid, cfid;
|
|
|
|
sc = device_get_softc(dev);
|
|
sc->errata_a0 = FALSE;
|
|
status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
|
|
|
|
pc = cpu_get_pcpu(dev);
|
|
if (pc == NULL)
|
|
return (ENODEV);
|
|
|
|
cpu_est_clockrate(pc->pc_cpuid, &rate);
|
|
|
|
switch (cpu_id & 0xf00) {
|
|
case 0x600:
|
|
sfid = PN7_STA_SFID(status);
|
|
mfid = PN7_STA_MFID(status);
|
|
cfid = PN7_STA_CFID(status);
|
|
sc->pn_type = PN7_TYPE;
|
|
sc->fsb = rate / 100000 / pn7_fid_to_mult[cfid];
|
|
|
|
/*
|
|
* If start FID is different to max FID, then it is a
|
|
* mobile processor. If not, it is a low powered desktop
|
|
* processor.
|
|
*/
|
|
if (PN7_STA_SFID(status) != PN7_STA_MFID(status)) {
|
|
sc->vid_to_volts = pn7_mobile_vid_to_volts;
|
|
device_set_desc(dev, "PowerNow! K7");
|
|
} else {
|
|
sc->vid_to_volts = pn7_desktop_vid_to_volts;
|
|
device_set_desc(dev, "Cool`n'Quiet K7");
|
|
}
|
|
break;
|
|
|
|
case 0xf00:
|
|
sfid = PN8_STA_SFID(status);
|
|
mfid = PN8_STA_MFID(status);
|
|
cfid = PN8_STA_CFID(status);
|
|
sc->pn_type = PN8_TYPE;
|
|
sc->vid_to_volts = pn8_vid_to_volts;
|
|
sc->fsb = rate / 100000 / pn8_fid_to_mult[cfid >> 1];
|
|
|
|
if (PN8_STA_SFID(status) != PN8_STA_MFID(status))
|
|
device_set_desc(dev, "PowerNow! K8");
|
|
else
|
|
device_set_desc(dev, "Cool`n'Quiet K8");
|
|
break;
|
|
default:
|
|
return (ENODEV);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
pn_attach(device_t dev)
|
|
{
|
|
int rv;
|
|
device_t child;
|
|
|
|
child = device_find_child(device_get_parent(dev), "acpi_perf", -1);
|
|
if (child) {
|
|
rv = pn_decode_acpi(dev, child);
|
|
if (rv)
|
|
rv = pn_decode_pst(dev);
|
|
} else
|
|
rv = pn_decode_pst(dev);
|
|
|
|
if (rv != 0)
|
|
return (ENXIO);
|
|
cpufreq_register(dev);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
pn_detach(device_t dev)
|
|
{
|
|
|
|
cpufreq_unregister(dev);
|
|
return (0);
|
|
}
|