diff --git a/sys/i386/cpufreq/powernow.c b/sys/i386/cpufreq/powernow.c new file mode 100644 index 000000000000..691034218cc4 --- /dev/null +++ b/sys/i386/cpufreq/powernow.c @@ -0,0 +1,928 @@ +/*- + * Copyright (c) 2004-2005 Bruno Ducrot + * Copyright (c) 2004 FUKUDA Nobuhiko + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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: + if (sc->pn_type != PN8_TYPE || psb->numpst != 1) + 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); +}