0,0 → 1,1547 |
/* |
* 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]); |
} |