0,0 → 1,548 |
/* |
* Copyright 2007-2008 Luc Verhaegen <lverhaegen@novell.com> |
* Copyright 2007-2008 Matthias Hopf <mhopf@novell.com> |
* Copyright 2007-2008 Egbert Eich <eich@novell.com> |
* Copyright 2007-2008 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. |
*/ |
|
/* |
* Deals with the Primary TMDS device (TMDSA) of R500s, R600s. |
* Gets replaced by DDIA on RS690 and DIG/UNIPHY on RV620. |
*/ |
|
#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_connector.h" |
#include "rhd_output.h" |
#include "rhd_regs.h" |
#include "rhd_hdmi.h" |
|
#ifdef ATOM_BIOS |
#include "rhd_atombios.h" |
#endif |
|
struct rhdTMDSPrivate { |
Bool RunsDualLink; |
DisplayModePtr Mode; |
Bool Coherent; |
int PowerState; |
|
struct rhdHdmi *Hdmi; |
|
Bool Stored; |
|
CARD32 StoreControl; |
CARD32 StoreSource; |
CARD32 StoreFormat; |
CARD32 StoreForce; |
CARD32 StoreReduction; |
CARD32 StoreDCBalancer; |
CARD32 StoreDataSynchro; |
CARD32 StoreTXEnable; |
CARD32 StoreMacro; |
CARD32 StoreTXControl; |
CARD32 StoreTXAdjust; |
}; |
|
/* |
* We cannot sense for dual link here at all, plus, we need a bit more work |
* for enabling the transmitter for sensing to happen on most R5xx cards. |
* RV570 (0x7280) and R600 and above seem ok. |
*/ |
static enum rhdSensedOutput |
TMDSASense(struct rhdOutput *Output, struct rhdConnector *Connector) |
{ |
RHDPtr rhdPtr = RHDPTRI(Output); |
CARD32 Enable, Control, Detect; |
enum rhdConnectorType Type = Connector->Type; |
Bool ret; |
|
RHDFUNC(Output); |
|
if ((Type != RHD_CONNECTOR_DVI) && (Type != RHD_CONNECTOR_DVI_SINGLE)) { |
xf86DrvMsg(Output->scrnIndex, X_WARNING, |
"%s: connector type %d is not supported.\n", |
__func__, Type); |
return RHD_SENSED_NONE; |
} |
|
Enable = RHDRegRead(Output, TMDSA_TRANSMITTER_ENABLE); |
Control = RHDRegRead(Output, TMDSA_TRANSMITTER_CONTROL); |
Detect = RHDRegRead(Output, TMDSA_LOAD_DETECT); |
|
if (rhdPtr->ChipSet < RHD_R600) { |
RHDRegMask(Output, TMDSA_TRANSMITTER_ENABLE, 0x00000003, 0x00000003); |
RHDRegMask(Output, TMDSA_TRANSMITTER_CONTROL, 0x00000001, 0x00000003); |
} |
|
RHDRegMask(Output, TMDSA_LOAD_DETECT, 0x00000001, 0x00000001); |
usleep(1); |
ret = RHDRegRead(Output, TMDSA_LOAD_DETECT) & 0x00000010; |
|
RHDRegMask(Output, TMDSA_LOAD_DETECT, Detect, 0x00000001); |
|
if (rhdPtr->ChipSet < RHD_R600) { |
RHDRegWrite(Output, TMDSA_TRANSMITTER_ENABLE, Enable); |
RHDRegWrite(Output, TMDSA_TRANSMITTER_CONTROL, Control); |
} |
|
RHDDebug(Output->scrnIndex, "%s: %s\n", __func__, |
ret ? "Attached" : "Disconnected"); |
|
if (ret) |
return RHD_SENSED_DVI; |
else |
return RHD_SENSED_NONE; |
} |
|
/* |
* |
*/ |
static ModeStatus |
TMDSAModeValid(struct rhdOutput *Output, DisplayModePtr Mode) |
{ |
RHDFUNC(Output); |
|
if (Mode->Flags & V_INTERLACE) |
return MODE_NO_INTERLACE; |
|
if (Mode->Clock < 25000) |
return MODE_CLOCK_LOW; |
|
if (Output->Connector->Type == RHD_CONNECTOR_DVI_SINGLE) { |
if (Mode->Clock > 165000) |
return MODE_CLOCK_HIGH; |
} else if (Output->Connector->Type == RHD_CONNECTOR_DVI) { |
if (Mode->Clock > 330000) /* could go higher still */ |
return MODE_CLOCK_HIGH; |
} |
|
return MODE_OK; |
} |
|
/* |
* This information is not provided in an atombios data table. |
*/ |
static struct R5xxTMDSAMacro { |
CARD16 Device; |
CARD32 Macro; |
} R5xxTMDSAMacro[] = { |
{ 0x7104, 0x00C00414 }, /* R520 */ |
{ 0x7142, 0x00A00415 }, /* RV515 */ |
{ 0x7145, 0x00A00416 }, /* M54 */ |
{ 0x7146, 0x00C0041F }, /* RV515 */ |
{ 0x7147, 0x00C00418 }, /* RV505 */ |
{ 0x7149, 0x00800416 }, /* M56 */ |
{ 0x7152, 0x00A00415 }, /* RV515 */ |
{ 0x7183, 0x00600412 }, /* RV530 */ |
{ 0x71C1, 0x00C0041F }, /* RV535 */ |
{ 0x71C2, 0x00A00416 }, /* RV530 */ |
{ 0x71C4, 0x00A00416 }, /* M56 */ |
{ 0x71C5, 0x00A00416 }, /* M56 */ |
{ 0x71C6, 0x00A00513 }, /* RV530 */ |
{ 0x71D2, 0x00A00513 }, /* RV530 */ |
{ 0x71D5, 0x00A00513 }, /* M66 */ |
{ 0x7249, 0x00A00513 }, /* R580 */ |
{ 0x724B, 0x00A00513 }, /* R580 */ |
{ 0x7280, 0x00C0041F }, /* RV570 */ |
{ 0x7288, 0x00C0041F }, /* RV570 */ |
{ 0x9400, 0x00910419 }, /* R600: */ |
{ 0, 0} /* End marker */ |
}; |
|
static struct Rv6xxTMDSAMacro { |
CARD16 Device; |
CARD32 PLL; |
CARD32 TX; |
} Rv6xxTMDSAMacro[] = { |
{ 0x94C1, 0x00010416, 0x00010308 }, /* RV610 */ |
{ 0x94C3, 0x00010416, 0x00010308 }, /* RV610 */ |
{ 0x9501, 0x00010416, 0x00010308 }, /* RV670: != atombios */ |
{ 0x9505, 0x00010416, 0x00010308 }, /* RV670: != atombios */ |
{ 0x950F, 0x00010416, 0x00010308 }, /* R680 : != atombios */ |
{ 0x9581, 0x00030410, 0x00301044 }, /* M76 */ |
{ 0x9587, 0x00010416, 0x00010308 }, /* RV630 */ |
{ 0x9588, 0x00010416, 0x00010388 }, /* RV630 */ |
{ 0x9589, 0x00010416, 0x00010388 }, /* RV630 */ |
{ 0, 0, 0} /* End marker */ |
}; |
|
static void |
TMDSAVoltageControl(struct rhdOutput *Output) |
{ |
RHDPtr rhdPtr = RHDPTRI(Output); |
int i; |
|
if (rhdPtr->ChipSet < RHD_RV610) { |
for (i = 0; R5xxTMDSAMacro[i].Device; i++) |
if (R5xxTMDSAMacro[i].Device == rhdPtr->PciDeviceID) { |
RHDRegWrite(Output, TMDSA_MACRO_CONTROL, R5xxTMDSAMacro[i].Macro); |
return; |
} |
|
xf86DrvMsg(Output->scrnIndex, X_ERROR, "%s: unhandled chipset: 0x%04X.\n", |
__func__, rhdPtr->PciDeviceID); |
xf86DrvMsg(Output->scrnIndex, X_INFO, "TMDSA_MACRO_CONTROL: 0x%08X\n", |
(unsigned int) RHDRegRead(Output, TMDSA_MACRO_CONTROL)); |
} else { |
for (i = 0; Rv6xxTMDSAMacro[i].Device; i++) |
if (Rv6xxTMDSAMacro[i].Device == rhdPtr->PciDeviceID) { |
RHDRegWrite(Output, TMDSA_PLL_ADJUST, Rv6xxTMDSAMacro[i].PLL); |
RHDRegWrite(Output, TMDSA_TRANSMITTER_ADJUST, Rv6xxTMDSAMacro[i].TX); |
return; |
} |
xf86DrvMsg(Output->scrnIndex, X_ERROR, "%s: unhandled chipset: 0x%04X.\n", |
__func__, rhdPtr->PciDeviceID); |
xf86DrvMsg(Output->scrnIndex, X_INFO, "TMDSA_PLL_ADJUST: 0x%08X\n", |
(unsigned int) RHDRegRead(Output, TMDSA_PLL_ADJUST)); |
xf86DrvMsg(Output->scrnIndex, X_INFO, "TMDSA_TRANSMITTER_ADJUST: 0x%08X\n", |
(unsigned int) RHDRegRead(Output, TMDSA_TRANSMITTER_ADJUST)); |
} |
} |
|
/* |
* |
*/ |
static Bool |
TMDSAPropertyControl(struct rhdOutput *Output, |
enum rhdPropertyAction Action, enum rhdOutputProperty Property, union rhdPropertyData *val) |
{ |
struct rhdTMDSPrivate *Private = (struct rhdTMDSPrivate *) Output->Private; |
|
RHDFUNC(Output); |
switch (Action) { |
case rhdPropertyCheck: |
switch (Property) { |
case RHD_OUTPUT_COHERENT: |
return TRUE; |
default: |
return FALSE; |
} |
case rhdPropertyGet: |
switch (Property) { |
case RHD_OUTPUT_COHERENT: |
val->Bool = Private->Coherent; |
return TRUE; |
break; |
default: |
return FALSE; |
} |
break; |
case rhdPropertySet: |
switch (Property) { |
case RHD_OUTPUT_COHERENT: |
Private->Coherent = val->Bool; |
Output->Mode(Output, Private->Mode); |
Output->Power(Output, RHD_POWER_ON); |
break; |
default: |
return FALSE; |
} |
break; |
} |
return TRUE; |
} |
|
/* |
* |
*/ |
static void |
TMDSASet(struct rhdOutput *Output, DisplayModePtr Mode) |
{ |
RHDPtr rhdPtr = RHDPTRI(Output); |
struct rhdTMDSPrivate *Private = (struct rhdTMDSPrivate *) Output->Private; |
|
RHDFUNC(Output); |
|
/* Clear out some HPD events first: this should be under driver control. */ |
RHDRegMask(Output, TMDSA_TRANSMITTER_CONTROL, 0, 0x0000000C); |
RHDRegMask(Output, TMDSA_TRANSMITTER_ENABLE, 0, 0x00070000); |
RHDRegMask(Output, TMDSA_CNTL, 0, 0x00000010); |
|
/* Disable the transmitter */ |
RHDRegMask(Output, TMDSA_TRANSMITTER_ENABLE, 0, 0x00001D1F); |
|
/* Disable bit reduction and reset temporal dither */ |
RHDRegMask(Output, TMDSA_BIT_DEPTH_CONTROL, 0, 0x00010101); |
if (rhdPtr->ChipSet < RHD_R600) { |
RHDRegMask(Output, TMDSA_BIT_DEPTH_CONTROL, 0x04000000, 0x04000000); |
usleep(2); |
RHDRegMask(Output, TMDSA_BIT_DEPTH_CONTROL, 0, 0x04000000); |
} else { |
RHDRegMask(Output, TMDSA_BIT_DEPTH_CONTROL, 0x02000000, 0x02000000); |
usleep(2); |
RHDRegMask(Output, TMDSA_BIT_DEPTH_CONTROL, 0, 0x02000000); |
} |
|
/* reset phase on vsync and use RGB */ |
RHDRegMask(Output, TMDSA_CNTL, 0x00001000, 0x00011000); |
|
/* Select CRTC, select syncA, no stereosync */ |
RHDRegMask(Output, TMDSA_SOURCE_SELECT, Output->Crtc->Id, 0x00010101); |
|
/* Single link, for now */ |
RHDRegWrite(Output, TMDSA_COLOR_FORMAT, 0); |
|
/* store this for TRANSMITTER_ENABLE in TMDSAPower */ |
Private->Mode = Mode; |
if (Mode->SynthClock > 165000) { |
RHDRegMask(Output, TMDSA_CNTL, 0x01000000, 0x01000000); |
Private->RunsDualLink = TRUE; /* for TRANSMITTER_ENABLE in TMDSAPower */ |
} else { |
RHDRegMask(Output, TMDSA_CNTL, 0, 0x01000000); |
Private->RunsDualLink = FALSE; |
} |
|
/* Disable force data */ |
RHDRegMask(Output, TMDSA_FORCE_OUTPUT_CNTL, 0, 0x00000001); |
|
/* DC balancer enable */ |
RHDRegMask(Output, TMDSA_DCBALANCER_CONTROL, 0x00000001, 0x00000001); |
|
TMDSAVoltageControl(Output); |
|
/* use IDCLK */ |
RHDRegMask(Output, TMDSA_TRANSMITTER_CONTROL, 0x00000010, 0x00000010); |
|
if (Private->Coherent) |
RHDRegMask(Output, TMDSA_TRANSMITTER_CONTROL, 0x00000000, 0x10000000); |
else |
RHDRegMask(Output, TMDSA_TRANSMITTER_CONTROL, 0x10000000, 0x10000000); |
|
RHDHdmiSetMode(Private->Hdmi, Mode); |
} |
|
/* |
* |
*/ |
static void |
TMDSAPower(struct rhdOutput *Output, int Power) |
{ |
RHDPtr rhdPtr = RHDPTRI(Output); |
struct rhdTMDSPrivate *Private = (struct rhdTMDSPrivate *) Output->Private; |
|
RHDDebug(Output->scrnIndex, "%s(%s,%s)\n",__func__,Output->Name, |
rhdPowerString[Power]); |
|
switch (Power) { |
case RHD_POWER_ON: |
if (Private->PowerState == RHD_POWER_SHUTDOWN |
|| Private->PowerState == RHD_POWER_UNKNOWN) { |
RHDRegMask(Output, TMDSA_CNTL, 0x1, 0x00000001); |
|
RHDRegMask(Output, TMDSA_TRANSMITTER_CONTROL, 0x00000001, 0x00000001); |
usleep(20); |
|
/* reset transmitter PLL */ |
RHDRegMask(Output, TMDSA_TRANSMITTER_CONTROL, 0x00000002, 0x00000002); |
usleep(2); |
RHDRegMask(Output, TMDSA_TRANSMITTER_CONTROL, 0, 0x00000002); |
|
usleep(30); |
|
/* restart data synchronisation */ |
if (rhdPtr->ChipSet < RHD_R600) { |
RHDRegMask(Output, TMDSA_DATA_SYNCHRONIZATION_R500, 0x00000001, 0x00000001); |
usleep(2); |
RHDRegMask(Output, TMDSA_DATA_SYNCHRONIZATION_R500, 0x00000100, 0x00000100); |
RHDRegMask(Output, TMDSA_DATA_SYNCHRONIZATION_R500, 0, 0x00000001); |
} else { |
RHDRegMask(Output, TMDSA_DATA_SYNCHRONIZATION_R600, 0x00000001, 0x00000001); |
usleep(2); |
RHDRegMask(Output, TMDSA_DATA_SYNCHRONIZATION_R600, 0x00000100, 0x00000100); |
RHDRegMask(Output, TMDSA_DATA_SYNCHRONIZATION_R600, 0, 0x00000001); |
} |
} |
|
if (Private->RunsDualLink) { |
/* bit 9 is not known by anything below RV610, but is ignored by |
the hardware anyway */ |
RHDRegMask(Output, TMDSA_TRANSMITTER_ENABLE, 0x00001F1F, 0x00001F1F); |
} else |
RHDRegMask(Output, TMDSA_TRANSMITTER_ENABLE, 0x0000001F, 0x00001F1F); |
|
if(Output->Connector != NULL && RHDConnectorEnableHDMI(Output->Connector)) |
RHDHdmiEnable(Private->Hdmi, TRUE); |
else |
RHDHdmiEnable(Private->Hdmi, FALSE); |
Private->PowerState = RHD_POWER_ON; |
return; |
|
case RHD_POWER_RESET: |
RHDRegMask(Output, TMDSA_TRANSMITTER_ENABLE, 0, 0x00001F1F); |
/* if we do a RESET after a SHUTDOWN don't raise the power level, |
* and similarly, don't raise from UNKNOWN state. */ |
if (Private->PowerState == RHD_POWER_ON) |
Private->PowerState = RHD_POWER_RESET; |
return; |
|
case RHD_POWER_SHUTDOWN: |
default: |
RHDRegMask(Output, TMDSA_TRANSMITTER_CONTROL, 0x00000002, 0x00000002); |
usleep(2); |
RHDRegMask(Output, TMDSA_TRANSMITTER_CONTROL, 0, 0x00000001); |
RHDRegMask(Output, TMDSA_TRANSMITTER_ENABLE, 0, 0x00001F1F); |
RHDRegMask(Output, TMDSA_CNTL, 0, 0x00000001); |
RHDHdmiEnable(Private->Hdmi, FALSE); |
Private->PowerState = RHD_POWER_SHUTDOWN; |
return; |
} |
} |
|
/* |
* |
*/ |
static void |
TMDSASave(struct rhdOutput *Output) |
{ |
int ChipSet = RHDPTRI(Output)->ChipSet; |
struct rhdTMDSPrivate *Private = (struct rhdTMDSPrivate *) Output->Private; |
|
RHDFUNC(Output); |
|
Private->StoreControl = RHDRegRead(Output, TMDSA_CNTL); |
Private->StoreSource = RHDRegRead(Output, TMDSA_SOURCE_SELECT); |
Private->StoreFormat = RHDRegRead(Output, TMDSA_COLOR_FORMAT); |
Private->StoreForce = RHDRegRead(Output, TMDSA_FORCE_OUTPUT_CNTL); |
Private->StoreReduction = RHDRegRead(Output, TMDSA_BIT_DEPTH_CONTROL); |
Private->StoreDCBalancer = RHDRegRead(Output, TMDSA_DCBALANCER_CONTROL); |
|
if (ChipSet < RHD_R600) |
Private->StoreDataSynchro = RHDRegRead(Output, TMDSA_DATA_SYNCHRONIZATION_R500); |
else |
Private->StoreDataSynchro = RHDRegRead(Output, TMDSA_DATA_SYNCHRONIZATION_R600); |
|
Private->StoreTXEnable = RHDRegRead(Output, TMDSA_TRANSMITTER_ENABLE); |
Private->StoreMacro = RHDRegRead(Output, TMDSA_MACRO_CONTROL); |
Private->StoreTXControl = RHDRegRead(Output, TMDSA_TRANSMITTER_CONTROL); |
|
if (ChipSet >= RHD_RV610) |
Private->StoreTXAdjust = RHDRegRead(Output, TMDSA_TRANSMITTER_ADJUST); |
|
RHDHdmiSave(Private->Hdmi); |
|
Private->Stored = TRUE; |
} |
|
/* |
* |
*/ |
static void |
TMDSARestore(struct rhdOutput *Output) |
{ |
int ChipSet = RHDPTRI(Output)->ChipSet; |
struct rhdTMDSPrivate *Private = (struct rhdTMDSPrivate *) Output->Private; |
|
RHDFUNC(Output); |
|
if (!Private->Stored) { |
xf86DrvMsg(Output->scrnIndex, X_ERROR, |
"%s: No registers stored.\n", __func__); |
return; |
} |
|
RHDRegWrite(Output, TMDSA_CNTL, Private->StoreControl); |
RHDRegWrite(Output, TMDSA_SOURCE_SELECT, Private->StoreSource); |
RHDRegWrite(Output, TMDSA_COLOR_FORMAT, Private->StoreFormat); |
RHDRegWrite(Output, TMDSA_FORCE_OUTPUT_CNTL, Private->StoreForce); |
RHDRegWrite(Output, TMDSA_BIT_DEPTH_CONTROL, Private->StoreReduction); |
RHDRegWrite(Output, TMDSA_DCBALANCER_CONTROL, Private->StoreDCBalancer); |
|
if (ChipSet < RHD_R600) |
RHDRegWrite(Output, TMDSA_DATA_SYNCHRONIZATION_R500, Private->StoreDataSynchro); |
else |
RHDRegWrite(Output, TMDSA_DATA_SYNCHRONIZATION_R600, Private->StoreDataSynchro); |
|
RHDRegWrite(Output, TMDSA_TRANSMITTER_ENABLE, Private->StoreTXEnable); |
RHDRegWrite(Output, TMDSA_MACRO_CONTROL, Private->StoreMacro); |
RHDRegWrite(Output, TMDSA_TRANSMITTER_CONTROL, Private->StoreTXControl); |
|
if (ChipSet >= RHD_RV610) |
RHDRegWrite(Output, TMDSA_TRANSMITTER_ADJUST, Private->StoreTXAdjust); |
|
RHDHdmiRestore(Private->Hdmi); |
} |
|
/* |
* |
*/ |
static void |
TMDSADestroy(struct rhdOutput *Output) |
{ |
struct rhdTMDSPrivate *Private = (struct rhdTMDSPrivate *) Output->Private; |
RHDFUNC(Output); |
|
if (!Private) |
return; |
|
RHDHdmiDestroy(Private->Hdmi); |
|
xfree(Private); |
Output->Private = NULL; |
} |
|
/* |
* |
*/ |
struct rhdOutput * |
RHDTMDSAInit(RHDPtr rhdPtr) |
{ |
struct rhdOutput *Output; |
struct rhdTMDSPrivate *Private; |
|
RHDFUNC(rhdPtr); |
|
Output = xnfcalloc(sizeof(struct rhdOutput), 1); |
|
Output->scrnIndex = rhdPtr->scrnIndex; |
Output->Name = "TMDS A"; |
Output->Id = RHD_OUTPUT_TMDSA; |
|
Output->Sense = TMDSASense; |
Output->ModeValid = TMDSAModeValid; |
Output->Mode = TMDSASet; |
Output->Power = TMDSAPower; |
Output->Save = TMDSASave; |
Output->Restore = TMDSARestore; |
Output->Destroy = TMDSADestroy; |
Output->Property = TMDSAPropertyControl; |
|
Private = xnfcalloc(sizeof(struct rhdTMDSPrivate), 1); |
Private->RunsDualLink = FALSE; |
Private->Coherent = FALSE; |
Private->PowerState = RHD_POWER_UNKNOWN; |
|
Output->Private = Private; |
|
return Output; |
} |