/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* 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>
#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/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
/* Flags for some hardware bugs. */
#define A0_ERRATA 0x1 /* Bugs for the rev. A0 of Athlon (K7):
* Interrupts must be disabled and no half
* multipliers are allowed */
#define PENDING_STUCK 0x2 /* With some buggy chipset and some newer AMD64
* processor (Rev. G?):
* the pending bit from the msr FIDVID_STATUS
* is set forever. No workaround :( */
/* 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 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[64] = {
40, 45, 50, 55, 60, 65, 70, 75,
80, 85, 90, 95, 100, 105, 110, 115,
120, 125, 130, 135, 140, 145, 150, 155,
160, 165, 170, 175, 180, 185, 190, 195,
200, 205, 210, 215, 220, 225, 230, 235,
240, 245, 250, 255, 260, 265, 270, 275,
280, 285, 290, 295, 300, 305, 310, 315,
320, 325, 330, 335, 340, 345, 350, 355,
};
/*
* 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;
u_int errata;
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 driver_t pn_driver = {
"powernow",
pn_methods,
sizeof(struct pn_softc),
};
DRIVER_MODULE(powernow, cpu, pn_driver, 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_ERRATA)
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_ERRATA)
enable_intr();
return (0);
}
static int
pn8_read_pending_wait(uint64_t *status)
{
int i = 10000;
do
*status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
while (PN8_STA_PENDING(*status) && --i);
return (i == 0 ? ENXIO : 0);
}
static int
pn8_write_fidvid(u_int fid, u_int vid, uint64_t ctrl, uint64_t *status)
{
int i = 100;
do
WRITE_FIDVID(fid, vid, ctrl);
while (pn8_read_pending_wait(status) && --i);
return (i == 0 ? ENXIO : 0);
}
static int
pn8_setfidvid(struct pn_softc *sc, int fid, int vid)
{
uint64_t status;
int cfid, cvid;
int rvo;
int rv;
u_int val;
rv = pn8_read_pending_wait(&status);
if (rv)
return (rv);
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);
rv = pn8_write_fidvid(cfid, (val > 0) ? val : 0, 1ULL, &status);
if (rv) {
sc->errata |= PENDING_STUCK;
return (rv);
}
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 */
rv = pn8_write_fidvid(cfid, cvid - 1, 1ULL, &status);
if (rv) {
sc->errata |= PENDING_STUCK;
return (rv);
}
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, fid_delta;
vco_fid = FID_TO_VCO_FID(fid);
vco_cfid = FID_TO_VCO_FID(cfid);
while (abs(vco_fid - vco_cfid) > 2) {
fid_delta = (vco_cfid & 1) ? 1 : 2;
if (fid > cfid) {
if (cfid > 7)
val = cfid + fid_delta;
else
val = FID_TO_VCO_FID(cfid) + fid_delta;
} else
val = cfid - fid_delta;
rv = pn8_write_fidvid(val, cvid,
sc->pll * (uint64_t) sc->fsb,
&status);
if (rv) {
sc->errata |= PENDING_STUCK;
return (rv);
}
cfid = PN8_STA_CFID(status);
COUNT_OFF_IRT(sc->irt);
vco_cfid = FID_TO_VCO_FID(cfid);
}
rv = pn8_write_fidvid(fid, cvid,
sc->pll * (uint64_t) sc->fsb,
&status);
if (rv) {
sc->errata |= PENDING_STUCK;
return (rv);
}
cfid = PN8_STA_CFID(status);
COUNT_OFF_IRT(sc->irt);
}
/* Phase 3: change to requested voltage */
if (cvid != vid) {
rv = pn8_write_fidvid(cfid, vid, 1ULL, &status);
cvid = PN8_STA_CVID(status);
COUNT_OFF_VST(sc->vst);
}
/* Check if transition failed. */
if (cfid != fid || cvid != vid)
rv = ENXIO;
return (rv);
}
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);
if (sc->errata & PENDING_STUCK)
return (ENXIO);
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);
if (sc->errata & PENDING_STUCK)
return (ENXIO);
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_ERRATA) &&
(pn7_fid_to_mult[state.fid] % 10) == 5)
continue;
break;
case PN8_TYPE:
state.freq = 100 * pn8_fid_to_mult[state.fid] * 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_ERRATA;
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);
}
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_ERRATA;
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_ERRATA) &&
(pn7_fid_to_mult[state.fid] % 10) == 5)
continue;
break;
case PN8_TYPE:
state.fid = ACPI_PN8_CTRL_TO_FID(ctrl);
state.vid = ACPI_PN8_CTRL_TO_VID(ctrl);
break;
}
state.freq = sets[i].freq * 1000;
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);
}
sc->fsb = state.freq / 100 / pn7_fid_to_mult[state.fid];
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);
sc->fsb = state.freq / 100 / pn8_fid_to_mult[state.fid];
break;
}
return (0);
}
static void
pn_identify(driver_t *driver, device_t parent)
{
if ((amd_pminfo & AMDPM_FID) == 0 || (amd_pminfo & AMDPM_VID) == 0)
return;
switch (cpu_id & 0xf00) {
case 0x600:
case 0xf00:
break;
default:
return;
}
if (device_find_child(parent, "powernow", -1) != NULL)
return;
if (BUS_ADD_CHILD(parent, 10, "powernow", device_get_unit(parent))
== 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 = 0;
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 (sfid != mfid) {
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];
if (sfid != mfid)
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)
{
return (cpufreq_unregister(dev));
}