/* * Copyright 2007-2008 Luc Verhaegen * Copyright 2007-2008 Matthias Hopf * Copyright 2007-2008 Egbert Eich * 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 #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; Bool HdmiEnabled; 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->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: case RHD_OUTPUT_HDMI: case RHD_OUTPUT_AUDIO_WORKAROUND: return TRUE; default: return FALSE; } case rhdPropertyGet: switch (Property) { case RHD_OUTPUT_COHERENT: val->Bool = Private->Coherent; return TRUE; case RHD_OUTPUT_HDMI: val->Bool = Private->HdmiEnabled; return TRUE; case RHD_OUTPUT_AUDIO_WORKAROUND: val->Bool = RHDHdmiGetAudioWorkaround(Private->Hdmi); return TRUE; default: return FALSE; } break; case rhdPropertySet: switch (Property) { case RHD_OUTPUT_COHERENT: Private->Coherent = val->Bool; break; case RHD_OUTPUT_HDMI: Private->HdmiEnabled = val->Bool; break; case RHD_OUTPUT_AUDIO_WORKAROUND: RHDHdmiSetAudioWorkaround(Private->Hdmi, val->Bool); break; default: return FALSE; } break; case rhdPropertyCommit: switch (Property) { case RHD_OUTPUT_COHERENT: case RHD_OUTPUT_HDMI: Output->Mode(Output, Private->Mode); Output->Power(Output, RHD_POWER_ON); break; case RHD_OUTPUT_AUDIO_WORKAROUND: RHDHdmiCommitAudioWorkaround(Private->Hdmi); 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); RHDHdmiEnable(Private->Hdmi, Private->HdmiEnabled); 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; Private->Hdmi = RHDHdmiInit(rhdPtr, Output); Output->Private = Private; return Output; }