mirror of
https://github.com/KolibriOS/kolibrios.git
synced 2024-11-28 19:53:12 +03:00
7c0a5de1e7
git-svn-id: svn://kolibrios.org@1407 a494cfbc-eb01-0410-851d-a64ba20cac60
1548 lines
39 KiB
C
1548 lines
39 KiB
C
/*
|
|
* Copyright 2007 Luc Verhaegen <lverhaegen@novell.com>
|
|
* Copyright 2007 Matthias Hopf <mhopf@novell.com>
|
|
* Copyright 2007 Egbert Eich <eich@novell.com>
|
|
* Copyright 2007 Advanced Micro Devices, Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "xf86.h"
|
|
|
|
/* for usleep */
|
|
#if HAVE_XF86_ANSIC_H
|
|
# include "xf86_ansic.h"
|
|
#else
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#include "rhd.h"
|
|
#include "rhd_crtc.h"
|
|
#include "rhd_pll.h"
|
|
#include "rhd_regs.h"
|
|
#include "rhd_atombios.h"
|
|
|
|
|
|
#define PLL_CALIBRATE_WAIT 0x100000
|
|
|
|
/*
|
|
* Get gain, charge pump, loop filter and current bias.
|
|
* For R500, this is done in atombios by ASIC_RegistersInit
|
|
* Some data table in atom should've provided this information.
|
|
*/
|
|
|
|
struct PLL_Control {
|
|
CARD16 FeedbackDivider; /* 0xFFFF/-1 is the endmarker here */
|
|
CARD32 Control;
|
|
};
|
|
|
|
/* From hardcoded values. */
|
|
static struct PLL_Control RV610PLLControl[] =
|
|
{
|
|
{ 0x0049, 0x159F8704 },
|
|
{ 0x006C, 0x159B8704 },
|
|
{ 0xFFFF, 0x159EC704 }
|
|
};
|
|
|
|
/* Some tables are provided by atombios,
|
|
* it's just that they are hidden away deliberately and not exposed */
|
|
static struct PLL_Control RV670PLLControl[] =
|
|
{
|
|
{ 0x004A, 0x159FC704 },
|
|
{ 0x0067, 0x159BC704 },
|
|
{ 0x00C4, 0x159EC704 },
|
|
{ 0x00F4, 0x1593A704 },
|
|
{ 0x0136, 0x1595A704 },
|
|
{ 0x01A4, 0x1596A704 },
|
|
{ 0x022C, 0x159CE504 },
|
|
{ 0xFFFF, 0x1591E404 }
|
|
};
|
|
|
|
/*
|
|
* Used by PLLElectrical() for r5xx+ and by rv620/35 code.
|
|
*/
|
|
static CARD32
|
|
PLLControlTableRetrieve(struct PLL_Control *Table, CARD16 FeedbackDivider)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; Table[i].FeedbackDivider < 0xFFFF ; i++)
|
|
if (Table[i].FeedbackDivider >= FeedbackDivider)
|
|
break;
|
|
|
|
return Table[i].Control;
|
|
}
|
|
|
|
/*
|
|
* Not used by rv620/35 code.
|
|
*/
|
|
static CARD32
|
|
PLLElectrical(RHDPtr rhdPtr, CARD16 FeedbackDivider)
|
|
{
|
|
switch (rhdPtr->ChipSet) {
|
|
case RHD_RV515:
|
|
if (rhdPtr->PciDeviceID == 0x7146)
|
|
return 0x00120704;
|
|
else
|
|
return 0;
|
|
case RHD_RV535:
|
|
if (rhdPtr->PciDeviceID == 0x71C1)
|
|
return 0x00230704;
|
|
else
|
|
return 0;
|
|
case RHD_RS600:
|
|
case RHD_RS690:
|
|
case RHD_RS740:
|
|
/* depending on MiscInfo also 0x00120004 */
|
|
return 0x00120704;
|
|
case RHD_R600:
|
|
return 0x01130704;
|
|
case RHD_RV610:
|
|
case RHD_RV630:
|
|
case RHD_M72:
|
|
case RHD_M74:
|
|
case RHD_M76:
|
|
return PLLControlTableRetrieve(RV610PLLControl, FeedbackDivider);
|
|
case RHD_RV670:
|
|
case RHD_R680:
|
|
return PLLControlTableRetrieve(RV670PLLControl, FeedbackDivider);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* All R500s, RS6x0, R600, RV610 and RV630.
|
|
*/
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
PLL1Calibrate(struct rhdPLL *PLL)
|
|
{
|
|
int i;
|
|
|
|
RHDFUNC(PLL);
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 1, 0x01); /* Reset */
|
|
usleep(2);
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x01); /* Set */
|
|
for (i = 0; i < PLL_CALIBRATE_WAIT; i++)
|
|
if (((RHDRegRead(PLL, P1PLL_CNTL) >> 20) & 0x03) == 0x03)
|
|
break;
|
|
|
|
if (i == PLL_CALIBRATE_WAIT) {
|
|
if (RHDRegRead(PLL, P1PLL_CNTL) & 0x00100000) /* Calibration done? */
|
|
xf86DrvMsg(PLL->scrnIndex, X_ERROR,
|
|
"%s: Calibration failed.\n", __func__);
|
|
if (RHDRegRead(PLL, P1PLL_CNTL) & 0x00200000) /* PLL locked? */
|
|
xf86DrvMsg(PLL->scrnIndex, X_ERROR,
|
|
"%s: Locking failed.\n", __func__);
|
|
} else
|
|
RHDDebug(PLL->scrnIndex, "%s: lock in %d loops\n", __func__, i);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
PLL2Calibrate(struct rhdPLL *PLL)
|
|
{
|
|
int i;
|
|
|
|
RHDFUNC(PLL);
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 1, 0x01); /* Reset */
|
|
usleep(2);
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x01); /* Set */
|
|
|
|
for (i = 0; i < PLL_CALIBRATE_WAIT; i++)
|
|
if (((RHDRegRead(PLL, P2PLL_CNTL) >> 20) & 0x03) == 0x03)
|
|
break;
|
|
|
|
if (i == PLL_CALIBRATE_WAIT) {
|
|
if (RHDRegRead(PLL, P2PLL_CNTL) & 0x00100000) /* Calibration done? */
|
|
xf86DrvMsg(PLL->scrnIndex, X_ERROR,
|
|
"%s: Calibration failed.\n", __func__);
|
|
if (RHDRegRead(PLL, P2PLL_CNTL) & 0x00200000) /* PLL locked? */
|
|
xf86DrvMsg(PLL->scrnIndex, X_ERROR,
|
|
"%s: Locking failed.\n", __func__);
|
|
} else
|
|
RHDDebug(PLL->scrnIndex, "%s: lock in %d loops\n", __func__, i);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
R500PLL1Power(struct rhdPLL *PLL, int Power)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
switch (Power) {
|
|
case RHD_POWER_ON:
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x02); /* Powah */
|
|
usleep(2);
|
|
|
|
PLL1Calibrate(PLL);
|
|
|
|
return;
|
|
case RHD_POWER_RESET:
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x01, 0x01); /* Reset */
|
|
usleep(2);
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x02); /* Powah */
|
|
usleep(2);
|
|
|
|
return;
|
|
case RHD_POWER_SHUTDOWN:
|
|
default:
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x01, 0x01); /* Reset */
|
|
usleep(2);
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x02, 0x02); /* Power down */
|
|
usleep(200);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
R500PLL2Power(struct rhdPLL *PLL, int Power)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
switch (Power) {
|
|
case RHD_POWER_ON:
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x02); /* Powah */
|
|
usleep(2);
|
|
|
|
PLL2Calibrate(PLL);
|
|
|
|
return;
|
|
case RHD_POWER_RESET:
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x01, 0x01); /* Reset */
|
|
usleep(2);
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x02); /* Powah */
|
|
usleep(2);
|
|
|
|
return;
|
|
case RHD_POWER_SHUTDOWN:
|
|
default:
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x01, 0x01); /* Reset */
|
|
usleep(2);
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x02, 0x02); /* Power down */
|
|
usleep(200);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
R500PLL1SetLow(struct rhdPLL *PLL, CARD32 RefDiv, CARD32 FBDiv, CARD32 PostDiv,
|
|
CARD32 Control)
|
|
{
|
|
RHDFUNC(PLL);
|
|
RHDRegWrite(PLL, EXT1_PPLL_REF_DIV_SRC, 0x01); /* XTAL */
|
|
RHDRegWrite(PLL, EXT1_PPLL_POST_DIV_SRC, 0x00); /* source = reference */
|
|
|
|
RHDRegWrite(PLL, EXT1_PPLL_UPDATE_LOCK, 0x01); /* lock */
|
|
|
|
RHDRegWrite(PLL, EXT1_PPLL_REF_DIV, RefDiv);
|
|
RHDRegWrite(PLL, EXT1_PPLL_FB_DIV, FBDiv);
|
|
RHDRegWrite(PLL, EXT1_PPLL_POST_DIV, PostDiv);
|
|
RHDRegWrite(PLL, EXT1_PPLL_CNTL, Control);
|
|
|
|
RHDRegMask(PLL, EXT1_PPLL_UPDATE_CNTL, 0x00010000, 0x00010000); /* no autoreset */
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x04); /* don't bypass calibration */
|
|
|
|
/* We need to reset the anti glitch logic */
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x00000002); /* power up */
|
|
|
|
/* reset anti glitch logic */
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x00002000, 0x00002000);
|
|
usleep(2);
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x00002000);
|
|
|
|
/* powerdown and reset */
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x00000003, 0x00000003);
|
|
usleep(2);
|
|
|
|
RHDRegWrite(PLL, EXT1_PPLL_UPDATE_LOCK, 0); /* unlock */
|
|
RHDRegMask(PLL, EXT1_PPLL_UPDATE_CNTL, 0, 0x01); /* we're done updating! */
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x02); /* Powah */
|
|
usleep(2);
|
|
|
|
PLL1Calibrate(PLL);
|
|
|
|
RHDRegWrite(PLL, EXT1_PPLL_POST_DIV_SRC, 0x01); /* source is PLL itself */
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
R500PLL2SetLow(struct rhdPLL *PLL, CARD32 RefDiv, CARD32 FBDiv, CARD32 PostDiv,
|
|
CARD32 Control)
|
|
{
|
|
RHDRegWrite(PLL, EXT2_PPLL_REF_DIV_SRC, 0x01); /* XTAL */
|
|
RHDRegWrite(PLL, EXT2_PPLL_POST_DIV_SRC, 0x00); /* source = reference */
|
|
|
|
RHDRegWrite(PLL, EXT2_PPLL_UPDATE_LOCK, 0x01); /* lock */
|
|
|
|
RHDRegWrite(PLL, EXT2_PPLL_REF_DIV, RefDiv);
|
|
RHDRegWrite(PLL, EXT2_PPLL_FB_DIV, FBDiv);
|
|
RHDRegWrite(PLL, EXT2_PPLL_POST_DIV, PostDiv);
|
|
RHDRegWrite(PLL, EXT2_PPLL_CNTL, Control);
|
|
|
|
RHDRegMask(PLL, EXT2_PPLL_UPDATE_CNTL, 0x00010000, 0x00010000); /* no autoreset */
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x04); /* don't bypass calibration */
|
|
|
|
/* We need to reset the anti glitch logic */
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x00000002); /* power up */
|
|
|
|
/* reset anti glitch logic */
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x00002000, 0x00002000);
|
|
usleep(2);
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x00002000);
|
|
|
|
/* powerdown and reset */
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x00000003, 0x00000003);
|
|
usleep(2);
|
|
|
|
RHDRegWrite(PLL, EXT2_PPLL_UPDATE_LOCK, 0); /* unlock */
|
|
RHDRegMask(PLL, EXT2_PPLL_UPDATE_CNTL, 0, 0x01); /* we're done updating! */
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x02); /* Powah */
|
|
usleep(2);
|
|
|
|
PLL2Calibrate(PLL);
|
|
|
|
RHDRegWrite(PLL, EXT2_PPLL_POST_DIV_SRC, 0x01); /* source is PLL itself */
|
|
}
|
|
|
|
/*
|
|
* The CRTC ownership of each PLL is multiplexed on the PLL blocks, and the
|
|
* ownership can only be switched when the currently referenced PLL is active.
|
|
* This makes handling a slight bit more complex.
|
|
*/
|
|
static void
|
|
R500PLLCRTCGrab(struct rhdPLL *PLL, Bool Crtc2)
|
|
{
|
|
CARD32 Stored;
|
|
Bool PLL2IsCurrent;
|
|
|
|
if (!Crtc2) {
|
|
PLL2IsCurrent = RHDRegRead(PLL, PCLK_CRTC1_CNTL) & 0x00010000;
|
|
|
|
if (PLL->Id == PLL_ID_PLL1)
|
|
RHDRegMask(PLL, PCLK_CRTC1_CNTL, 0, 0x00010000);
|
|
else
|
|
RHDRegMask(PLL, PCLK_CRTC1_CNTL, 0x00010000, 0x00010000);
|
|
} else {
|
|
PLL2IsCurrent = RHDRegRead(PLL, PCLK_CRTC2_CNTL) & 0x00010000;
|
|
|
|
if (PLL->Id == PLL_ID_PLL1)
|
|
RHDRegMask(PLL, PCLK_CRTC2_CNTL, 0, 0x00010000);
|
|
else
|
|
RHDRegMask(PLL, PCLK_CRTC2_CNTL, 0x00010000, 0x00010000);
|
|
}
|
|
|
|
/* if the current pll is not active, then poke it just enough to flip
|
|
* owners */
|
|
if (!PLL2IsCurrent) {
|
|
Stored = RHDRegRead(PLL, P1PLL_CNTL);
|
|
|
|
if (Stored & 0x03) {
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x03);
|
|
usleep(10);
|
|
RHDRegMask(PLL, P1PLL_CNTL, Stored, 0x03);
|
|
}
|
|
} else {
|
|
Stored = RHDRegRead(PLL, P2PLL_CNTL);
|
|
|
|
if (Stored & 0x03) {
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x03);
|
|
usleep(10);
|
|
RHDRegMask(PLL, P2PLL_CNTL, Stored, 0x03);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
R500PLL1Set(struct rhdPLL *PLL, int PixelClock, CARD16 ReferenceDivider,
|
|
CARD16 FeedbackDivider, CARD8 PostDivider)
|
|
{
|
|
RHDPtr rhdPtr = RHDPTRI(PLL);
|
|
CARD32 RefDiv, FBDiv, PostDiv, Control;
|
|
|
|
RHDFUNC(PLL);
|
|
|
|
RefDiv = ReferenceDivider;
|
|
|
|
FBDiv = FeedbackDivider << 16;
|
|
|
|
if (rhdPtr->ChipSet > RHD_R600) { /* set up Feedbackdivider slip */
|
|
if (FeedbackDivider <= 0x24)
|
|
FBDiv |= 0x00000030;
|
|
else if (FeedbackDivider <= 0x3F)
|
|
FBDiv |= 0x00000020;
|
|
} else if (rhdPtr->ChipSet >= RHD_RS600) /* RS600, RS690, R600 */
|
|
FBDiv |= 0x00000030;
|
|
else
|
|
FBDiv |= RHDRegRead(PLL, EXT1_PPLL_FB_DIV) & 0x00000030;
|
|
|
|
PostDiv = RHDRegRead(PLL, EXT1_PPLL_POST_DIV) & ~0x0000007F;
|
|
PostDiv |= PostDivider & 0x0000007F;
|
|
|
|
Control = PLLElectrical(rhdPtr, FeedbackDivider);
|
|
if (!Control)
|
|
Control = RHDRegRead(PLL, EXT1_PPLL_CNTL);
|
|
|
|
/* Disable Spread Spectrum */
|
|
RHDRegMask(PLL, P1PLL_INT_SS_CNTL, 0, 0x00000001);
|
|
|
|
R500PLL1SetLow(PLL, RefDiv, FBDiv, PostDiv, Control);
|
|
|
|
if (rhdPtr->Crtc[0]->PLL == PLL)
|
|
R500PLLCRTCGrab(PLL, FALSE);
|
|
if (rhdPtr->Crtc[1]->PLL == PLL)
|
|
R500PLLCRTCGrab(PLL, TRUE);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
R500PLL2Set(struct rhdPLL *PLL, int PixelClock, CARD16 ReferenceDivider,
|
|
CARD16 FeedbackDivider, CARD8 PostDivider)
|
|
{
|
|
RHDPtr rhdPtr = RHDPTRI(PLL);
|
|
CARD32 RefDiv, FBDiv, PostDiv, Control;
|
|
|
|
RHDFUNC(PLL);
|
|
|
|
RefDiv = ReferenceDivider;
|
|
|
|
FBDiv = FeedbackDivider << 16;
|
|
|
|
if (rhdPtr->ChipSet > RHD_R600) { /* set up Feedbackdivider slip */
|
|
if (FeedbackDivider <= 0x24)
|
|
FBDiv |= 0x00000030;
|
|
else if (FeedbackDivider <= 0x3F)
|
|
FBDiv |= 0x00000020;
|
|
} else if (rhdPtr->ChipSet >= RHD_RS600) /* RS600, RS690, R600 */
|
|
FBDiv |= 0x00000030;
|
|
else
|
|
FBDiv |= RHDRegRead(PLL, EXT2_PPLL_FB_DIV) & 0x00000030;
|
|
|
|
PostDiv = RHDRegRead(PLL, EXT2_PPLL_POST_DIV) & ~0x0000007F;
|
|
PostDiv |= PostDivider & 0x0000007F;
|
|
|
|
Control = PLLElectrical(rhdPtr, FeedbackDivider);
|
|
if (!Control)
|
|
Control = RHDRegRead(PLL, EXT2_PPLL_CNTL);
|
|
|
|
/* Disable Spread Spectrum */
|
|
RHDRegMask(PLL, P2PLL_INT_SS_CNTL, 0, 0x00000001);
|
|
|
|
R500PLL2SetLow(PLL, RefDiv, FBDiv, PostDiv, Control);
|
|
|
|
if (rhdPtr->Crtc[0]->PLL == PLL)
|
|
R500PLLCRTCGrab(PLL, FALSE);
|
|
if (rhdPtr->Crtc[1]->PLL == PLL)
|
|
R500PLLCRTCGrab(PLL, TRUE);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
R500PLL1Save(struct rhdPLL *PLL)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
PLL->StoreActive = !(RHDRegRead(PLL, P1PLL_CNTL) & 0x03);
|
|
PLL->StoreRefDiv = RHDRegRead(PLL, EXT1_PPLL_REF_DIV);
|
|
PLL->StoreFBDiv = RHDRegRead(PLL, EXT1_PPLL_FB_DIV);
|
|
PLL->StorePostDiv = RHDRegRead(PLL, EXT1_PPLL_POST_DIV);
|
|
PLL->StoreControl = RHDRegRead(PLL, EXT1_PPLL_CNTL);
|
|
PLL->StoreSpreadSpectrum = RHDRegRead(PLL, P1PLL_INT_SS_CNTL);
|
|
PLL->StoreCrtc1Owner = !(RHDRegRead(PLL, PCLK_CRTC1_CNTL) & 0x00010000);
|
|
PLL->StoreCrtc2Owner = !(RHDRegRead(PLL, PCLK_CRTC2_CNTL) & 0x00010000);
|
|
|
|
PLL->Stored = TRUE;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
R500PLL2Save(struct rhdPLL *PLL)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
PLL->StoreActive = !(RHDRegRead(PLL, P2PLL_CNTL) & 0x03);
|
|
PLL->StoreRefDiv = RHDRegRead(PLL, EXT2_PPLL_REF_DIV);
|
|
PLL->StoreFBDiv = RHDRegRead(PLL, EXT2_PPLL_FB_DIV);
|
|
PLL->StorePostDiv = RHDRegRead(PLL, EXT2_PPLL_POST_DIV);
|
|
PLL->StoreControl = RHDRegRead(PLL, EXT2_PPLL_CNTL);
|
|
PLL->StoreSpreadSpectrum = RHDRegRead(PLL, P2PLL_INT_SS_CNTL);
|
|
PLL->StoreCrtc1Owner = RHDRegRead(PLL, PCLK_CRTC1_CNTL) & 0x00010000;
|
|
PLL->StoreCrtc2Owner = RHDRegRead(PLL, PCLK_CRTC2_CNTL) & 0x00010000;
|
|
|
|
PLL->Stored = TRUE;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
R500PLL1Restore(struct rhdPLL *PLL)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
if (!PLL->Stored) {
|
|
xf86DrvMsg(PLL->scrnIndex, X_ERROR, "%s: %s: trying to restore "
|
|
"uninitialized values.\n", __func__, PLL->Name);
|
|
return;
|
|
}
|
|
|
|
if (PLL->StoreActive) {
|
|
R500PLL1SetLow(PLL, PLL->StoreRefDiv, PLL->StoreFBDiv,
|
|
PLL->StorePostDiv, PLL->StoreControl);
|
|
|
|
/* HotFix: always keep spread spectrum disabled on restore */
|
|
if (0 && RHDPTRI(PLL)->ChipSet != RHD_M54)
|
|
RHDRegMask(PLL, P1PLL_INT_SS_CNTL,
|
|
PLL->StoreSpreadSpectrum, 0x00000001);
|
|
} else {
|
|
PLL->Power(PLL, RHD_POWER_SHUTDOWN);
|
|
|
|
/* lame attempt at at least restoring the old values */
|
|
RHDRegWrite(PLL, EXT1_PPLL_REF_DIV, PLL->StoreRefDiv);
|
|
RHDRegWrite(PLL, EXT1_PPLL_FB_DIV, PLL->StoreFBDiv);
|
|
RHDRegWrite(PLL, EXT1_PPLL_POST_DIV, PLL->StorePostDiv);
|
|
RHDRegWrite(PLL, EXT1_PPLL_CNTL, PLL->StoreControl);
|
|
RHDRegWrite(PLL, P1PLL_INT_SS_CNTL, PLL->StoreSpreadSpectrum);
|
|
}
|
|
|
|
if (PLL->StoreCrtc1Owner)
|
|
R500PLLCRTCGrab(PLL, FALSE);
|
|
if (PLL->StoreCrtc2Owner)
|
|
R500PLLCRTCGrab(PLL, TRUE);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
R500PLL2Restore(struct rhdPLL *PLL)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
if (!PLL->Stored) {
|
|
xf86DrvMsg(PLL->scrnIndex, X_ERROR, "%s: %s: trying to restore "
|
|
"uninitialized values.\n", __func__, PLL->Name);
|
|
return;
|
|
}
|
|
|
|
if (PLL->StoreActive) {
|
|
R500PLL2SetLow(PLL, PLL->StoreRefDiv, PLL->StoreFBDiv,
|
|
PLL->StorePostDiv, PLL->StoreControl);
|
|
|
|
if (RHDPTRI(PLL)->ChipSet != RHD_M54)
|
|
RHDRegMask(PLL, P2PLL_INT_SS_CNTL,
|
|
PLL->StoreSpreadSpectrum, 0x00000001);
|
|
} else {
|
|
PLL->Power(PLL, RHD_POWER_SHUTDOWN);
|
|
|
|
/* lame attempt at at least restoring the old values */
|
|
RHDRegWrite(PLL, EXT2_PPLL_REF_DIV, PLL->StoreRefDiv);
|
|
RHDRegWrite(PLL, EXT2_PPLL_FB_DIV, PLL->StoreFBDiv);
|
|
RHDRegWrite(PLL, EXT2_PPLL_POST_DIV, PLL->StorePostDiv);
|
|
RHDRegWrite(PLL, EXT2_PPLL_CNTL, PLL->StoreControl);
|
|
RHDRegWrite(PLL, P2PLL_INT_SS_CNTL, PLL->StoreSpreadSpectrum);
|
|
}
|
|
|
|
if (PLL->StoreCrtc1Owner)
|
|
R500PLLCRTCGrab(PLL, FALSE);
|
|
if (PLL->StoreCrtc2Owner)
|
|
R500PLLCRTCGrab(PLL, TRUE);
|
|
}
|
|
|
|
/*
|
|
* RV620 and up
|
|
*/
|
|
|
|
/*
|
|
*
|
|
*/
|
|
#define RV620_DCCGCLK_RESET 0
|
|
#define RV620_DCCGCLK_GRAB 1
|
|
#define RV620_DCCGCLK_RELEASE 2
|
|
|
|
/*
|
|
* I still have no idea what DCCG stands for and why it needs to hook off some
|
|
* pixelclock...
|
|
*/
|
|
static void
|
|
RV620DCCGCLKSet(struct rhdPLL *PLL, int set)
|
|
{
|
|
CARD32 tmp;
|
|
|
|
RHDFUNC(PLL);
|
|
|
|
switch(set) {
|
|
case RV620_DCCGCLK_GRAB:
|
|
if (PLL->Id == PLL_ID_PLL1)
|
|
RHDRegMask(PLL, DCCG_DISP_CLK_SRCSEL, 0, 0x00000003);
|
|
else if (PLL->Id == PLL_ID_PLL2)
|
|
RHDRegMask(PLL, DCCG_DISP_CLK_SRCSEL, 1, 0x00000003);
|
|
else
|
|
RHDRegMask(PLL, DCCG_DISP_CLK_SRCSEL, 3, 0x00000003);
|
|
break;
|
|
case RV620_DCCGCLK_RELEASE:
|
|
tmp = RHDRegRead(PLL, DCCG_DISP_CLK_SRCSEL) & 0x03;
|
|
|
|
if ((PLL->Id == PLL_ID_PLL1) && (tmp == 0)) {
|
|
/* set to other PLL or external */
|
|
tmp = RHDRegRead(PLL, P2PLL_CNTL);
|
|
if (!(tmp & 0x03) && /* powered and not in reset */
|
|
((tmp & 0x00300000) == 0x00300000)) /* calibrated and locked */
|
|
RHDRegMask(PLL, DCCG_DISP_CLK_SRCSEL, 1, 0x00000003);
|
|
else
|
|
RHDRegMask(PLL, DCCG_DISP_CLK_SRCSEL, 3, 0x00000003);
|
|
} else if ((PLL->Id == PLL_ID_PLL2) && (tmp == 1)) {
|
|
/* set to other PLL or external */
|
|
tmp = RHDRegRead(PLL, P1PLL_CNTL);
|
|
if (!(tmp & 0x03) && /* powered and not in reset */
|
|
((tmp & 0x00300000) == 0x00300000)) /* calibrated and locked */
|
|
RHDRegMask(PLL, DCCG_DISP_CLK_SRCSEL, 0, 0x00000003);
|
|
else
|
|
RHDRegMask(PLL, DCCG_DISP_CLK_SRCSEL, 3, 0x00000003);
|
|
|
|
} /* no other action needs to be taken */
|
|
break;
|
|
case RV620_DCCGCLK_RESET:
|
|
tmp = RHDRegRead(PLL, DCCG_DISP_CLK_SRCSEL) & 0x03;
|
|
|
|
if (((PLL->Id == PLL_ID_PLL1) && (tmp == 0)) ||
|
|
((PLL->Id == PLL_ID_PLL2) && (tmp == 1)))
|
|
RHDRegMask(PLL, DCCG_DISP_CLK_SRCSEL, 3, 0x00000003);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static Bool
|
|
RV620DCCGCLKAvailable(struct rhdPLL *PLL)
|
|
{
|
|
CARD32 Dccg = RHDRegRead(PLL, DCCG_DISP_CLK_SRCSEL) & 0x03;
|
|
|
|
RHDFUNC(PLL);
|
|
|
|
if (Dccg & 0x02)
|
|
return TRUE;
|
|
|
|
if ((PLL->Id == PLL_ID_PLL1) && (Dccg == 0))
|
|
return TRUE;
|
|
if ((PLL->Id == PLL_ID_PLL2) && (Dccg == 1))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
RV620PLL1Power(struct rhdPLL *PLL, int Power)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
switch (Power) {
|
|
case RHD_POWER_ON:
|
|
{
|
|
Bool HasDccg = RV620DCCGCLKAvailable(PLL);
|
|
|
|
if (HasDccg)
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_RESET);
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x02); /* Powah */
|
|
usleep(2);
|
|
|
|
PLL1Calibrate(PLL);
|
|
|
|
if (HasDccg)
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_GRAB);
|
|
return;
|
|
}
|
|
case RHD_POWER_RESET:
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_RELEASE);
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x01, 0x01); /* Reset */
|
|
usleep(2);
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x02); /* Powah */
|
|
usleep(2);
|
|
|
|
return;
|
|
case RHD_POWER_SHUTDOWN:
|
|
default:
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_RELEASE);
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x01, 0x01); /* Reset */
|
|
usleep(2);
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x02, 0x02); /* Power down */
|
|
usleep(200);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
RV620PLL2Power(struct rhdPLL *PLL, int Power)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
switch (Power) {
|
|
case RHD_POWER_ON:
|
|
{
|
|
Bool HasDccg = RV620DCCGCLKAvailable(PLL);
|
|
|
|
if (HasDccg)
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_RESET);
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x02); /* Powah */
|
|
usleep(2);
|
|
|
|
PLL2Calibrate(PLL);
|
|
|
|
if (HasDccg)
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_GRAB);
|
|
return;
|
|
}
|
|
case RHD_POWER_RESET:
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_RELEASE);
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x01, 0x01); /* Reset */
|
|
usleep(2);
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x02); /* Powah */
|
|
usleep(2);
|
|
|
|
return;
|
|
case RHD_POWER_SHUTDOWN:
|
|
default:
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_RELEASE);
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x01, 0x01); /* Reset */
|
|
usleep(2);
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x02, 0x02); /* Power down */
|
|
usleep(200);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
RV620PLL1SetLow(struct rhdPLL *PLL, CARD32 RefDiv, CARD32 FBDiv, CARD32 PostDiv,
|
|
CARD8 ScalerDiv, CARD8 SymPostDiv, CARD32 Control)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
/* switch to external */
|
|
RHDRegWrite(PLL, EXT1_PPLL_POST_DIV_SRC, 0);
|
|
RHDRegMask(PLL, P1PLL_DISP_CLK_CNTL, 0x00000200, 0x00000300);
|
|
RHDRegMask(PLL, EXT1_SYM_PPLL_POST_DIV, 0, 0x00000100);
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x00000001, 0x00000001); /* reset */
|
|
usleep(2);
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x00000002, 0x00000002); /* power down */
|
|
usleep(10);
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x00002000, 0x00002000); /* reset anti-glitch */
|
|
|
|
RHDRegWrite(PLL, EXT1_PPLL_CNTL, Control);
|
|
|
|
RHDRegMask(PLL, P1PLL_DISP_CLK_CNTL, ScalerDiv, 0x0000003F);
|
|
|
|
RHDRegWrite(PLL, EXT1_PPLL_UPDATE_LOCK, 1); /* lock */
|
|
|
|
RHDRegWrite(PLL, EXT1_PPLL_POST_DIV_SRC, 0x00000001);
|
|
|
|
RHDRegWrite(PLL, EXT1_PPLL_REF_DIV, RefDiv);
|
|
RHDRegWrite(PLL, EXT1_PPLL_FB_DIV, FBDiv);
|
|
RHDRegMask(PLL, EXT1_PPLL_POST_DIV, PostDiv, 0x0000007F);
|
|
RHDRegMask(PLL, EXT1_SYM_PPLL_POST_DIV, SymPostDiv, 0x0000007F);
|
|
|
|
usleep(10);
|
|
RHDRegWrite(PLL, EXT1_PPLL_UPDATE_LOCK, 0); /* unlock */
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x00000002); /* power up */
|
|
usleep(10);
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x00002000); /* undo reset anti-glitch */
|
|
|
|
PLL1Calibrate(PLL);
|
|
|
|
/* switch back to the pll */
|
|
RHDRegMask(PLL, P1PLL_DISP_CLK_CNTL, 0, 0x00000300);
|
|
RHDRegMask(PLL, EXT1_SYM_PPLL_POST_DIV, 0x00000100, 0x00000100);
|
|
RHDRegWrite(PLL, EXT1_PPLL_POST_DIV_SRC, 0x00000001);
|
|
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x80000000); /* new and undocumented */
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
RV620PLL2SetLow(struct rhdPLL *PLL, CARD32 RefDiv, CARD32 FBDiv, CARD32 PostDiv,
|
|
CARD8 ScalerDiv, CARD8 SymPostDiv, CARD32 Control)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
/* switch to external */
|
|
RHDRegWrite(PLL, EXT2_PPLL_POST_DIV_SRC, 0);
|
|
RHDRegMask(PLL, P2PLL_DISP_CLK_CNTL, 0x00000200, 0x00000300);
|
|
RHDRegMask(PLL, EXT2_SYM_PPLL_POST_DIV, 0, 0x00000100);
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x00000001, 0x00000001); /* reset */
|
|
usleep(2);
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x00000002, 0x00000002); /* power down */
|
|
usleep(10);
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x00002000, 0x00002000); /* reset anti-glitch */
|
|
|
|
RHDRegWrite(PLL, EXT2_PPLL_CNTL, Control);
|
|
|
|
RHDRegMask(PLL, P2PLL_DISP_CLK_CNTL, ScalerDiv, 0x0000003F);
|
|
|
|
RHDRegWrite(PLL, EXT2_PPLL_UPDATE_LOCK, 1); /* lock */
|
|
|
|
RHDRegWrite(PLL, EXT2_PPLL_POST_DIV_SRC, 0x00000001);
|
|
|
|
RHDRegWrite(PLL, EXT2_PPLL_REF_DIV, RefDiv);
|
|
RHDRegWrite(PLL, EXT2_PPLL_FB_DIV, FBDiv);
|
|
RHDRegMask(PLL, EXT2_PPLL_POST_DIV, PostDiv, 0x0000007F);
|
|
RHDRegMask(PLL, EXT2_SYM_PPLL_POST_DIV, SymPostDiv, 0x0000007F);
|
|
|
|
usleep(10);
|
|
RHDRegWrite(PLL, EXT2_PPLL_UPDATE_LOCK, 0); /* unlock */
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x00000002); /* power up */
|
|
usleep(10);
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x00002000); /* undo reset anti-glitch */
|
|
|
|
PLL2Calibrate(PLL);
|
|
|
|
/* switch back to the pll */
|
|
RHDRegMask(PLL, P2PLL_DISP_CLK_CNTL, 0, 0x00000300);
|
|
RHDRegMask(PLL, EXT2_SYM_PPLL_POST_DIV, 0x00000100, 0x00000100);
|
|
RHDRegWrite(PLL, EXT2_PPLL_POST_DIV_SRC, 0x00000001);
|
|
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x80000000); /* new and undocumented */
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
RV620PLL1Set(struct rhdPLL *PLL, int PixelClock, CARD16 ReferenceDivider,
|
|
CARD16 FeedbackDivider, CARD8 PostDivider)
|
|
{
|
|
RHDPtr rhdPtr = RHDPTRI(PLL);
|
|
Bool HasDccg = RV620DCCGCLKAvailable(PLL);
|
|
CARD32 RefDiv, FBDiv, PostDiv, Control;
|
|
CARD8 ScalerDiv, SymPostDiv;
|
|
|
|
RHDFUNC(PLL);
|
|
|
|
if (HasDccg)
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_RESET);
|
|
|
|
/* Disable Spread Spectrum */
|
|
RHDRegMask(PLL, P1PLL_INT_SS_CNTL, 0, 0x00000001);
|
|
|
|
RefDiv = ReferenceDivider;
|
|
|
|
FBDiv = RHDRegRead(PLL, EXT1_PPLL_FB_DIV) & ~0x07FF003F;
|
|
FBDiv |= ((FeedbackDivider << 16) | 0x0030) & 0x07FF003F;
|
|
|
|
PostDiv = RHDRegRead(PLL, EXT1_PPLL_POST_DIV) & ~0x0000007F;
|
|
PostDiv |= PostDivider & 0x0000007F;
|
|
|
|
/* introduce flags for this, like on unichrome */
|
|
ScalerDiv = 2; /* scaler post divider, 4 for UPDP */
|
|
|
|
SymPostDiv = PostDivider & 0x0000007F;
|
|
|
|
Control = PLLControlTableRetrieve(RV670PLLControl, FeedbackDivider);
|
|
|
|
RV620PLL1SetLow(PLL, RefDiv, FBDiv, PostDiv, ScalerDiv, SymPostDiv,
|
|
Control);
|
|
|
|
if (rhdPtr->Crtc[0]->PLL == PLL)
|
|
R500PLLCRTCGrab(PLL, FALSE);
|
|
if (rhdPtr->Crtc[1]->PLL == PLL)
|
|
R500PLLCRTCGrab(PLL, TRUE);
|
|
|
|
if (HasDccg)
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_GRAB);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
RV620PLL2Set(struct rhdPLL *PLL, int PixelClock, CARD16 ReferenceDivider,
|
|
CARD16 FeedbackDivider, CARD8 PostDivider)
|
|
{
|
|
RHDPtr rhdPtr = RHDPTRI(PLL);
|
|
Bool HasDccg = RV620DCCGCLKAvailable(PLL);
|
|
CARD32 RefDiv, FBDiv, PostDiv, Control;
|
|
CARD8 ScalerDiv, SymPostDiv;
|
|
|
|
RHDFUNC(PLL);
|
|
|
|
if (HasDccg)
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_RESET);
|
|
|
|
/* Disable Spread Spectrum */
|
|
RHDRegMask(PLL, P2PLL_INT_SS_CNTL, 0, 0x00000001);
|
|
|
|
RefDiv = ReferenceDivider;
|
|
|
|
FBDiv = RHDRegRead(PLL, EXT2_PPLL_FB_DIV) & ~0x07FF003F;
|
|
FBDiv |= ((FeedbackDivider << 16) | 0x0030) & 0x07FF003F;
|
|
|
|
PostDiv = RHDRegRead(PLL, EXT2_PPLL_POST_DIV) & ~0x0000007F;
|
|
PostDiv |= PostDivider & 0x0000007F;
|
|
|
|
/* introduce flags for this, like on unichrome */
|
|
ScalerDiv = 2; /* scaler post divider, 4 for UPDP */
|
|
|
|
SymPostDiv = PostDivider & 0x0000007F;
|
|
|
|
Control = PLLControlTableRetrieve(RV670PLLControl, FeedbackDivider);
|
|
|
|
RV620PLL2SetLow(PLL, RefDiv, FBDiv, PostDiv, ScalerDiv, SymPostDiv,
|
|
Control);
|
|
|
|
if (rhdPtr->Crtc[0]->PLL == PLL)
|
|
R500PLLCRTCGrab(PLL, FALSE);
|
|
if (rhdPtr->Crtc[1]->PLL == PLL)
|
|
R500PLLCRTCGrab(PLL, TRUE);
|
|
|
|
if (HasDccg)
|
|
RV620DCCGCLKSet(PLL, RV620_DCCGCLK_GRAB);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
RV620PLL1Save(struct rhdPLL *PLL)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
PLL->StoreActive = !(RHDRegRead(PLL, P1PLL_CNTL) & 0x03);
|
|
PLL->StoreRefDiv = RHDRegRead(PLL, EXT1_PPLL_REF_DIV);
|
|
PLL->StoreFBDiv = RHDRegRead(PLL, EXT1_PPLL_FB_DIV);
|
|
PLL->StorePostDiv = RHDRegRead(PLL, EXT1_PPLL_POST_DIV);
|
|
PLL->StorePostDivSrc = RHDRegRead(PLL, EXT1_PPLL_POST_DIV_SRC);
|
|
PLL->StoreControl = RHDRegRead(PLL, EXT1_PPLL_CNTL);
|
|
PLL->StoreSpreadSpectrum = RHDRegRead(PLL, P1PLL_INT_SS_CNTL);
|
|
|
|
PLL->StoreGlitchReset = RHDRegRead(PLL, P1PLL_CNTL) & 0x00002000;
|
|
|
|
PLL->StoreScalerPostDiv = RHDRegRead(PLL, P1PLL_DISP_CLK_CNTL) & 0x003F;
|
|
PLL->StoreSymPostDiv = RHDRegRead(PLL, EXT1_SYM_PPLL_POST_DIV) & 0x007F;
|
|
|
|
PLL->StoreCrtc1Owner = !(RHDRegRead(PLL, PCLK_CRTC1_CNTL) & 0x00010000);
|
|
PLL->StoreCrtc2Owner = !(RHDRegRead(PLL, PCLK_CRTC2_CNTL) & 0x00010000);
|
|
|
|
PLL->StoreDCCGCLKOwner = RV620DCCGCLKAvailable(PLL);
|
|
if (PLL->StoreDCCGCLKOwner)
|
|
PLL->StoreDCCGCLK = RHDRegRead(PLL, DCCG_DISP_CLK_SRCSEL);
|
|
else
|
|
PLL->StoreDCCGCLK = 0;
|
|
|
|
PLL->Stored = TRUE;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
RV620PLL2Save(struct rhdPLL *PLL)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
PLL->StoreActive = !(RHDRegRead(PLL, P2PLL_CNTL) & 0x03);
|
|
PLL->StoreRefDiv = RHDRegRead(PLL, EXT2_PPLL_REF_DIV);
|
|
PLL->StoreFBDiv = RHDRegRead(PLL, EXT2_PPLL_FB_DIV);
|
|
PLL->StorePostDiv = RHDRegRead(PLL, EXT2_PPLL_POST_DIV);
|
|
PLL->StorePostDivSrc = RHDRegRead(PLL, EXT2_PPLL_POST_DIV_SRC);
|
|
PLL->StoreControl = RHDRegRead(PLL, EXT2_PPLL_CNTL);
|
|
PLL->StoreSpreadSpectrum = RHDRegRead(PLL, P2PLL_INT_SS_CNTL);
|
|
|
|
PLL->StoreGlitchReset = RHDRegRead(PLL, P2PLL_CNTL) & 0x00002000;
|
|
|
|
PLL->StoreScalerPostDiv = RHDRegRead(PLL, P2PLL_DISP_CLK_CNTL) & 0x003F;
|
|
PLL->StoreSymPostDiv = RHDRegRead(PLL, EXT2_SYM_PPLL_POST_DIV) & 0x007F;
|
|
|
|
PLL->StoreCrtc1Owner = RHDRegRead(PLL, PCLK_CRTC1_CNTL) & 0x00010000;
|
|
PLL->StoreCrtc2Owner = RHDRegRead(PLL, PCLK_CRTC2_CNTL) & 0x00010000;
|
|
|
|
PLL->StoreDCCGCLKOwner = RV620DCCGCLKAvailable(PLL);
|
|
if (PLL->StoreDCCGCLKOwner)
|
|
PLL->StoreDCCGCLK = RHDRegRead(PLL, DCCG_DISP_CLK_SRCSEL);
|
|
else
|
|
PLL->StoreDCCGCLK = 0;
|
|
|
|
PLL->Stored = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Notice how we handle the DCCG ownership here. There is a difference between
|
|
* currently holding the DCCG and what was held when in the VT. With the
|
|
* solution here we no longer hardlock, but we do have the danger of keeping
|
|
* the DCCG in external mode for too long a time, if both PLL restores are
|
|
* too far apart. This is currently not an issue as VT restoration goes over
|
|
* the whole device in one go anyway; no partial restoration going on
|
|
*/
|
|
static void
|
|
RV620PLL1Restore(struct rhdPLL *PLL)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
if (RV620DCCGCLKAvailable(PLL))
|
|
RHDRegMask(PLL, DCCG_DISP_CLK_SRCSEL, 0x03, 0x00000003);
|
|
|
|
if (PLL->StoreActive) {
|
|
RV620PLL1SetLow(PLL, PLL->StoreRefDiv, PLL->StoreFBDiv,
|
|
PLL->StorePostDiv, PLL->StoreScalerPostDiv,
|
|
PLL->StoreSymPostDiv, PLL->StoreControl);
|
|
RHDRegMask(PLL, P1PLL_INT_SS_CNTL,
|
|
PLL->StoreSpreadSpectrum, 0x00000001);
|
|
|
|
if (PLL->StoreDCCGCLKOwner)
|
|
RHDRegWrite(PLL, DCCG_DISP_CLK_SRCSEL, PLL->StoreDCCGCLK);
|
|
|
|
} else {
|
|
PLL->Power(PLL, RHD_POWER_SHUTDOWN);
|
|
|
|
/* lame attempt at at least restoring the old values */
|
|
RHDRegWrite(PLL, EXT1_PPLL_REF_DIV, PLL->StoreRefDiv);
|
|
RHDRegWrite(PLL, EXT1_PPLL_FB_DIV, PLL->StoreFBDiv);
|
|
RHDRegWrite(PLL, EXT1_PPLL_POST_DIV, PLL->StorePostDiv);
|
|
RHDRegWrite(PLL, EXT1_PPLL_POST_DIV_SRC, PLL->StorePostDivSrc);
|
|
RHDRegWrite(PLL, EXT1_PPLL_CNTL, PLL->StoreControl);
|
|
RHDRegMask(PLL, P1PLL_DISP_CLK_CNTL, PLL->StoreScalerPostDiv, 0x003F);
|
|
RHDRegMask(PLL, EXT1_SYM_PPLL_POST_DIV, PLL->StoreSymPostDiv, 0x007F);
|
|
RHDRegWrite(PLL, P1PLL_INT_SS_CNTL, PLL->StoreSpreadSpectrum);
|
|
|
|
if (PLL->StoreGlitchReset)
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0x00002000, 0x00002000);
|
|
else
|
|
RHDRegMask(PLL, P1PLL_CNTL, 0, 0x00002000);
|
|
}
|
|
|
|
if (PLL->StoreCrtc1Owner)
|
|
R500PLLCRTCGrab(PLL, FALSE);
|
|
if (PLL->StoreCrtc2Owner)
|
|
R500PLLCRTCGrab(PLL, TRUE);
|
|
|
|
if (PLL->StoreDCCGCLKOwner)
|
|
RHDRegWrite(PLL, DCCG_DISP_CLK_SRCSEL, PLL->StoreDCCGCLK);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void
|
|
RV620PLL2Restore(struct rhdPLL *PLL)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
if (RV620DCCGCLKAvailable(PLL))
|
|
RHDRegMask(PLL, DCCG_DISP_CLK_SRCSEL, 0x03, 0x00000003);
|
|
|
|
if (PLL->StoreActive) {
|
|
RV620PLL2SetLow(PLL, PLL->StoreRefDiv, PLL->StoreFBDiv,
|
|
PLL->StorePostDiv, PLL->StoreScalerPostDiv,
|
|
PLL->StoreSymPostDiv, PLL->StoreControl);
|
|
RHDRegMask(PLL, P2PLL_INT_SS_CNTL,
|
|
PLL->StoreSpreadSpectrum, 0x00000001);
|
|
} else {
|
|
PLL->Power(PLL, RHD_POWER_SHUTDOWN);
|
|
|
|
/* lame attempt at at least restoring the old values */
|
|
RHDRegWrite(PLL, EXT2_PPLL_REF_DIV, PLL->StoreRefDiv);
|
|
RHDRegWrite(PLL, EXT2_PPLL_FB_DIV, PLL->StoreFBDiv);
|
|
RHDRegWrite(PLL, EXT2_PPLL_POST_DIV, PLL->StorePostDiv);
|
|
RHDRegWrite(PLL, EXT2_PPLL_POST_DIV_SRC, PLL->StorePostDivSrc);
|
|
RHDRegWrite(PLL, EXT2_PPLL_CNTL, PLL->StoreControl);
|
|
RHDRegMask(PLL, P2PLL_DISP_CLK_CNTL, PLL->StoreScalerPostDiv, 0x003F);
|
|
RHDRegMask(PLL, EXT2_SYM_PPLL_POST_DIV, PLL->StoreSymPostDiv, 0x007F);
|
|
RHDRegWrite(PLL, P2PLL_INT_SS_CNTL, PLL->StoreSpreadSpectrum);
|
|
|
|
if (PLL->StoreGlitchReset)
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0x00002000, 0x00002000);
|
|
else
|
|
RHDRegMask(PLL, P2PLL_CNTL, 0, 0x00002000);
|
|
}
|
|
|
|
if (PLL->StoreCrtc1Owner)
|
|
R500PLLCRTCGrab(PLL, FALSE);
|
|
if (PLL->StoreCrtc2Owner)
|
|
R500PLLCRTCGrab(PLL, TRUE);
|
|
|
|
if (PLL->StoreDCCGCLKOwner)
|
|
RHDRegWrite(PLL, DCCG_DISP_CLK_SRCSEL, PLL->StoreDCCGCLK);
|
|
}
|
|
|
|
/* Some defaults for when we don't have this info */
|
|
/* XTAL is visible on the cards */
|
|
#define RHD_PLL_REFERENCE_DEFAULT 27000
|
|
/* these required quite some testing */
|
|
#define RHD_R500_PLL_INTERNAL_MIN_DEFAULT 648000
|
|
#define RHD_RV620_PLL_INTERNAL_MIN_DEFAULT 702000
|
|
/* Lowest value seen so far */
|
|
#define RHD_PLL_INTERNAL_MAX_DEFAULT 1100000
|
|
#define RHD_PLL_MIN_DEFAULT 16000 /* guess */
|
|
#define RHD_PLL_MAX_DEFAULT 400000 /* 400Mhz modes... hrm */
|
|
|
|
enum pllComp {
|
|
PLL_NONE,
|
|
PLL_MIN,
|
|
PLL_MAX
|
|
};
|
|
|
|
/*
|
|
*
|
|
*/
|
|
#ifdef ATOM_BIOS
|
|
static Bool
|
|
getPLLValuesFromAtomBIOS(RHDPtr rhdPtr,
|
|
AtomBiosRequestID func, char *msg, CARD32 *val, enum pllComp comp)
|
|
{
|
|
AtomBiosArgRec arg;
|
|
AtomBiosResult ret;
|
|
|
|
if (rhdPtr->atomBIOS) {
|
|
ret = RHDAtomBiosFunc(rhdPtr->scrnIndex, rhdPtr->atomBIOS,
|
|
func, &arg);
|
|
if (ret == ATOM_SUCCESS) {
|
|
if (arg.val) {
|
|
switch (comp) {
|
|
case PLL_MAX:
|
|
if (arg.val < *val)
|
|
xf86DrvMsg(rhdPtr->scrnIndex, X_WARNING,
|
|
"Lower %s detected than the default: %lu %lu.\n"
|
|
"Please contact the authors ASAP.\n", msg,
|
|
(unsigned long)*val, (unsigned long)arg.val * 10);
|
|
break;
|
|
case PLL_MIN:
|
|
if (arg.val > *val)
|
|
xf86DrvMsg(rhdPtr->scrnIndex, X_WARNING,
|
|
"Higher %s detected than the default: %lu %lu.\n"
|
|
"Please contact the authors ASAP.\n", msg,
|
|
(unsigned long)*val, (unsigned long)arg.val * 10);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
*val = arg.val;
|
|
}
|
|
}
|
|
return TRUE;
|
|
} else
|
|
xf86DrvMsg(rhdPtr->scrnIndex, X_ERROR, "Failed to retrieve the %s"
|
|
" clock from ATOM.\n",msg);
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void
|
|
RHDSetupLimits(RHDPtr rhdPtr, CARD32 *RefClock,
|
|
CARD32 *IntMin, CARD32 *IntMax,
|
|
CARD32 *PixMin, CARD32 *PixMax)
|
|
{
|
|
/* Retrieve the internal PLL frequency limits*/
|
|
*RefClock = RHD_PLL_REFERENCE_DEFAULT;
|
|
if (rhdPtr->ChipSet < RHD_RV620)
|
|
*IntMin = RHD_R500_PLL_INTERNAL_MIN_DEFAULT;
|
|
else
|
|
*IntMin = RHD_RV620_PLL_INTERNAL_MIN_DEFAULT;
|
|
|
|
*IntMax = RHD_PLL_INTERNAL_MAX_DEFAULT;
|
|
|
|
/* keep the defaults */
|
|
*PixMin = RHD_PLL_MIN_DEFAULT;
|
|
*PixMax = RHD_PLL_MAX_DEFAULT;
|
|
|
|
#ifdef ATOM_BIOS
|
|
getPLLValuesFromAtomBIOS(rhdPtr, GET_MIN_PIXEL_CLOCK_PLL_OUTPUT, "minimum PLL output",
|
|
IntMin, PLL_MIN);
|
|
getPLLValuesFromAtomBIOS(rhdPtr, GET_MAX_PIXEL_CLOCK_PLL_OUTPUT, "maximum PLL output",
|
|
IntMax, PLL_MAX);
|
|
getPLLValuesFromAtomBIOS(rhdPtr, GET_MAX_PIXEL_CLK, "Pixel Clock",
|
|
PixMax, PLL_MAX);
|
|
getPLLValuesFromAtomBIOS(rhdPtr, GET_REF_CLOCK, "reference clock",
|
|
RefClock, PLL_NONE);
|
|
if (*IntMax == 0) {
|
|
if (rhdPtr->ChipSet < RHD_RV620)
|
|
*IntMax = RHD_R500_PLL_INTERNAL_MIN_DEFAULT;
|
|
else
|
|
*IntMax = RHD_RV620_PLL_INTERNAL_MIN_DEFAULT;
|
|
|
|
xf86DrvMsg(rhdPtr->scrnIndex, X_WARNING, "AtomBIOS reports maximum VCO freq 0. "
|
|
"Using %lu instead\n",(unsigned long)*IntMax);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
Bool
|
|
RHDPLLsInit(RHDPtr rhdPtr)
|
|
{
|
|
struct rhdPLL *PLL;
|
|
CARD32 RefClock, IntMin, IntMax, PixMin, PixMax;
|
|
|
|
RHDFUNC(rhdPtr);
|
|
|
|
if (RHDUseAtom(rhdPtr, NULL, atomUsagePLL))
|
|
return FALSE;
|
|
|
|
RHDSetupLimits(rhdPtr, &RefClock, &IntMin, &IntMax, &PixMin, &PixMax);
|
|
|
|
/* PLL1 */
|
|
PLL = (struct rhdPLL *) xnfcalloc(sizeof(struct rhdPLL), 1);
|
|
|
|
PLL->scrnIndex = rhdPtr->scrnIndex;
|
|
PLL->Name = PLL_NAME_PLL1;
|
|
PLL->Id = PLL_ID_PLL1;
|
|
|
|
PLL->RefClock = RefClock;
|
|
PLL->IntMin = IntMin;
|
|
PLL->IntMax = IntMax;
|
|
PLL->PixMin = PixMin;
|
|
PLL->PixMax = PixMax;
|
|
|
|
PLL->Valid = NULL;
|
|
if (rhdPtr->ChipSet < RHD_RV620) {
|
|
PLL->Set = R500PLL1Set;
|
|
PLL->Power = R500PLL1Power;
|
|
PLL->Save = R500PLL1Save;
|
|
PLL->Restore = R500PLL1Restore;
|
|
} else {
|
|
PLL->Set = RV620PLL1Set;
|
|
PLL->Power = RV620PLL1Power;
|
|
PLL->Save = RV620PLL1Save;
|
|
PLL->Restore = RV620PLL1Restore;
|
|
}
|
|
|
|
rhdPtr->PLLs[0] = PLL;
|
|
|
|
/* PLL2 */
|
|
PLL = (struct rhdPLL *) xnfcalloc(sizeof(struct rhdPLL), 1);
|
|
|
|
PLL->scrnIndex = rhdPtr->scrnIndex;
|
|
PLL->Name = PLL_NAME_PLL2;
|
|
PLL->Id = PLL_ID_PLL2;
|
|
|
|
PLL->RefClock = RefClock;
|
|
PLL->IntMin = IntMin;
|
|
PLL->IntMax = IntMax;
|
|
PLL->PixMin = PixMin;
|
|
PLL->PixMax = PixMax;
|
|
|
|
PLL->Valid = NULL;
|
|
if (rhdPtr->ChipSet < RHD_RV620) {
|
|
PLL->Set = R500PLL2Set;
|
|
PLL->Power = R500PLL2Power;
|
|
PLL->Save = R500PLL2Save;
|
|
PLL->Restore = R500PLL2Restore;
|
|
} else {
|
|
PLL->Set = RV620PLL2Set;
|
|
PLL->Power = RV620PLL2Power;
|
|
PLL->Save = RV620PLL2Save;
|
|
PLL->Restore = RV620PLL2Restore;
|
|
}
|
|
|
|
rhdPtr->PLLs[1] = PLL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
ModeStatus
|
|
RHDPLLValid(struct rhdPLL *PLL, CARD32 Clock)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
if (Clock < PLL->PixMin)
|
|
return MODE_CLOCK_LOW;
|
|
if (Clock > PLL->PixMax)
|
|
return MODE_CLOCK_HIGH;
|
|
|
|
if (PLL->Valid)
|
|
return PLL->Valid(PLL, Clock);
|
|
else
|
|
return MODE_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
* Calculate the PLL parameters for a given dotclock.
|
|
*
|
|
* This calculation uses a linear approximation of an experimentally found
|
|
* curve that delimits reference versus feedback dividers on rv610. This curve
|
|
* can be shifted towards higher feedback divider through increasing the gain
|
|
* control, but the effect of this is rather limited.
|
|
*
|
|
* Since this upper limit still provides a wide enough range with enough
|
|
* granularity, we use it for all r5xx and r6xx devices.
|
|
*/
|
|
static Bool
|
|
PLLCalculate(struct rhdPLL *PLL, CARD32 PixelClock,
|
|
CARD16 *RefDivider, CARD16 *FBDivider, CARD8 *PostDivider)
|
|
{
|
|
/* limited by the number of bits available */
|
|
#define FB_DIV_LIMIT 2048
|
|
#define REF_DIV_LIMIT 1024
|
|
#define POST_DIV_LIMIT 128
|
|
|
|
CARD32 FBDiv, RefDiv, PostDiv, BestDiff = 0xFFFFFFFF;
|
|
float Ratio;
|
|
|
|
Ratio = ((float) PixelClock) / ((float) PLL->RefClock);
|
|
|
|
for (PostDiv = 2; PostDiv < POST_DIV_LIMIT; PostDiv++) {
|
|
CARD32 VCOOut = PixelClock * PostDiv;
|
|
|
|
/* we are conservative and avoid the limits */
|
|
if (VCOOut <= PLL->IntMin)
|
|
continue;
|
|
if (VCOOut >= PLL->IntMax)
|
|
break;
|
|
|
|
for (RefDiv = 1; RefDiv <= REF_DIV_LIMIT; RefDiv++) {
|
|
CARD32 Diff;
|
|
|
|
FBDiv = (CARD32) ((Ratio * PostDiv * RefDiv) + 0.5);
|
|
|
|
if (FBDiv >= FB_DIV_LIMIT)
|
|
break;
|
|
if (FBDiv > (500 + (13 * RefDiv))) /* rv6x0 limit */
|
|
break;
|
|
|
|
Diff = abs( PixelClock - (FBDiv * PLL->RefClock) / (PostDiv * RefDiv) );
|
|
|
|
if (Diff < BestDiff) {
|
|
*FBDivider = FBDiv;
|
|
*RefDivider = RefDiv;
|
|
*PostDivider = PostDiv;
|
|
BestDiff = Diff;
|
|
}
|
|
|
|
if (BestDiff == 0)
|
|
break;
|
|
}
|
|
if (BestDiff == 0)
|
|
break;
|
|
}
|
|
|
|
if (BestDiff != 0xFFFFFFFF) {
|
|
RHDDebug(PLL->scrnIndex, "PLL Calculation: %dkHz = "
|
|
"(((%i / 0x%X) * 0x%X) / 0x%X) (%dkHz off)\n",
|
|
(int) PixelClock, (unsigned int) PLL->RefClock, *RefDivider,
|
|
*FBDivider, *PostDivider, (int) BestDiff);
|
|
return TRUE;
|
|
} else { /* Should never happen */
|
|
xf86DrvMsg(PLL->scrnIndex, X_ERROR,
|
|
"%s: Failed to get a valid PLL setting for %dkHz\n",
|
|
__func__, (int) PixelClock);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void
|
|
RHDPLLSet(struct rhdPLL *PLL, CARD32 Clock)
|
|
{
|
|
CARD16 RefDivider = 0, FBDivider = 0;
|
|
CARD8 PostDivider = 0;
|
|
|
|
RHDDebug(PLL->scrnIndex, "%s: Setting %s to %dkHz\n", __func__,
|
|
PLL->Name, Clock);
|
|
|
|
if (PLLCalculate(PLL, Clock, &RefDivider, &FBDivider, &PostDivider)) {
|
|
PLL->Set(PLL, Clock, RefDivider, FBDivider, PostDivider);
|
|
|
|
PLL->CurrentClock = Clock;
|
|
PLL->Active = TRUE;
|
|
} else
|
|
xf86DrvMsg(PLL->scrnIndex, X_WARNING,
|
|
"%s: Not altering any settings.\n", __func__);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void
|
|
RHDPLLPower(struct rhdPLL *PLL, int Power)
|
|
{
|
|
RHDFUNC(PLL);
|
|
|
|
if (PLL->Power)
|
|
PLL->Power(PLL, Power);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void
|
|
RHDPLLsPowerAll(RHDPtr rhdPtr, int Power)
|
|
{
|
|
struct rhdPLL *PLL;
|
|
|
|
RHDFUNC(rhdPtr);
|
|
|
|
PLL = rhdPtr->PLLs[0];
|
|
if (PLL->Power)
|
|
PLL->Power(PLL, Power);
|
|
|
|
PLL = rhdPtr->PLLs[1];
|
|
if (PLL->Power)
|
|
PLL->Power(PLL, Power);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void
|
|
RHDPLLsShutdownInactive(RHDPtr rhdPtr)
|
|
{
|
|
struct rhdPLL *PLL;
|
|
|
|
RHDFUNC(rhdPtr);
|
|
|
|
PLL = rhdPtr->PLLs[0];
|
|
if (PLL->Power && !PLL->Active)
|
|
PLL->Power(PLL, RHD_POWER_SHUTDOWN);
|
|
|
|
PLL = rhdPtr->PLLs[1];
|
|
if (PLL->Power && !PLL->Active)
|
|
PLL->Power(PLL, RHD_POWER_SHUTDOWN);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void
|
|
RHDPLLsSave(RHDPtr rhdPtr)
|
|
{
|
|
struct rhdPLL *PLL;
|
|
|
|
RHDFUNC(rhdPtr);
|
|
|
|
PLL = rhdPtr->PLLs[0];
|
|
if (PLL->Save)
|
|
PLL->Save(PLL);
|
|
|
|
PLL = rhdPtr->PLLs[1];
|
|
if (PLL->Save)
|
|
PLL->Save(PLL);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void
|
|
RHDPLLsRestore(RHDPtr rhdPtr)
|
|
{
|
|
struct rhdPLL *PLL;
|
|
|
|
RHDFUNC(rhdPtr);
|
|
|
|
PLL = rhdPtr->PLLs[0];
|
|
if (PLL->Restore)
|
|
PLL->Restore(PLL);
|
|
|
|
PLL = rhdPtr->PLLs[1];
|
|
if (PLL->Restore)
|
|
PLL->Restore(PLL);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void
|
|
RHDPLLsDestroy(RHDPtr rhdPtr)
|
|
{
|
|
RHDFUNC(rhdPtr);
|
|
|
|
if (rhdPtr->PLLs[0] && rhdPtr->PLLs[0]->Private)
|
|
xfree(rhdPtr->PLLs[0]->Private);
|
|
xfree(rhdPtr->PLLs[0]);
|
|
if (rhdPtr->PLLs[1] && rhdPtr->PLLs[1]->Private)
|
|
xfree(rhdPtr->PLLs[1]->Private);
|
|
xfree(rhdPtr->PLLs[1]);
|
|
}
|