summaryrefslogtreecommitdiffstats
path: root/sys/i386/cpufreq
diff options
context:
space:
mode:
Diffstat (limited to 'sys/i386/cpufreq')
-rw-r--r--sys/i386/cpufreq/powernow.c928
1 files changed, 928 insertions, 0 deletions
diff --git a/sys/i386/cpufreq/powernow.c b/sys/i386/cpufreq/powernow.c
new file mode 100644
index 0000000..6910342
--- /dev/null
+++ b/sys/i386/cpufreq/powernow.c
@@ -0,0 +1,928 @@
+/*-
+ * 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:
+ 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);
+}
OpenPOWER on IntegriCloud