root/sys/x86/cpufreq/powernow.c
/*-
 * 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/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", DEVICE_UNIT_ANY) != 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",
            DEVICE_UNIT_ANY);
        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));
}