NetBSD-5.0.2/sys/arch/x86/x86/powernow_k8.c

Compare this file to the similar file:
Show the results in this format:

/*	$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
}