/* NVClock 0.8 - Linux overclocker for NVIDIA cards
*
* site: http://nvclock.sourceforge.net
*
* Copyright(C) 2001-2005 Roderick Colenbrander
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
/* This source file uses some clock calculation code from nvidia's xfree86 driver.
To keep Nvidia happy I have added their copyright. The way they interpret it (see linux kernel riva_hw.h)
is that you need to add the disclaimer and copyright and when that's done
you can basicly do what you want.
*/
/***************************************************************************\
|* *|
|* Copyright 1993-2003 NVIDIA, Corporation. All rights reserved. *|
|* *|
|* NOTICE TO USER: The source code is copyrighted under U.S. and *|
|* international laws. Users and possessors of this source code are *|
|* hereby granted a nonexclusive, royalty-free copyright license to *|
|* use this code in individual and commercial software. *|
|* *|
|* Any use of this source code must include, in the user documenta- *|
|* tion and internal comments to the code, notices to the end user *|
|* as follows: *|
|* *|
|* Copyright 1993-2003 NVIDIA, Corporation. All rights reserved. *|
|* *|
|* NVIDIA, CORPORATION MAKES NO REPRESENTATION ABOUT THE SUITABILITY *|
|* OF THIS SOURCE CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" *|
|* WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. NVIDIA, CORPOR- *|
|* ATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOURCE CODE, *|
|* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGE- *|
|* MENT, AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL *|
|* NVIDIA, CORPORATION BE LIABLE FOR ANY SPECIAL, INDIRECT, INCI- *|
|* DENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RE- *|
|* SULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION *|
|* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *|
|* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOURCE CODE. *|
|* *|
|* U.S. Government End Users. This source code is a "commercial *|
|* item," as that term is defined at 48 C.F.R. 2.101 (OCT 1995), *|
|* consisting of "commercial computer software" and "commercial *|
|* computer software documentation," as such terms are used in *|
|* 48 C.F.R. 12.212 (SEPT 1995) and is provided to the U.S. Govern- *|
|* ment only as a commercial end item. Consistent with 48 C.F.R. *|
|* 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (JUNE 1995), *|
|* all U.S. Government End Users acquire the source code with only *|
|* those rights set forth herein. *|
|* *|
\***************************************************************************/
#include <stdio.h>
#include "nvclock.h"
#include "backend.h"
static int CalcSpeed_nv31(int base_freq, int m1, int m2, int n1, int n2, int p)
{
return (int)((float)(n1*n2)/(m1*m2) * base_freq) >> p;
}
float GetClock_nv31(int base_freq, unsigned int pll, unsigned int pll2)
{
/* Geforce FX5600 cards and the FX5700?? (0x340) use a different algorithm */
int m1, m2, n1, n2, p;
m1 = pll & 0xff;
n1 = (pll >> 8) & 0xff;
p = (pll >> 16) & 0x0f;
/* For some reasons a speed consists of two plls */
if(pll2 & 0x80000000)
{
m2 = pll2 & 0xff;
n2 = (pll2 >> 8) & 0xff;
}
else
{
m2 = 1;
n2 = 1;
}
if(nv_card->debug)
printf("m1=%d m2=%d n1=%d n2=%d p=%d\n", m1, m2, n1, n2, p);
return (float)CalcSpeed_nv31(base_freq, m1, m2, n1, n2, p)/1000;
}
static void ClockSelect_nv31(int clockIn, int p_current, unsigned int *pllOut, unsigned int *pllBOut)
{
unsigned diff, diffOld;
unsigned VClk, Freq;
unsigned m, m2, n, n2, p = 0;
int base_freq = 27000;
unsigned plow = 0;
diffOld = 0xFFFFFFFF;
plow = p_current;
VClk = (unsigned)clockIn;
for (p = plow; p <= 1; p++)
{
Freq = VClk;
if ((Freq >= 100000) && (Freq <= 1100000))
{
for (m2 = 1; m2 <= 13; m2++)
{
for (m = 1; m <= 13; m++)
{
for(n2 = 2; n2 <= 10; n2++)
{
n = (int)((float)((VClk << p) * m * m2) / (float)(base_freq * n2)+.5);
/* For now this line seems to create "good" speeds, it makes sure that n/m isn't much bigger than n2/m2 */
if(((n/m >= 1) && (n/m <= 8)) && ((n2/m2 >= 1) && (n2/m2 <= 8)))
{
Freq = ((base_freq * n * n2) / (m * m2)) >> p;
if (Freq > VClk)
diff = Freq - VClk;
else
diff = VClk - Freq;
/* When the difference is 0 or less than .5% accept the speed */
if(((diff == 0) || ((float)diff/(float)clockIn <= 0.005)))
{
*pllOut = (p << 16) | (n << 8) | m;
*pllBOut = (1<<31) | (n2 << 8) | m2;
return;
}
if (diff < diffOld)
{
*pllOut = (p << 16) | (n << 8) | m;
*pllBOut = (1<<31) | (n2 << 8) | m2;
diffOld = diff;
}
}
}
}
}
}
}
}
static float nv31_get_gpu_speed()
{
int pll = nv_card->PRAMDAC[0x500/4];
int pll2 = nv_card->PRAMDAC[0x570/4];
if(nv_card->debug == 1)
{
printf("NVPLL_COEFF=%08x\n", pll);
printf("NVPLL_COEFF2=%08x\n", pll2);
}
return (float)GetClock_nv31(nv_card->base_freq, pll, pll2);
}
static void nv31_set_gpu_speed(unsigned int nvclk)
{
unsigned int PLL=0, PLL2=0;
int p = (nv_card->PRAMDAC[0x504/4] >> 16) & 0x0f;
nvclk *= 1000;
/*
Pass the current postdivider value to the clock calculation;
Changing the postdivider can lead to stability problems.
*/
ClockSelect_nv31(nvclk, p, &PLL, &PLL2);
/* Unlock the programmable NVPLL/MPLL */
nv_card->PRAMDAC[0x50c/4] |= 0x500;
/* When no speed is found, don't change the PLL */
/* The algorithm doesn't allow too low speeds */
if(PLL)
{
if(nv_card->debug)
{
printf("NVPLL_COEFF: %08x\n", PLL);
printf("NVPLL2_COEFF: %08x\n", PLL2);
}
nv_card->PRAMDAC[0x500/4] = PLL;
nv_card->PRAMDAC[0x570/4] = PLL2;
}
}
static float nv31_get_memory_speed()
{
int pll = nv_card->PRAMDAC[0x504/4];
int pll2 = nv_card->PRAMDAC[0x574/4];
if(nv_card->debug == 1)
{
printf("MPLL_COEFF=%08x\n", pll);
printf("MPLL_COEFF2=%08x\n", pll2);
}
return (float)GetClock_nv31(nv_card->base_freq, pll, pll2);
}
static void nv31_set_memory_speed(unsigned int memclk)
{
unsigned int PLL=0, PLL2=0;
int p = (nv_card->PRAMDAC[0x504/4] >> 16) & 0x0f;
memclk *= 1000;
ClockSelect_nv31(memclk, p, &PLL, &PLL2);
/* Unlock the programmable NVPLL/MPLL */
nv_card->PRAMDAC[0x50c/4] |= 0x500;
/* When no speed is found, don't change the PLL */
/* The algorithm doesn't allow too low speeds */
if(PLL)
{
if(nv_card->debug)
{
printf("MPLL_COEFF: %08x\n", PLL);
printf("MPLL2_COEFF: %08x\n", PLL2);
}
nv_card->PRAMDAC[0x504/4] = PLL;
nv_card->PRAMDAC[0x574/4] = PLL2;
}
}
static void nv31_reset_gpu_speed()
{
/* FIXME: we need to use our bios info */
/* Unlock the programmable NVPLL/MPLL */
nv_card->PRAMDAC[0x50c/4] |= 0x500;
/* Set the gpu speed */
nv_card->PRAMDAC[0x500/4] = nv_card->nvpll;
nv_card->PRAMDAC[0x570/4] = nv_card->nvpll2;
}
static void nv31_reset_memory_speed()
{
/* FIXME: we need to use our bios info */
/* Unlock the programmable NVPLL/MPLL */
nv_card->PRAMDAC[0x50c/4] |= 0x500;
/* Set the memory speed */
nv_card->PRAMDAC[0x504/4] = nv_card->mpll;
nv_card->PRAMDAC[0x574/4] = nv_card->mpll2;
}
static void nv31_set_state(int state)
{
nv_card->state = state;
#ifdef HAVE_NVCONTROL
if(state & (STATE_2D | STATE_3D))
{
nv_card->get_gpu_speed = nvcontrol_get_gpu_speed;
nv_card->get_memory_speed = nvcontrol_get_memory_speed;
nv_card->set_gpu_speed = nvcontrol_set_gpu_speed;
nv_card->set_memory_speed = nvcontrol_set_memory_speed;
nv_card->reset_gpu_speed = nvcontrol_reset_gpu_speed;
nv_card->reset_memory_speed = nvcontrol_reset_memory_speed;
}
else
#endif
{
nv_card->get_gpu_speed = nv31_get_gpu_speed;
nv_card->get_memory_speed = nv31_get_memory_speed;
nv_card->set_memory_speed = nv31_set_memory_speed;
nv_card->set_gpu_speed = nv31_set_gpu_speed;
nv_card->reset_gpu_speed = nv31_reset_gpu_speed;
nv_card->reset_memory_speed = nv31_reset_memory_speed;
}
}
void nv31_init(void)
{
nv_card->base_freq = 27000;
nv_card->set_state = nv31_set_state;
nv_card->set_state(nv_card->state); /* Set the clock function pointers */
nv_card->get_gpu_speed = nv31_get_gpu_speed;
nv_card->get_memory_speed = nv31_get_memory_speed;
nv_card->set_gpu_speed = nv31_set_gpu_speed;
nv_card->set_memory_speed = nv31_set_memory_speed;
nv_card->reset_gpu_speed = nv31_reset_gpu_speed;
nv_card->reset_memory_speed = nv31_reset_memory_speed;
/* Mobile GPU check; we don't want to overclock those unless the user wants it */
if(nv_card->gpu == MOBILE)
{
nv_card->caps = ~(~nv_card->caps | GPU_OVERCLOCKING | MEM_OVERCLOCKING);
}
else
nv_card->caps |= (GPU_OVERCLOCKING | MEM_OVERCLOCKING);
/* Set the speed range */
if(nv_card->bios)
{
/* GeforceFX models have different clocks in 2d and 3d; Useally the second entry is for 3D
/ but in case of the FX5600Ultra this is the second one.
*/
if(nv_card->device_id == 0x311)
{
nv_card->memclk_3d = (short)nv_card->bios->perf_lst[1].memclk;
nv_card->nvclk_3d = (short)nv_card->bios->perf_lst[1].nvclk;
}
else
{
nv_card->memclk_3d = (short)nv_card->bios->perf_lst[2].memclk;
nv_card->nvclk_3d = (short)nv_card->bios->perf_lst[2].nvclk;
}
nv_card->memclk_min = (short)(nv_card->bios->perf_lst[0].memclk * .75);
nv_card->memclk_max = nv_card->memclk_3d * 1.25;
nv_card->nvclk_min = (short)(nv_card->bios->perf_lst[0].nvclk * .75);
nv_card->nvclk_max = nv_card->nvclk_3d * 1.25;
}
else
{
float memclk = GetClock_nv31(nv_card->base_freq, nv_card->mpll, nv_card->mpll2);
float nvclk = GetClock_nv31(nv_card->base_freq, nv_card->nvpll, nv_card->nvpll2);
/* Not great but better than nothing .. */
nv_card->memclk_min = (short)(memclk * .75);
nv_card->memclk_max = (short)(memclk * 1.5);
nv_card->nvclk_min = (short)(nvclk * .75);
nv_card->nvclk_max = (short)(nvclk * 1.5);
}
}
syntax highlighted by Code2HTML, v. 0.9.1