/*
 * 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 Shared LVDS/TMDS encoder.
 *
 */

#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

/*
 * First of all, make it more managable to code for both R500 and R600, as
 * there was a 1 register shift, right in the middle of the register block.
 * There are of course much nicer ways to do the workaround i am doing here,
 * but speed is not an issue here.
 */
static inline CARD16
LVTMAChipGenerationSelect(int ChipSet, CARD32 R500, CARD32 R600)
{
    if (ChipSet >= RHD_RS600)
	return R600;
    else
	return R500;
}

#define LVTMAGENSEL(r500, r600) LVTMAChipGenerationSelect(rhdPtr->ChipSet, (r500), (r600))
#define LVTMA_DATA_SYNCHRONIZATION \
    LVTMAGENSEL(LVTMA_R500_DATA_SYNCHRONIZATION, LVTMA_R600_DATA_SYNCHRONIZATION)
#define LVTMA_PWRSEQ_REF_DIV \
    LVTMAGENSEL(LVTMA_R500_PWRSEQ_REF_DIV, LVTMA_R600_PWRSEQ_REF_DIV)
#define LVTMA_PWRSEQ_DELAY1 \
    LVTMAGENSEL(LVTMA_R500_PWRSEQ_DELAY1, LVTMA_R600_PWRSEQ_DELAY1)
#define LVTMA_PWRSEQ_DELAY2 \
    LVTMAGENSEL(LVTMA_R500_PWRSEQ_DELAY2, LVTMA_R600_PWRSEQ_DELAY2)
#define LVTMA_PWRSEQ_CNTL \
    LVTMAGENSEL(LVTMA_R500_PWRSEQ_CNTL, LVTMA_R600_PWRSEQ_CNTL)
#define LVTMA_PWRSEQ_STATE \
    LVTMAGENSEL(LVTMA_R500_PWRSEQ_STATE, LVTMA_R600_PWRSEQ_STATE)
#define LVTMA_LVDS_DATA_CNTL \
    LVTMAGENSEL(LVTMA_R500_LVDS_DATA_CNTL, LVTMA_R600_LVDS_DATA_CNTL)
#define LVTMA_MODE LVTMAGENSEL(LVTMA_R500_MODE, LVTMA_R600_MODE)
#define LVTMA_TRANSMITTER_ENABLE \
    LVTMAGENSEL(LVTMA_R500_TRANSMITTER_ENABLE, LVTMA_R600_TRANSMITTER_ENABLE)
#define LVTMA_MACRO_CONTROL \
    LVTMAGENSEL(LVTMA_R500_MACRO_CONTROL, LVTMA_R600_MACRO_CONTROL)
#define LVTMA_TRANSMITTER_CONTROL \
    LVTMAGENSEL(LVTMA_R500_TRANSMITTER_CONTROL, LVTMA_R600_TRANSMITTER_CONTROL)
#define LVTMA_REG_TEST_OUTPUT \
    LVTMAGENSEL(LVTMA_R500_REG_TEST_OUTPUT, LVTMA_R600_REG_TEST_OUTPUT)
#define LVTMA_BL_MOD_CNTL \
    LVTMAGENSEL(LVTMA_R500_BL_MOD_CNTL, LVTMA_R600_BL_MOD_CNTL)

#define LVTMA_DITHER_RESET_BIT LVTMAGENSEL(0x04000000, 0x02000000)

/*
 *
 * Handling for LVTMA block as LVDS.
 *
 */

struct LVDSPrivate {
    Bool DualLink;
    Bool LVDS24Bit;
    Bool FPDI; /* LDI otherwise */
    CARD16 TXClockPattern;
    int BlLevel;
    CARD32 MacroControl;

    /* Power timing for LVDS */
    CARD16 PowerRefDiv;
    CARD16 BlonRefDiv;
    CARD16 PowerDigToDE;
    CARD16 PowerDEToBL;
    CARD16 OffDelay;
    Bool   TemporalDither;
    Bool   SpatialDither;
    int    GreyLevel;

    Bool Stored;

    CARD32 StoreControl;
    CARD32 StoreSourceSelect;
    CARD32 StoreBitDepthControl;
    CARD32 StoreDataSynchronisation;
    CARD32 StorePWRSEQRefDiv;
    CARD32 StorePWRSEQDelay1;
    CARD32 StorePWRSEQDelay2;
    CARD32 StorePWRSEQControl;
    CARD32 StorePWRSEQState;
    CARD32 StoreLVDSDataControl;
    CARD32 StoreMode;
    CARD32 StoreTxEnable;
    CARD32 StoreMacroControl;
    CARD32 StoreTXControl;
    CARD32 StoreBlModCntl;
#ifdef NOT_YET
    /* to hook in AtomBIOS property callback */
    Bool (*WrappedPropertyCallback) (struct rhdOutput *Output,
		      enum rhdPropertyAction Action, enum rhdOutputProperty Property, union rhdPropertyData *val);
    void *PropertyPrivate;
#endif
};

/*
 *
 */
static ModeStatus
LVDSModeValid(struct rhdOutput *Output, DisplayModePtr Mode)
{
    RHDFUNC(Output);

    if (Mode->Flags & V_INTERLACE)
        return MODE_NO_INTERLACE;

    return MODE_OK;
}

/*
 *
 */
static void
LVDSDebugBacklight(struct rhdOutput *Output)
{
    RHDPtr rhdPtr = RHDPTRI(Output);
    CARD32 tmp;
    Bool Blon, BlonOvrd, BlonPol, BlModEn;
    int BlModLevel, BlModRes = 0;

    if (rhdPtr->verbosity < 7)
	return;

    tmp = (RHDRegRead(Output, LVTMA_PWRSEQ_STATE) >> 3) & 0x01;
    RHDDebug(rhdPtr->scrnIndex, "%s: PWRSEQ BLON State: %s\n",
	    __func__, tmp ? "on" : "off");
    tmp = RHDRegRead(rhdPtr, LVTMA_PWRSEQ_CNTL);
    Blon = (tmp >> 24) & 0x1;
    BlonOvrd = (tmp >> 25) & 0x1;
    BlonPol = (tmp >> 26) & 0x1;

    RHDDebug(rhdPtr->scrnIndex, "%s: BLON: %s BLON_OVRD: %s BLON_POL: %s\n",
	    __func__, Blon ? "on" : "off",
	    BlonOvrd ? "enabled" : "disabled",
	    BlonPol ? "invert" : "non-invert");

    tmp = RHDRegRead(rhdPtr, LVTMA_BL_MOD_CNTL);
    BlModEn = tmp & 0x1;
    BlModLevel = (tmp >> 8) & 0xFF;
    if (rhdPtr->ChipSet >= RHD_RS600)
	BlModRes = (tmp >> 16) & 0xFF;

    xf86DrvMsgVerb(rhdPtr->scrnIndex, X_INFO, 3,
		   "%s: BL_MOD: %s BL_MOD_LEVEL: %d BL_MOD_RES: %d\n",
		   __func__, BlModEn ? "enable" : "disable",
	    BlModLevel, BlModRes);
}

/*
 *
 */
static void
LVDSSetBacklight(struct rhdOutput *Output, int level)
{
    struct LVDSPrivate *Private = (struct LVDSPrivate *) Output->Private;
    RHDPtr rhdPtr = RHDPTRI(Output);

    Private->BlLevel = level;

    xf86DrvMsg(rhdPtr->scrnIndex, X_INFO,
	       "%s: trying to set BL_MOD_LEVEL to: %d\n",
	       __func__, level);

    if (rhdPtr->ChipSet >= RHD_RS600)
  _RHDRegMask(rhdPtr, LVTMA_BL_MOD_CNTL,
		   0xFF << 16 | (level << 8) | 0x1,
		   0xFFFF01);
    else
  _RHDRegMask(rhdPtr, LVTMA_BL_MOD_CNTL,
		   (level << 8) | 0x1,
		   0xFF01);

    /*
     * Poor man's debug
     */
    LVDSDebugBacklight(Output);
}

/*
 *
 */
static Bool
LVDSPropertyControl(struct rhdOutput *Output, enum rhdPropertyAction Action,
		    enum rhdOutputProperty Property, union rhdPropertyData *val)
{
    struct LVDSPrivate *Private = (struct LVDSPrivate *) Output->Private;

    switch (Action) {
	case rhdPropertyCheck:
	    switch (Property) {
		if (Private->BlLevel < 0)
		    return FALSE;
		case RHD_OUTPUT_BACKLIGHT:
		    return TRUE;
		default:
		    return FALSE;
	    }
	case rhdPropertyGet:
	    switch (Property) {
		case RHD_OUTPUT_BACKLIGHT:
		    if (Private->BlLevel < 0)
			return FALSE;
		    val->integer = Private->BlLevel;
		    break;
		default:
		    return FALSE;
	    }
	    break;
	case rhdPropertySet:
	    switch (Property) {
		case RHD_OUTPUT_BACKLIGHT:
		    if (Private->BlLevel < 0)
			return FALSE;
		    LVDSSetBacklight(Output, val->integer);
		    break;
		default:
		    return FALSE;
	    }
	    break;
    }
    return TRUE;
}

/*
 *
 */
static void
LVDSSet(struct rhdOutput *Output, DisplayModePtr Mode)
{
    struct LVDSPrivate *Private = (struct LVDSPrivate *) Output->Private;
    RHDPtr rhdPtr = RHDPTRI(Output);

    RHDFUNC(Output);

    RHDRegMask(Output, LVTMA_CNTL, 0x00000001, 0x00000001); /* enable */
    usleep(20);

    RHDRegWrite(Output, LVTMA_MODE, 0); /* set to LVDS */

    /* Select CRTC, select syncA, no stereosync */
    RHDRegMask(Output, LVTMA_SOURCE_SELECT, Output->Crtc->Id, 0x00010101);

    if (Private->LVDS24Bit) { /* 24bits */
	RHDRegMask(Output, LVTMA_LVDS_DATA_CNTL, 0x00000001, 0x00000001); /* enable 24bits */
	RHDRegMask(Output, LVTMA_BIT_DEPTH_CONTROL, 0x00101010, 0x00101010); /* dithering bit depth = 24 */

	if (Private->FPDI) /* FPDI? */
	    RHDRegMask(Output, LVTMA_LVDS_DATA_CNTL, 0x00000010, 0x00000010); /* 24 bit format: FPDI or LDI? */
	else
	    RHDRegMask(Output, LVTMA_LVDS_DATA_CNTL, 0, 0x00000010);
    } else {
	RHDRegMask(Output, LVTMA_LVDS_DATA_CNTL, 0, 0x00000001); /* disable 24bits */
	RHDRegMask(Output, LVTMA_BIT_DEPTH_CONTROL, 0, 0x00101010); /* dithering bit depth != 24 */
    }

    /* enable temporal dithering, disable spatial dithering and disable truncation */
    RHDRegMask(Output, LVTMA_BIT_DEPTH_CONTROL,
	       Private->TemporalDither ? 1 << 16 : 0
	       | Private->SpatialDither ? 1 << 8 : 0
	       | (Private->GreyLevel > 2) ? 1 << 24 : 0,
	       0x01010101);

    /* reset the temporal dithering */
    RHDRegMask(Output, LVTMA_BIT_DEPTH_CONTROL, LVTMA_DITHER_RESET_BIT, LVTMA_DITHER_RESET_BIT);
    RHDRegMask(Output, LVTMA_BIT_DEPTH_CONTROL, 0, LVTMA_DITHER_RESET_BIT);

    /* go for RGB 4:4:4 RGB/YCbCr  */
    RHDRegMask(Output, LVTMA_CNTL, 0, 0x00010000);

    if (Private->DualLink)
	RHDRegMask(Output, LVTMA_CNTL, 0x01000000, 0x01000000);
    else
	RHDRegMask(Output, LVTMA_CNTL, 0, 0x01000000);

    /* PLL and TX voltages */
    RHDRegWrite(Output, LVTMA_MACRO_CONTROL, Private->MacroControl);

    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0x00000010, 0x00000010); /* use pclk_lvtma_direct */
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0, 0xCC000000);
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, Private->TXClockPattern << 16, 0x03FF0000);
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0x00000001, 0x00000001); /* enable PLL */
    usleep(20);

    /* reset transmitter */
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0x00000002, 0x00000002);
    usleep(2);
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0, 0x00000002);
    usleep(20);

    /* start data synchronisation */
    RHDRegMask(Output, LVTMA_DATA_SYNCHRONIZATION, 0x00000001, 0x00000001);
    RHDRegMask(Output, LVTMA_DATA_SYNCHRONIZATION, 0x00000100, 0x00000100); /* reset */
    usleep(2);
    RHDRegMask(Output, LVTMA_DATA_SYNCHRONIZATION, 0, 0x00000100);
}

/*
 *
 */
static void
LVDSPWRSEQInit(struct rhdOutput *Output)
{
    struct LVDSPrivate *Private = (struct LVDSPrivate *) Output->Private;
    RHDPtr rhdPtr = RHDPTRI(Output);

    CARD32 tmp = 0;

    tmp = Private->PowerDigToDE >> 2;
    RHDRegMask(Output, LVTMA_PWRSEQ_DELAY1, tmp, 0x000000FF);
    RHDRegMask(Output, LVTMA_PWRSEQ_DELAY1, tmp << 24, 0xFF000000);

    tmp = Private->PowerDEToBL >> 2;
    RHDRegMask(Output, LVTMA_PWRSEQ_DELAY1, tmp << 8, 0x0000FF00);
    RHDRegMask(Output, LVTMA_PWRSEQ_DELAY1, tmp << 16, 0x00FF0000);

    RHDRegWrite(Output, LVTMA_PWRSEQ_DELAY2, Private->OffDelay >> 2);
    RHDRegWrite(Output, LVTMA_PWRSEQ_REF_DIV,
		Private->PowerRefDiv | (Private->BlonRefDiv << 16));

    /* Enable power sequencer and allow it to override everything */
    RHDRegMask(Output, LVTMA_PWRSEQ_CNTL, 0x0000000D, 0x0000000D);

    /* give full control to the sequencer */
    RHDRegMask(Output, LVTMA_PWRSEQ_CNTL, 0, 0x02020200);
}

/*
 *
 */
static void
LVDSEnable(struct rhdOutput *Output)
{
    struct LVDSPrivate *Private = (struct LVDSPrivate *) Output->Private;
    RHDPtr rhdPtr = RHDPTRI(Output);
    CARD32 tmp = 0;
    int i;

    RHDFUNC(Output);

    LVDSPWRSEQInit(Output);

    /* set up the transmitter */
    RHDRegMask(Output, LVTMA_TRANSMITTER_ENABLE, 0x0000001E, 0x0000001E);
    if (Private->LVDS24Bit) /* 24bit ? */
	RHDRegMask(Output, LVTMA_TRANSMITTER_ENABLE, 0x00000020, 0x00000020);

    if (Private->DualLink) {
	RHDRegMask(Output, LVTMA_TRANSMITTER_ENABLE, 0x00001E00, 0x00001E00);

	if (Private->LVDS24Bit)
	    RHDRegMask(Output, LVTMA_TRANSMITTER_ENABLE, 0x00002000, 0x00002000);
    }

    RHDRegMask(Output, LVTMA_PWRSEQ_CNTL, 0x00000010, 0x00000010);

    for (i = 0; i <= Private->OffDelay; i++) {
	usleep(1000);

	tmp = (RHDRegRead(Output, LVTMA_PWRSEQ_STATE) >> 8) & 0x0F;
	if (tmp == 4)
	    break;
    }

    if (i == Private->OffDelay) {
	xf86DrvMsg(Output->scrnIndex, X_ERROR, "%s: failed to reach "
             "POWERUP_DONE state after %d loops (%d)\n",
             __func__, i, (int) tmp);
  }
    if (Private->BlLevel >= 0) {
	union rhdPropertyData data;
	data.integer = Private->BlLevel;
	Output->Property(Output, rhdPropertySet, RHD_OUTPUT_BACKLIGHT,
			 &data);
    }
}

/*
 *
 */
static void
LVDSDisable(struct rhdOutput *Output)
{
    struct LVDSPrivate *Private = (struct LVDSPrivate *) Output->Private;
    RHDPtr rhdPtr = RHDPTRI(Output);
    CARD32 tmp = 0;
    int i;

    RHDFUNC(Output);

    if (!(RHDRegRead(Output, LVTMA_PWRSEQ_CNTL) & 0x00000010))
	return;

    LVDSPWRSEQInit(Output);

    RHDRegMask(Output, LVTMA_PWRSEQ_CNTL, 0, 0x00000010);

    for (i = 0; i <= Private->OffDelay; i++) {
	usleep(1000);

	tmp = (RHDRegRead(Output, LVTMA_PWRSEQ_STATE) >> 8) & 0x0F;
	if (tmp == 9)
	    break;
    }

    if (i == Private->OffDelay) {
	xf86DrvMsg(Output->scrnIndex, X_ERROR, "%s: failed to reach "
		   "POWERDOWN_DONE state after %d loops (%d)\n",
		   __func__, i, (int) tmp);
    }

    RHDRegMask(Output, LVTMA_TRANSMITTER_ENABLE, 0, 0x0000FFFF);
}

#if 0
/*
 *
 */
static void
LVDSShutdown(struct rhdOutput *Output)
{
    RHDPtr rhdPtr = RHDPTRI(Output);
    RHDFUNC(Output);

    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0x00000002, 0x00000002); /* PLL in reset */
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0, 0x00000001); /* disable LVDS */
    RHDRegMask(Output, LVTMA_DATA_SYNCHRONIZATION, 0, 0x00000001);
    RHDRegMask(Output, LVTMA_BIT_DEPTH_CONTROL, LVTMA_DITHER_RESET_BIT, LVTMA_DITHER_RESET_BIT); /* reset temp dithering */
    RHDRegMask(Output, LVTMA_BIT_DEPTH_CONTROL, 0, 0x00111111); /* disable all dithering */
    RHDRegWrite(Output, LVTMA_CNTL, 0); /* disable */
}
#endif

/*
 *
 */
static void
LVDSPower(struct rhdOutput *Output, int Power)
{
    RHDDebug(Output->scrnIndex, "%s(%s,%s)\n",__func__,Output->Name,
	     rhdPowerString[Power]);

    switch (Power) {
    case RHD_POWER_ON:
	LVDSEnable(Output);
	break;
    case RHD_POWER_RESET:
	/*	LVDSDisable(Output);
		break;*/
    case RHD_POWER_SHUTDOWN:
    default:
	LVDSDisable(Output);
	/* LVDSShutdown(Output); */
	break;
    }
    return;
}

/*
 *
 */
static void
LVDSSave(struct rhdOutput *Output)
{
    struct LVDSPrivate *Private = (struct LVDSPrivate *) Output->Private;
    RHDPtr rhdPtr = RHDPTRI(Output);

    RHDFUNC(Output);

    Private->StoreControl = RHDRegRead(Output, LVTMA_CNTL);
    Private->StoreSourceSelect = RHDRegRead(Output,  LVTMA_SOURCE_SELECT);
    Private->StoreBitDepthControl = RHDRegRead(Output, LVTMA_BIT_DEPTH_CONTROL);
    Private->StoreDataSynchronisation = RHDRegRead(Output, LVTMA_DATA_SYNCHRONIZATION);
    Private->StorePWRSEQRefDiv = RHDRegRead(Output, LVTMA_PWRSEQ_REF_DIV);
    Private->StorePWRSEQDelay1 = RHDRegRead(Output, LVTMA_PWRSEQ_DELAY1);
    Private->StorePWRSEQDelay2 = RHDRegRead(Output, LVTMA_PWRSEQ_DELAY2);
    Private->StorePWRSEQControl = RHDRegRead(Output, LVTMA_PWRSEQ_CNTL);
    Private->StorePWRSEQState = RHDRegRead(Output, LVTMA_PWRSEQ_STATE);
    Private->StoreLVDSDataControl = RHDRegRead(Output, LVTMA_LVDS_DATA_CNTL);
    Private->StoreMode = RHDRegRead(Output, LVTMA_MODE);
    Private->StoreTxEnable = RHDRegRead(Output, LVTMA_TRANSMITTER_ENABLE);
    Private->StoreMacroControl = RHDRegRead(Output, LVTMA_MACRO_CONTROL);
    Private->StoreTXControl = RHDRegRead(Output, LVTMA_TRANSMITTER_CONTROL);
    Private->StoreBlModCntl = RHDRegRead(Output, LVTMA_BL_MOD_CNTL);

    Private->Stored = TRUE;
}

/*
 * This needs to reset things like the temporal dithering and the TX appropriately.
 * Currently it's a dumb register dump.
 */
static void
LVDSRestore(struct rhdOutput *Output)
{
    struct LVDSPrivate *Private = (struct LVDSPrivate *) Output->Private;
    RHDPtr rhdPtr = RHDPTRI(Output);

    RHDFUNC(Output);

    if (!Private->Stored) {
	xf86DrvMsg(Output->scrnIndex, X_ERROR,
		   "%s: No registers stored.\n", __func__);
	return;
    }

    RHDRegWrite(Output, LVTMA_CNTL, Private->StoreControl);
    RHDRegWrite(Output, LVTMA_SOURCE_SELECT, Private->StoreSourceSelect);
    RHDRegWrite(Output, LVTMA_BIT_DEPTH_CONTROL,  Private->StoreBitDepthControl);
    RHDRegWrite(Output, LVTMA_DATA_SYNCHRONIZATION, Private->StoreDataSynchronisation);
    RHDRegWrite(Output, LVTMA_PWRSEQ_REF_DIV, Private->StorePWRSEQRefDiv);
    RHDRegWrite(Output, LVTMA_PWRSEQ_DELAY1, Private->StorePWRSEQDelay1);
    RHDRegWrite(Output, LVTMA_PWRSEQ_DELAY2,  Private->StorePWRSEQDelay2);
    RHDRegWrite(Output, LVTMA_PWRSEQ_CNTL, Private->StorePWRSEQControl);
    RHDRegWrite(Output, LVTMA_PWRSEQ_STATE, Private->StorePWRSEQState);
    RHDRegWrite(Output, LVTMA_LVDS_DATA_CNTL, Private->StoreLVDSDataControl);
    RHDRegWrite(Output, LVTMA_MODE, Private->StoreMode);
    RHDRegWrite(Output, LVTMA_TRANSMITTER_ENABLE, Private->StoreTxEnable);
    RHDRegWrite(Output, LVTMA_MACRO_CONTROL, Private->StoreMacroControl);
    RHDRegWrite(Output, LVTMA_TRANSMITTER_CONTROL,  Private->StoreTXControl);
    RHDRegWrite(Output, LVTMA_BL_MOD_CNTL, Private->StoreBlModCntl);

    /*
     * Poor man's debug
     */
    LVDSDebugBacklight(Output);
}

/*
 * Here we pretty much assume that ATOM has either initialised the panel already
 * or that we can find information from ATOM BIOS data tables. We know that the
 * latter assumption is false for some values, but there is no getting around
 * ATI clinging desperately to a broken concept.
 */
static struct LVDSPrivate *
LVDSInfoRetrieve(RHDPtr rhdPtr)
{
    struct LVDSPrivate *Private = xnfcalloc(sizeof(struct LVDSPrivate), 1);
    CARD32 tmp;

    /* These values are not available from atombios data tables at all. */
    Private->MacroControl = RHDRegRead(rhdPtr, LVTMA_MACRO_CONTROL);
    Private->TXClockPattern =
  (_RHDRegRead(rhdPtr, LVTMA_TRANSMITTER_CONTROL) >> 16) & 0x3FF;

    /* For these values, we try to retrieve them from register space first,
       and later override with atombios data table information */
    Private->PowerDigToDE =
  (_RHDRegRead(rhdPtr, LVTMA_PWRSEQ_DELAY1) & 0x000000FF) << 2;

    Private->PowerDEToBL =
  (_RHDRegRead(rhdPtr, LVTMA_PWRSEQ_DELAY1) & 0x0000FF00) >> 6;

    Private->OffDelay = (_RHDRegRead(rhdPtr, LVTMA_PWRSEQ_DELAY2) & 0xFF) << 2;

    tmp = _RHDRegRead(rhdPtr, LVTMA_PWRSEQ_REF_DIV);
    Private->PowerRefDiv = tmp & 0x0FFF;
    Private->BlonRefDiv = (tmp >> 16) & 0x0FFF;
    tmp = _RHDRegRead(rhdPtr, LVTMA_BL_MOD_CNTL);
    if (tmp & 0x1)
	Private->BlLevel = (tmp >> 8) & 0xff;
    else
	Private->BlLevel = -1; /* Backlight control seems to be done some other way */

    Private->DualLink = (_RHDRegRead(rhdPtr, LVTMA_CNTL) >> 24) & 0x00000001;
    Private->LVDS24Bit = _RHDRegRead(rhdPtr, LVTMA_LVDS_DATA_CNTL) & 0x00000001;
    Private->FPDI = _RHDRegRead(rhdPtr, LVTMA_LVDS_DATA_CNTL) & 0x00000010;

    tmp = _RHDRegRead(rhdPtr, LVTMA_BIT_DEPTH_CONTROL);
    Private->TemporalDither =  ((tmp & (1 << 16)) != 0);
    Private->SpatialDither = ((tmp & (1 << 8)) != 0);
    Private->GreyLevel = (tmp & (1 << 24)) ? 4 : 2;

#ifdef ATOM_BIOS
    {
	AtomBiosArgRec data;

  if(RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
			   ATOM_LVDS_SEQ_DIG_ONTO_DE, &data) == ATOM_SUCCESS)
	    Private->PowerDigToDE = data.val;

  if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
			    ATOM_LVDS_SEQ_DE_TO_BL, &data) == ATOM_SUCCESS)
	    Private->PowerDEToBL = 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_DUALLINK, &data) == ATOM_SUCCESS)
	    Private->DualLink = data.val;

  if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
			    ATOM_LVDS_24BIT, &data) == ATOM_SUCCESS)
	    Private->LVDS24Bit = data.val;

  if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
				 ATOM_LVDS_FPDI, &data) == ATOM_SUCCESS)
	    Private->FPDI = data.val;

  if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
			    ATOM_LVDS_TEMPORAL_DITHER, &data) == ATOM_SUCCESS)
	    Private->TemporalDither = data.val;

  if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
			    ATOM_LVDS_SPATIAL_DITHER, &data) == ATOM_SUCCESS)
	    Private->SpatialDither = data.val;

  if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
			    ATOM_LVDS_GREYLVL, &data) == ATOM_SUCCESS) {
	    Private->GreyLevel = data.val;
	    xf86DrvMsg(rhdPtr->scrnIndex, X_INFO, "AtomBIOS returned %i Grey Levels\n",
		       Private->GreyLevel);
    }
    }
#endif

    if (Private->LVDS24Bit)
	xf86DrvMsg(rhdPtr->scrnIndex, X_PROBED,
		   "Detected a 24bit %s, %s link panel.\n",
            Private->DualLink ? "dual" : "single",
            Private->FPDI ? "FPDI": "LDI");
    else
	xf86DrvMsg(rhdPtr->scrnIndex, X_PROBED,
		   "Detected a 18bit %s link panel.\n",
             Private->DualLink ? "dual" : "single");

    /* extra noise */
    RHDDebug(rhdPtr->scrnIndex, "Printing LVDS paramaters:\n");
    xf86MsgVerb(X_NONE, LOG_DEBUG, "\tMacroControl: 0x%08X\n",
		(unsigned int) Private->MacroControl);
    xf86MsgVerb(X_NONE, LOG_DEBUG, "\tTXClockPattern: 0x%04X\n",
		Private->TXClockPattern);
    xf86MsgVerb(X_NONE, LOG_DEBUG, "\tPowerDigToDE: 0x%04X\n",
		Private->PowerDigToDE);
    xf86MsgVerb(X_NONE, LOG_DEBUG, "\tPowerDEToBL: 0x%04X\n",
		Private->PowerDEToBL);
    xf86MsgVerb(X_NONE, LOG_DEBUG, "\tOffDelay: 0x%04X\n",
		Private->OffDelay);
    xf86MsgVerb(X_NONE, LOG_DEBUG, "\tPowerRefDiv: 0x%04X\n",
		Private->PowerRefDiv);
    xf86MsgVerb(X_NONE, LOG_DEBUG, "\tBlonRefDiv: 0x%04X\n",
		Private->BlonRefDiv);

    return Private;
}

/*
 *
 */
static void
LVDSDestroy(struct rhdOutput *Output)
{

    struct LVDSPrivate *Private = (struct LVDSPrivate *) Output->Private;

    RHDFUNC(Output);

    if (!Private)
	return;

#ifdef NOT_YET
    if (Private->PropertyPrivate)
	RhdAtomDestroyBacklightControlProperty(Output, Private->PropertyPrivate);
#endif
    xfree(Private);
    Output->Private = NULL;
}

/*
 *
 * Handling for LVTMA block as TMDS.
 *
 */
struct rhdTMDSBPrivate {
    Bool RunsDualLink;
    Bool Coherent;
    DisplayModePtr Mode;

    struct rhdHdmi *Hdmi;

    Bool Stored;

    CARD32 StoreControl;
    CARD32 StoreSource;
    CARD32 StoreFormat;
    CARD32 StoreForce;
    CARD32 StoreReduction;
    CARD32 StoreDCBalancer;
    CARD32 StoreDataSynchro;
    CARD32 StoreMode;
    CARD32 StoreTXEnable;
    CARD32 StoreMacro;
    CARD32 StoreTXControl;
    CARD32 StoreTXAdjust;
    CARD32 StoreTestOutput;

    CARD32 StoreRs690Unknown;
    CARD32 StoreRv600TXAdjust;
    CARD32 StoreRv600PreEmphasis;
};

/*
 *
 */
static ModeStatus
TMDSBModeValid(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;
}

/*
 *
 */
static void
RS600VoltageControl(struct rhdOutput *Output, DisplayModePtr Mode)
{
    struct rhdTMDSBPrivate *Private = (struct rhdTMDSBPrivate *) Output->Private;

    RHDFUNC(Output);
#ifdef NOTYET
    if (Output->Connector == RHD_CONNECTOR_HDMI || Output->Connector == RHD_CONNECTOR_HDMI_DUAL) {
	int clock = Mode->SynthClock;

	if (Private->RunsDualLink)
	    clock >>= 1;
	if (clock <= 75000) {
	    RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x00010213);
	    RHDRegWrite(Output, LVTMA_R600_REG_TEST_OUTPUT, 0x000a0000);
	} else {
	    RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x00000213);
	    RHDRegWrite(Output, LVTMA_R600_REG_TEST_OUTPUT, 0x00100000);
	}
    } else
#endif
    {
	if (Private->RunsDualLink) {
	    RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x0000020f);
	    RHDRegWrite(Output, LVTMA_R600_REG_TEST_OUTPUT, 0x00100000);
	} else {
	    if (Mode->SynthClock < 39000)
		RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x0002020f);
	    else
		RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x0000020f);
	    RHDRegWrite(Output, LVTMA_R600_REG_TEST_OUTPUT, 0x00100000);
	}
    }
}

/*
 *
 */
static void
RS690VoltageControl(struct rhdOutput *Output, DisplayModePtr Mode)
{
    RHDPtr rhdPtr = RHDPTRI(Output);
    struct rhdTMDSBPrivate *Private = (struct rhdTMDSBPrivate *) Output->Private;
    CARD32 rev = (RHDRegRead(Output, CONFIG_CNTL) && RS69_CFG_ATI_REV_ID_MASK) >> RS69_CFG_ATI_REV_ID_SHIFT;

    if (rev < 3) {
#ifdef NOTYET
	if (Output->Connector == RHD_CONNECTOR_HDMI || Output->Connector == RHD_CONNECTOR_HDMI_DUAL) {
	    if (Mode->SynthClock > 75000) {
		RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0xa001632f);
		RHDRegWrite(Output, LVTMA_R600_REG_TEST_OUTPUT, 0x05120000);
		RHDRegMask(Output,  LVTMA_R600_TRANSMITTER_CONTROL, 0x10000000, 0x10000000);
	    } else if (Mode->SynthClock > 41000) {
		RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x0000632f);
		RHDRegWrite(Output, LVTMA_R600_REG_TEST_OUTPUT, 0x05120000);
		RHDRegMask(Output,  LVTMA_R600_TRANSMITTER_CONTROL, 0x10000000, 0x10000000);
	    } else {
		RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x0003632f);
		RHDRegWrite(Output, LVTMA_R600_REG_TEST_OUTPUT, 0x050b000);
		RHDRegMask(Output,  LVTMA_R600_TRANSMITTER_CONTROL, 0x0, 0x10000000);
	    }
	} else
#endif
	{
	    int clock = Mode->SynthClock;

	    if (Private->RunsDualLink)
		clock >>= 1;

	    RHDRegWrite(Output, LVTMA_R600_REG_TEST_OUTPUT, 0x05120000);

	    if (clock > 75000) {
		RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0xa001631f);
		RHDRegMask(Output,  LVTMA_R600_TRANSMITTER_CONTROL, 0x10000000, 0x10000000);
	    } else if (clock > 41000) {
		RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x0000631f);
		RHDRegMask(Output,  LVTMA_R600_TRANSMITTER_CONTROL, 0x10000000, 0x10000000);
	    } else {
		RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x0003631f);
		RHDRegMask(Output,  LVTMA_R600_TRANSMITTER_CONTROL, 0x0, 0x10000000);
	    }
	}
    } else {
#ifdef NOTYET
	if (Output->Connector == RHD_CONNECTOR_HDMI || Output->Connector == RHD_CONNECTOR_HDMI_DUAL) {
	    if (Mode->SynthClock <= 75000) {
		RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x0002612f);
		RHDRegWrite(Output, LVTMA_R600_REG_TEST_OUTPUT, 0x010b0000);
		RHDRegMask(Output,  LVTMA_R600_TRANSMITTER_CONTROL, 0x0, 0x10000000);
	    } else {
		RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x0000642f);
		RHDRegWrite(Output, LVTMA_R600_REG_TEST_OUTPUT, 0x01120000);
		RHDRegMask(Output,  LVTMA_R600_TRANSMITTER_CONTROL, 0x10000000, 0x10000000);
	    }
	} else
#endif
	{
	    int clock = Mode->SynthClock;

	    if (Private->RunsDualLink)
		clock >>= 1;

	    RHDRegWrite(Output, LVTMA_R600_REG_TEST_OUTPUT, 0x01120000);
	    RHDRegMask(Output,  LVTMA_R600_TRANSMITTER_CONTROL, 0x10000000, 0x10000000);

	    if (Mode->SynthClock > 75000) {
		RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x00016318);
	    } else {
		{
#ifdef ATOM_BIOS
		    AtomBiosArgRec data;

        if (RHDAtomBiosFunc(rhdPtr, rhdPtr->atomBIOS,
					ATOM_GET_CAPABILITY_FLAG, &data) == ATOM_SUCCESS) {
			if (((data.val & 0x60) == 0x20 || (data.val & 0x80))) {
			    RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x00016318);
			} else {
			    RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x00006318);
			}
		    } else
#endif
		    {
			RHDRegWrite(Output, LVTMA_R600_MACRO_CONTROL, 0x00006318);
		    }
		}
	    }
	}
    }
}

/*
 * This information is not provided in an atombios data table.
 */
static struct R5xxTMDSBMacro {
    CARD16 Device;
    CARD32 MacroSingle;
    CARD32 MacroDual;
} R5xxTMDSBMacro[] = {
    /*
     * this list isn't complete yet.
     *  Some more values for dual need to be dug up
     */
    { 0x7104, 0x00F20616, 0x00F20616 }, /* R520  */
    { 0x7142, 0x00F2061C, 0x00F2061C }, /* RV515 */
    { 0x7145, 0x00F1061D, 0x00F2061D }, /**/
    { 0x7146, 0x00F1061D, 0x00F1061D }, /* RV515 */
    { 0x7147, 0x0082041D, 0x0082041D }, /* RV505 */
    { 0x7149, 0x00F1061D, 0x00D2061D }, /**/
    { 0x7152, 0x00F2061C, 0x00F2061C }, /* RV515 */
    { 0x7183, 0x00B2050C, 0x00B2050C }, /* RV530 */
    { 0x71C0, 0x00F1061F, 0x00f2061D }, /**/
    { 0x71C1, 0x0062041D, 0x0062041D }, /* RV535 *//**/
    { 0x71C2, 0x00F1061D, 0x00F2061D }, /* RV530 *//**/
    { 0x71C5, 0x00D1061D, 0x00D2061D }, /**/
    { 0x71C6, 0x00F2061D, 0x00F2061D }, /* RV530 */
    { 0x71D2, 0x00F10610, 0x00F20610 }, /* RV530: atombios uses 0x00F1061D *//**/
    { 0x7249, 0x00F1061D, 0x00F1061D }, /* R580  */
    { 0x724B, 0x00F10610, 0x00F10610 }, /* R580: atombios uses 0x00F1061D */
    { 0x7280, 0x0042041F, 0x0042041F }, /* RV570 *//**/
    { 0x7288, 0x0042041F, 0x0042041F }, /* RV570 */
    { 0x791E, 0x0001642F, 0x0001642F }, /* RS690 */
    { 0x791F, 0x0001642F, 0x0001642F }, /* RS690 */
    { 0x9400, 0x00020213, 0x00020213 }, /* R600  */
    { 0x9401, 0x00020213, 0x00020213 }, /* R600  */
    { 0x9402, 0x00020213, 0x00020213 }, /* R600  */
    { 0x9403, 0x00020213, 0x00020213 }, /* R600  */
    { 0x9405, 0x00020213, 0x00020213 }, /* R600  */
    { 0x940A, 0x00020213, 0x00020213 }, /* R600  */
    { 0x940B, 0x00020213, 0x00020213 }, /* R600  */
    { 0x940F, 0x00020213, 0x00020213 }, /* R600  */
    { 0, 0, 0 } /* End marker */
};

static struct RV6xxTMDSBMacro {
    CARD16 Device;
    CARD32 Macro;
    CARD32 TX;
    CARD32 PreEmphasis;
} RV6xxTMDSBMacro[] = {
    { 0x94C1, 0x01030311, 0x10001A00, 0x01801015}, /* RV610 */
    { 0x94C3, 0x01030311, 0x10001A00, 0x01801015}, /* RV610 */
    { 0x9501, 0x0533041A, 0x020010A0, 0x41002045}, /* RV670 */
    { 0x9505, 0x0533041A, 0x020010A0, 0x41002045}, /* RV670 */
    { 0x950F, 0x0533041A, 0x020010A0, 0x41002045}, /* R680  */
    { 0x9587, 0x01030311, 0x10001C00, 0x01C01011}, /* RV630 */
    { 0x9588, 0x01030311, 0x10001C00, 0x01C01011}, /* RV630 */
    { 0x9589, 0x01030311, 0x10001C00, 0x01C01011}, /* RV630 */
    { 0, 0, 0, 0} /* End marker */
};

static void
TMDSBVoltageControl(struct rhdOutput *Output, DisplayModePtr Mode)
{
    struct rhdTMDSBPrivate *Private = (struct rhdTMDSBPrivate *) Output->Private;
    RHDPtr rhdPtr = RHDPTRI(Output);
  int i;

    /* IGP chipsets are rather special */
    if (rhdPtr->ChipSet == RHD_RS690) {
	RS690VoltageControl(Output, Mode);
	return;
    } else if (rhdPtr->ChipSet == RHD_RS600) {
	RS600VoltageControl(Output, Mode);
	return;
    }

    /* TEST_OUTPUT register - IGPs are handled above */
    if (rhdPtr->ChipSet < RHD_RS600) /* r5xx */
	RHDRegMask(Output, LVTMA_REG_TEST_OUTPUT, 0x00200000, 0x00200000);
    else if (rhdPtr->ChipSet < RHD_RV670)
	RHDRegMask(Output, LVTMA_REG_TEST_OUTPUT, 0x00100000, 0x00100000);

    /* macro control values */
    if (rhdPtr->ChipSet < RHD_RV610) { /* R5xx and R600 */
    for (i = 0; R5xxTMDSBMacro[i].Device; i++)
	    if (R5xxTMDSBMacro[i].Device == rhdPtr->PciDeviceID) {
		if (!Private->RunsDualLink)
		    RHDRegWrite(Output, LVTMA_MACRO_CONTROL, R5xxTMDSBMacro[i].MacroSingle);
		else
		    RHDRegWrite(Output, LVTMA_MACRO_CONTROL, R5xxTMDSBMacro[i].MacroDual);
        return;
	    }

	xf86DrvMsg(Output->scrnIndex, X_ERROR, "%s: unhandled chipset: 0x%04X.\n",
		   __func__, rhdPtr->PciDeviceID);
	xf86DrvMsg(Output->scrnIndex, X_INFO, "LVTMA_MACRO_CONTROL: 0x%08X\n",
		   (unsigned int) RHDRegRead(Output, LVTMA_MACRO_CONTROL));
    } else { /* RV6x0 and up */
    for (i = 0; RV6xxTMDSBMacro[i].Device; i++)
	    if (RV6xxTMDSBMacro[i].Device == rhdPtr->PciDeviceID) {
        RHDRegWrite(Output, LVTMA_MACRO_CONTROL, RV6xxTMDSBMacro[i].Macro);
        RHDRegWrite(Output, LVTMA_TRANSMITTER_ADJUST, RV6xxTMDSBMacro[i].TX);
        RHDRegWrite(Output, LVTMA_PREEMPHASIS_CONTROL, RV6xxTMDSBMacro[i].PreEmphasis);
        return;
	    }

	xf86DrvMsg(Output->scrnIndex, X_ERROR, "%s: unhandled chipset: 0x%04X.\n",
		   __func__, rhdPtr->PciDeviceID);
	xf86DrvMsg(Output->scrnIndex, X_INFO, "LVTMA_MACRO_CONTROL: 0x%08X\n",
            (unsigned int) RHDRegRead(Output, LVTMA_MACRO_CONTROL));
	xf86DrvMsg(Output->scrnIndex, X_INFO, "LVTMA_TRANSMITTER_ADJUST: 0x%08X\n",
            (unsigned int) RHDRegRead(Output, LVTMA_TRANSMITTER_ADJUST));
	xf86DrvMsg(Output->scrnIndex, X_INFO, "LVTMA_PREEMPHASIS_CONTROL: 0x%08X\n",
            (unsigned int) RHDRegRead(Output, LVTMA_PREEMPHASIS_CONTROL));
    }
}

/*
 *
 */
static Bool
TMDSBPropertyControl(struct rhdOutput *Output,
	     enum rhdPropertyAction Action, enum rhdOutputProperty Property, union rhdPropertyData *val)
{
    struct rhdTMDSBPrivate *Private = (struct rhdTMDSBPrivate *) 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
TMDSBSet(struct rhdOutput *Output, DisplayModePtr Mode)
{
    RHDPtr rhdPtr = RHDPTRI(Output);
    struct rhdTMDSBPrivate *Private = (struct rhdTMDSBPrivate *) Output->Private;

    RHDFUNC(Output);

    RHDRegMask(Output, LVTMA_MODE, 0x00000001, 0x00000001); /* select TMDS */

    /* Clear out some HPD events first: this should be under driver control. */
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0, 0x0000000C);
    RHDRegMask(Output, LVTMA_TRANSMITTER_ENABLE, 0, 0x00070000);
    RHDRegMask(Output, LVTMA_CNTL, 0, 0x00000010);

    /* Disable the transmitter */
	RHDRegMask(Output, LVTMA_TRANSMITTER_ENABLE, 0, 0x00003E3E);

    /* Disable bit reduction and reset temporal dither */
    RHDRegMask(Output, LVTMA_BIT_DEPTH_CONTROL, 0, 0x00010101);
    RHDRegMask(Output, LVTMA_BIT_DEPTH_CONTROL, LVTMA_DITHER_RESET_BIT, LVTMA_DITHER_RESET_BIT);
    usleep(2);
    RHDRegMask(Output, LVTMA_BIT_DEPTH_CONTROL, 0, LVTMA_DITHER_RESET_BIT);
    RHDRegMask(Output, LVTMA_BIT_DEPTH_CONTROL, 0, 0xF0000000); /* not documented */

    /* reset phase on vsync and use RGB */
    RHDRegMask(Output, LVTMA_CNTL, 0x00001000, 0x00011000);

    /* Select CRTC, select syncA, no stereosync */
    RHDRegMask(Output, LVTMA_SOURCE_SELECT, Output->Crtc->Id, 0x00010101);

    RHDRegWrite(Output, LVTMA_COLOR_FORMAT, 0);

    Private->Mode = Mode;
    if (Mode->SynthClock > 165000) {
	RHDRegMask(Output, LVTMA_CNTL, 0x01000000, 0x01000000);
	Private->RunsDualLink = TRUE; /* for TRANSMITTER_ENABLE in TMDSBPower */
    } else {
    RHDRegMask(Output, LVTMA_CNTL, 0, 0x01000000);
	Private->RunsDualLink = FALSE;
    }

    if (rhdPtr->ChipSet > RHD_R600) /* Rv6xx: disable split mode */
	RHDRegMask(Output, LVTMA_CNTL, 0, 0x20000000);

    /* Disable force data */
    RHDRegMask(Output, LVTMA_FORCE_OUTPUT_CNTL, 0, 0x00000001);

    /* DC balancer enable */
    RHDRegMask(Output, LVTMA_DCBALANCER_CONTROL, 0x00000001, 0x00000001);

    TMDSBVoltageControl(Output, Mode);

    /* use IDCLK */
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0x00000010, 0x00000010);
    /* LVTMA only: use clock selected by next write */
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0x20000000, 0x20000000);
    /* coherent mode */
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL,
	       Private->Coherent ? 0 : 0x10000000, 0x10000000);
    /* clear LVDS clock pattern */
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0, 0x03FF0000);

    /* reset transmitter pll */
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0x00000002, 0x00000002);
    usleep(2);
    RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0, 0x00000002);
    usleep(20);

    /* restart data synchronisation */
    RHDRegMask(Output, LVTMA_DATA_SYNCHRONIZATION, 0x00000001, 0x00000001);
    RHDRegMask(Output, LVTMA_DATA_SYNCHRONIZATION, 0x00000100, 0x00000100);
    usleep(2);
    RHDRegMask(Output, LVTMA_DATA_SYNCHRONIZATION, 0, 0x00000001);

    RHDHdmiSetMode(Private->Hdmi, Mode);
}

/*
 *
 */
static void
TMDSBPower(struct rhdOutput *Output, int Power)
{
    RHDPtr rhdPtr = RHDPTRI(Output);
    struct rhdTMDSBPrivate *Private = (struct rhdTMDSBPrivate *) Output->Private;

    RHDDebug(Output->scrnIndex, "%s(%s,%s)\n",__func__,Output->Name,
	     rhdPowerString[Power]);

    RHDRegMask(Output, LVTMA_MODE, 0x00000001, 0x00000001); /* select TMDS */

    switch (Power) {
    case RHD_POWER_ON:
	RHDRegMask(Output, LVTMA_CNTL, 0x1, 0x00000001);

	if (Private->RunsDualLink)
	    RHDRegMask(Output, LVTMA_TRANSMITTER_ENABLE, 0x00003E3E,0x00003E3E);
	else
	    RHDRegMask(Output, LVTMA_TRANSMITTER_ENABLE, 0x0000003E, 0x00003E3E);

	RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0x00000001, 0x00000001);
	usleep(2);
	RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0, 0x00000002);
	if(Output->Connector != NULL && RHDConnectorEnableHDMI(Output->Connector))
	    RHDHdmiEnable(Private->Hdmi, TRUE);
	else
	    RHDHdmiEnable(Private->Hdmi, FALSE);
	return;
    case RHD_POWER_RESET:
	RHDRegMask(Output, LVTMA_TRANSMITTER_ENABLE, 0, 0x00003E3E);
	return;
    case RHD_POWER_SHUTDOWN:
    default:
	RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0x00000002, 0x00000002);
	usleep(2);
	RHDRegMask(Output, LVTMA_TRANSMITTER_CONTROL, 0, 0x00000001);
	RHDRegMask(Output, LVTMA_TRANSMITTER_ENABLE, 0, 0x00003E3E);
	RHDRegMask(Output, LVTMA_CNTL, 0, 0x00000001);
	RHDHdmiEnable(Private->Hdmi, FALSE);
	return;
    }
}

/*
 *
 */
static void
TMDSBSave(struct rhdOutput *Output)
{
    struct rhdTMDSBPrivate *Private = (struct rhdTMDSBPrivate *) Output->Private;
    RHDPtr rhdPtr = RHDPTRI(Output);

    RHDFUNC(Output);

    Private->StoreControl = RHDRegRead(Output, LVTMA_CNTL);
    Private->StoreSource = RHDRegRead(Output, LVTMA_SOURCE_SELECT);
    Private->StoreFormat = RHDRegRead(Output, LVTMA_COLOR_FORMAT);
    Private->StoreForce = RHDRegRead(Output, LVTMA_FORCE_OUTPUT_CNTL);
    Private->StoreReduction = RHDRegRead(Output, LVTMA_BIT_DEPTH_CONTROL);
    Private->StoreDCBalancer = RHDRegRead(Output, LVTMA_DCBALANCER_CONTROL);

    Private->StoreDataSynchro = RHDRegRead(Output, LVTMA_DATA_SYNCHRONIZATION);
    Private->StoreMode = RHDRegRead(Output, LVTMA_MODE);
    Private->StoreTXEnable = RHDRegRead(Output, LVTMA_TRANSMITTER_ENABLE);
    Private->StoreMacro = RHDRegRead(Output, LVTMA_MACRO_CONTROL);
    Private->StoreTXControl = RHDRegRead(Output, LVTMA_TRANSMITTER_CONTROL);
    Private->StoreTestOutput = RHDRegRead(Output, LVTMA_REG_TEST_OUTPUT);

    if (rhdPtr->ChipSet > RHD_R600) { /* Rv6x0 */
       Private->StoreRv600TXAdjust = RHDRegRead(Output, LVTMA_TRANSMITTER_ADJUST);
       Private->StoreRv600PreEmphasis = RHDRegRead(Output, LVTMA_PREEMPHASIS_CONTROL);
    }

    RHDHdmiSave(Private->Hdmi);

    Private->Stored = TRUE;
}

/*
 *
 */
static void
TMDSBRestore(struct rhdOutput *Output)
{
    struct rhdTMDSBPrivate *Private = (struct rhdTMDSBPrivate *) Output->Private;
    RHDPtr rhdPtr = RHDPTRI(Output);

    RHDFUNC(Output);

    if (!Private->Stored) {
	xf86DrvMsg(Output->scrnIndex, X_ERROR,
		   "%s: No registers stored.\n", __func__);
	return;
    }

    RHDRegWrite(Output, LVTMA_CNTL, Private->StoreControl);
    RHDRegWrite(Output, LVTMA_SOURCE_SELECT, Private->StoreSource);
    RHDRegWrite(Output, LVTMA_COLOR_FORMAT, Private->StoreFormat);
    RHDRegWrite(Output, LVTMA_FORCE_OUTPUT_CNTL, Private->StoreForce);
    RHDRegWrite(Output, LVTMA_BIT_DEPTH_CONTROL, Private->StoreReduction);
    RHDRegWrite(Output, LVTMA_DCBALANCER_CONTROL, Private->StoreDCBalancer);

    RHDRegWrite(Output, LVTMA_DATA_SYNCHRONIZATION, Private->StoreDataSynchro);
    RHDRegWrite(Output, LVTMA_MODE, Private->StoreMode);
    RHDRegWrite(Output, LVTMA_TRANSMITTER_ENABLE, Private->StoreTXEnable);
    RHDRegWrite(Output, LVTMA_MACRO_CONTROL, Private->StoreMacro);
    RHDRegWrite(Output, LVTMA_TRANSMITTER_CONTROL, Private->StoreTXControl);
    RHDRegWrite(Output, LVTMA_REG_TEST_OUTPUT, Private->StoreTestOutput);

    if (rhdPtr->ChipSet > RHD_R600) { /* Rv6x0 */
	RHDRegWrite(Output, LVTMA_TRANSMITTER_ADJUST, Private->StoreRv600TXAdjust);
	RHDRegWrite(Output, LVTMA_PREEMPHASIS_CONTROL, Private->StoreRv600PreEmphasis);
    }

    RHDHdmiRestore(Private->Hdmi);
}


/*
 *
 */
static void
TMDSBDestroy(struct rhdOutput *Output)
{
    struct rhdTMDSBPrivate *Private = (struct rhdTMDSBPrivate *) Output->Private;
    RHDFUNC(Output);

    if (!Private)
	return;

    RHDHdmiDestroy(Private->Hdmi);

    xfree(Private);
    Output->Private = NULL;
}

#ifdef NOT_YET
static Bool
LVDSPropertyWrapper(struct rhdOutput *Output,
		    enum rhdPropertyAction Action,
		    enum rhdOutputProperty Property,
		    union rhdPropertyData *val)
{
    struct LVDSPrivate *Private = (struct LVDSPrivate *) Output->Private;
    void *storePrivate = Output->Private;
    Bool (*func)(struct rhdOutput *,enum rhdPropertyAction, enum rhdOutputProperty,
		  union rhdPropertyData *) = Private->WrappedPropertyCallback;
    Bool ret;

    Output->Private = Private->PropertyPrivate;
    ret = func(Output, Action, Property, val);
    Output->Private = storePrivate;

    return ret;
}
#endif

/*
 *
 */
struct rhdOutput *
RHDLVTMAInit(RHDPtr rhdPtr, CARD8 Type)
{
    struct rhdOutput *Output;

    RHDFUNC(rhdPtr);

    /* Stop weird connector types */
    if ((Type != RHD_CONNECTOR_PANEL)
	&& (Type != RHD_CONNECTOR_DVI)
	&& (Type != RHD_CONNECTOR_DVI_SINGLE)) {
	xf86DrvMsg(rhdPtr->scrnIndex, X_ERROR, "%s: unhandled connector type:"
		   " %d\n", __func__, Type);
	return NULL;
    }

    Output = xnfcalloc(sizeof(struct rhdOutput), 1);

    Output->scrnIndex = rhdPtr->scrnIndex;
    Output->Id = RHD_OUTPUT_LVTMA;

    Output->Sense = NULL; /* not implemented in hw */

    if (Type == RHD_CONNECTOR_PANEL) {
	struct LVDSPrivate *Private;

	Output->Name = "LVDS";

	Output->ModeValid = LVDSModeValid;
	Output->Mode = LVDSSet;
	Output->Power = LVDSPower;
	Output->Save = LVDSSave;
	Output->Restore = LVDSRestore;
	Output->Property = LVDSPropertyControl;
	Output->Destroy = LVDSDestroy;
	Output->Private = Private =  LVDSInfoRetrieve(rhdPtr);
#ifdef NOT_YET
	if (Private->BlLevel < 0) {
	    Private->BlLevel = RhdAtomSetupBacklightControlProperty(Output, &Private->WrappedPropertyCallback,
								    &Private->PropertyPrivate);
	    if (Private->PropertyPrivate)
		Output->Property = LVDSPropertyWrapper;
	} else
#else
	if (Private->BlLevel >= 0)
#endif
	    LVDSDebugBacklight(Output);

    } else {
	struct rhdTMDSBPrivate *Private = xnfcalloc(sizeof(struct rhdTMDSBPrivate), 1);

	Output->Name = "TMDS B";

	Output->ModeValid = TMDSBModeValid;
	Output->Mode = TMDSBSet;
	Output->Power = TMDSBPower;
	Output->Save = TMDSBSave;
	Output->Restore = TMDSBRestore;
	Output->Property = TMDSBPropertyControl;
	Output->Destroy = TMDSBDestroy;

	Private->Hdmi = RHDHdmiInit(rhdPtr, Output);
	Output->Private = Private;

	Private->RunsDualLink = FALSE;
	Private->Coherent = FALSE;
    }

    return Output;
}