1870 lines
54 KiB
C
1870 lines
54 KiB
C
|
/*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
#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"
|
||
|
#include "rhd_atomout.h"
|
||
|
#endif
|
||
|
|
||
|
#define FMT2_OFFSET 0x800
|
||
|
#define DIG1_OFFSET 0x000
|
||
|
#define DIG2_OFFSET 0x400
|
||
|
|
||
|
/*
|
||
|
* Transmitter
|
||
|
*/
|
||
|
struct transmitter {
|
||
|
enum rhdSensedOutput (*Sense) (struct rhdOutput *Output,
|
||
|
enum rhdConnectorType Type);
|
||
|
ModeStatus (*ModeValid) (struct rhdOutput *Output, DisplayModePtr Mode);
|
||
|
void (*Mode) (struct rhdOutput *Output, struct rhdCrtc *Crtc, DisplayModePtr Mode);
|
||
|
void (*Power) (struct rhdOutput *Output, int Power);
|
||
|
void (*Save) (struct rhdOutput *Output);
|
||
|
void (*Restore) (struct rhdOutput *Output);
|
||
|
void (*Destroy) (struct rhdOutput *Output);
|
||
|
Bool (*Property) (struct rhdOutput *Output,
|
||
|
enum rhdPropertyAction Action, enum rhdOutputProperty Property, union rhdPropertyData *val);
|
||
|
#ifdef NOT_YET
|
||
|
Bool (*WrappedPropertyCallback) (struct rhdOutput *Output,
|
||
|
enum rhdPropertyAction Action, enum rhdOutputProperty Property, union rhdPropertyData *val);
|
||
|
void *PropertyPrivate;
|
||
|
#endif
|
||
|
void *Private;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Encoder
|
||
|
*/
|
||
|
struct encoder {
|
||
|
ModeStatus (*ModeValid) (struct rhdOutput *Output, DisplayModePtr Mode);
|
||
|
void (*Mode) (struct rhdOutput *Output, struct rhdCrtc *Crtc, DisplayModePtr Mode);
|
||
|
void (*Power) (struct rhdOutput *Output, int Power);
|
||
|
void (*Save) (struct rhdOutput *Output);
|
||
|
void (*Restore) (struct rhdOutput *Output);
|
||
|
void (*Destroy) (struct rhdOutput *Output);
|
||
|
void *Private;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
enum encoderMode {
|
||
|
DISPLAYPORT = 0,
|
||
|
LVDS = 1,
|
||
|
TMDS_DVI = 2,
|
||
|
TMDS_HDMI = 3,
|
||
|
SDVO = 4
|
||
|
};
|
||
|
|
||
|
enum encoderID {
|
||
|
ENCODER_NONE,
|
||
|
ENCODER_DIG1,
|
||
|
ENCODER_DIG2
|
||
|
};
|
||
|
|
||
|
struct DIGPrivate
|
||
|
{
|
||
|
struct encoder Encoder;
|
||
|
struct transmitter Transmitter;
|
||
|
enum encoderID EncoderID;
|
||
|
enum encoderMode EncoderMode;
|
||
|
Bool Coherent;
|
||
|
Bool RunDualLink;
|
||
|
DisplayModePtr Mode;
|
||
|
struct rhdHdmi *Hdmi;
|
||
|
|
||
|
/* LVDS */
|
||
|
Bool FPDI;
|
||
|
CARD32 PowerSequenceDe2Bl;
|
||
|
CARD32 PowerSequenceDig2De;
|
||
|
CARD32 OffDelay;
|
||
|
struct rhdFMTDither FMTDither;
|
||
|
int BlLevel;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* LVTMA Transmitter
|
||
|
*/
|
||
|
|
||
|
struct LVTMATransmitterPrivate
|
||
|
{
|
||
|
Bool Stored;
|
||
|
|
||
|
CARD32 StoredTransmitterControl;
|
||
|
CARD32 StoredTransmitterAdjust;
|
||
|
CARD32 StoredPreemphasisControl;
|
||
|
CARD32 StoredMacroControl;
|
||
|
CARD32 StoredLVTMADataSynchronization;
|
||
|
CARD32 StoredTransmiterEnable;
|
||
|
CARD32 StoredPwrSeqCntl;
|
||
|
CARD32 StoredPwrSeqRevDiv;
|
||
|
CARD32 StoredPwrSeqDelay1;
|
||
|
CARD32 StoredPwrSeqDelay2;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static ModeStatus
|
||
|
LVTMATransmitterModeValid(struct rhdOutput *Output, DisplayModePtr Mode)
|
||
|
{
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
if (Mode->Flags & V_INTERLACE)
|
||
|
return MODE_NO_INTERLACE;
|
||
|
|
||
|
if (Output->Connector->Type == RHD_CONNECTOR_DVI_SINGLE
|
||
|
&& Mode->SynthClock > 165000)
|
||
|
return MODE_CLOCK_HIGH;
|
||
|
|
||
|
return MODE_OK;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
LVDSSetBacklight(struct rhdOutput *Output, int level)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *) Output->Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
Private->BlLevel = level;
|
||
|
|
||
|
RHDRegMask(Output, RV620_LVTMA_PWRSEQ_REF_DIV,
|
||
|
0x144 << LVTMA_BL_MOD_REF_DI_SHIFT,
|
||
|
0x7ff << LVTMA_BL_MOD_REF_DI_SHIFT);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_BL_MOD_CNTL,
|
||
|
0xff << LVTMA_BL_MOD_RES_SHIFT
|
||
|
| level << LVTMA_BL_MOD_LEVEL_SHIFT
|
||
|
| LVTMA_BL_MOD_EN);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static Bool
|
||
|
LVDSTransmitterPropertyControl(struct rhdOutput *Output,
|
||
|
enum rhdPropertyAction Action, enum rhdOutputProperty Property, union rhdPropertyData *val)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *) Output->Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
switch (Action) {
|
||
|
case rhdPropertyCheck:
|
||
|
if (Private->BlLevel < 0)
|
||
|
return FALSE;
|
||
|
switch (Property) {
|
||
|
case RHD_OUTPUT_BACKLIGHT:
|
||
|
return TRUE;
|
||
|
default:
|
||
|
return FALSE;
|
||
|
}
|
||
|
case rhdPropertyGet:
|
||
|
if (Private->BlLevel < 0)
|
||
|
return FALSE;
|
||
|
switch (Property) {
|
||
|
case RHD_OUTPUT_BACKLIGHT:
|
||
|
val->integer = Private->BlLevel;
|
||
|
return TRUE;
|
||
|
default:
|
||
|
return FALSE;
|
||
|
}
|
||
|
break;
|
||
|
case rhdPropertySet:
|
||
|
if (Private->BlLevel < 0)
|
||
|
return FALSE;
|
||
|
switch (Property) {
|
||
|
case RHD_OUTPUT_BACKLIGHT:
|
||
|
LVDSSetBacklight(Output, val->integer);
|
||
|
return TRUE;
|
||
|
default:
|
||
|
return FALSE;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static Bool
|
||
|
TMDSTransmitterPropertyControl(struct rhdOutput *Output,
|
||
|
enum rhdPropertyAction Action, enum rhdOutputProperty Property, union rhdPropertyData *val)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *) 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;
|
||
|
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
|
||
|
LVTMATransmitterSet(struct rhdOutput *Output, struct rhdCrtc *Crtc, DisplayModePtr Mode)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
CARD32 value = 0;
|
||
|
#ifdef ATOM_BIOS
|
||
|
AtomBiosArgRec data;
|
||
|
#endif
|
||
|
RHDPtr rhdPtr = RHDPTRI(Output);
|
||
|
Bool doCoherent = Private->Coherent;
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
/* set coherent / not coherent mode; whatever that is */
|
||
|
if (Output->Connector->Type != RHD_CONNECTOR_PANEL)
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
doCoherent ? 0 : RV62_LVTMA_BYPASS_PLL, RV62_LVTMA_BYPASS_PLL);
|
||
|
|
||
|
Private->Mode = Mode;
|
||
|
#ifdef ATOM_BIOS
|
||
|
RHDDebug(Output->scrnIndex, "%s: SynthClock: %i Hex: %x EncoderMode: %x\n",__func__,
|
||
|
(Mode->SynthClock),(Mode->SynthClock / 10), Private->EncoderMode);
|
||
|
|
||
|
/* Set up magic value that's used for list lookup */
|
||
|
value = ((Mode->SynthClock / 10 / ((Private->RunDualLink) ? 2 : 1)) & 0xffff)
|
||
|
| (Private->EncoderMode << 16)
|
||
|
| ((doCoherent ? 0x2 : 0) << 24);
|
||
|
|
||
|
RHDDebug(Output->scrnIndex, "%s: GetConditionalGoldenSettings for: %x\n", __func__, value);
|
||
|
|
||
|
/* Get data from DIG2TransmitterControl table */
|
||
|
data.val = 0x4d;
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS, ATOMBIOS_GET_CODE_DATA_TABLE,
|
||
|
&data) == ATOM_SUCCESS) {
|
||
|
AtomBiosArgRec data1;
|
||
|
CARD32 *d_p = NULL;
|
||
|
|
||
|
data1.GoldenSettings.BIOSPtr = data.CommandDataTable.loc;
|
||
|
data1.GoldenSettings.End = data1.GoldenSettings.BIOSPtr + data.CommandDataTable.size;
|
||
|
data1.GoldenSettings.value = value;
|
||
|
|
||
|
/* now find pointer */
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
|
||
|
ATOM_GET_CONDITIONAL_GOLDEN_SETTINGS, &data1) == ATOM_SUCCESS) {
|
||
|
d_p = (CARD32*)data1.GoldenSettings.BIOSPtr;
|
||
|
} else {
|
||
|
/* nothing found, now try toggling the coherent setting */
|
||
|
doCoherent = !doCoherent;
|
||
|
value = (value & ~(0x2 << 24)) | ((doCoherent ? 0x2 : 0) << 24);
|
||
|
data1.GoldenSettings.value = value;
|
||
|
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
|
||
|
ATOM_GET_CONDITIONAL_GOLDEN_SETTINGS, &data1) == ATOM_SUCCESS) {
|
||
|
d_p = (CARD32*)data1.GoldenSettings.BIOSPtr;
|
||
|
/* set coherent / not coherent mode; whatever that is */
|
||
|
xf86DrvMsg(Output->scrnIndex, X_INFO, "%s: %soherent Mode not supported, switching to %soherent.\n",
|
||
|
__func__, doCoherent ? "Inc" : "C", doCoherent ? "C" : "Inc");
|
||
|
if (Output->Connector->Type != RHD_CONNECTOR_PANEL)
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
doCoherent ? 0 : RV62_LVTMA_BYPASS_PLL, RV62_LVTMA_BYPASS_PLL);
|
||
|
} else
|
||
|
doCoherent = Private->Coherent; /* reset old value if nothing found either */
|
||
|
}
|
||
|
if (d_p) {
|
||
|
RHDDebug(Output->scrnIndex, "TransmitterAdjust: 0x%8.8x\n",d_p[0]);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_TRANSMITTER_ADJUST, d_p[0]);
|
||
|
|
||
|
RHDDebug(Output->scrnIndex, "PreemphasisControl: 0x%8.8x\n",d_p[1]);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_PREEMPHASIS_CONTROL, d_p[1]);
|
||
|
|
||
|
RHDDebug(Output->scrnIndex, "MacroControl: 0x%8.8x\n",d_p[2]);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_MACRO_CONTROL, d_p[2]);
|
||
|
} else
|
||
|
xf86DrvMsg(Output->scrnIndex, X_WARNING, "%s: cannot get golden settings\n",__func__);
|
||
|
} else
|
||
|
#endif
|
||
|
{
|
||
|
xf86DrvMsg(Output->scrnIndex, X_WARNING, "%s: No AtomBIOS supplied "
|
||
|
"electrical parameters available\n", __func__);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVTMATransmitterSave(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *digPrivate = (struct DIGPrivate *)Output->Private;
|
||
|
struct LVTMATransmitterPrivate *Private = (struct LVTMATransmitterPrivate*)digPrivate->Transmitter.Private;
|
||
|
|
||
|
Private->StoredTransmitterControl = RHDRegRead(Output, RV620_LVTMA_TRANSMITTER_CONTROL);
|
||
|
Private->StoredTransmitterAdjust = RHDRegRead(Output, RV620_LVTMA_TRANSMITTER_ADJUST);
|
||
|
Private->StoredPreemphasisControl = RHDRegRead(Output, RV620_LVTMA_PREEMPHASIS_CONTROL);
|
||
|
Private->StoredMacroControl = RHDRegRead(Output, RV620_LVTMA_MACRO_CONTROL);
|
||
|
Private->StoredLVTMADataSynchronization = RHDRegRead(Output, RV620_LVTMA_DATA_SYNCHRONIZATION);
|
||
|
Private->StoredTransmiterEnable = RHDRegRead(Output, RV620_LVTMA_TRANSMITTER_ENABLE);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVTMATransmitterRestore(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *digPrivate = (struct DIGPrivate *)Output->Private;
|
||
|
struct LVTMATransmitterPrivate *Private = (struct LVTMATransmitterPrivate*)digPrivate->Transmitter.Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
/* write control values back */
|
||
|
RHDRegWrite(Output, RV620_LVTMA_TRANSMITTER_CONTROL,Private->StoredTransmitterControl);
|
||
|
usleep (14);
|
||
|
/* reset PLL */
|
||
|
RHDRegWrite(Output, RV620_LVTMA_TRANSMITTER_CONTROL,Private->StoredTransmitterControl
|
||
|
| RV62_LVTMA_PLL_RESET);
|
||
|
usleep (10);
|
||
|
/* unreset PLL */
|
||
|
RHDRegWrite(Output, RV620_LVTMA_TRANSMITTER_CONTROL,Private->StoredTransmitterControl);
|
||
|
usleep(1000);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_TRANSMITTER_ADJUST, Private->StoredTransmitterAdjust);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_PREEMPHASIS_CONTROL, Private->StoredPreemphasisControl);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_MACRO_CONTROL, Private->StoredMacroControl);
|
||
|
/* start data synchronization */
|
||
|
RHDRegWrite(Output, RV620_LVTMA_DATA_SYNCHRONIZATION, (Private->StoredLVTMADataSynchronization
|
||
|
& ~(CARD32)RV62_LVTMA_DSYNSEL)
|
||
|
| RV62_LVTMA_PFREQCHG);
|
||
|
usleep (1);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_DATA_SYNCHRONIZATION, Private->StoredLVTMADataSynchronization);
|
||
|
usleep(10);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_DATA_SYNCHRONIZATION, Private->StoredLVTMADataSynchronization);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_TRANSMITTER_ENABLE, Private->StoredTransmiterEnable);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVTMA_TMDSTransmitterSet(struct rhdOutput *Output, struct rhdCrtc *Crtc, DisplayModePtr Mode)
|
||
|
{
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
/* TMDS Mode */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
RV62_LVTMA_USE_CLK_DATA, RV62_LVTMA_USE_CLK_DATA);
|
||
|
|
||
|
LVTMATransmitterSet(Output, Crtc, Mode);
|
||
|
|
||
|
/* use differential post divider input */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
RV62_LVTMA_IDSCKSEL, RV62_LVTMA_IDSCKSEL);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVTMA_TMDSTransmitterPower(struct rhdOutput *Output, int Power)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
switch (Power) {
|
||
|
case RHD_POWER_ON:
|
||
|
/* enable PLL */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
RV62_LVTMA_PLL_ENABLE, RV62_LVTMA_PLL_ENABLE);
|
||
|
usleep(14);
|
||
|
/* PLL reset on */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
RV62_LVTMA_PLL_RESET, RV62_LVTMA_PLL_RESET);
|
||
|
usleep(10);
|
||
|
/* PLL reset off */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
0, RV62_LVTMA_PLL_RESET);
|
||
|
usleep(1000);
|
||
|
/* start data synchronization */
|
||
|
RHDRegMask(Output, RV620_LVTMA_DATA_SYNCHRONIZATION,
|
||
|
RV62_LVTMA_PFREQCHG, RV62_LVTMA_PFREQCHG);
|
||
|
usleep(1);
|
||
|
/* restart write address logic */
|
||
|
RHDRegMask(Output, RV620_LVTMA_DATA_SYNCHRONIZATION,
|
||
|
RV62_LVTMA_DSYNSEL, RV62_LVTMA_DSYNSEL);
|
||
|
#if 1
|
||
|
/* TMDS Mode ?? */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
RV62_LVTMA_MODE, RV62_LVTMA_MODE);
|
||
|
#endif
|
||
|
/* enable lower link */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_ENABLE,
|
||
|
RV62_LVTMA_LNKL,
|
||
|
RV62_LVTMA_LNK_ALL);
|
||
|
if (Private->RunDualLink) {
|
||
|
usleep (28);
|
||
|
/* enable upper link */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_ENABLE,
|
||
|
RV62_LVTMA_LNKU,
|
||
|
RV62_LVTMA_LNKU);
|
||
|
}
|
||
|
return;
|
||
|
case RHD_POWER_RESET:
|
||
|
/* disable all links */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_ENABLE,
|
||
|
0, RV62_LVTMA_LNK_ALL);
|
||
|
return;
|
||
|
case RHD_POWER_SHUTDOWN:
|
||
|
default:
|
||
|
/* disable transmitter */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_ENABLE,
|
||
|
0, RV62_LVTMA_LNK_ALL);
|
||
|
/* PLL reset */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
RV62_LVTMA_PLL_RESET, RV62_LVTMA_PLL_RESET);
|
||
|
usleep(10);
|
||
|
/* end PLL reset */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
0, RV62_LVTMA_PLL_RESET);
|
||
|
/* disable data synchronization */
|
||
|
RHDRegMask(Output, RV620_LVTMA_DATA_SYNCHRONIZATION,
|
||
|
0, RV62_LVTMA_DSYNSEL);
|
||
|
/* reset macro control */
|
||
|
RHDRegWrite(Output, RV620_LVTMA_TRANSMITTER_ADJUST, 0);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVTMA_TMDSTransmitterSave(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *digPrivate = (struct DIGPrivate *)Output->Private;
|
||
|
struct LVTMATransmitterPrivate *Private = (struct LVTMATransmitterPrivate*)digPrivate->Transmitter.Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
LVTMATransmitterSave(Output);
|
||
|
|
||
|
Private->Stored = TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVTMA_TMDSTransmitterRestore(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *digPrivate = (struct DIGPrivate *)Output->Private;
|
||
|
struct LVTMATransmitterPrivate *Private = (struct LVTMATransmitterPrivate*)digPrivate->Transmitter.Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
if (!Private->Stored) {
|
||
|
xf86DrvMsg(Output->scrnIndex, X_ERROR,
|
||
|
"%s: No registers stored.\n", __func__);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
LVTMATransmitterRestore(Output);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVTMA_LVDSTransmitterSet(struct rhdOutput *Output, struct rhdCrtc *Crtc, DisplayModePtr Mode)
|
||
|
{
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
/* LVDS Mode */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
0, RV62_LVTMA_USE_CLK_DATA);
|
||
|
|
||
|
LVTMATransmitterSet(Output, Crtc, Mode);
|
||
|
|
||
|
/* use IDCLK */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL, RV62_LVTMA_IDSCKSEL, RV62_LVTMA_IDSCKSEL);
|
||
|
/* enable pwrseq, pwrseq overwrite PPL enable, reset */
|
||
|
RHDRegMask(Output, RV620_LVTMA_PWRSEQ_CNTL,
|
||
|
RV62_LVTMA_PWRSEQ_EN
|
||
|
| RV62_LVTMA_PLL_ENABLE_PWRSEQ_MASK
|
||
|
| RV62_LVTMA_PLL_RESET_PWRSEQ_MASK,
|
||
|
RV62_LVTMA_PWRSEQ_EN
|
||
|
| RV62_LVTMA_PLL_ENABLE_PWRSEQ_MASK
|
||
|
| RV62_LVTMA_PLL_RESET_PWRSEQ_MASK
|
||
|
);
|
||
|
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVTMA_LVDSTransmitterPower(struct rhdOutput *Output, int Power)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
CARD32 tmp, tmp1;
|
||
|
int i;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
switch (Power) {
|
||
|
case RHD_POWER_ON:
|
||
|
/* enable PLL */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
RV62_LVTMA_PLL_ENABLE, RV62_LVTMA_PLL_ENABLE);
|
||
|
usleep(14);
|
||
|
/* PLL reset on */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
RV62_LVTMA_PLL_RESET, RV62_LVTMA_PLL_RESET);
|
||
|
usleep(10);
|
||
|
/* PLL reset off */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
0, RV62_LVTMA_PLL_RESET);
|
||
|
usleep(1000);
|
||
|
/* start data synchronization */
|
||
|
RHDRegMask(Output, RV620_LVTMA_DATA_SYNCHRONIZATION,
|
||
|
RV62_LVTMA_PFREQCHG, RV62_LVTMA_PFREQCHG);
|
||
|
usleep(1);
|
||
|
/* restart write address logic */
|
||
|
RHDRegMask(Output, RV620_LVTMA_DATA_SYNCHRONIZATION,
|
||
|
RV62_LVTMA_DSYNSEL, RV62_LVTMA_DSYNSEL);
|
||
|
/* SYNCEN disables pwrseq ?? */
|
||
|
RHDRegMask(Output, RV620_LVTMA_PWRSEQ_CNTL,
|
||
|
RV62_LVTMA_PWRSEQ_DISABLE_SYNCEN_CONTROL_OF_TX_EN,
|
||
|
RV62_LVTMA_PWRSEQ_DISABLE_SYNCEN_CONTROL_OF_TX_EN);
|
||
|
/* LVDS Mode ?? */
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_CONTROL,
|
||
|
0, RV62_LVTMA_MODE);
|
||
|
/* enable links */
|
||
|
if (Private->RunDualLink) {
|
||
|
if (Private->FMTDither.LVDS24Bit)
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_ENABLE, 0x3ff, 0x3ff);
|
||
|
else
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_ENABLE, 0x1ef, 0x3ff);
|
||
|
} else {
|
||
|
if (Private->FMTDither.LVDS24Bit)
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_ENABLE, 0x1f, 0x3ff);
|
||
|
else
|
||
|
RHDRegMask(Output, RV620_LVTMA_TRANSMITTER_ENABLE, 0x0f, 0x3ff);
|
||
|
}
|
||
|
RHDRegMask(Output, RV620_LVTMA_PWRSEQ_CNTL, 0,
|
||
|
RV62_LVTMA_DIGON_OVRD | RV62_LVTMA_BLON_OVRD);
|
||
|
RHDRegMask(Output, RV620_LVTMA_PWRSEQ_REF_DIV, 3999, 0xffff); /* 4000 - 1 */
|
||
|
tmp = Private->PowerSequenceDe2Bl * 10 / 4;
|
||
|
tmp1 = Private->PowerSequenceDig2De * 10 / 4;
|
||
|
/* power sequencing delay for on / off between DIGON and SYNCEN, and SYNCEN and BLON */
|
||
|
RHDRegWrite(Output, RV620_LVTMA_PWRSEQ_DELAY1, (tmp1 << 24) | tmp1 | (tmp << 8) | (tmp << 16));
|
||
|
RHDRegWrite(Output, RV620_LVTMA_PWRSEQ_DELAY2, Private->OffDelay / 4);
|
||
|
RHDRegMask(Output, RV620_LVTMA_PWRSEQ_CNTL, 0, RV62_LVTMA_PWRSEQ_DISABLE_SYNCEN_CONTROL_OF_TX_EN);
|
||
|
for (i = 0; i < 500; i++) {
|
||
|
CARD32 tmp;
|
||
|
|
||
|
usleep(1000);
|
||
|
tmp = RHDRegRead(Output, RV620_LVTMA_PWRSEQ_STATE);
|
||
|
tmp >>= RV62_LVTMA_PWRSEQ_STATE_SHIFT;
|
||
|
tmp &= 0xff;
|
||
|
if (tmp <= RV62_POWERUP_DONE)
|
||
|
break;
|
||
|
if (tmp >= RV62_POWERDOWN_DONE)
|
||
|
break;
|
||
|
}
|
||
|
/* LCD on */
|
||
|
RHDRegMask(Output, RV620_LVTMA_PWRSEQ_CNTL, RV62_LVTMA_PWRSEQ_TARGET_STATE,
|
||
|
RV62_LVTMA_PWRSEQ_TARGET_STATE);
|
||
|
return;
|
||
|
|
||
|
case RHD_POWER_RESET:
|
||
|
/* Disable LCD and BL */
|
||
|
RHDRegMask(Output, RV620_LVTMA_PWRSEQ_CNTL, 0,
|
||
|
RV62_LVTMA_PWRSEQ_TARGET_STATE
|
||
|
| RV62_LVTMA_DIGON_OVRD
|
||
|
| RV62_LVTMA_BLON_OVRD);
|
||
|
for (i = 0; i < 500; i++) {
|
||
|
CARD32 tmp;
|
||
|
|
||
|
usleep(1000);
|
||
|
tmp = RHDRegRead(Output, RV620_LVTMA_PWRSEQ_STATE);
|
||
|
tmp >>= RV62_LVTMA_PWRSEQ_STATE_SHIFT;
|
||
|
tmp &= 0xff;
|
||
|
if (tmp >= RV62_POWERDOWN_DONE)
|
||
|
break;
|
||
|
}
|
||
|
return;
|
||
|
case RHD_POWER_SHUTDOWN:
|
||
|
LVTMA_LVDSTransmitterPower(Output, RHD_POWER_RESET);
|
||
|
/* op-amp down, bias current for output driver down, shunt resistor down */
|
||
|
RHDRegWrite(Output, RV620_LVTMA_TRANSMITTER_ADJUST, 0x00e00000);
|
||
|
/* set macro control */
|
||
|
RHDRegWrite(Output, RV620_LVTMA_MACRO_CONTROL, 0x07430408);
|
||
|
default:
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVTMA_LVDSTransmitterSave(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *digPrivate = (struct DIGPrivate *)Output->Private;
|
||
|
struct LVTMATransmitterPrivate *Private = (struct LVTMATransmitterPrivate*)digPrivate->Transmitter.Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
LVTMATransmitterSave(Output);
|
||
|
|
||
|
Private->StoredPwrSeqCntl = RHDRegRead(Output, RV620_LVTMA_PWRSEQ_CNTL);
|
||
|
Private->StoredPwrSeqRevDiv = RHDRegRead(Output, RV620_LVTMA_PWRSEQ_REF_DIV);
|
||
|
Private->StoredPwrSeqDelay1 = RHDRegRead(Output, RV620_LVTMA_PWRSEQ_DELAY1);
|
||
|
Private->StoredPwrSeqDelay2 = RHDRegRead(Output, RV620_LVTMA_PWRSEQ_DELAY2);
|
||
|
|
||
|
Private->Stored = TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVTMA_LVDSTransmitterRestore(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *digPrivate = (struct DIGPrivate *)Output->Private;
|
||
|
struct LVTMATransmitterPrivate *Private = (struct LVTMATransmitterPrivate*)digPrivate->Transmitter.Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
if (!Private->Stored) {
|
||
|
xf86DrvMsg(Output->scrnIndex, X_ERROR,
|
||
|
"%s: No registers stored.\n", __func__);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
LVTMATransmitterRestore(Output);
|
||
|
|
||
|
RHDRegWrite(Output, RV620_LVTMA_PWRSEQ_REF_DIV, Private->StoredPwrSeqRevDiv);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_PWRSEQ_DELAY1, Private->StoredPwrSeqDelay1);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_PWRSEQ_DELAY2, Private->StoredPwrSeqDelay2);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_PWRSEQ_CNTL, Private->StoredPwrSeqCntl);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVTMATransmitterDestroy(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *digPrivate = (struct DIGPrivate *)Output->Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
if (!digPrivate)
|
||
|
return;
|
||
|
|
||
|
xfree(digPrivate->Transmitter.Private);
|
||
|
}
|
||
|
|
||
|
#if defined(ATOM_BIOS) && defined(ATOM_BIOS_PARSER)
|
||
|
|
||
|
struct ATOMTransmitterPrivate
|
||
|
{
|
||
|
struct atomTransmitterConfig atomTransmitterConfig;
|
||
|
enum atomTransmitter atomTransmitterID;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static ModeStatus
|
||
|
ATOMTransmitterModeValid(struct rhdOutput *Output, DisplayModePtr Mode)
|
||
|
{
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
if (Output->Connector->Type == RHD_CONNECTOR_DVI_SINGLE
|
||
|
&& Mode->SynthClock > 165000)
|
||
|
return MODE_CLOCK_HIGH;
|
||
|
|
||
|
return MODE_OK;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
void
|
||
|
rhdPrintDigDebug(RHDPtr rhdPtr, const char *name)
|
||
|
{
|
||
|
xf86DrvMsgVerb(rhdPtr->scrnIndex, X_INFO, 7, "%s: DIGn_CNTL: n=1: 0x%x n=2: 0x%x\n",
|
||
|
name, RHDRegRead(rhdPtr, RV620_DIG1_CNTL),
|
||
|
RHDRegRead(rhdPtr, DIG2_OFFSET + RV620_DIG1_CNTL));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
ATOMTransmitterSet(struct rhdOutput *Output, struct rhdCrtc *Crtc, DisplayModePtr Mode)
|
||
|
{
|
||
|
RHDPtr rhdPtr = RHDPTRI(Output);
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
struct ATOMTransmitterPrivate *transPrivate
|
||
|
= (struct ATOMTransmitterPrivate*) Private->Transmitter.Private;
|
||
|
struct atomTransmitterConfig *atc = &transPrivate->atomTransmitterConfig;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
atc->Coherent = Private->Coherent;
|
||
|
atc->PixelClock = Mode->SynthClock;
|
||
|
|
||
|
rhdPrintDigDebug(rhdPtr,__func__);
|
||
|
|
||
|
if (Private->RunDualLink) {
|
||
|
atc->Mode = atomDualLink;
|
||
|
|
||
|
if (atc->Link == atomTransLinkA)
|
||
|
atc->Link = atomTransLinkAB;
|
||
|
else if (atc->Link == atomTransLinkB)
|
||
|
atc->Link = atomTransLinkBA;
|
||
|
|
||
|
} else {
|
||
|
atc->Mode = atomSingleLink;
|
||
|
|
||
|
if (atc->Link == atomTransLinkAB)
|
||
|
atc->Link = atomTransLinkA;
|
||
|
else if (atc->Link == atomTransLinkBA)
|
||
|
atc->Link = atomTransLinkB;
|
||
|
|
||
|
}
|
||
|
|
||
|
atc->PixelClock = Mode->SynthClock;
|
||
|
|
||
|
rhdAtomDigTransmitterControl(rhdPtr->atomBIOS, transPrivate->atomTransmitterID,
|
||
|
atomTransSetup, atc);
|
||
|
rhdPrintDigDebug(rhdPtr,__func__);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static CARD32
|
||
|
digProbeEncoder(struct rhdOutput *Output)
|
||
|
{
|
||
|
if (Output->Id == RHD_OUTPUT_KLDSKP_LVTMA) {
|
||
|
return ENCODER_DIG2;
|
||
|
} else {
|
||
|
Bool swap = (RHDRegRead(Output, RV620_DCIO_LINK_STEER_CNTL)
|
||
|
& RV62_LINK_STEER_SWAP) == RV62_LINK_STEER_SWAP;
|
||
|
|
||
|
switch (Output->Id) {
|
||
|
case RHD_OUTPUT_UNIPHYA:
|
||
|
if (swap) {
|
||
|
RHDDebug(Output->scrnIndex, "%s: detected ENCODER_DIG2 for UNIPHYA\n",__func__);
|
||
|
return ENCODER_DIG2;
|
||
|
} else {
|
||
|
RHDDebug(Output->scrnIndex, "%s: detected ENCODER_DIG1 for UNIPHYA\n",__func__);
|
||
|
return ENCODER_DIG1;
|
||
|
}
|
||
|
break;
|
||
|
case RHD_OUTPUT_UNIPHYB:
|
||
|
if (swap) {
|
||
|
RHDDebug(Output->scrnIndex, "%s: detected ENCODER_DIG1 for UNIPHYB\n",__func__);
|
||
|
return ENCODER_DIG1;
|
||
|
} else {
|
||
|
RHDDebug(Output->scrnIndex, "%s: detected ENCODER_DIG2 for UNIPHYB\n",__func__);
|
||
|
return ENCODER_DIG2;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
return ENCODER_NONE; /* should not get here */
|
||
|
}
|
||
|
}
|
||
|
return ENCODER_NONE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
ATOMTransmitterPower(struct rhdOutput *Output, int Power)
|
||
|
{
|
||
|
RHDPtr rhdPtr = RHDPTRI(Output);
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
struct ATOMTransmitterPrivate *transPrivate
|
||
|
= (struct ATOMTransmitterPrivate*) Private->Transmitter.Private;
|
||
|
struct atomTransmitterConfig *atc = &transPrivate->atomTransmitterConfig;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
rhdPrintDigDebug(rhdPtr,__func__);
|
||
|
|
||
|
if (Private->RunDualLink)
|
||
|
atc->LinkCnt = atomDualLink;
|
||
|
else
|
||
|
atc->LinkCnt = atomSingleLink;
|
||
|
|
||
|
atc->Coherent = Private->Coherent;
|
||
|
|
||
|
if (atc->Encoder == atomEncoderNone) {
|
||
|
switch (digProbeEncoder(Output)) {
|
||
|
case ENCODER_DIG1:
|
||
|
if (rhdPtr->DigEncoderOutput[0]) {
|
||
|
RHDDebug(Output->scrnIndex,"%s: DIG1 for %s already taken\n",__func__,Output->Name);
|
||
|
return;
|
||
|
}
|
||
|
atc->Encoder = atomEncoderDIG1;
|
||
|
break;
|
||
|
case ENCODER_DIG2:
|
||
|
if (rhdPtr->DigEncoderOutput[1]) {
|
||
|
RHDDebug(Output->scrnIndex,"%s: DIG2 for %s already taken\n",__func__,Output->Name);
|
||
|
return;
|
||
|
}
|
||
|
atc->Encoder = atomEncoderDIG2;
|
||
|
break;
|
||
|
default:
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch (Power) {
|
||
|
case RHD_POWER_ON:
|
||
|
rhdAtomDigTransmitterControl(rhdPtr->atomBIOS, transPrivate->atomTransmitterID,
|
||
|
atomTransEnable, atc);
|
||
|
rhdAtomDigTransmitterControl(rhdPtr->atomBIOS, transPrivate->atomTransmitterID,
|
||
|
atomTransEnableOutput, atc);
|
||
|
break;
|
||
|
case RHD_POWER_RESET:
|
||
|
rhdAtomDigTransmitterControl(rhdPtr->atomBIOS, transPrivate->atomTransmitterID,
|
||
|
atomTransDisableOutput, atc);
|
||
|
break;
|
||
|
case RHD_POWER_SHUTDOWN:
|
||
|
if (!Output->Connector || Output->Connector->Type == RHD_CONNECTOR_DVI)
|
||
|
atc->Mode = atomDVI;
|
||
|
|
||
|
rhdAtomDigTransmitterControl(rhdPtr->atomBIOS, transPrivate->atomTransmitterID,
|
||
|
atomTransDisableOutput, atc);
|
||
|
rhdAtomDigTransmitterControl(rhdPtr->atomBIOS, transPrivate->atomTransmitterID,
|
||
|
atomTransDisable, atc);
|
||
|
break;
|
||
|
}
|
||
|
rhdPrintDigDebug(rhdPtr,__func__);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
ATOMTransmitterSave(struct rhdOutput *Output)
|
||
|
{
|
||
|
RHDFUNC(Output);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
ATOMTransmitterRestore(struct rhdOutput *Output)
|
||
|
{
|
||
|
RHDFUNC(Output);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
ATOMTransmitterDestroy(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *digPrivate = (struct DIGPrivate *)Output->Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
if (!digPrivate)
|
||
|
return;
|
||
|
|
||
|
xfree(digPrivate->Transmitter.Private);
|
||
|
}
|
||
|
|
||
|
#endif /* ATOM_BIOS && ATOM_BIOS_PASER */
|
||
|
|
||
|
/*
|
||
|
* Encoder
|
||
|
*/
|
||
|
|
||
|
struct DIGEncoder
|
||
|
{
|
||
|
Bool Stored;
|
||
|
|
||
|
CARD32 StoredOff;
|
||
|
|
||
|
CARD32 StoredRegExt1DiffPostDivCntl;
|
||
|
CARD32 StoredRegExt2DiffPostDivCntl;
|
||
|
CARD32 StoredDIGClockPattern;
|
||
|
CARD32 StoredLVDSDataCntl;
|
||
|
CARD32 StoredTMDSPixelEncoding;
|
||
|
CARD32 StoredTMDSCntl;
|
||
|
CARD32 StoredDIGCntl;
|
||
|
CARD32 StoredDIGMisc1;
|
||
|
CARD32 StoredDIGMisc2;
|
||
|
CARD32 StoredDIGMisc3;
|
||
|
CARD32 StoredDCCGPclkDigCntl;
|
||
|
CARD32 StoredDCCGSymclkCntl;
|
||
|
CARD32 StoredDCIOLinkSteerCntl;
|
||
|
CARD32 StoredBlModCntl;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static ModeStatus
|
||
|
EncoderModeValid(struct rhdOutput *Output, DisplayModePtr Mode)
|
||
|
{
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
return MODE_OK;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
LVDSEncoder(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
CARD32 off;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
|
||
|
off = (Private->EncoderID == ENCODER_DIG2) ? DIG2_OFFSET : DIG1_OFFSET;
|
||
|
/* Clock pattern ? */
|
||
|
RHDRegMask(Output, off + RV620_DIG1_CLOCK_PATTERN, 0x0063, 0xFFFF);
|
||
|
/* set panel type: 18/24 bit mode */
|
||
|
RHDRegMask(Output, off + RV620_LVDS1_DATA_CNTL,
|
||
|
(Private->FMTDither.LVDS24Bit ? RV62_LVDS_24BIT_ENABLE : 0)
|
||
|
| (Private->FPDI ? RV62_LVDS_24BIT_FORMAT : 0),
|
||
|
RV62_LVDS_24BIT_ENABLE | RV62_LVDS_24BIT_FORMAT);
|
||
|
|
||
|
Output->Crtc->FMTModeSet(Output->Crtc, &Private->FMTDither);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
TMDSEncoder(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
CARD32 off;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
off = (Private->EncoderID == ENCODER_DIG2) ? DIG2_OFFSET : DIG1_OFFSET;
|
||
|
/* clock pattern ? */
|
||
|
RHDRegMask(Output, off + RV620_DIG1_CLOCK_PATTERN, 0x001F, 0xFFFF);
|
||
|
/* color format RGB - normal color format 24bpp, Twin-Single 30bpp or Dual 48bpp*/
|
||
|
RHDRegMask(Output, off + RV620_TMDS1_CNTL, 0x0,
|
||
|
RV62_TMDS_PIXEL_ENCODING | RV62_TMDS_COLOR_FORMAT);
|
||
|
/* no dithering */
|
||
|
Output->Crtc->FMTModeSet(Output->Crtc, NULL);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
EncoderSet(struct rhdOutput *Output, struct rhdCrtc *Crtc, DisplayModePtr Mode)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
RHDPtr rhdPtr = RHDPTRI(Output);
|
||
|
CARD32 off;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
off = (Private->EncoderID == ENCODER_DIG2) ? DIG2_OFFSET : DIG1_OFFSET;
|
||
|
|
||
|
rhdPrintDigDebug(rhdPtr,__func__);
|
||
|
|
||
|
RHDRegMask(Output, off + RV620_DIG1_CNTL, Output->Crtc->Id,
|
||
|
RV62_DIG_SOURCE_SELECT);
|
||
|
|
||
|
if (Output->Id == RHD_OUTPUT_UNIPHYA) {
|
||
|
/* select LinkA ?? */
|
||
|
RHDRegMask(Output, RV620_DCIO_LINK_STEER_CNTL,
|
||
|
((Private->EncoderID == ENCODER_DIG2)
|
||
|
? RV62_LINK_STEER_SWAP
|
||
|
: 0), RV62_LINK_STEER_SWAP); /* swap if DIG2 */
|
||
|
if (!Private->RunDualLink) {
|
||
|
RHDRegMask(Output, off + RV620_DIG1_CNTL,
|
||
|
0,
|
||
|
RV62_DIG_SWAP |RV62_DIG_DUAL_LINK_ENABLE);
|
||
|
} else {
|
||
|
RHDRegMask(Output, off + RV620_DIG1_CNTL,
|
||
|
RV62_DIG_DUAL_LINK_ENABLE,
|
||
|
RV62_DIG_SWAP | RV62_DIG_DUAL_LINK_ENABLE);
|
||
|
}
|
||
|
} else if (Output->Id == RHD_OUTPUT_UNIPHYB) {
|
||
|
/* select LinkB ?? */
|
||
|
RHDRegMask(Output, RV620_DCIO_LINK_STEER_CNTL,
|
||
|
((Private->EncoderID == ENCODER_DIG2)
|
||
|
? 0
|
||
|
: RV62_LINK_STEER_SWAP), RV62_LINK_STEER_SWAP);
|
||
|
if (!Private->RunDualLink)
|
||
|
RHDRegMask(Output, off + RV620_DIG1_CNTL,
|
||
|
0,
|
||
|
RV62_DIG_SWAP | RV62_DIG_DUAL_LINK_ENABLE);
|
||
|
else
|
||
|
RHDRegMask(Output, off + RV620_DIG1_CNTL,
|
||
|
RV62_DIG_SWAP | RV62_DIG_DUAL_LINK_ENABLE,
|
||
|
RV62_DIG_SWAP | RV62_DIG_DUAL_LINK_ENABLE);
|
||
|
} else { /* LVTMA */
|
||
|
RHDRegMask(Output, RV620_EXT2_DIFF_POST_DIV_CNTL, 0, RV62_EXT2_DIFF_DRIVER_ENABLE);
|
||
|
}
|
||
|
|
||
|
if (Private->EncoderMode == LVDS)
|
||
|
LVDSEncoder(Output);
|
||
|
else if (Private->EncoderMode == DISPLAYPORT)
|
||
|
dbgprintf("No displayport support yet!",__FILE__, __LINE__, __func__); /* bugger ! */
|
||
|
else
|
||
|
TMDSEncoder(Output);
|
||
|
|
||
|
/* Start DIG, set links, disable stereo sync, select FMT source */
|
||
|
RHDRegMask(Output, off + RV620_DIG1_CNTL,
|
||
|
(Private->EncoderMode & 0x7) << 8
|
||
|
| RV62_DIG_START
|
||
|
| (Private->RunDualLink ? RV62_DIG_DUAL_LINK_ENABLE : 0)
|
||
|
| Output->Crtc->Id,
|
||
|
RV62_DIG_MODE
|
||
|
| RV62_DIG_START
|
||
|
| RV62_DIG_DUAL_LINK_ENABLE
|
||
|
| RV62_DIG_STEREOSYNC_SELECT
|
||
|
| RV62_DIG_SOURCE_SELECT);
|
||
|
rhdPrintDigDebug(rhdPtr,__func__);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
EncoderPower(struct rhdOutput *Output, int Power)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
CARD32 off;
|
||
|
enum encoderID EncoderID = Private->EncoderID;
|
||
|
RHDPtr rhdPtr = Output->rhdPtr;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
if (EncoderID == ENCODER_NONE) {
|
||
|
EncoderID = digProbeEncoder(Output);
|
||
|
switch (EncoderID) {
|
||
|
case ENCODER_DIG1:
|
||
|
if (rhdPtr->DigEncoderOutput[0]) {
|
||
|
RHDDebug(Output->scrnIndex,"%s: DIG1 for %s already taken\n",__func__,Output->Name);
|
||
|
return;
|
||
|
}
|
||
|
break;
|
||
|
case ENCODER_DIG2:
|
||
|
if (rhdPtr->DigEncoderOutput[1]) {
|
||
|
RHDDebug(Output->scrnIndex,"%s: DIG2 for %s already taken\n",__func__,Output->Name);
|
||
|
return;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
off = (EncoderID == ENCODER_DIG2) ? DIG2_OFFSET : DIG1_OFFSET;
|
||
|
|
||
|
/* clock src is pixel PLL */
|
||
|
RHDRegMask(Output, RV620_DCCG_SYMCLK_CNTL, 0x0,
|
||
|
0x3 << ((EncoderID == ENCODER_DIG2)
|
||
|
? RV62_SYMCLKB_SRC_SHIFT
|
||
|
: RV62_SYMCLKA_SRC_SHIFT));
|
||
|
|
||
|
rhdPrintDigDebug(rhdPtr,__func__);
|
||
|
switch (Power) {
|
||
|
case RHD_POWER_ON:
|
||
|
RHDDebug(Output->scrnIndex,"%s(RHD_POWER_ON, %i)\n",__func__,
|
||
|
EncoderID);
|
||
|
/* enable DIG */
|
||
|
RHDRegMask(Output, off + RV620_DIG1_CNTL, 0x10, 0x10);
|
||
|
RHDRegMask(Output, (EncoderID == ENCODER_DIG2)
|
||
|
? RV620_DCCG_PCLK_DIGB_CNTL
|
||
|
: RV620_DCCG_PCLK_DIGA_CNTL,
|
||
|
RV62_PCLK_DIGA_ON, RV62_PCLK_DIGA_ON); /* @@@ */
|
||
|
rhdPrintDigDebug(rhdPtr,__func__);
|
||
|
return;
|
||
|
case RHD_POWER_RESET:
|
||
|
case RHD_POWER_SHUTDOWN:
|
||
|
default:
|
||
|
RHDDebug(Output->scrnIndex,"%s(RHD_POWER_SHUTDOWN, %i)\n",__func__,
|
||
|
EncoderID);
|
||
|
/* disable differential clock driver */
|
||
|
if (EncoderID == ENCODER_DIG1)
|
||
|
RHDRegMask(Output, RV620_EXT1_DIFF_POST_DIV_CNTL,
|
||
|
0,
|
||
|
RV62_EXT1_DIFF_DRIVER_ENABLE);
|
||
|
else
|
||
|
RHDRegMask(Output, RV620_EXT2_DIFF_POST_DIV_CNTL,
|
||
|
0,
|
||
|
RV62_EXT2_DIFF_DRIVER_ENABLE);
|
||
|
/* disable DIG */
|
||
|
RHDRegMask(Output, off + RV620_DIG1_CNTL, 0x0, 0x10);
|
||
|
RHDRegMask(Output, (EncoderID == ENCODER_DIG2)
|
||
|
? RV620_DCCG_PCLK_DIGB_CNTL
|
||
|
: RV620_DCCG_PCLK_DIGA_CNTL,
|
||
|
0, RV62_PCLK_DIGA_ON); /* @@@ */
|
||
|
rhdPrintDigDebug(rhdPtr,__func__);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
EncoderSave(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *digPrivate = (struct DIGPrivate *)Output->Private;
|
||
|
struct DIGEncoder *Private = (struct DIGEncoder *)(digPrivate->Encoder.Private);
|
||
|
CARD32 off;
|
||
|
enum encoderID EncoderID;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
EncoderID = digProbeEncoder(Output);
|
||
|
off = (EncoderID == ENCODER_DIG2) ? DIG2_OFFSET : DIG1_OFFSET;
|
||
|
Private->StoredOff = off;
|
||
|
|
||
|
Private->StoredRegExt1DiffPostDivCntl = RHDRegRead(Output, off + RV620_EXT1_DIFF_POST_DIV_CNTL);
|
||
|
Private->StoredRegExt2DiffPostDivCntl = RHDRegRead(Output, off + RV620_EXT2_DIFF_POST_DIV_CNTL);
|
||
|
Private->StoredDIGClockPattern = RHDRegRead(Output, off + RV620_DIG1_CLOCK_PATTERN);
|
||
|
Private->StoredLVDSDataCntl = RHDRegRead(Output, off + RV620_LVDS1_DATA_CNTL);
|
||
|
Private->StoredDIGCntl = RHDRegRead(Output, off + RV620_DIG1_CNTL);
|
||
|
Private->StoredTMDSCntl = RHDRegRead(Output, off + RV620_TMDS1_CNTL);
|
||
|
Private->StoredDCIOLinkSteerCntl = RHDRegRead(Output, RV620_DCIO_LINK_STEER_CNTL);
|
||
|
Private->StoredDCCGPclkDigCntl = RHDRegRead(Output,
|
||
|
(off == DIG2_OFFSET)
|
||
|
? RV620_DCCG_PCLK_DIGB_CNTL
|
||
|
: RV620_DCCG_PCLK_DIGA_CNTL);
|
||
|
Private->StoredDCCGSymclkCntl = RHDRegRead(Output, RV620_DCCG_SYMCLK_CNTL);
|
||
|
Private->StoredBlModCntl = RHDRegRead(Output, RV620_LVTMA_BL_MOD_CNTL);
|
||
|
|
||
|
Private->Stored = TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
EncoderRestore(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *digPrivate = (struct DIGPrivate *)Output->Private;
|
||
|
struct DIGEncoder *Private = (struct DIGEncoder *)(digPrivate->Encoder.Private);
|
||
|
CARD32 off;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
if (!Private->Stored) {
|
||
|
xf86DrvMsg(Output->scrnIndex, X_ERROR,
|
||
|
"%s: No registers stored.\n", __func__);
|
||
|
return;
|
||
|
}
|
||
|
off = Private->StoredOff;
|
||
|
|
||
|
RHDRegWrite(Output, off + RV620_EXT1_DIFF_POST_DIV_CNTL, Private->StoredRegExt1DiffPostDivCntl);
|
||
|
RHDRegWrite(Output, off + RV620_EXT2_DIFF_POST_DIV_CNTL, Private->StoredRegExt2DiffPostDivCntl);
|
||
|
/* reprogram all values but don't start the encoder, yet */
|
||
|
RHDRegWrite(Output, off + RV620_DIG1_CNTL, Private->StoredDIGCntl & ~(CARD32)RV62_DIG_START);
|
||
|
RHDRegWrite(Output, RV620_DCIO_LINK_STEER_CNTL, Private->StoredDCIOLinkSteerCntl);
|
||
|
RHDRegWrite(Output, off + RV620_DIG1_CLOCK_PATTERN, Private->StoredDIGClockPattern);
|
||
|
RHDRegWrite(Output, off + RV620_LVDS1_DATA_CNTL, Private->StoredLVDSDataCntl);
|
||
|
RHDRegWrite(Output, off + RV620_TMDS1_CNTL, Private->StoredTMDSCntl);
|
||
|
RHDRegWrite(Output, (off == DIG2_OFFSET)
|
||
|
? RV620_DCCG_PCLK_DIGB_CNTL
|
||
|
: RV620_DCCG_PCLK_DIGA_CNTL,
|
||
|
Private->StoredDCCGPclkDigCntl);
|
||
|
/* now enable the encoder */
|
||
|
RHDRegWrite(Output, off + RV620_DIG1_CNTL, Private->StoredDIGCntl);
|
||
|
RHDRegWrite(Output, RV620_DCCG_SYMCLK_CNTL, Private->StoredDCCGSymclkCntl);
|
||
|
RHDRegWrite(Output, RV620_LVTMA_BL_MOD_CNTL, Private->StoredBlModCntl);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
EncoderDestroy(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *digPrivate = (struct DIGPrivate *)Output->Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
if (!digPrivate || !digPrivate->Encoder.Private)
|
||
|
return;
|
||
|
|
||
|
xfree(digPrivate->Encoder.Private);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Housekeeping
|
||
|
*/
|
||
|
void
|
||
|
GetLVDSInfo(RHDPtr rhdPtr, struct DIGPrivate *Private)
|
||
|
{
|
||
|
CARD32 off = (Private->EncoderID == ENCODER_DIG2) ? DIG2_OFFSET : DIG1_OFFSET;
|
||
|
CARD32 tmp;
|
||
|
|
||
|
RHDFUNC(rhdPtr);
|
||
|
|
||
|
Private->FPDI = ((RHDRegRead(rhdPtr, off + RV620_LVDS1_DATA_CNTL)
|
||
|
& RV62_LVDS_24BIT_FORMAT) != 0);
|
||
|
Private->RunDualLink = ((RHDRegRead(rhdPtr, off + RV620_DIG1_CNTL)
|
||
|
& RV62_DIG_DUAL_LINK_ENABLE) != 0);
|
||
|
Private->FMTDither.LVDS24Bit = ((RHDRegRead(rhdPtr, off + RV620_LVDS1_DATA_CNTL)
|
||
|
& RV62_LVDS_24BIT_ENABLE) != 0);
|
||
|
|
||
|
tmp = RHDRegRead(rhdPtr, RV620_LVTMA_BL_MOD_CNTL);
|
||
|
if (tmp & 0x1)
|
||
|
Private->BlLevel = ( tmp >> LVTMA_BL_MOD_LEVEL_SHIFT ) & 0xff;
|
||
|
else
|
||
|
Private->BlLevel = -1;
|
||
|
|
||
|
tmp = RHDRegRead(rhdPtr, RV620_LVTMA_PWRSEQ_REF_DIV);
|
||
|
tmp &= 0xffff;
|
||
|
tmp += 1;
|
||
|
tmp /= 1000;
|
||
|
Private->PowerSequenceDig2De = Private->PowerSequenceDe2Bl =
|
||
|
RHDRegRead(rhdPtr, RV620_LVTMA_PWRSEQ_REF_DIV);
|
||
|
Private->PowerSequenceDig2De = ((Private->PowerSequenceDig2De & 0xff) * tmp) / 10;
|
||
|
Private->PowerSequenceDe2Bl = (((Private->PowerSequenceDe2Bl >> 8) & 0xff) * tmp) / 10;
|
||
|
Private->OffDelay = RHDRegRead(rhdPtr, RV620_LVTMA_PWRSEQ_DELAY2);
|
||
|
Private->OffDelay *= tmp;
|
||
|
|
||
|
/* This is really ugly! */
|
||
|
{
|
||
|
CARD32 fmt_offset;
|
||
|
|
||
|
tmp = RHDRegRead(rhdPtr, off + RV620_DIG1_CNTL);
|
||
|
fmt_offset = (tmp & RV62_DIG_SOURCE_SELECT) ? FMT2_OFFSET :0;
|
||
|
tmp = RHDRegRead(rhdPtr, fmt_offset + RV620_FMT1_BIT_DEPTH_CONTROL);
|
||
|
Private->FMTDither.LVDSSpatialDither = ((tmp & 0x100) != 0);
|
||
|
Private->FMTDither.LVDSGreyLevel = ((tmp & 0x10000) != 0);
|
||
|
Private->FMTDither.LVDSTemporalDither
|
||
|
= (Private->FMTDither.LVDSGreyLevel > 0) || ((tmp & 0x1000000) != 0);
|
||
|
}
|
||
|
|
||
|
#ifdef ATOM_BIOS
|
||
|
{
|
||
|
AtomBiosArgRec data;
|
||
|
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
|
||
|
ATOM_LVDS_FPDI, &data) == ATOM_SUCCESS)
|
||
|
Private->FPDI = data.val;
|
||
|
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
|
||
|
ATOM_LVDS_DUALLINK, &data) == ATOM_SUCCESS)
|
||
|
Private->RunDualLink = data.val;
|
||
|
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
|
||
|
ATOM_LVDS_GREYLVL, &data) == ATOM_SUCCESS)
|
||
|
Private->FMTDither.LVDSGreyLevel = data.val;
|
||
|
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
|
||
|
ATOM_LVDS_SEQ_DIG_ONTO_DE, &data) == ATOM_SUCCESS)
|
||
|
Private->PowerSequenceDig2De = data.val;
|
||
|
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
|
||
|
ATOM_LVDS_SEQ_DE_TO_BL, &data) == ATOM_SUCCESS)
|
||
|
Private->PowerSequenceDe2Bl = data.val;
|
||
|
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
|
||
|
ATOM_LVDS_OFF_DELAY, &data) == ATOM_SUCCESS)
|
||
|
Private->OffDelay = data.val;
|
||
|
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
|
||
|
ATOM_LVDS_24BIT, &data) == ATOM_SUCCESS)
|
||
|
Private->FMTDither.LVDS24Bit = data.val;
|
||
|
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
|
||
|
ATOM_LVDS_SPATIAL_DITHER, &data) == ATOM_SUCCESS)
|
||
|
Private->FMTDither.LVDSSpatialDither = data.val;
|
||
|
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
|
||
|
ATOM_LVDS_TEMPORAL_DITHER, &data) == ATOM_SUCCESS)
|
||
|
Private->FMTDither.LVDSTemporalDither = data.val;
|
||
|
|
||
|
Private->PowerSequenceDe2Bl = data.val;
|
||
|
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Infrastructure
|
||
|
*/
|
||
|
|
||
|
static ModeStatus
|
||
|
DigModeValid(struct rhdOutput *Output, DisplayModePtr Mode)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
struct transmitter *Transmitter = &Private->Transmitter;
|
||
|
struct encoder *Encoder = &Private->Encoder;
|
||
|
ModeStatus Status;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
if ((Status = Transmitter->ModeValid(Output, Mode)) == MODE_OK)
|
||
|
return ((Encoder->ModeValid(Output, Mode)));
|
||
|
else
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
DigPower(struct rhdOutput *Output, int Power)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
struct transmitter *Transmitter = &Private->Transmitter;
|
||
|
struct encoder *Encoder = &Private->Encoder;
|
||
|
Bool enableHDMI;
|
||
|
|
||
|
RHDDebug(Output->scrnIndex, "%s(%s,%s)\n",__func__,Output->Name,
|
||
|
rhdPowerString[Power]);
|
||
|
|
||
|
if(Output->Connector != NULL) {
|
||
|
/* check if attached monitor supports HDMI */
|
||
|
enableHDMI = RHDConnectorEnableHDMI(Output->Connector);
|
||
|
if (enableHDMI && Private->EncoderMode == TMDS_DVI)
|
||
|
Private->EncoderMode = TMDS_HDMI;
|
||
|
else if (!enableHDMI && Private->EncoderMode == TMDS_HDMI)
|
||
|
Private->EncoderMode = TMDS_DVI;
|
||
|
}
|
||
|
|
||
|
switch (Power) {
|
||
|
case RHD_POWER_ON:
|
||
|
Encoder->Power(Output, Power);
|
||
|
Transmitter->Power(Output, Power);
|
||
|
RHDHdmiEnable(Private->Hdmi, Private->EncoderMode == TMDS_HDMI);
|
||
|
return;
|
||
|
case RHD_POWER_RESET:
|
||
|
Transmitter->Power(Output, Power);
|
||
|
Encoder->Power(Output, Power);
|
||
|
return;
|
||
|
case RHD_POWER_SHUTDOWN:
|
||
|
default:
|
||
|
Transmitter->Power(Output, Power);
|
||
|
Encoder->Power(Output, Power);
|
||
|
RHDHdmiEnable(Private->Hdmi, FALSE);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static Bool
|
||
|
DigPropertyControl(struct rhdOutput *Output,
|
||
|
enum rhdPropertyAction Action, enum rhdOutputProperty Property, union rhdPropertyData *val)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
switch(Property) {
|
||
|
case RHD_OUTPUT_COHERENT:
|
||
|
case RHD_OUTPUT_BACKLIGHT:
|
||
|
{
|
||
|
if (!Private->Transmitter.Property)
|
||
|
return FALSE;
|
||
|
Private->Transmitter.Property(Output, Action, Property, val);
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
return FALSE;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
DigMode(struct rhdOutput *Output, DisplayModePtr Mode)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
struct transmitter *Transmitter = &Private->Transmitter;
|
||
|
struct encoder *Encoder = &Private->Encoder;
|
||
|
struct rhdCrtc *Crtc = Output->Crtc;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
/*
|
||
|
* For dual link capable DVI we need to decide from the pix clock if we are dual link.
|
||
|
* Do it here as it is convenient.
|
||
|
*/
|
||
|
if (Output->Connector->Type == RHD_CONNECTOR_DVI)
|
||
|
Private->RunDualLink = (Mode->SynthClock > 165000) ? TRUE : FALSE;
|
||
|
|
||
|
Encoder->Mode(Output, Crtc, Mode);
|
||
|
Transmitter->Mode(Output, Crtc, Mode);
|
||
|
RHDHdmiSetMode(Private->Hdmi, Mode);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
DigSave(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
struct transmitter *Transmitter = &Private->Transmitter;
|
||
|
struct encoder *Encoder = &Private->Encoder;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
Encoder->Save(Output);
|
||
|
Transmitter->Save(Output);
|
||
|
RHDHdmiSave(Private->Hdmi);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
DigRestore(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
struct transmitter *Transmitter = &Private->Transmitter;
|
||
|
struct encoder *Encoder = &Private->Encoder;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
Encoder->Restore(Output);
|
||
|
Transmitter->Restore(Output);
|
||
|
RHDHdmiRestore(Private->Hdmi);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static void
|
||
|
DigDestroy(struct rhdOutput *Output)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
struct transmitter *Transmitter = &Private->Transmitter;
|
||
|
struct encoder *Encoder = &Private->Encoder;
|
||
|
|
||
|
RHDFUNC(Output);
|
||
|
|
||
|
Encoder->Destroy(Output);
|
||
|
Transmitter->Destroy(Output);
|
||
|
RHDHdmiDestroy(Private->Hdmi);
|
||
|
#ifdef NOT_YET
|
||
|
if (Transmitter->PropertyPrivate)
|
||
|
RhdAtomDestroyBacklightControlProperty(Output, Transmitter->PropertyPrivate);
|
||
|
#endif
|
||
|
xfree(Private);
|
||
|
Output->Private = NULL;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static Bool
|
||
|
DigAllocFree(struct rhdOutput *Output, enum rhdOutputAllocation Alloc)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
RHDPtr rhdPtr = RHDPTRI(Output);
|
||
|
char *TransmitterName;
|
||
|
|
||
|
RHDFUNC(rhdPtr);
|
||
|
|
||
|
switch (Output->Id) {
|
||
|
case RHD_OUTPUT_KLDSKP_LVTMA:
|
||
|
TransmitterName = "KLDSKP_LVTMA";
|
||
|
break;
|
||
|
case RHD_OUTPUT_UNIPHYA:
|
||
|
TransmitterName = "UNIPHYA";
|
||
|
break;
|
||
|
case RHD_OUTPUT_UNIPHYB:
|
||
|
TransmitterName = "UNIPHYB";
|
||
|
break;
|
||
|
default:
|
||
|
return FALSE;
|
||
|
}
|
||
|
switch (Alloc) {
|
||
|
case RHD_OUTPUT_ALLOC:
|
||
|
|
||
|
if (Private->EncoderID != ENCODER_NONE)
|
||
|
return TRUE;
|
||
|
|
||
|
/*
|
||
|
* LVTMA can only use DIG2. Thus exclude
|
||
|
* DIG1 for LVTMA and prefer it for the
|
||
|
* UNIPHYs.
|
||
|
*/
|
||
|
if (Output->Id == RHD_OUTPUT_KLDSKP_LVTMA) {
|
||
|
if (!rhdPtr->DigEncoderOutput[1]) {
|
||
|
rhdPtr->DigEncoderOutput[1] = Output;
|
||
|
Private->EncoderID = ENCODER_DIG2;
|
||
|
xf86DrvMsg(Output->scrnIndex, X_INFO,
|
||
|
"Mapping DIG2 encoder to %s\n",TransmitterName);
|
||
|
return TRUE;
|
||
|
} else
|
||
|
return FALSE;
|
||
|
} else {
|
||
|
struct ATOMTransmitterPrivate *transPrivate =
|
||
|
(struct ATOMTransmitterPrivate *)Private->Transmitter.Private;
|
||
|
struct atomTransmitterConfig *atc = &transPrivate->atomTransmitterConfig;
|
||
|
if (!rhdPtr->DigEncoderOutput[0]) {
|
||
|
rhdPtr->DigEncoderOutput[0] = Output;
|
||
|
Private->EncoderID = ENCODER_DIG1;
|
||
|
atc->Encoder = atomEncoderDIG1;
|
||
|
xf86DrvMsg(Output->scrnIndex, X_INFO,
|
||
|
"Mapping DIG1 encoder to %s\n",TransmitterName);
|
||
|
return TRUE;
|
||
|
} else if (!rhdPtr->DigEncoderOutput[1]) {
|
||
|
rhdPtr->DigEncoderOutput[1] = Output;
|
||
|
Private->EncoderID = ENCODER_DIG2;
|
||
|
atc->Encoder = atomEncoderDIG2;
|
||
|
xf86DrvMsg(Output->scrnIndex, X_INFO,
|
||
|
"Mapping DIG2 encoder to %s\n",TransmitterName);
|
||
|
return TRUE;
|
||
|
} else
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
case RHD_OUTPUT_FREE:
|
||
|
Private->EncoderID = ENCODER_NONE;
|
||
|
if (rhdPtr->DigEncoderOutput[0] == Output) {
|
||
|
rhdPtr->DigEncoderOutput[0] = NULL;
|
||
|
return TRUE;
|
||
|
} else if (rhdPtr->DigEncoderOutput[1] == Output) {
|
||
|
rhdPtr->DigEncoderOutput[1] = NULL;
|
||
|
return TRUE;
|
||
|
} else
|
||
|
return FALSE;
|
||
|
break;
|
||
|
default:
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
static Bool
|
||
|
rhdDIGSetCoherent(RHDPtr rhdPtr,struct rhdOutput *Output)
|
||
|
{
|
||
|
Bool coherent = FALSE;
|
||
|
// int from = X_CONFIG;
|
||
|
|
||
|
// switch (RhdParseBooleanOption(&rhdPtr->coherent, Output->Name)) {
|
||
|
// case RHD_OPTION_NOT_SET:
|
||
|
// case RHD_OPTION_DEFAULT:
|
||
|
// from = X_DEFAULT;
|
||
|
// coherent = FALSE;
|
||
|
// break;
|
||
|
// case RHD_OPTION_ON:
|
||
|
// coherent = TRUE;
|
||
|
// break;
|
||
|
// case RHD_OPTION_OFF:
|
||
|
// coherent = FALSE;
|
||
|
// break;
|
||
|
// }
|
||
|
// xf86DrvMsg(rhdPtr->scrnIndex,from,"Setting %s to %scoherent\n",
|
||
|
// Output->Name,coherent ? "" : "in");
|
||
|
|
||
|
return coherent;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
#ifdef NOT_YET
|
||
|
static Bool
|
||
|
digTransmitterPropertyWrapper(struct rhdOutput *Output,
|
||
|
enum rhdPropertyAction Action,
|
||
|
enum rhdOutputProperty Property,
|
||
|
union rhdPropertyData *val)
|
||
|
{
|
||
|
struct DIGPrivate *Private = (struct DIGPrivate *)Output->Private;
|
||
|
void *storePrivate = Output->Private;
|
||
|
Bool (*func)(struct rhdOutput *,enum rhdPropertyAction, enum rhdOutputProperty,
|
||
|
union rhdPropertyData *) = Private->Transmitter.WrappedPropertyCallback;
|
||
|
Bool ret;
|
||
|
|
||
|
Output->Private = Private->Transmitter.PropertyPrivate;
|
||
|
ret = func(Output, Action, Property, val);
|
||
|
Output->Private = storePrivate;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
struct rhdOutput *
|
||
|
RHDDIGInit(RHDPtr rhdPtr, enum rhdOutputType outputType, CARD8 ConnectorType)
|
||
|
{
|
||
|
struct rhdOutput *Output;
|
||
|
struct DIGPrivate *Private;
|
||
|
struct DIGEncoder *Encoder;
|
||
|
|
||
|
RHDFUNC(rhdPtr);
|
||
|
|
||
|
Output = xnfcalloc(sizeof(struct rhdOutput), 1);
|
||
|
|
||
|
Output->scrnIndex = rhdPtr->scrnIndex;
|
||
|
Output->Id = outputType;
|
||
|
|
||
|
Output->Sense = NULL;
|
||
|
Output->ModeValid = DigModeValid;
|
||
|
Output->Mode = DigMode;
|
||
|
Output->Power = DigPower;
|
||
|
Output->Save = DigSave;
|
||
|
Output->Restore = DigRestore;
|
||
|
Output->Destroy = DigDestroy;
|
||
|
Output->Property = DigPropertyControl;
|
||
|
Output->AllocFree = DigAllocFree;
|
||
|
|
||
|
Private = xnfcalloc(sizeof(struct DIGPrivate), 1);
|
||
|
Output->Private = Private;
|
||
|
|
||
|
Private->EncoderID = ENCODER_NONE;
|
||
|
|
||
|
switch (outputType) {
|
||
|
case RHD_OUTPUT_UNIPHYA:
|
||
|
#if defined (ATOM_BIOS) && defined (ATOM_BIOS_PARSER)
|
||
|
Output->Name = "UNIPHY_A";
|
||
|
Private->Transmitter.Private =
|
||
|
(struct ATOMTransmitterPrivate *)xnfcalloc(sizeof (struct ATOMTransmitterPrivate), 1);
|
||
|
|
||
|
Private->Transmitter.Sense = NULL;
|
||
|
Private->Transmitter.ModeValid = ATOMTransmitterModeValid;
|
||
|
Private->Transmitter.Mode = ATOMTransmitterSet;
|
||
|
Private->Transmitter.Power = ATOMTransmitterPower;
|
||
|
Private->Transmitter.Save = ATOMTransmitterSave;
|
||
|
Private->Transmitter.Restore = ATOMTransmitterRestore;
|
||
|
Private->Transmitter.Destroy = ATOMTransmitterDestroy;
|
||
|
Private->Transmitter.Property = TMDSTransmitterPropertyControl;
|
||
|
{
|
||
|
struct ATOMTransmitterPrivate *transPrivate =
|
||
|
(struct ATOMTransmitterPrivate *)Private->Transmitter.Private;
|
||
|
struct atomTransmitterConfig *atc = &transPrivate->atomTransmitterConfig;
|
||
|
atc->Coherent = Private->Coherent = rhdDIGSetCoherent(rhdPtr, Output);
|
||
|
atc->Link = atomTransLinkA;
|
||
|
atc->Encoder = atomEncoderNone;
|
||
|
if (RHDIsIGP(rhdPtr->ChipSet)) {
|
||
|
AtomBiosArgRec data;
|
||
|
data.val = 1;
|
||
|
if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS, ATOM_GET_PCIE_LANES,
|
||
|
&data) == ATOM_SUCCESS)
|
||
|
atc->Lanes = data.pcieLanes.Chassis; /* only do 'chassis' for now */
|
||
|
else {
|
||
|
xfree(Private);
|
||
|
xfree(Output);
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
if (RHDIsIGP(rhdPtr->ChipSet))
|
||
|
transPrivate->atomTransmitterID = atomTransmitterPCIEPHY;
|
||
|
else
|
||
|
transPrivate->atomTransmitterID = atomTransmitterUNIPHY;
|
||
|
}
|
||
|
break;
|
||
|
#else
|
||
|
xfree(Private);
|
||
|
xfree(Output);
|
||
|
return NULL;
|
||
|
#endif /* ATOM_BIOS && ATOM_BIOS_PARSER */
|
||
|
|
||
|
case RHD_OUTPUT_UNIPHYB:
|
||
|
#if defined (ATOM_BIOS) && defined (ATOM_BIOS_PARSER)
|
||
|
Output->Name = "UNIPHY_B";
|
||
|
Private->Transmitter.Private =
|
||
|
(struct atomTransmitterPrivate *)xnfcalloc(sizeof (struct ATOMTransmitterPrivate), 1);
|
||
|
|
||
|
Private->Transmitter.Sense = NULL;
|
||
|
Private->Transmitter.ModeValid = ATOMTransmitterModeValid;
|
||
|
Private->Transmitter.Mode = ATOMTransmitterSet;
|
||
|
Private->Transmitter.Power = ATOMTransmitterPower;
|
||
|
Private->Transmitter.Save = ATOMTransmitterSave;
|
||
|
Private->Transmitter.Restore = ATOMTransmitterRestore;
|
||
|
Private->Transmitter.Destroy = ATOMTransmitterDestroy;
|
||
|
Private->Transmitter.Property = TMDSTransmitterPropertyControl;
|
||
|
{
|
||
|
struct ATOMTransmitterPrivate *transPrivate =
|
||
|
(struct ATOMTransmitterPrivate *)Private->Transmitter.Private;
|
||
|
struct atomTransmitterConfig *atc = &transPrivate->atomTransmitterConfig;
|
||
|
atc->Coherent = Private->Coherent = rhdDIGSetCoherent(rhdPtr, Output);
|
||
|
atc->Link = atomTransLinkB;
|
||
|
atc->Encoder = atomEncoderNone;
|
||
|
if (RHDIsIGP(rhdPtr->ChipSet)) {
|
||
|
AtomBiosArgRec data;
|
||
|
data.val = 2;
|
||
|
if (RHDAtomBiosFunc(rhdPtr->scrnIndex, rhdPtr->atomBIOS, ATOM_GET_PCIE_LANES,
|
||
|
&data) == ATOM_SUCCESS)
|
||
|
atc->Lanes = data.pcieLanes.Chassis; /* only do 'chassis' for now */
|
||
|
else {
|
||
|
xfree(Private);
|
||
|
xfree(Output);
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
if (RHDIsIGP(rhdPtr->ChipSet))
|
||
|
transPrivate->atomTransmitterID = atomTransmitterPCIEPHY;
|
||
|
else
|
||
|
transPrivate->atomTransmitterID = atomTransmitterUNIPHY;
|
||
|
}
|
||
|
break;
|
||
|
#else
|
||
|
xfree(Private);
|
||
|
xfree(Output);
|
||
|
return NULL;
|
||
|
#endif /* ATOM_BIOS && ATOM_BIOS_PARSER */
|
||
|
|
||
|
case RHD_OUTPUT_KLDSKP_LVTMA:
|
||
|
Output->Name = "UNIPHY_KLDSKP_LVTMA";
|
||
|
Private->Coherent = rhdDIGSetCoherent(rhdPtr, Output);
|
||
|
Private->Transmitter.Private =
|
||
|
(struct LVTMATransmitterPrivate *)xnfcalloc(sizeof (struct LVTMATransmitterPrivate), 1);
|
||
|
|
||
|
Private->Transmitter.Sense = NULL;
|
||
|
Private->Transmitter.ModeValid = LVTMATransmitterModeValid;
|
||
|
if (ConnectorType != RHD_CONNECTOR_PANEL) {
|
||
|
Private->Transmitter.Mode = LVTMA_TMDSTransmitterSet;
|
||
|
Private->Transmitter.Power = LVTMA_TMDSTransmitterPower;
|
||
|
Private->Transmitter.Save = LVTMA_TMDSTransmitterSave;
|
||
|
Private->Transmitter.Restore = LVTMA_TMDSTransmitterRestore;
|
||
|
} else {
|
||
|
Private->Transmitter.Mode = LVTMA_LVDSTransmitterSet;
|
||
|
Private->Transmitter.Power = LVTMA_LVDSTransmitterPower;
|
||
|
Private->Transmitter.Save = LVTMA_LVDSTransmitterSave;
|
||
|
Private->Transmitter.Restore = LVTMA_LVDSTransmitterRestore;
|
||
|
}
|
||
|
Private->Transmitter.Destroy = LVTMATransmitterDestroy;
|
||
|
if (ConnectorType == RHD_CONNECTOR_PANEL)
|
||
|
Private->Transmitter.Property = LVDSTransmitterPropertyControl;
|
||
|
else
|
||
|
Private->Transmitter.Property = TMDSTransmitterPropertyControl;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
xfree(Private);
|
||
|
xfree(Output);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
Encoder = (struct DIGEncoder *)(xnfcalloc(sizeof (struct DIGEncoder),1));
|
||
|
Private->Encoder.Private = Encoder;
|
||
|
Private->Encoder.ModeValid = EncoderModeValid;
|
||
|
Private->Encoder.Mode = EncoderSet;
|
||
|
Private->Encoder.Power = EncoderPower;
|
||
|
Private->Encoder.Save = EncoderSave;
|
||
|
Private->Encoder.Restore = EncoderRestore;
|
||
|
Private->Encoder.Destroy = EncoderDestroy;
|
||
|
|
||
|
switch (ConnectorType) {
|
||
|
case RHD_CONNECTOR_PANEL:
|
||
|
Private->EncoderMode = LVDS;
|
||
|
GetLVDSInfo(rhdPtr, Private);
|
||
|
#ifdef ATOM_BIOS
|
||
|
#ifdef NOT_YET
|
||
|
if (Private->BlLevel < 0) {
|
||
|
Private->BlLevel = RhdAtomSetupBacklightControlProperty(Output,
|
||
|
&Private->Transmitter.WrappedPropertyCallback,
|
||
|
&Private->Transmitter.PropertyPrivate);
|
||
|
if (Private->Transmitter.PropertyPrivate)
|
||
|
Private->Transmitter.Property = digTransmitterPropertyWrapper;
|
||
|
}
|
||
|
#endif
|
||
|
#endif
|
||
|
Private->Hdmi = NULL;
|
||
|
break;
|
||
|
case RHD_CONNECTOR_DVI:
|
||
|
Private->RunDualLink = FALSE; /* will be set later acc to pxclk */
|
||
|
Private->EncoderMode = TMDS_DVI;
|
||
|
Private->Hdmi = RHDHdmiInit(rhdPtr, Output);
|
||
|
break;
|
||
|
case RHD_CONNECTOR_DVI_SINGLE:
|
||
|
Private->RunDualLink = FALSE;
|
||
|
Private->EncoderMode = TMDS_DVI; /* changed later to HDMI if aplicateable */
|
||
|
Private->Hdmi = RHDHdmiInit(rhdPtr, Output);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return Output;
|
||
|
}
|