/* $OpenBSD: powernow-k8.c,v 1.35 2023/01/30 10:49:05 jsg Exp $ */ /* * Copyright (c) 2004 Martin Végiard. * 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. */ /* AMD POWERNOW K8 driver */ #include #include #include #include #include #include #include #include #include "acpicpu.h" #if NACPICPU > 0 #include #endif #define BIOS_START 0xe0000 #define BIOS_LEN 0x20000 #define BIOS_STEP 16 /* * MSRs and bits used by PowerNow! technology */ #define MSR_AMDK7_FIDVID_CTL 0xc0010041 #define MSR_AMDK7_FIDVID_STATUS 0xc0010042 #define AMD_PN_FID_VID 0x06 /* 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_VERSION 0x14 #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 PN8_ACPI_CTRL_TO_FID(x) ((x) & 0x3f) #define PN8_ACPI_CTRL_TO_VID(x) (((x) >> 6) & 0x1f) #define PN8_ACPI_CTRL_TO_VST(x) (((x) >> 11) & 0x1f) #define PN8_ACPI_CTRL_TO_MVS(x) (((x) >> 18) & 0x03) #define PN8_ACPI_CTRL_TO_PLL(x) (((x) >> 20) & 0x7f) #define PN8_ACPI_CTRL_TO_RVO(x) (((x) >> 28) & 0x03) #define PN8_ACPI_CTRL_TO_IRT(x) (((x) >> 30) & 0x03) #define PN8_PSS_CFID(x) ((x) & 0x3f) #define PN8_PSS_CVID(x) (((x) >> 6) & 0x1f) #define PN8_PLL_LOCK(x) ((x) * 1000/5) #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)) #define POWERNOW_MAX_STATES 16 struct k8pnow_state { int freq; uint8_t fid; uint8_t vid; }; struct k8pnow_cpu_state { struct k8pnow_state state_table[POWERNOW_MAX_STATES]; unsigned int n_states; unsigned int sgtc; unsigned int vst; unsigned int mvs; unsigned int pll; unsigned int rvo; unsigned int irt; int low; }; struct psb_s { char signature[10]; /* AMDK7PNOW! */ uint8_t version; uint8_t flags; uint16_t ttime; /* Min Settling time */ uint8_t reserved; uint8_t n_pst; }; struct pst_s { uint32_t cpuid; uint8_t pll; uint8_t fid; uint8_t vid; uint8_t n_states; }; struct k8pnow_cpu_state *k8pnow_current_state = NULL; extern int setperf_prio; extern int perflevel; int k8pnow_read_pending_wait(uint64_t *); int k8pnow_decode_pst(struct k8pnow_cpu_state *, uint8_t *); int k8pnow_states(struct k8pnow_cpu_state *, uint32_t, unsigned int, unsigned int); void k8pnow_transition(struct k8pnow_cpu_state *e, int); #if NACPICPU > 0 int k8pnow_acpi_init(struct k8pnow_cpu_state *, uint64_t); void k8pnow_acpi_pss_changed(struct acpicpu_pss *, int); int k8pnow_acpi_states(struct k8pnow_cpu_state *, struct acpicpu_pss *, int, uint64_t); #endif int k8pnow_read_pending_wait(uint64_t *status) { unsigned int i = 100000; while (i--) { *status = rdmsr(MSR_AMDK7_FIDVID_STATUS); if (!PN8_STA_PENDING(*status)) return 0; } printf("k8pnow_read_pending_wait: change pending stuck.\n"); return 1; } void k8_powernow_setperf(int level) { unsigned int i; struct k8pnow_cpu_state *cstate; cstate = k8pnow_current_state; i = ((level * cstate->n_states) + 1) / 101; if (i >= cstate->n_states) i = cstate->n_states - 1; k8pnow_transition(cstate, i); } void k8pnow_transition(struct k8pnow_cpu_state *cstate, int level) { uint64_t status; int cfid, cvid, fid = 0, vid = 0; int rvo; u_int val; /* * We dont do a k8pnow_read_pending_wait here, need to ensure that the * change pending bit isn't stuck, */ status = rdmsr(MSR_AMDK7_FIDVID_STATUS); if (PN8_STA_PENDING(status)) return; cfid = PN8_STA_CFID(status); cvid = PN8_STA_CVID(status); fid = cstate->state_table[level].fid; vid = cstate->state_table[level].vid; if (fid == cfid && vid == cvid) return; /* * Phase 1: Raise core voltage to requested VID if frequency is * going up. */ while (cvid > vid) { val = cvid - (1 << cstate->mvs); WRITE_FIDVID(cfid, (val > 0) ? val : 0, 1ULL); if (k8pnow_read_pending_wait(&status)) return; cvid = PN8_STA_CVID(status); COUNT_OFF_VST(cstate->vst); } /* ... then raise to voltage + RVO (if required) */ for (rvo = cstate->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); if (k8pnow_read_pending_wait(&status)) return; cvid = PN8_STA_CVID(status); COUNT_OFF_VST(cstate->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, (uint64_t)cstate->pll * 1000 / 5); if (k8pnow_read_pending_wait(&status)) return; cfid = PN8_STA_CFID(status); COUNT_OFF_IRT(cstate->irt); vco_cfid = FID_TO_VCO_FID(cfid); } WRITE_FIDVID(fid, cvid, (uint64_t) cstate->pll * 1000 / 5); if (k8pnow_read_pending_wait(&status)) return; cfid = PN8_STA_CFID(status); COUNT_OFF_IRT(cstate->irt); } /* Phase 3: change to requested voltage */ if (cvid != vid) { WRITE_FIDVID(cfid, vid, 1ULL); if (k8pnow_read_pending_wait(&status)) return; cvid = PN8_STA_CVID(status); COUNT_OFF_VST(cstate->vst); } if (cfid == fid || cvid == vid) cpuspeed = cstate->state_table[level].freq; } /* * Given a set of pair of fid/vid, and number of performance states, * compute state_table via an insertion sort. */ int k8pnow_decode_pst(struct k8pnow_cpu_state *cstate, uint8_t *p) { int i, j, n; struct k8pnow_state state; for (n = 0, i = 0; i < cstate->n_states; i++) { state.fid = *p++; state.vid = *p++; /* * The minimum supported frequency per the data sheet is 800MHz * The maximum supported frequency is 5000MHz. */ state.freq = 800 + state.fid * 100; j = n; while (j > 0 && cstate->state_table[j - 1].freq > state.freq) { memcpy(&cstate->state_table[j], &cstate->state_table[j - 1], sizeof(struct k8pnow_state)); --j; } memcpy(&cstate->state_table[j], &state, sizeof(struct k8pnow_state)); n++; } return 1; } int k8pnow_states(struct k8pnow_cpu_state *cstate, uint32_t cpusig, unsigned int fid, unsigned int vid) { struct psb_s *psb; struct pst_s *pst; uint8_t *p; int i; /* * Look in the 0xe0000 - 0x100000 physical address * range for the pst tables; 16 byte blocks. End 10 bytes * before the end of the range to avoid memcmp across a * page boundary into unmapped memory. */ for (p = (u_int8_t *)ISA_HOLE_VADDR(BIOS_START); p < (u_int8_t *)ISA_HOLE_VADDR(BIOS_START + BIOS_LEN) - 10; p += BIOS_STEP) { if (memcmp(p, "AMDK7PNOW!", 10) == 0) { psb = (struct psb_s *)p; if (psb->version != PN8_PSB_VERSION) return 0; cstate->vst = psb->ttime; cstate->rvo = PN8_PSB_TO_RVO(psb->reserved); cstate->irt = PN8_PSB_TO_IRT(psb->reserved); cstate->mvs = PN8_PSB_TO_MVS(psb->reserved); cstate->low = PN8_PSB_TO_BATT(psb->reserved); p+= sizeof(struct psb_s); for (i = 0; i < psb->n_pst; ++i) { pst = (struct pst_s *) p; cstate->pll = pst->pll; cstate->n_states = pst->n_states; if (cpusig == pst->cpuid && pst->fid == fid && pst->vid == vid) { return (k8pnow_decode_pst(cstate, p+= sizeof (struct pst_s))); } p += sizeof(struct pst_s) + 2 * cstate->n_states; } } } return 0; } #if NACPICPU > 0 int k8pnow_acpi_states(struct k8pnow_cpu_state * cstate, struct acpicpu_pss * pss, int nstates, uint64_t status) { struct k8pnow_state state; int j, k, n; uint32_t ctrl; k = -1; for (n = 0; n < cstate->n_states; n++) { if ((PN8_STA_CFID(status) == PN8_PSS_CFID(pss[n].pss_status)) && (PN8_STA_CVID(status) == PN8_PSS_CVID(pss[n].pss_status))) k = n; ctrl = pss[n].pss_ctrl; state.fid = PN8_ACPI_CTRL_TO_FID(ctrl); state.vid = PN8_ACPI_CTRL_TO_VID(ctrl); state.freq = pss[n].pss_core_freq; j = n; while (j > 0 && cstate->state_table[j - 1].freq > state.freq) { memcpy(&cstate->state_table[j], &cstate->state_table[j - 1], sizeof(struct k8pnow_state)); --j; } memcpy(&cstate->state_table[j], &state, sizeof(struct k8pnow_state)); } return k; } void k8pnow_acpi_pss_changed(struct acpicpu_pss * pss, int npss) { int curs; struct k8pnow_cpu_state * cstate; uint32_t ctrl; uint64_t status; status = rdmsr(MSR_AMDK7_FIDVID_STATUS); cstate = k8pnow_current_state; curs = k8pnow_acpi_states(cstate, pss, npss, status); ctrl = pss[curs].pss_ctrl; cstate->rvo = PN8_ACPI_CTRL_TO_RVO(ctrl); cstate->vst = PN8_ACPI_CTRL_TO_VST(ctrl); cstate->mvs = PN8_ACPI_CTRL_TO_MVS(ctrl); cstate->pll = PN8_ACPI_CTRL_TO_PLL(ctrl); cstate->irt = PN8_ACPI_CTRL_TO_IRT(ctrl); cstate->low = 0; cstate->n_states = npss; } int k8pnow_acpi_init(struct k8pnow_cpu_state * cstate, uint64_t status) { int curs; uint32_t ctrl; struct acpicpu_pss *pss; cstate->n_states = acpicpu_fetch_pss(&pss); if (cstate->n_states == 0) return 0; acpicpu_set_notify(k8pnow_acpi_pss_changed); curs = k8pnow_acpi_states(cstate, pss, cstate->n_states, status); ctrl = pss[curs].pss_ctrl; cstate->rvo = PN8_ACPI_CTRL_TO_RVO(ctrl); cstate->vst = PN8_ACPI_CTRL_TO_VST(ctrl); cstate->mvs = PN8_ACPI_CTRL_TO_MVS(ctrl); cstate->pll = PN8_ACPI_CTRL_TO_PLL(ctrl); cstate->irt = PN8_ACPI_CTRL_TO_IRT(ctrl); cstate->low = 0; return 1; } #endif /* NACPICPU */ void k8_powernow_init(void) { uint64_t status; u_int maxfid, maxvid, i; struct k8pnow_cpu_state *cstate; struct k8pnow_state *state; struct cpu_info * ci; char * techname = NULL; u_int32_t regs[4]; ci = curcpu(); if (setperf_prio > 1) return; if (k8pnow_current_state) return; cpuid(0x80000000, regs); if (regs[0] < 0x80000007) return; cpuid(0x80000007, regs); if (!(regs[3] & AMD_PN_FID_VID)) return; /* Extended CPUID signature value */ cpuid(0x80000001, regs); cstate = malloc(sizeof(struct k8pnow_cpu_state), M_DEVBUF, M_NOWAIT); if (!cstate) return; cstate->n_states = 0; status = rdmsr(MSR_AMDK7_FIDVID_STATUS); maxfid = PN8_STA_MFID(status); maxvid = PN8_STA_MVID(status); /* * If start FID is different to max FID, then it is a * mobile processor. If not, it is a low powered desktop * processor. */ if (PN8_STA_SFID(status) != PN8_STA_MFID(status)) techname = "PowerNow! K8"; else techname = "Cool'n'Quiet K8"; #if NACPICPU > 0 /* If we have acpi check acpi first */ if (!k8pnow_acpi_init(cstate, status)) #endif { if (!k8pnow_states(cstate, ci->ci_signature, maxfid, maxvid)) k8pnow_states(cstate, regs[0], maxfid, maxvid); } if (cstate->n_states) { printf("%s: %s %d MHz: speeds:", ci->ci_dev->dv_xname, techname, cpuspeed); for (i = cstate->n_states; i > 0; i--) { state = &cstate->state_table[i-1]; printf(" %d", state->freq); } printf(" MHz\n"); k8pnow_current_state = cstate; cpu_setperf = k8_powernow_setperf; setperf_prio = 1; return; } free(cstate, M_DEVBUF, sizeof(*cstate)); }