summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorTed Unangst <tedu@cvs.openbsd.org>2005-10-28 07:03:42 +0000
committerTed Unangst <tedu@cvs.openbsd.org>2005-10-28 07:03:42 +0000
commitd42fc8ad09f099e6ef849a99eb522a0dd2f44938 (patch)
tree3278e0b05829e86c8508bf651b99a1a76c1fe243 /sys
parentc07b3df802c25f129d2b3406e08ee55466b2bfd0 (diff)
update support for powernow (cool and quiet) on k7, and add support
for k8. preliminary and not well tested yet. from freebsd via gordon klok.
Diffstat (limited to 'sys')
-rw-r--r--sys/arch/i386/conf/files.i3863
-rw-r--r--sys/arch/i386/i386/machdep.c16
-rw-r--r--sys/arch/i386/i386/powernow-k7.c443
-rw-r--r--sys/arch/i386/i386/powernow-k8.c431
-rw-r--r--sys/arch/i386/include/cpu.h12
5 files changed, 749 insertions, 156 deletions
diff --git a/sys/arch/i386/conf/files.i386 b/sys/arch/i386/conf/files.i386
index b05a2937e34..f1259e5fd41 100644
--- a/sys/arch/i386/conf/files.i386
+++ b/sys/arch/i386/conf/files.i386
@@ -1,4 +1,4 @@
-# $OpenBSD: files.i386,v 1.138 2005/10/04 22:04:23 marco Exp $
+# $OpenBSD: files.i386,v 1.139 2005/10/28 07:03:41 tedu Exp $
#
# new style config file for i386 architecture
#
@@ -34,6 +34,7 @@ file arch/i386/i386/p4tcc.c !small_kernel & i686_cpu
file arch/i386/i386/pmap.c
file arch/i386/i386/powernow.c !small_kernel
file arch/i386/i386/powernow-k7.c !small_kernel
+file arch/i386/i386/powernow-k8.c !small_kernel
file arch/i386/i386/process_machdep.c
file arch/i386/i386/procfs_machdep.c procfs
file arch/i386/i386/random.s
diff --git a/sys/arch/i386/i386/machdep.c b/sys/arch/i386/i386/machdep.c
index 8ba5161d065..37a7e1a8f56 100644
--- a/sys/arch/i386/i386/machdep.c
+++ b/sys/arch/i386/i386/machdep.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: machdep.c,v 1.326 2005/10/26 20:32:59 marco Exp $ */
+/* $OpenBSD: machdep.c,v 1.327 2005/10/28 07:03:41 tedu Exp $ */
/* $NetBSD: machdep.c,v 1.214 1996/11/10 03:16:17 thorpej Exp $ */
/*-
@@ -1416,10 +1416,16 @@ amd_family6_setup(struct cpu_info *ci)
}
printf("\n");
-#ifndef MULTIPROCESSOR
- if (regs[3] & 0x06)
- k7_powernow_init(curcpu()->ci_signature);
-#endif
+ if (regs[3] & 0x06) {
+ switch(ci->ci_signature & 0xF00) {
+ case 0x600:
+ k7_powernow_init();
+ break;
+ case 0xf00:
+ k8_powernow_init();
+ break;
+ }
+ }
}
#endif
}
diff --git a/sys/arch/i386/i386/powernow-k7.c b/sys/arch/i386/i386/powernow-k7.c
index 8b3d3c1bb76..15f4d52b91c 100644
--- a/sys/arch/i386/i386/powernow-k7.c
+++ b/sys/arch/i386/i386/powernow-k7.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: powernow-k7.c,v 1.4 2005/10/20 16:38:51 mickey Exp $ */
+/* $OpenBSD: powernow-k7.c,v 1.5 2005/10/28 07:03:41 tedu Exp $ */
/*
* Copyright (c) 2004 Martin Végiard.
* All rights reserved.
@@ -24,6 +24,29 @@
* 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.
+ *
+ * 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.
*/
/* AMD POWERNOW K7 driver */
@@ -34,204 +57,330 @@
#include <sys/malloc.h>
#include <sys/sysctl.h>
-#include <dev/isa/isareg.h>
-
#include <machine/cpu.h>
+#include <machine/cpufunc.h>
#include <machine/bus.h>
-#if 0
-/* WTF? */
-#define BIOS_START 0xe0000
-#define BIOS_END 0x20000
-#define BIOS_LEN BIOS_END - BIOS_START
-#endif
+#include <dev/isa/isareg.h>
+#include <i386/isa/isa_machdep.h>
+
#define BIOS_START 0xe0000
#define BIOS_LEN 0x20000
-#define MSR_K7_CTL 0xC0010041
-#define CTL_SET_FID 0x0000000000010000ULL
-#define CTL_SET_VID 0x0000000000020000ULL
+/*
+ * MSRs and bits used by Powernow technology
+ */
+#define MSR_AMDK7_FIDVID_CTL 0xc0010041
+#define MSR_AMDK7_FIDVID_STATUS 0xc0010042
-#define cpufreq(x) k7pnow_fsb * k7pnow_fid_codes[x] / 10
+/* Bitfields used by K7 */
-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;
-};
+#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)
-struct pst_s {
- uint32_t signature;
- uint8_t fsb; /* Front Side Bus frequency (Mhz) */
- uint8_t fid; /* Max Frequency code */
- uint8_t vid; /* Max Voltage code */
- uint8_t n_states; /* Number of states */
-};
+#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)
-struct state_s {
- uint8_t fid; /* Frequency code */
- uint8_t vid; /* Voltage code */
-};
+/*
+ * ACPI ctr_val status register to powernow k7 configuration
+ */
+#define ACPI_PN7_CTRL_TO_VID(x) (((x) >> 5) & 0x1f)
+#define ACPI_PN7_CTRL_TO_SGTC(x) (((x) >> 10) & 0xffff)
-struct k7pnow_freq_table_s {
- unsigned int frequency;
- struct state_s *state;
-};
+#define WRITE_FIDVID(fid, vid, ctrl) \
+ wrmsr(MSR_AMDK7_FIDVID_CTL, \
+ (((ctrl) << 32) | (1ULL << 16) | ((vid) << 8) | (fid)))
-/* Taken from powernow-k7.c/Linux by Dave Jones */
-int k7pnow_fid_codes[32] = {
+/*
+ * Divide each value by 10 to get the processor multiplier.
+ * Taken from powernow-k7.c/Linux by Dave Jones
+ */
+static int k7pnow_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, -1, -1
};
-/* Static variables */
-unsigned int k7pnow_fsb;
-unsigned int k7pnow_cur_freq;
-unsigned int k7pnow_ttime;
-unsigned int k7pnow_nstates;
-struct k7pnow_freq_table_s *k7pnow_freq_table;
+/*
+ * Units are in mV.
+ */
+/*
+ * Mobile VRM (K7)
+ */
+static int k7pnow_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,
+};
-/* Prototypes */
-struct state_s *k7_powernow_getstates(uint32_t);
+/*
+ * Desktop VRM (K7)
+ */
-struct state_s *
-k7_powernow_getstates(uint32_t signature)
-{
- unsigned int i, j;
- struct psb_s *psb;
- struct pst_s *pst;
- char *ptr;
- bus_space_handle_t bh;
+static int k7pnow_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,
+};
- /*
- * Look in the 0xe0000 - 0x100000 physical address
- * range for the pst tables; 16 byte blocks
- */
- if (bus_space_map(I386_BUS_SPACE_MEM, BIOS_START, BIOS_LEN, 0, &bh)) {
- printf("k7_powernow: couldn't map BIOS\n");
- return NULL;
- }
- ptr = malloc(BIOS_LEN, M_DEVBUF, M_NOWAIT);
- memcpy(ptr, (void *)bh, BIOS_LEN);
- bus_space_unmap(I386_BUS_SPACE_MEM, bh, BIOS_LEN);
+#define POWERNOW_MAX_STATES 16
- for (i = 0; i < BIOS_LEN; i += 16, ptr += 16) {
- if (memcmp(ptr, "AMDK7PNOW!", 10) == 0) {
- psb = (struct psb_s *) ptr;
- ptr += sizeof(struct psb_s);
+struct k7pnow_state {
+ int freq;
+ int fid;
+ int vid;
+};
- k7pnow_ttime = psb->ttime;
+struct k7pnow_cpu_state {
+ unsigned int fsb;
+ unsigned int sgtc;
+ struct k7pnow_state state_table[POWERNOW_MAX_STATES];
+ unsigned int n_states;
+ unsigned int max_states;
+ int errata_a0;
+ int *vid_to_volts;
+};
- /* Only this version is supported */
- if (psb->version != 0x12)
- return 0;
+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;
+};
- /* Find the right PST */
- for (j = 0; j < psb->n_pst; j++) {
- pst = (struct pst_s *) ptr;
- ptr += sizeof(struct pst_s);
-
- /* Use the first PST with matching CPUID */
- if (signature == pst->signature) {
- /*
- * XXX I need more info on this.
- * For now, let's just ignore it
- */
- if ((signature & 0xFF) == 0x60)
- return 0;
+struct pst_s {
+ uint32_t signature;
+ uint8_t fsb; /* Front Side Bus frequency (Mhz) */
+ uint8_t fid; /* Max Frequency code */
+ uint8_t vid; /* Max Voltage code */
+ uint8_t n_states; /* Number of states */
+};
- k7pnow_fsb = pst->fsb;
- k7pnow_nstates = pst->n_states;
- return (struct state_s *)ptr;
- } else
- ptr += sizeof(struct state_s) *
- pst->n_states;
- }
- /* printf("No match was found for your CPUID\n"); */
- return 0;
- }
- }
- /* printf("Power state table not found\n"); */
- return 0;
-}
+struct k7pnow_cpu_state * k7pnow_current_state[I386_MAXPROCS];
+
+/*
+ * Prototypes
+ */
+int k7pnow_decode_pst(struct k7pnow_cpu_state *, uint8_t *, int);
+int k7pnow_states(struct k7pnow_cpu_state *, uint32_t, unsigned int, unsigned int);
int
k7_powernow_setperf(int level)
{
- unsigned int low, high, freq, i;
- uint32_t sgtc, vid = 0, fid = 0;
- uint64_t ctl;
-
- high = k7pnow_freq_table[k7pnow_nstates - 1].frequency;
- low = k7pnow_freq_table[0].frequency;
+ unsigned int i, low, high, freq;
+ int cvid, cfid, vid = 0, fid = 0;
+ uint64_t status, ctl;
+ struct k7pnow_cpu_state * cstate;
+
+ cstate = k7pnow_current_state[cpu_number()];
+ high = cstate->state_table[cstate->n_states - 1].freq;
+ low = cstate->state_table[0].freq;
freq = low + (high - low) * level / 100;
- for (i = 0; i < k7pnow_nstates; i++) {
- /* Do we know how to set that frequency? */
- if (k7pnow_freq_table[i].frequency >= freq) {
- fid = k7pnow_freq_table[i].state->fid;
- vid = k7pnow_freq_table[i].state->vid;
+ for (i = 0; i < cstate->n_states; i++) {
+ if (cstate->state_table[i].freq >= freq) {
+ fid = cstate->state_table[i].fid;
+ vid = cstate->state_table[i].vid;
break;
}
}
if (fid == 0 || vid == 0)
- return (-1);
+ return (0);
- /* Get CTL and only modify fid/vid/sgtc */
- ctl = rdmsr(MSR_K7_CTL);
+ status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
+ cfid = PN7_STA_CFID(status);
+ cvid = PN7_STA_CVID(status);
- /* FID */
- ctl &= 0xFFFFFFFFFFFFFF00ULL;
- ctl |= fid;
+ /*
+ * We're already at the requested level.
+ */
+ if (fid == cfid && vid == cvid)
+ return (0);
+
+ ctl = rdmsr(MSR_AMDK7_FIDVID_CTL) & PN7_CTR_FIDCHRATIO;
- /* VID */
- ctl &= 0xFFFFFFFFFFFF00FFULL;
- ctl |= vid << 8;
+ ctl |= PN7_CTR_FID(fid);
+ ctl |= PN7_CTR_VID(vid);
+ ctl |= PN7_CTR_SGTC(cstate->sgtc);
- /* SGTC */
- if ((sgtc = k7pnow_ttime * 100) < 10000) sgtc = 10000;
- ctl &= 0xFFF00000FFFFFFFFULL;
- ctl |= (uint64_t)sgtc << 32;
+ if (cstate->errata_a0)
+ disable_intr();
- if (k7pnow_cur_freq > freq) {
- wrmsr(MSR_K7_CTL, ctl | CTL_SET_FID);
- wrmsr(MSR_K7_CTL, ctl | CTL_SET_VID);
+ if (k7pnow_fid_to_mult[fid] < k7pnow_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_K7_CTL, ctl | CTL_SET_VID);
- wrmsr(MSR_K7_CTL, ctl | CTL_SET_FID);
+ wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_VIDC);
+ if (fid != cfid)
+ wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_FIDC);
}
- ctl = rdmsr(MSR_K7_CTL);
- return (0);
+
+ if (cstate->errata_a0);
+ enable_intr();
+
+ calibrate_cyclecounter();
+
+ return 1;
}
-void
-k7_powernow_init(uint32_t signature)
+/*
+ * Given a set of pair of fid/vid, and number of performance states,
+ * compute state_table via an insertion sort.
+ */
+int
+k7pnow_decode_pst(struct k7pnow_cpu_state * cstate, uint8_t *p, int npst)
{
- unsigned int i;
- struct state_s *s;
+ int i, j, n;
+ struct k7pnow_state state;
+
+ for (i = 0; i < POWERNOW_MAX_STATES; ++i)
+ cstate->state_table[i].freq = -1;
+
+ for (n = 0, i = 0; i < npst; ++i) {
+ state.fid = *p++;
+ state.vid = *p++;
+ state.freq = 100 * k7pnow_fid_to_mult[state.fid] * cstate->fsb;
+ if (cstate->errata_a0 &&
+ (k7pnow_fid_to_mult[state.fid] % 10) == 5)
+ continue;
+
+ 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 k7pnow_state));
+ --j;
+ }
+ memcpy(&cstate->state_table[j], &state,
+ sizeof(struct k7pnow_state));
+ ++n;
+ }
+ /*
+ * Fix powernow_max_states, if errata_a0 give us less states
+ * than expected.
+ */
+ cstate->max_states = n;
+ return 1;
+}
- s = k7_powernow_getstates(signature);
- if (s == 0)
- return;
+int
+k7pnow_states(struct k7pnow_cpu_state *cstate, uint32_t cpusig,
+ unsigned int fid, unsigned int vid)
+{
+ int maxpst;
+ struct psb_s *psb;
+ struct pst_s *pst;
+ uint8_t *p;
- k7pnow_freq_table = malloc(sizeof(struct k7pnow_freq_table_s) *
- k7pnow_nstates, M_TEMP, M_WAITOK);
+ /*
+ * Look in the 0xe0000 - 0x100000 physical address
+ * range for the pst tables; 16 byte blocks
+ */
+ for (p = (u_int8_t *)ISA_HOLE_VADDR(BIOS_START);
+ p < (u_int8_t *)ISA_HOLE_VADDR(BIOS_START + BIOS_LEN); p+= 16) {
+ if (memcmp(p, "AMDK7PNOW!", 10) == 0) {
+ psb = (struct psb_s *)p;
+ if (psb->version != 0x12)
+ return 0;
- for (i = 0; i < k7pnow_nstates; i++, s++) {
- k7pnow_freq_table[i].frequency = cpufreq(s->fid);
- k7pnow_freq_table[i].state = s;
+ cstate->sgtc = psb->ttime * cstate->fsb;
+ if (cstate->sgtc < 100 * cstate->fsb)
+ cstate->sgtc = 100 * cstate->fsb;
+
+ p += sizeof(struct psb_s);
+
+ for (maxpst = 0; maxpst < 200; maxpst++) {
+ pst = (struct pst_s*) p;
+
+ if (cpusig == pst->signature && fid == pst->fid
+ && vid == pst->vid) {
+ switch(pst->signature) {
+ case 0x760:
+ case 0x761:
+ case 0x762:
+ case 0x770:
+ case 0x771:
+ case 0x780:
+ case 0x781:
+ case 0x7a0:
+ break;
+ default:
+ return 0;
+ }
+
+ if(abs(cstate->fsb - pst->fsb) > 5)
+ continue;
+ cstate->max_states = pst->n_states;
+ return (k7pnow_decode_pst(cstate,
+ p + sizeof(struct pst_s),
+ cstate->max_states));
+ }
+ p += sizeof(struct pst_s) + (2 * pst->n_states);
+ }
+ }
}
- /* On bootup the frequency should be at it's max */
- k7pnow_cur_freq = k7pnow_freq_table[i-1].frequency;
+ return 0;
+}
- printf("cpu0: AMD POWERNOW: %d available states\n", k7pnow_nstates);
- cpu_setperf = k7_powernow_setperf;
+void
+k7_powernow_init(void)
+{
+ u_int regs[4];
+ uint64_t status, rate;
+ u_int maxfid, startvid, currentfid;
+ struct k7pnow_cpu_state *cstate;
+ struct cpu_info *ci;
+ char *techname = NULL;
+ ci = curcpu();
+
+ cstate = malloc(sizeof(struct k7pnow_cpu_state), M_TEMP, M_WAITOK);
+
+ cpuid(0x80000001, regs);
+ if ((regs[0] & 0xfff) == 0x760)
+ cstate->errata_a0 = TRUE;
+ else
+ cstate->errata_a0 = FALSE;
+
+ rate = pentium_mhz;
+ status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
+ maxfid = PN7_STA_MFID(status);
+ startvid = PN7_STA_SVID(status);
+ currentfid = PN7_STA_CFID(status);
+
+ cstate->fsb = rate / 100000 / k7pnow_fid_to_mult[currentfid];
+ /*
+ * If start FID is different to max FID, then it is a
+ * mobile processor. If not, it is a low powered desktop
+ * processor.
+ */
+ if (maxfid != currentfid) {
+ cstate->vid_to_volts = k7pnow_mobile_vid_to_volts;
+ techname = "PowerNow! K7";
+ } else {
+ cstate->vid_to_volts = k7pnow_desktop_vid_to_volts;
+ techname = "Cool`n'Quiet K7";
+ }
+ if (k7pnow_states(cstate, ci->ci_signature, maxfid, startvid)) {
+ printf("%s: AMD %s: %d available states\n", ci->ci_dev.dv_xname,
+ techname, cstate->n_states);
+ k7pnow_current_state[cpu_number()] = cstate;
+ cpu_setperf = k7_powernow_setperf;
+ }
}
diff --git a/sys/arch/i386/i386/powernow-k8.c b/sys/arch/i386/i386/powernow-k8.c
new file mode 100644
index 00000000000..8724f5d593d
--- /dev/null
+++ b/sys/arch/i386/i386/powernow-k8.c
@@ -0,0 +1,431 @@
+/* $OpenBSD: powernow-k8.c,v 1.1 2005/10/28 07:03:41 tedu Exp $ */
+/*
+ * Copyright (c) 2004 Martin Végiard.
+ * All rights reserved.
+ *
+ * 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.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * 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.
+ */
+/* AMD POWERNOW K8 driver */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/sysctl.h>
+
+#include <dev/isa/isareg.h>
+#include <i386/isa/isa_machdep.h>
+
+#include <machine/cpu.h>
+#include <machine/cpufunc.h>
+#include <machine/bus.h>
+
+#define BIOS_START 0xe0000
+#define BIOS_LEN 0x20000
+
+/*
+ * MSRs and bits used by Powernow technology
+ */
+#define MSR_AMDK7_FIDVID_CTL 0xc0010041
+#define MSR_AMDK7_FIDVID_STATUS 0xc0010042
+
+/* 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.
+ */
+
+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.
+ */
+
+/* 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 k8pnow_state {
+ int freq;
+ int fid;
+ int vid;
+};
+
+struct k8pnow_cpu_state {
+ struct k8pnow_state state_table[POWERNOW_MAX_STATES];
+ unsigned int n_states;
+ unsigned int fsb;
+ unsigned int sgtc;
+ unsigned int vst;
+ unsigned int mvs;
+ unsigned int pll;
+ unsigned int rvo;
+ unsigned int irt;
+ int low;
+ int *vid_to_volts;
+};
+
+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 signature;
+ uint8_t fsb; /* Front Side Bus frequency (Mhz) */
+ uint8_t fid; /* Max Frequency code */
+ uint8_t vid; /* Max Voltage code */
+ uint8_t n_states; /* Number of states */
+};
+
+struct k8pnow_cpu_state *k8pnow_current_state[I386_MAXPROCS];
+
+/*
+ * Prototypes
+ */
+int k8pnow_read_pending_wait(uint64_t *);
+int k8pnow_decode_pst(struct k8pnow_cpu_state *, uint8_t *, int);
+int k8pnow_states(struct k8pnow_cpu_state *, uint32_t, unsigned int,
+ unsigned int);
+
+
+int k8pnow_read_pending_wait(uint64_t * status) {
+ unsigned int i = 0;
+ while(PN8_STA_PENDING(*status)) {
+ i++;
+ if (i > 0x1000000) {
+ printf("k8pnow_read_pending_wait: change pending stuck"
+ ".\n");
+ return 1;
+ }
+ *status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
+ }
+ return 0;
+}
+
+int
+k8_powernow_setperf(int level)
+{
+ unsigned int i, low, high, freq;
+ uint64_t status;
+ int cfid, cvid, fid = 0, vid = 0;
+ int rvo;
+ u_int val;
+ struct k8pnow_cpu_state *cstate;
+
+ /*
+ * 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 1;
+ cfid = PN8_STA_CFID(status);
+ cvid = PN8_STA_CVID(status);
+
+ cstate = k8pnow_current_state[cpu_number()];
+ high = cstate->state_table[cstate->n_states - 1].freq;
+ low = cstate->state_table[0].freq;
+ freq = low + (high - low) * level / 100;
+
+ for (i = 0; i < cstate->n_states; i++) {
+ if (cstate->state_table[i].freq >= freq) {
+ fid = cstate->state_table[i].fid;
+ vid = cstate->state_table[i].vid;
+ break;
+ }
+ }
+
+ 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 << cstate->mvs);
+ WRITE_FIDVID(cfid, (val > 0) ? val : 0, 1ULL);
+ if (k8pnow_read_pending_wait(&status))
+ return 1;
+ 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 1;
+ 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, cstate->pll *
+ (uint64_t)cstate->fsb);
+
+ if (k8pnow_read_pending_wait(&status))
+ return 1;
+ cfid = PN8_STA_CFID(status);
+ COUNT_OFF_IRT(cstate->irt);
+
+ vco_cfid = FID_TO_VCO_FID(cfid);
+ }
+
+ WRITE_FIDVID(fid, cvid, cstate->pll * (uint64_t)cstate->fsb);
+ if (k8pnow_read_pending_wait(&status))
+ return 1;
+ 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 1;
+ cvid = PN8_STA_CVID(status);
+ COUNT_OFF_VST(cstate->vst);
+ }
+
+ /* Check if transition failed. */
+ if (cfid != fid || cvid != vid)
+ return (1);
+
+ calibrate_cyclecounter();
+ return (0);
+}
+
+/*
+ * 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 npst)
+{
+ int i, j, n;
+ struct k8pnow_state state;
+
+ for (i = 0; i < POWERNOW_MAX_STATES; ++i)
+ cstate->state_table[i].freq = -1;
+
+ for (n = 0, i = 0; i < npst; ++i) {
+ state.fid = *p++;
+ state.vid = *p++;
+
+ state.freq = 100 * pn8_fid_to_mult[state.fid >> 1] *
+ cstate->fsb;
+ 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)
+{
+ int maxpst;
+ struct psb_s *psb;
+ struct pst_s *pst;
+ uint8_t *p;
+
+ for (p = (u_int8_t *)ISA_HOLE_VADDR(BIOS_START);
+ p < (u_int8_t *)ISA_HOLE_VADDR(BIOS_START + BIOS_LEN); p += 16) {
+ if (memcmp(p, "AMDK7PNOW!", 10) == 0) {
+ psb = (struct psb_s *)p;
+ if (psb->version != 0x14)
+ 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);
+ /*
+ * XXX: FreeBSD completely ignores psb->n_pst
+ * XXX: n_pst might be 2 its not supposed to be
+ * XXX: but why 200?
+ */
+ for (maxpst = 0; maxpst < 200; maxpst++) {
+ pst = (struct pst_s*) p;
+ if (cpusig == pst->signature && fid == pst->fid
+ && vid == pst->vid) {
+ cstate->n_states = pst->n_states;
+ return (k8pnow_decode_pst(cstate,
+ p + sizeof(struct pst_s),
+ cstate->n_states));
+ }
+ p += sizeof(struct pst_s) + (2 * pst->n_states);
+ }
+ }
+ }
+
+ return 0;
+
+}
+
+void
+k8_powernow_init(void)
+{
+ uint64_t status, rate;
+ u_int maxfid, maxvid, currentfid;
+ struct k8pnow_cpu_state *cstate;
+ struct cpu_info * ci;
+ char * techname = NULL;
+ ci = curcpu();
+
+ cstate = malloc(sizeof(struct k8pnow_cpu_state), M_TEMP, M_WAITOK);
+
+ rate = pentium_mhz;
+ status = rdmsr(MSR_AMDK7_FIDVID_STATUS);
+ maxfid = PN8_STA_MFID(status);
+ maxvid = PN8_STA_MVID(status);
+ currentfid = PN8_STA_CFID(status);
+
+ cstate->fsb = rate / 100000 / pn8_fid_to_mult[currentfid >> 1];
+ cstate->vid_to_volts = pn8_vid_to_volts;
+ /*
+ * 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 (k8pnow_states(cstate, ci->ci_signature, maxfid, maxvid)) {
+ printf("%s: AMD %s: %d available states\n", ci->ci_dev.dv_xname,
+ techname, cstate->n_states);
+ if (cstate->n_states) {
+ k8pnow_current_state[cpu_number()] = cstate;
+ cpu_setperf = k8_powernow_setperf;
+ }
+ }
+}
diff --git a/sys/arch/i386/include/cpu.h b/sys/arch/i386/include/cpu.h
index 6b963f4dbeb..46080abc651 100644
--- a/sys/arch/i386/include/cpu.h
+++ b/sys/arch/i386/include/cpu.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: cpu.h,v 1.71 2005/09/25 20:48:23 miod Exp $ */
+/* $OpenBSD: cpu.h,v 1.72 2005/10/28 07:03:41 tedu Exp $ */
/* $NetBSD: cpu.h,v 1.35 1996/05/05 19:29:26 christos Exp $ */
/*-
@@ -369,11 +369,17 @@ void p4tcc_init(int, int);
int p4tcc_setperf(int);
#endif
+#if !defined(SMALL_KERNEL) && defined(I686_CPU)
+/* powernow.c */
void k6_powernow_init(void);
int k6_powernow_setperf(int);
-void k7_powernow_init(uint32_t);
+/* powernow-k7.c */
+void k7_powernow_init(void);
int k7_powernow_setperf(int);
-
+/* powernow-k8.c */
+void k8_powernow_init(void);
+int k8_powernow_setperf(int);
+#endif
/* npx.c */
void npxdrop(struct proc *);