NetBSD-5.0.2/sys/arch/x86/x86/powernow_k8.c
/* $NetBSD: powernow_k8.c,v 1.23 2008/05/11 23:05:45 cegger Exp $ */
/* $OpenBSD: powernow-k8.c,v 1.8 2006/06/16 05:58:50 gwk Exp $ */
/*-
* Copyright (c) 2004, 2006 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Juan Romero Pardines and Martin Vegiard.
*
* 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 FOUNDATION OR CONTRIBUTORS
* 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>
* Copyright (c) 2004 Martin Vegiard
*
* 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/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: powernow_k8.c,v 1.23 2008/05/11 23:05:45 cegger Exp $");
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/once.h>
#include <x86/cpu_msr.h>
#include <x86/powernow.h>
#include <dev/isa/isareg.h>
#include <machine/isa_machdep.h>
#include <machine/cpu.h>
#include <machine/cpufunc.h>
#include <machine/bus.h>
#define WRITE_FIDVID(fid, vid, ctrl) \
mcb.msr_read = false; \
mcb.msr_value = (((ctrl) << 32) | (1ULL << 16) | ((vid) << 8) | (fid)); \
mcb.msr_type = MSR_AMDK7_FIDVID_CTL; \
msr_cpu_broadcast(&mcb);
#ifdef _LKM
static struct sysctllog *sysctllog;
#define SYSCTLLOG &sysctllog
#else
#define SYSCTLLOG NULL
#endif
#define READ_PENDING_WAIT(status) \
do { \
(status) = rdmsr(MSR_AMDK7_FIDVID_STATUS); \
} while (PN8_STA_PENDING(status))
static struct powernow_cpu_state *k8pnow_current_state;
static unsigned int cur_freq;
static int powernow_node_target, powernow_node_current;
static char *freq_names;
static size_t freq_names_len;
static int k8pnow_sysctl_helper(SYSCTLFN_PROTO);
static int k8pnow_decode_pst(struct powernow_cpu_state *, uint8_t *);
static int k8pnow_states(struct powernow_cpu_state *, uint32_t, unsigned int,
unsigned int);
static int k8_powernow_setperf(unsigned int);
static int k8_powernow_init_once(void);
static void k8_powernow_init_main(void);
static int
k8pnow_sysctl_helper(SYSCTLFN_ARGS)
{
struct sysctlnode node;
int fq, oldfq, error;
node = *rnode;
node.sysctl_data = &fq;
oldfq = 0;
if (rnode->sysctl_num == powernow_node_target)
fq = oldfq = cur_freq;
else if (rnode->sysctl_num == powernow_node_current)
fq = cur_freq;
else
return EOPNOTSUPP;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
/* support writing to ...frequency.target */
if (rnode->sysctl_num == powernow_node_target && fq != oldfq) {
if (k8_powernow_setperf(fq) == 0)
cur_freq = fq;
else
return EINVAL;
}
return 0;
}
static int
k8_powernow_setperf(unsigned int freq)
{
unsigned int i;
uint64_t status;
uint32_t val;
int cfid, cvid, fid = 0, vid = 0;
int rvo;
struct powernow_cpu_state *cstate;
struct msr_cpu_broadcast mcb;
/*
* 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;
DPRINTF(("%s: cstate->n_states=%d\n", __func__, cstate->n_states));
for (i = 0; i < cstate->n_states; i++) {
if (cstate->state_table[i].freq >= freq) {
DPRINTF(("%s: freq=%d\n", __func__, freq));
fid = cstate->state_table[i].fid;
vid = cstate->state_table[i].vid;
DPRINTF(("%s: fid=%d vid=%d\n", __func__, fid, 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);
READ_PENDING_WAIT(status);
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);
READ_PENDING_WAIT(status);
cvid = PN8_STA_CVID(status);
COUNT_OFF_VST(cstate->vst);
}
/* Phase 2: change to requested core frequency */
if (cfid != fid) {
uint32_t 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);
READ_PENDING_WAIT(status);
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);
READ_PENDING_WAIT(status);
cfid = PN8_STA_CFID(status);
COUNT_OFF_IRT(cstate->irt);
}
/* Phase 3: change to requested voltage */
if (cvid != vid) {
WRITE_FIDVID(cfid, vid, 1ULL);
READ_PENDING_WAIT(status);
cvid = PN8_STA_CVID(status);
COUNT_OFF_VST(cstate->vst);
}
if (cfid == fid || cvid == vid)
freq = cstate->state_table[i].freq;
return 0;
}
/*
* Given a set of pair of fid/vid, and number of performance states,
* compute state_table via an insertion sort.
*/
static int
k8pnow_decode_pst(struct powernow_cpu_state *cstate, uint8_t *p)
{
int i, j, n;
struct powernow_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 powernow_state));
--j;
}
memcpy(&cstate->state_table[j], &state,
sizeof(struct powernow_state));
n++;
}
return 1;
}
static int
k8pnow_states(struct powernow_cpu_state *cstate, uint32_t cpusig,
unsigned int fid, unsigned int vid)
{
struct powernow_psb_s *psb;
struct powernow_pst_s *pst;
uint8_t *p;
int i;
DPRINTF(("%s: before the for loop\n", __func__));
for (p = (uint8_t *)ISA_HOLE_VADDR(BIOS_START);
p < (uint8_t *)ISA_HOLE_VADDR(BIOS_START + BIOS_LEN); p += 16) {
if (memcmp(p, "AMDK7PNOW!", 10) != 0)
continue;
DPRINTF(("%s: inside the for loop\n", __func__));
psb = (struct powernow_psb_s *)p;
if (psb->version != 0x14) {
DPRINTF(("%s: psb->version != 0x14\n",
__func__));
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 powernow_psb_s);
for(i = 0; i < psb->n_pst; ++i) {
pst = (struct powernow_pst_s *) p;
cstate->pll = pst->pll;
cstate->n_states = pst->n_states;
if (cpusig == pst->signature &&
pst->fid == fid && pst->vid == vid) {
DPRINTF(("%s: cpusig = signature\n",
__func__));
return (k8pnow_decode_pst(cstate,
p+= sizeof(struct powernow_pst_s)));
}
p += sizeof(struct powernow_pst_s) +
2 * cstate->n_states;
}
}
DPRINTF(("%s: returns 0!\n", __func__));
return 0;
}
static int
k8_powernow_init_once(void)
{
k8_powernow_init_main();
return 0;
}
void
k8_powernow_init(void)
{
int error;
static ONCE_DECL(powernow_initialized);
error = RUN_ONCE(&powernow_initialized, k8_powernow_init_once);
if (__predict_false(error != 0)) {
return;
}
}
static void
k8_powernow_init_main(void)
{
uint64_t status;
uint32_t maxfid, maxvid, i;
const struct sysctlnode *freqnode, *node, *pnownode;
struct powernow_cpu_state *cstate;
const char *cpuname, *techname;
size_t len;
freq_names_len = 0;
cpuname = device_xname(curcpu()->ci_dev);
k8pnow_current_state = NULL;
cstate = malloc(sizeof(struct powernow_cpu_state), M_DEVBUF, M_NOWAIT);
if (!cstate) {
DPRINTF(("%s: cstate failed to malloc!\n", __func__));
return;
}
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!";
else
techname = "Cool`n'Quiet";
if (k8pnow_states(cstate, curcpu()->ci_signature, maxfid, maxvid)) {
freq_names_len = cstate->n_states * (sizeof("9999 ")-1) + 1;
freq_names = malloc(freq_names_len, M_SYSCTLDATA, M_WAITOK);
freq_names[0] = '\0';
len = 0;
if (cstate->n_states) {
for (i = 0; i < cstate->n_states; i++) {
DPRINTF(("%s: cstate->state_table.freq=%d\n",
__func__, cstate->state_table[i].freq));
DPRINTF(("%s: fid=%d vid=%d\n", __func__,
cstate->state_table[i].fid,
cstate->state_table[i].vid));
char tmp[6];
len += snprintf(tmp, sizeof(tmp), "%d%s",
cstate->state_table[i].freq,
i < cstate->n_states - 1 ? " " : "");
DPRINTF(("%s: tmp=%s\n", __func__, tmp));
(void)strlcat(freq_names, tmp, freq_names_len);
}
k8pnow_current_state = cstate;
DPRINTF(("%s: freq_names=%s\n", __func__, freq_names));
}
} else {
DPRINTF(("%s: returned 0!\n", __func__));
}
if (k8pnow_current_state == NULL) {
DPRINTF(("%s: k8pnow_current_state is NULL!\n", __func__));
goto err;
}
/* Create sysctl machdep.powernow.frequency. */
if (sysctl_createv(SYSCTLLOG, 0, NULL, &node,
CTLFLAG_PERMANENT,
CTLTYPE_NODE, "machdep", NULL,
NULL, 0, NULL, 0,
CTL_MACHDEP, CTL_EOL) != 0)
goto err;
if (sysctl_createv(SYSCTLLOG, 0, &node, &pnownode,
0,
CTLTYPE_NODE, "powernow", NULL,
NULL, 0, NULL, 0,
CTL_CREATE, CTL_EOL) != 0)
goto err;
if (sysctl_createv(SYSCTLLOG, 0, &pnownode, &freqnode,
0,
CTLTYPE_NODE, "frequency", NULL,
NULL, 0, NULL, 0,
CTL_CREATE, CTL_EOL) != 0)
goto err;
if (sysctl_createv(SYSCTLLOG, 0, &freqnode, &node,
CTLFLAG_READWRITE,
CTLTYPE_INT, "target", NULL,
k8pnow_sysctl_helper, 0, NULL, 0,
CTL_CREATE, CTL_EOL) != 0)
goto err;
powernow_node_target = node->sysctl_num;
if (sysctl_createv(SYSCTLLOG, 0, &freqnode, &node,
0,
CTLTYPE_INT, "current", NULL,
k8pnow_sysctl_helper, 0, NULL, 0,
CTL_CREATE, CTL_EOL) != 0)
goto err;
powernow_node_current = node->sysctl_num;
if (sysctl_createv(SYSCTLLOG, 0, &freqnode, &node,
0,
CTLTYPE_STRING, "available", NULL,
NULL, 0, freq_names, freq_names_len,
CTL_CREATE, CTL_EOL) != 0)
goto err;
cur_freq = cstate->state_table[cstate->n_states-1].freq;
aprint_normal("%s: AMD %s Technology %d MHz\n",
cpuname, techname, cur_freq);
aprint_normal("%s: available frequencies (Mhz): %s\n",
cpuname, freq_names);
return;
err:
if (cstate)
free(cstate, M_DEVBUF);
if (freq_names)
free(freq_names, M_SYSCTLDATA);
}
void
k8_powernow_destroy(void)
{
#ifdef _LKM
sysctl_teardown(SYSCTLLOG);
if (k8pnow_current_state)
free(k8pnow_current_state, M_DEVBUF);
if (freq_names)
free(freq_names, M_SYSCTLDATA);
#endif
}