diff options
author | Ted Unangst <tedu@cvs.openbsd.org> | 2005-10-28 07:03:42 +0000 |
---|---|---|
committer | Ted Unangst <tedu@cvs.openbsd.org> | 2005-10-28 07:03:42 +0000 |
commit | d42fc8ad09f099e6ef849a99eb522a0dd2f44938 (patch) | |
tree | 3278e0b05829e86c8508bf651b99a1a76c1fe243 /sys | |
parent | c07b3df802c25f129d2b3406e08ee55466b2bfd0 (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.i386 | 3 | ||||
-rw-r--r-- | sys/arch/i386/i386/machdep.c | 16 | ||||
-rw-r--r-- | sys/arch/i386/i386/powernow-k7.c | 443 | ||||
-rw-r--r-- | sys/arch/i386/i386/powernow-k8.c | 431 | ||||
-rw-r--r-- | sys/arch/i386/include/cpu.h | 12 |
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 *); |