/*
 * Copyright © 2007 Red Hat, Inc.
 * Copyright 2007  Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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.
 *
 * Authors:
 *    Dave Airlie <airlied@redhat.com>
 *    Alex Deucher <alexdeucher@gmail.com>
 *
 */

/*
 * avivo output handling functions.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/* DPMS */
#ifdef HAVE_XEXTPROTO_71
#include <X11/extensions/dpmsconst.h>
#else
#define DPMS_SERVER
#include <X11/extensions/dpms.h>
#endif

#include <unistd.h>

#include "radeon.h"
#include "radeon_reg.h"
#include "radeon_macros.h"
#include "radeon_atombios.h"

#include "ati_pciids_gen.h"

const char *device_name[12] = {
    "CRT1",
    "LCD1",
    "TV1",
    "DFP1",
    "CRT2",
    "LCD2",
    "TV2",
    "DFP2",
    "CV",
    "DFP3",
    "DFP4",
    "DFP5",
};

#define AUX_NATIVE_WRITE                    0x8
#define AUX_NATIVE_READ                     0x9

#define AUX_I2C_WRITE                       0x0
#define AUX_I2C_READ                        0x1
#define AUX_I2C_STATUS                      0x2
#define AUX_I2C_MOT                         0x4

#define DP_DPCD_REV                         0x0
#define DP_MAX_LINK_RATE                    0x1
#define DP_MAX_LANE_COUNT                   0x2
#define DP_MAX_DOWNSPREAD                   0x3
#define DP_NORP                             0x4
#define DP_DOWNSTREAMPORT_PRESENT           0x5
#define DP_MAIN_LINK_CHANNEL_CONFIG         0x6
#define DP_DP11_DOWNSTREAM_PORT_COUNT       0x7

/* from intel i830_dp.h */
#define DP_LINK_BW_SET                      0x100
//# define DP_LINK_BW_1_62                    0x06
//# define DP_LINK_BW_2_7                     0x0a
#define DP_LANE_COUNT_SET                   0x101
# define DP_LANE_COUNT_MASK                 0x0f
# define DP_LANE_COUNT_ENHANCED_FRAME_EN    (1 << 7)

#define DP_TRAINING_PATTERN_SET             0x102

# define DP_TRAINING_PATTERN_DISABLE        0
# define DP_TRAINING_PATTERN_1              1
# define DP_TRAINING_PATTERN_2              2
# define DP_TRAINING_PATTERN_MASK           0x3

# define DP_LINK_QUAL_PATTERN_DISABLE       (0 << 2)
# define DP_LINK_QUAL_PATTERN_D10_2         (1 << 2)
# define DP_LINK_QUAL_PATTERN_ERROR_RATE    (2 << 2)
# define DP_LINK_QUAL_PATTERN_PRBS7         (3 << 2)
# define DP_LINK_QUAL_PATTERN_MASK          (3 << 2)
# define DP_RECOVERED_CLOCK_OUT_EN          (1 << 4)
# define DP_LINK_SCRAMBLING_DISABLE         (1 << 5)

# define DP_SYMBOL_ERROR_COUNT_BOTH         (0 << 6)
# define DP_SYMBOL_ERROR_COUNT_DISPARITY    (1 << 6)
# define DP_SYMBOL_ERROR_COUNT_SYMBOL       (2 << 6)
# define DP_SYMBOL_ERROR_COUNT_MASK         (3 << 6)

#define DP_TRAINING_LANE0_SET               0x103
#define DP_TRAINING_LANE1_SET               0x104
#define DP_TRAINING_LANE2_SET               0x105
#define DP_TRAINING_LANE3_SET               0x106
# define DP_TRAIN_VOLTAGE_SWING_MASK        0x3
# define DP_TRAIN_VOLTAGE_SWING_SHIFT       0
# define DP_TRAIN_MAX_SWING_REACHED         (1 << 2)
# define DP_TRAIN_VOLTAGE_SWING_400         (0 << 0)
# define DP_TRAIN_VOLTAGE_SWING_600         (1 << 0)
# define DP_TRAIN_VOLTAGE_SWING_800         (2 << 0)
# define DP_TRAIN_VOLTAGE_SWING_1200        (3 << 0)

# define DP_TRAIN_PRE_EMPHASIS_MASK         (3 << 3)
# define DP_TRAIN_PRE_EMPHASIS_0            (0 << 3)
# define DP_TRAIN_PRE_EMPHASIS_3_5          (1 << 3)
# define DP_TRAIN_PRE_EMPHASIS_6            (2 << 3)
# define DP_TRAIN_PRE_EMPHASIS_9_5          (3 << 3)

# define DP_TRAIN_PRE_EMPHASIS_SHIFT        3
# define DP_TRAIN_MAX_PRE_EMPHASIS_REACHED  (1 << 5)
#define DP_DOWNSPREAD_CTRL                  0x107
# define DP_SPREAD_AMP_0_5                  (1 << 4)

#define DP_MAIN_LINK_CHANNEL_CODING_SET     0x108
# define DP_SET_ANSI_8B10B                  (1 << 0)

#define DP_LANE0_1_STATUS                   0x202
#define DP_LANE2_3_STATUS                   0x203

# define DP_LANE_CR_DONE                    (1 << 0)
# define DP_LANE_CHANNEL_EQ_DONE            (1 << 1)
# define DP_LANE_SYMBOL_LOCKED              (1 << 2)

#define DP_LANE_ALIGN_STATUS_UPDATED        0x204
#define DP_INTERLANE_ALIGN_DONE             (1 << 0)
#define DP_DOWNSTREAM_PORT_STATUS_CHANGED   (1 << 6)
#define DP_LINK_STATUS_UPDATED              (1 << 7)

#define DP_SINK_STATUS                      0x205

#define DP_RECEIVE_PORT_0_STATUS            (1 << 0)
#define DP_RECEIVE_PORT_1_STATUS            (1 << 1)

#define DP_ADJUST_REQUEST_LANE0_1           0x206
#define DP_ADJUST_REQUEST_LANE2_3           0x207

#define DP_ADJUST_VOLTAGE_SWING_LANE0_MASK  0x03
#define DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT 0
#define DP_ADJUST_PRE_EMPHASIS_LANE0_MASK   0x0c
#define DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT  2
#define DP_ADJUST_VOLTAGE_SWING_LANE1_MASK  0x30
#define DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT 4
#define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK   0xc0
#define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT  6

#define DP_LINK_STATUS_SIZE                 6
#define DP_LINK_CONFIGURATION_SIZE          9

#define DP_SET_POWER_D0  0x1
#define DP_SET_POWER_D3  0x2

static void do_displayport_link_train(xf86OutputPtr output);

static int
atombios_output_dac_setup(xf86OutputPtr output, int action)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    radeon_encoder_ptr radeon_encoder = radeon_get_encoder(output);
    radeon_tvout_ptr tvout = &radeon_output->tvout;
    DAC_ENCODER_CONTROL_PS_ALLOCATION disp_data;
    AtomBiosArgRec data;
    unsigned char *space;
    int index = 0, num = 0;
    int clock = radeon_output->pixel_clock;

    if (radeon_encoder == NULL)
	return ATOM_NOT_IMPLEMENTED;

    memset(&disp_data,0, sizeof(disp_data));

    switch (radeon_encoder->encoder_id) {
    case ENCODER_OBJECT_ID_INTERNAL_DAC1:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1:
	index = GetIndexIntoMasterTable(COMMAND, DAC1EncoderControl);
	num = 1;
	break;
    case ENCODER_OBJECT_ID_INTERNAL_DAC2:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC2:
	index = GetIndexIntoMasterTable(COMMAND, DAC2EncoderControl);
	num = 2;
	break;
    }

    disp_data.ucAction =action;

    if (radeon_output->active_device & (ATOM_DEVICE_CRT_SUPPORT))
	disp_data.ucDacStandard = ATOM_DAC1_PS2;
    else if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT))
	disp_data.ucDacStandard = ATOM_DAC1_CV;
    else {
	switch (tvout->tvStd) {
	case TV_STD_PAL:
	case TV_STD_PAL_M:
	case TV_STD_SCART_PAL:
	case TV_STD_SECAM:
	case TV_STD_PAL_CN:
	    disp_data.ucDacStandard = ATOM_DAC1_PAL;
	    break;
	case TV_STD_NTSC:
	case TV_STD_NTSC_J:
	case TV_STD_PAL_60:
	default:
	    disp_data.ucDacStandard = ATOM_DAC1_NTSC;
	    break;
	}
    }
    disp_data.usPixelClock = cpu_to_le16(clock / 10);

    data.exec.index = index;
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &disp_data;

    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
/*	ErrorF("Output DAC%d setup success\n", num); */
	return ATOM_SUCCESS;
    }

    ErrorF("Output DAC%d setup failed\n", num);
    return ATOM_NOT_IMPLEMENTED;

}

static int
atombios_output_tv_setup(xf86OutputPtr output, int action)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    radeon_tvout_ptr tvout = &radeon_output->tvout;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    TV_ENCODER_CONTROL_PS_ALLOCATION disp_data;
    AtomBiosArgRec data;
    unsigned char *space;
    int clock = radeon_output->pixel_clock;

    memset(&disp_data,0, sizeof(disp_data));

    disp_data.sTVEncoder.ucAction = action;

    if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT))
	disp_data.sTVEncoder.ucTvStandard = ATOM_TV_CV;
    else {
	switch (tvout->tvStd) {
	case TV_STD_NTSC:
	    disp_data.sTVEncoder.ucTvStandard = ATOM_TV_NTSC;
	    break;
	case TV_STD_PAL:
	    disp_data.sTVEncoder.ucTvStandard = ATOM_TV_PAL;
	    break;
	case TV_STD_PAL_M:
	    disp_data.sTVEncoder.ucTvStandard = ATOM_TV_PALM;
	    break;
	case TV_STD_PAL_60:
	    disp_data.sTVEncoder.ucTvStandard = ATOM_TV_PAL60;
	    break;
	case TV_STD_NTSC_J:
	    disp_data.sTVEncoder.ucTvStandard = ATOM_TV_NTSCJ;
	    break;
	case TV_STD_SCART_PAL:
	    disp_data.sTVEncoder.ucTvStandard = ATOM_TV_PAL; /* ??? */
	    break;
	case TV_STD_SECAM:
	    disp_data.sTVEncoder.ucTvStandard = ATOM_TV_SECAM;
	    break;
	case TV_STD_PAL_CN:
	    disp_data.sTVEncoder.ucTvStandard = ATOM_TV_PALCN;
	    break;
	default:
	    disp_data.sTVEncoder.ucTvStandard = ATOM_TV_NTSC;
	    break;
	}
    }

    disp_data.sTVEncoder.usPixelClock = cpu_to_le16(clock / 10);
    data.exec.index = GetIndexIntoMasterTable(COMMAND, TVEncoderControl);
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &disp_data;

    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
/*	ErrorF("Output TV setup success\n"); */
	return ATOM_SUCCESS;
    }

    ErrorF("Output TV setup failed\n");
    return ATOM_NOT_IMPLEMENTED;

}

int
atombios_external_tmds_setup(xf86OutputPtr output, int action)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    ScrnInfoPtr pScrn = output->scrn;
    RADEONInfoPtr info       = RADEONPTR(pScrn);
    ENABLE_EXTERNAL_TMDS_ENCODER_PS_ALLOCATION disp_data;
    AtomBiosArgRec data;
    unsigned char *space;
    int clock = radeon_output->pixel_clock;

    memset(&disp_data,0, sizeof(disp_data));

    disp_data.sXTmdsEncoder.ucEnable = action;

    if (clock > 165000)
	disp_data.sXTmdsEncoder.ucMisc = PANEL_ENCODER_MISC_DUAL;

    if (pScrn->rgbBits == 8)
	disp_data.sXTmdsEncoder.ucMisc |= (1 << 1);

    data.exec.index = GetIndexIntoMasterTable(COMMAND, DVOEncoderControl);
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &disp_data;

    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
/*	ErrorF("External TMDS setup success\n"); */
	return ATOM_SUCCESS;
    }

    ErrorF("External TMDS setup failed\n");
    return ATOM_NOT_IMPLEMENTED;
}

static int
atombios_output_ddia_setup(xf86OutputPtr output, int action)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    DVO_ENCODER_CONTROL_PS_ALLOCATION disp_data;
    AtomBiosArgRec data;
    unsigned char *space;
    int clock = radeon_output->pixel_clock;

    memset(&disp_data,0, sizeof(disp_data));

    disp_data.sDVOEncoder.ucAction = action;
    disp_data.sDVOEncoder.usPixelClock = cpu_to_le16(clock / 10);

    if (clock > 165000)
	disp_data.sDVOEncoder.usDevAttr.sDigAttrib.ucAttribute = PANEL_ENCODER_MISC_DUAL;

    data.exec.index = GetIndexIntoMasterTable(COMMAND, DVOEncoderControl);
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &disp_data;

    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
/*	ErrorF("DDIA setup success\n"); */
	return ATOM_SUCCESS;
    }

    ErrorF("DDIA setup failed\n");
    return ATOM_NOT_IMPLEMENTED;
}

static int
atombios_output_digital_setup(xf86OutputPtr output, int action)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    ScrnInfoPtr pScrn = output->scrn;
    RADEONInfoPtr info       = RADEONPTR(pScrn);
    radeon_encoder_ptr radeon_encoder = radeon_get_encoder(output);
    LVDS_ENCODER_CONTROL_PS_ALLOCATION disp_data;
    LVDS_ENCODER_CONTROL_PS_ALLOCATION_V2 disp_data2;
    AtomBiosArgRec data;
    unsigned char *space;
    int index = 0;
    int major, minor;
    int lvds_misc = 0;
    int clock = radeon_output->pixel_clock;

    if (radeon_encoder == NULL)
	return ATOM_NOT_IMPLEMENTED;

    if (radeon_output->active_device & (ATOM_DEVICE_LCD_SUPPORT)) {
	radeon_lvds_ptr lvds = (radeon_lvds_ptr)radeon_encoder->dev_priv;
	if (lvds == NULL)
	    return ATOM_NOT_IMPLEMENTED;
	lvds_misc = lvds->lvds_misc;
    }

    memset(&disp_data,0, sizeof(disp_data));
    memset(&disp_data2,0, sizeof(disp_data2));

    switch (radeon_encoder->encoder_id) {
    case ENCODER_OBJECT_ID_INTERNAL_LVDS:
	index = GetIndexIntoMasterTable(COMMAND, LVDSEncoderControl);
	break;
    case ENCODER_OBJECT_ID_INTERNAL_TMDS1:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_TMDS1:
	index = GetIndexIntoMasterTable(COMMAND, TMDS1EncoderControl);
	break;
    case ENCODER_OBJECT_ID_INTERNAL_LVTM1:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
	if (radeon_output->active_device & (ATOM_DEVICE_LCD_SUPPORT))
	    index = GetIndexIntoMasterTable(COMMAND, LVDSEncoderControl);
	else
	    index = GetIndexIntoMasterTable(COMMAND, TMDS2EncoderControl);
	break;
    }

    atombios_get_command_table_version(info->atomBIOS, index, &major, &minor);

    /*ErrorF("table is %d %d\n", major, minor);*/
    switch (major) {
    case 0:
    case 1:
    case 2:
	switch (minor) {
	case 1:
	    disp_data.ucMisc = 0;
	    disp_data.ucAction = action;
	    if ((radeon_output->ConnectorType == CONNECTOR_HDMI_TYPE_A) ||
		(radeon_output->ConnectorType == CONNECTOR_HDMI_TYPE_B))
		disp_data.ucMisc |= PANEL_ENCODER_MISC_HDMI_TYPE;
	    disp_data.usPixelClock = cpu_to_le16(clock / 10);
	    if (radeon_output->active_device & (ATOM_DEVICE_LCD_SUPPORT)) {
		if (lvds_misc & (1 << 0))
		    disp_data.ucMisc |= PANEL_ENCODER_MISC_DUAL;
		if (lvds_misc & (1 << 1))
		    disp_data.ucMisc |= (1 << 1);
	    } else {
		if (radeon_output->linkb)
		    disp_data.ucMisc |= PANEL_ENCODER_MISC_TMDS_LINKB;
		if (clock > 165000)
		    disp_data.ucMisc |= PANEL_ENCODER_MISC_DUAL;
		if (pScrn->rgbBits == 8)
		    disp_data.ucMisc |= (1 << 1);
	    }
	    data.exec.pspace = &disp_data;
	    break;
	case 2:
	case 3:
	    disp_data2.ucMisc = 0;
	    disp_data2.ucAction = action;
	    if (minor == 3) {
		if (radeon_output->coherent_mode) {
		    disp_data2.ucMisc |= PANEL_ENCODER_MISC_COHERENT;
		    xf86DrvMsg(output->scrn->scrnIndex, X_INFO, "Coherent Mode enabled\n");
		}
	    }
	    if ((radeon_output->ConnectorType == CONNECTOR_HDMI_TYPE_A) ||
		(radeon_output->ConnectorType == CONNECTOR_HDMI_TYPE_B))
		disp_data2.ucMisc |= PANEL_ENCODER_MISC_HDMI_TYPE;
	    disp_data2.usPixelClock = cpu_to_le16(clock / 10);
	    disp_data2.ucTruncate = 0;
	    disp_data2.ucSpatial = 0;
	    disp_data2.ucTemporal = 0;
	    disp_data2.ucFRC = 0;
	    if (radeon_output->active_device & (ATOM_DEVICE_LCD_SUPPORT)) {
		if (lvds_misc & (1 << 0))
		    disp_data2.ucMisc |= PANEL_ENCODER_MISC_DUAL;
		if (lvds_misc & (1 << 5)) {
		    disp_data2.ucSpatial = PANEL_ENCODER_SPATIAL_DITHER_EN;
		    if (lvds_misc & (1 << 1))
			disp_data2.ucSpatial |= PANEL_ENCODER_SPATIAL_DITHER_DEPTH;
		}
		if (lvds_misc & (1 << 6)) {
		    disp_data2.ucTemporal = PANEL_ENCODER_TEMPORAL_DITHER_EN;
		    if (lvds_misc & (1 << 1))
			disp_data2.ucTemporal |= PANEL_ENCODER_TEMPORAL_DITHER_DEPTH;
		    if (((lvds_misc >> 2) & 0x3) == 2)
			disp_data2.ucTemporal |= PANEL_ENCODER_TEMPORAL_LEVEL_4;
		}
	    } else {
		if (radeon_output->linkb)
		    disp_data2.ucMisc |= PANEL_ENCODER_MISC_TMDS_LINKB;
		if (clock > 165000)
		    disp_data2.ucMisc |= PANEL_ENCODER_MISC_DUAL;
	    }
	    data.exec.pspace = &disp_data2;
	    break;
	default:
	    ErrorF("Unknown table version\n");
	    exit(-1);
	}
	break;
    default:
	ErrorF("Unknown table version\n");
	exit(-1);
    }

    data.exec.index = index;
    data.exec.dataSpace = (void *)&space;

    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
/*	ErrorF("Output digital setup success\n"); */
	return ATOM_SUCCESS;
    }

    ErrorF("Output digital setup failed\n");
    return ATOM_NOT_IMPLEMENTED;
}

static int
atombios_maybe_hdmi_mode(xf86OutputPtr output)
{
#ifndef EDID_COMPLETE_RAWDATA
    /* there's no getting this right unless we have complete EDID */
    return ATOM_ENCODER_MODE_DVI;
#else
    if (output && xf86MonitorIsHDMI(output->MonInfo))
	return ATOM_ENCODER_MODE_HDMI;

    return ATOM_ENCODER_MODE_DVI;
#endif
}

int
atombios_get_encoder_mode(xf86OutputPtr output)
{
    ScrnInfoPtr pScrn = output->scrn;
    RADEONInfoPtr info       = RADEONPTR(pScrn);
    RADEONOutputPrivatePtr radeon_output = output->driver_private;

    /* DVI should really be atombios_maybe_hdmi_mode() as well */
    switch (radeon_output->ConnectorType) {
    case CONNECTOR_DVI_I:
	if (radeon_output->active_device & (ATOM_DEVICE_DFP_SUPPORT))
	    return ATOM_ENCODER_MODE_DVI;
	else
	    return ATOM_ENCODER_MODE_CRT;
	break;
    case CONNECTOR_DVI_D:
    default:
	return ATOM_ENCODER_MODE_DVI;
	break;
    case CONNECTOR_HDMI_TYPE_A:
    case CONNECTOR_HDMI_TYPE_B:
	if (IS_DCE4_VARIANT)
	    return ATOM_ENCODER_MODE_DVI;
	else
	    return atombios_maybe_hdmi_mode(output);
	break;
    case CONNECTOR_LVDS:
	return ATOM_ENCODER_MODE_LVDS;
	break;
    case CONNECTOR_DISPLAY_PORT:
    case CONNECTOR_EDP:
	if (radeon_output->MonType == MT_DP)
	    return ATOM_ENCODER_MODE_DP;
	else {
	    if (IS_DCE4_VARIANT)
	        return ATOM_ENCODER_MODE_DVI;
	    else
	        return atombios_maybe_hdmi_mode(output);
	}
	break;
    case CONNECTOR_DVI_A:
    case CONNECTOR_VGA:
    case CONNECTOR_STV:
    case CONNECTOR_CTV:
    case CONNECTOR_DIN:
	if (radeon_output->active_device & (ATOM_DEVICE_TV_SUPPORT))
	    return ATOM_ENCODER_MODE_TV;
	else if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT))
	    return ATOM_ENCODER_MODE_CV;
	else
	    return ATOM_ENCODER_MODE_CRT;
	break;
    }

}

static const int dp_clocks[] = {
    5400,  // 1 lane, 1.62 Ghz
    9000,  // 1 lane, 2.70 Ghz
    10800, // 2 lane, 1.62 Ghz
    18000, // 2 lane, 2.70 Ghz
    21600, // 4 lane, 1.62 Ghz
    36000, // 4 lane, 2.70 Ghz
};
static const int num_dp_clocks = sizeof(dp_clocks) / sizeof(int);

# define DP_LINK_BW_1_62                    0x06
# define DP_LINK_BW_2_7                     0x0a
static int radeon_dp_max_lane_count(xf86OutputPtr output);

static int
dp_lanes_for_mode_clock(xf86OutputPtr output, int mode_clock)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    int i;
    int max_link_bw = radeon_output->dpcd[1];
    int max_lane_count = radeon_dp_max_lane_count(output);

    switch (max_link_bw) {
    case DP_LINK_BW_1_62:
    default:
	for (i = 0; i < num_dp_clocks; i++) {
		if (i % 2)
			continue;
		switch (max_lane_count) {
		case 1:
			if (i > 1)
				return 0;
			break;
		case 2:
			if (i > 3)
				return 0;
			break;
		case 4:
		default:
			break;
		}
		if (dp_clocks[i] > (mode_clock/10)) {
			if (i < 2)
				return 1;
			else if (i < 4)
				return 2;
			else
				return 4;
		}
	}
	break;
    case DP_LINK_BW_2_7:
	for (i = 0; i < num_dp_clocks; i++) {
		switch (max_lane_count) {
		case 1:
			if (i > 1)
				return 0;
			break;
		case 2:
			if (i > 3)
				return 0;
			break;
		case 4:
		default:
			break;
		}
		if (dp_clocks[i] > (mode_clock/10)) {
			if (i < 2)
				return 1;
			else if (i < 4)
				return 2;
			else
				return 4;
		}
	}
        break;
    }

    return 0;
}

static int
dp_link_clock_for_mode_clock(xf86OutputPtr output, int mode_clock)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    int i;
    int max_link_bw = radeon_output->dpcd[1];
    int max_lane_count = radeon_dp_max_lane_count(output);

    switch (max_link_bw) {
    case DP_LINK_BW_1_62:
    default:
	for (i = 0; i < num_dp_clocks; i++) {
		if (i % 2)
			continue;
		switch (max_lane_count) {
		case 1:
			if (i > 1)
				return 0;
			break;
		case 2:
			if (i > 3)
				return 0;
			break;
		case 4:
		default:
			break;
		}
		if (dp_clocks[i] > (mode_clock/10))
			return 16200;
	}
	break;
    case DP_LINK_BW_2_7:
	for (i = 0; i < num_dp_clocks; i++) {
		switch (max_lane_count) {
		case 1:
			if (i > 1)
				return 0;
			break;
		case 2:
			if (i > 3)
				return 0;
			break;
		case 4:
		default:
			break;
		}
		if (dp_clocks[i] > (mode_clock/10))
			return (i % 2) ? 27000 : 16200;
	}
        break;
    }

    return 0;
}

/*
 * DIG Encoder/Transmitter Setup
 *
 * DCE 3.0/3.1
 * - 2 DIG transmitter blocks. UNIPHY (links A and B) and LVTMA.
 * Supports up to 3 digital outputs
 * - 2 DIG encoder blocks.
 * DIG1 can drive UNIPHY link A or link B
 * DIG2 can drive UNIPHY link B or LVTMA
 *
 * DCE 3.2
 * - 3 DIG transmitter blocks. UNIPHY0/1/2 (links A and B).
 * Supports up to 5 digital outputs
 * - 2 DIG encoder blocks.
 * DIG1/2 can drive UNIPHY0/1/2 link A or link B
 *
 * DCE 4.0
 * - 3 DIG transmitter blocks UNPHY0/1/2 (links A and B).
 * Supports up to 6 digital outputs
 * - 6 DIG encoder blocks.
 * - DIG to PHY mapping is hardcoded
 * DIG1 drives UNIPHY0 link A, A+B
 * DIG2 drives UNIPHY0 link B
 * DIG3 drives UNIPHY1 link A, A+B
 * DIG4 drives UNIPHY1 link B
 * DIG5 drives UNIPHY2 link A, A+B
 * DIG6 drives UNIPHY2 link B
 *
 * Routing
 * crtc -> dig encoder -> UNIPHY/LVTMA (1 or 2 links)
 * Examples:
 * crtc0 -> dig2 -> LVTMA links A+B
 * crtc1 -> dig1 -> UNIPHY0 link B
 * crtc0 -> dig1 -> UNIPHY2 link  A   -> LVDS
 * crtc1 -> dig2 -> UNIPHY1 link  B+A -> TMDS/HDMI
 */

union dig_encoder_control {
	DIG_ENCODER_CONTROL_PS_ALLOCATION v1;
	DIG_ENCODER_CONTROL_PARAMETERS_V2 v2;
	DIG_ENCODER_CONTROL_PARAMETERS_V3 v3;
};

static int
atombios_output_dig_encoder_setup(xf86OutputPtr output, int action)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    radeon_encoder_ptr radeon_encoder = radeon_get_encoder(output);
    union dig_encoder_control disp_data;
    AtomBiosArgRec data;
    unsigned char *space;
    int index = 0, major, minor;
    int clock = radeon_output->pixel_clock;

    if (radeon_encoder == NULL)
	return ATOM_NOT_IMPLEMENTED;

    memset(&disp_data,0, sizeof(disp_data));

    if (IS_DCE4_VARIANT)
	index = GetIndexIntoMasterTable(COMMAND, DIGxEncoderControl);
    else if (radeon_output->dig_encoder)
        index = GetIndexIntoMasterTable(COMMAND, DIG2EncoderControl);
    else
        index = GetIndexIntoMasterTable(COMMAND, DIG1EncoderControl);

    atombios_get_command_table_version(info->atomBIOS, index, &major, &minor);

    disp_data.v1.ucAction = action;
    disp_data.v1.usPixelClock = cpu_to_le16(clock / 10);
    disp_data.v1.ucEncoderMode = atombios_get_encoder_mode(output);

    if (disp_data.v1.ucEncoderMode == ATOM_ENCODER_MODE_DP) {
	if (dp_link_clock_for_mode_clock(output, clock) == 27000)
	    disp_data.v1.ucConfig |= ATOM_ENCODER_CONFIG_DPLINKRATE_2_70GHZ;
	disp_data.v1.ucLaneNum = dp_lanes_for_mode_clock(output, clock);
    } else if (clock > 165000)
	disp_data.v1.ucLaneNum = 8;
    else
	disp_data.v1.ucLaneNum = 4;

    if (IS_DCE4_VARIANT) {
	disp_data.v3.acConfig.ucDigSel = radeon_output->dig_encoder;
	disp_data.v3.ucBitPerColor = PANEL_8BIT_PER_COLOR;
    } else {
	switch (radeon_encoder->encoder_id) {
	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
	    disp_data.v1.ucConfig = ATOM_ENCODER_CONFIG_V2_TRANSMITTER1;
	    break;
	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
	case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
	    disp_data.v1.ucConfig = ATOM_ENCODER_CONFIG_V2_TRANSMITTER2;
	    break;
	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
	    disp_data.v1.ucConfig = ATOM_ENCODER_CONFIG_V2_TRANSMITTER3;
	    break;
	}
	if (radeon_output->linkb)
	    disp_data.v1.ucConfig |= ATOM_ENCODER_CONFIG_LINKB;
	else
	    disp_data.v1.ucConfig |= ATOM_ENCODER_CONFIG_LINKA;
    }

    data.exec.index = index;
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &disp_data;

    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
/*	ErrorF("Output DIG%d encoder setup success\n", radeon_output->dig_encoder); */
	return ATOM_SUCCESS;
    }

    ErrorF("Output DIG%d setup failed\n", radeon_output->dig_encoder);
    return ATOM_NOT_IMPLEMENTED;

}

union dig_transmitter_control {
    DIG_TRANSMITTER_CONTROL_PS_ALLOCATION v1;
    DIG_TRANSMITTER_CONTROL_PARAMETERS_V2 v2;
    DIG_TRANSMITTER_CONTROL_PARAMETERS_V3 v3;
};

static int
atombios_output_dig_transmitter_setup(xf86OutputPtr output, int action, uint8_t lane_num, uint8_t lane_set)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    radeon_encoder_ptr radeon_encoder = radeon_get_encoder(output);
    union dig_transmitter_control disp_data;
    AtomBiosArgRec data;
    unsigned char *space;
    int index = 0, num = 0;
    int major, minor;
    int clock = radeon_output->pixel_clock;

    if (radeon_encoder == NULL)
        return ATOM_NOT_IMPLEMENTED;

    memset(&disp_data,0, sizeof(disp_data));

    if (IS_DCE32_VARIANT || IS_DCE4_VARIANT)
	index = GetIndexIntoMasterTable(COMMAND, UNIPHYTransmitterControl);
    else {
	switch (radeon_encoder->encoder_id) {
	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
	    index = GetIndexIntoMasterTable(COMMAND, DIG1TransmitterControl);
	    break;
	case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
	    index = GetIndexIntoMasterTable(COMMAND, DIG2TransmitterControl);
	    break;
	}
    }

    atombios_get_command_table_version(info->atomBIOS, index, &major, &minor);

    disp_data.v1.ucAction = action;
    if (action == ATOM_TRANSMITTER_ACTION_INIT) {
        disp_data.v1.usInitInfo = radeon_output->connector_object_id;
    } else if (action == ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH) {
	disp_data.v1.asMode.ucLaneSel = lane_num;
	disp_data.v1.asMode.ucLaneSet = lane_set;
    } else {
	if (radeon_output->MonType == MT_DP) 
	    disp_data.v1.usPixelClock =
		cpu_to_le16(dp_link_clock_for_mode_clock(output, clock));
	else if (clock > 165000)
	    disp_data.v1.usPixelClock = cpu_to_le16((clock / 2) / 10);
	else
	    disp_data.v1.usPixelClock = cpu_to_le16(clock / 10);
    }

    if (IS_DCE4_VARIANT) {
	if (radeon_output->MonType == MT_DP)
	    disp_data.v3.ucLaneNum = dp_lanes_for_mode_clock(output, clock);
	else if (clock > 165000)
	    disp_data.v3.ucLaneNum = 8;
	else
	    disp_data.v3.ucLaneNum = 4;

	if (radeon_output->linkb) {
	    disp_data.v3.acConfig.ucLinkSel = 1;
	    disp_data.v2.acConfig.ucEncoderSel = 1;
	}

	// select the PLL for the UNIPHY
	if (radeon_output->MonType == MT_DP && info->dp_extclk)
	    disp_data.v3.acConfig.ucRefClkSource = 2; /* ext clk */
	else
	    disp_data.v3.acConfig.ucRefClkSource = radeon_output->pll_id;

	switch (radeon_encoder->encoder_id) {
	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
	    disp_data.v3.acConfig.ucTransmitterSel = 0;
	    num = 0;
	    break;
	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
	    disp_data.v3.acConfig.ucTransmitterSel = 1;
	    num = 1;
	    break;
	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
	    disp_data.v3.acConfig.ucTransmitterSel = 2;
	    num = 2;
	    break;
	}

	if (radeon_output->MonType == MT_DP)
	    disp_data.v3.acConfig.fCoherentMode = 1; /* DP requires coherent */
	else if (radeon_output->active_device & (ATOM_DEVICE_DFP_SUPPORT)) {
	    if (radeon_output->coherent_mode)
		disp_data.v3.acConfig.fCoherentMode = 1;
	    if (clock > 165000)
		disp_data.v3.acConfig.fDualLinkConnector = 1;
	}
    } else if (IS_DCE32_VARIANT) {
	if (radeon_output->dig_encoder)
	    disp_data.v2.acConfig.ucEncoderSel = 1;

	if (radeon_output->linkb)
	    disp_data.v2.acConfig.ucLinkSel = 1;

	switch (radeon_encoder->encoder_id) {
	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
	    disp_data.v2.acConfig.ucTransmitterSel = 0;
	    num = 0;
	    break;
	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
	    disp_data.v2.acConfig.ucTransmitterSel = 1;
	    num = 1;
	    break;
	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
	    disp_data.v2.acConfig.ucTransmitterSel = 2;
	    num = 2;
	    break;
	}

	if (radeon_output->MonType == MT_DP)
	    disp_data.v2.acConfig.fCoherentMode = 1; /* DP requires coherent */
	else if (radeon_output->active_device & (ATOM_DEVICE_DFP_SUPPORT)) {
	    if (radeon_output->coherent_mode)
		disp_data.v2.acConfig.fCoherentMode = 1;
	    if (clock > 165000)
		disp_data.v2.acConfig.fDualLinkConnector = 1;
	}
    } else {
	disp_data.v1.ucConfig = ATOM_TRANSMITTER_CONFIG_CLKSRC_PPLL;

	if (radeon_output->dig_encoder)
	    disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG2_ENCODER;
	else
	    disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG1_ENCODER;

	switch (radeon_encoder->encoder_id) {
	case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
	    if (info->IsIGP) {
		if (clock > 165000) {
		    if (radeon_output->igp_lane_info & 0x3)
			disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_0_7;
		    else if (radeon_output->igp_lane_info & 0xc)
			disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_8_15;
		} else {
		    if (radeon_output->igp_lane_info & 0x1)
			disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_0_3;
		    else if (radeon_output->igp_lane_info & 0x2)
			disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_4_7;
		    else if (radeon_output->igp_lane_info & 0x4)
			disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_8_11;
		    else if (radeon_output->igp_lane_info & 0x8)
			disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_12_15;
		}
	    }
	    break;
	}
	if (radeon_output->linkb)
	    disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKB;
	else
	    disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA;

	if (radeon_output->MonType == MT_DP)
	    disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_COHERENT;  /* DP requires coherent */
	else if (radeon_output->active_device & (ATOM_DEVICE_DFP_SUPPORT)) {
	    if (radeon_output->coherent_mode)
		disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_COHERENT;
	    if (clock > 165000)
		disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_8LANE_LINK;
	}
    }

    data.exec.index = index;
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &disp_data;

    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
/*
	if (IS_DCE32_VARIANT)
	    ErrorF("Output UNIPHY%d transmitter setup success\n", num);
	else
	   ErrorF("Output DIG%d transmitter setup success\n", num);
*/
	return ATOM_SUCCESS;
    }

    ErrorF("Output DIG%d transmitter setup failed\n", num);
    return ATOM_NOT_IMPLEMENTED;

}

static void atom_rv515_force_tv_scaler(ScrnInfoPtr pScrn, RADEONCrtcPrivatePtr radeon_crtc)
{
    RADEONInfoPtr info       = RADEONPTR(pScrn);
    unsigned char *RADEONMMIO = info->MMIO;
    int index_reg = 0x6578, data_reg = 0x657c;

    index_reg += radeon_crtc->crtc_offset;
    data_reg += radeon_crtc->crtc_offset;

    OUTREG(0x659C + radeon_crtc->crtc_offset, 0x0);
    OUTREG(0x6594 + radeon_crtc->crtc_offset, 0x705);
    OUTREG(0x65A4 + radeon_crtc->crtc_offset, 0x10001);
    OUTREG(0x65D8 + radeon_crtc->crtc_offset, 0x0);
    OUTREG(0x65B0 + radeon_crtc->crtc_offset, 0x0);
    OUTREG(0x65C0 + radeon_crtc->crtc_offset, 0x0);
    OUTREG(0x65D4 + radeon_crtc->crtc_offset, 0x0);
    OUTREG(index_reg,0x0);
    OUTREG(data_reg,0x841880A8);
    OUTREG(index_reg,0x1);
    OUTREG(data_reg,0x84208680);
    OUTREG(index_reg,0x2);
    OUTREG(data_reg,0xBFF880B0);
    OUTREG(index_reg,0x100);
    OUTREG(data_reg,0x83D88088);
    OUTREG(index_reg,0x101);
    OUTREG(data_reg,0x84608680);
    OUTREG(index_reg,0x102);
    OUTREG(data_reg,0xBFF080D0);
    OUTREG(index_reg,0x200);
    OUTREG(data_reg,0x83988068);
    OUTREG(index_reg,0x201);
    OUTREG(data_reg,0x84A08680);
    OUTREG(index_reg,0x202);
    OUTREG(data_reg,0xBFF080F8);
    OUTREG(index_reg,0x300);
    OUTREG(data_reg,0x83588058);
    OUTREG(index_reg,0x301);
    OUTREG(data_reg,0x84E08660);
    OUTREG(index_reg,0x302);
    OUTREG(data_reg,0xBFF88120);
    OUTREG(index_reg,0x400);
    OUTREG(data_reg,0x83188040);
    OUTREG(index_reg,0x401);
    OUTREG(data_reg,0x85008660);
    OUTREG(index_reg,0x402);
    OUTREG(data_reg,0xBFF88150);
    OUTREG(index_reg,0x500);
    OUTREG(data_reg,0x82D88030);
    OUTREG(index_reg,0x501);
    OUTREG(data_reg,0x85408640);
    OUTREG(index_reg,0x502);
    OUTREG(data_reg,0xBFF88180);
    OUTREG(index_reg,0x600);
    OUTREG(data_reg,0x82A08018);
    OUTREG(index_reg,0x601);
    OUTREG(data_reg,0x85808620);
    OUTREG(index_reg,0x602);
    OUTREG(data_reg,0xBFF081B8);
    OUTREG(index_reg,0x700);
    OUTREG(data_reg,0x82608010);
    OUTREG(index_reg,0x701);
    OUTREG(data_reg,0x85A08600);
    OUTREG(index_reg,0x702);
    OUTREG(data_reg,0x800081F0);
    OUTREG(index_reg,0x800);
    OUTREG(data_reg,0x8228BFF8);
    OUTREG(index_reg,0x801);
    OUTREG(data_reg,0x85E085E0);
    OUTREG(index_reg,0x802);
    OUTREG(data_reg,0xBFF88228);
    OUTREG(index_reg,0x10000);
    OUTREG(data_reg,0x82A8BF00);
    OUTREG(index_reg,0x10001);
    OUTREG(data_reg,0x82A08CC0);
    OUTREG(index_reg,0x10002);
    OUTREG(data_reg,0x8008BEF8);
    OUTREG(index_reg,0x10100);
    OUTREG(data_reg,0x81F0BF28);
    OUTREG(index_reg,0x10101);
    OUTREG(data_reg,0x83608CA0);
    OUTREG(index_reg,0x10102);
    OUTREG(data_reg,0x8018BED0);
    OUTREG(index_reg,0x10200);
    OUTREG(data_reg,0x8148BF38);
    OUTREG(index_reg,0x10201);
    OUTREG(data_reg,0x84408C80);
    OUTREG(index_reg,0x10202);
    OUTREG(data_reg,0x8008BEB8);
    OUTREG(index_reg,0x10300);
    OUTREG(data_reg,0x80B0BF78);
    OUTREG(index_reg,0x10301);
    OUTREG(data_reg,0x85008C20);
    OUTREG(index_reg,0x10302);
    OUTREG(data_reg,0x8020BEA0);
    OUTREG(index_reg,0x10400);
    OUTREG(data_reg,0x8028BF90);
    OUTREG(index_reg,0x10401);
    OUTREG(data_reg,0x85E08BC0);
    OUTREG(index_reg,0x10402);
    OUTREG(data_reg,0x8018BE90);
    OUTREG(index_reg,0x10500);
    OUTREG(data_reg,0xBFB8BFB0);
    OUTREG(index_reg,0x10501);
    OUTREG(data_reg,0x86C08B40);
    OUTREG(index_reg,0x10502);
    OUTREG(data_reg,0x8010BE90);
    OUTREG(index_reg,0x10600);
    OUTREG(data_reg,0xBF58BFC8);
    OUTREG(index_reg,0x10601);
    OUTREG(data_reg,0x87A08AA0);
    OUTREG(index_reg,0x10602);
    OUTREG(data_reg,0x8010BE98);
    OUTREG(index_reg,0x10700);
    OUTREG(data_reg,0xBF10BFF0);
    OUTREG(index_reg,0x10701);
    OUTREG(data_reg,0x886089E0);
    OUTREG(index_reg,0x10702);
    OUTREG(data_reg,0x8018BEB0);
    OUTREG(index_reg,0x10800);
    OUTREG(data_reg,0xBED8BFE8);
    OUTREG(index_reg,0x10801);
    OUTREG(data_reg,0x89408940);
    OUTREG(index_reg,0x10802);
    OUTREG(data_reg,0xBFE8BED8);
    OUTREG(index_reg,0x20000);
    OUTREG(data_reg,0x80008000);
    OUTREG(index_reg,0x20001);
    OUTREG(data_reg,0x90008000);
    OUTREG(index_reg,0x20002);
    OUTREG(data_reg,0x80008000);
    OUTREG(index_reg,0x20003);
    OUTREG(data_reg,0x80008000);
    OUTREG(index_reg,0x20100);
    OUTREG(data_reg,0x80108000);
    OUTREG(index_reg,0x20101);
    OUTREG(data_reg,0x8FE0BF70);
    OUTREG(index_reg,0x20102);
    OUTREG(data_reg,0xBFE880C0);
    OUTREG(index_reg,0x20103);
    OUTREG(data_reg,0x80008000);
    OUTREG(index_reg,0x20200);
    OUTREG(data_reg,0x8018BFF8);
    OUTREG(index_reg,0x20201);
    OUTREG(data_reg,0x8F80BF08);
    OUTREG(index_reg,0x20202);
    OUTREG(data_reg,0xBFD081A0);
    OUTREG(index_reg,0x20203);
    OUTREG(data_reg,0xBFF88000);
    OUTREG(index_reg,0x20300);
    OUTREG(data_reg,0x80188000);
    OUTREG(index_reg,0x20301);
    OUTREG(data_reg,0x8EE0BEC0);
    OUTREG(index_reg,0x20302);
    OUTREG(data_reg,0xBFB082A0);
    OUTREG(index_reg,0x20303);
    OUTREG(data_reg,0x80008000);
    OUTREG(index_reg,0x20400);
    OUTREG(data_reg,0x80188000);
    OUTREG(index_reg,0x20401);
    OUTREG(data_reg,0x8E00BEA0);
    OUTREG(index_reg,0x20402);
    OUTREG(data_reg,0xBF8883C0);
    OUTREG(index_reg,0x20403);
    OUTREG(data_reg,0x80008000);
    OUTREG(index_reg,0x20500);
    OUTREG(data_reg,0x80188000);
    OUTREG(index_reg,0x20501);
    OUTREG(data_reg,0x8D00BE90);
    OUTREG(index_reg,0x20502);
    OUTREG(data_reg,0xBF588500);
    OUTREG(index_reg,0x20503);
    OUTREG(data_reg,0x80008008);
    OUTREG(index_reg,0x20600);
    OUTREG(data_reg,0x80188000);
    OUTREG(index_reg,0x20601);
    OUTREG(data_reg,0x8BC0BE98);
    OUTREG(index_reg,0x20602);
    OUTREG(data_reg,0xBF308660);
    OUTREG(index_reg,0x20603);
    OUTREG(data_reg,0x80008008);
    OUTREG(index_reg,0x20700);
    OUTREG(data_reg,0x80108000);
    OUTREG(index_reg,0x20701);
    OUTREG(data_reg,0x8A80BEB0);
    OUTREG(index_reg,0x20702);
    OUTREG(data_reg,0xBF0087C0);
    OUTREG(index_reg,0x20703);
    OUTREG(data_reg,0x80008008);
    OUTREG(index_reg,0x20800);
    OUTREG(data_reg,0x80108000);
    OUTREG(index_reg,0x20801);
    OUTREG(data_reg,0x8920BED0);
    OUTREG(index_reg,0x20802);
    OUTREG(data_reg,0xBED08920);
    OUTREG(index_reg,0x20803);
    OUTREG(data_reg,0x80008010);
    OUTREG(index_reg,0x30000);
    OUTREG(data_reg,0x90008000);
    OUTREG(index_reg,0x30001);
    OUTREG(data_reg,0x80008000);
    OUTREG(index_reg,0x30100);
    OUTREG(data_reg,0x8FE0BF90);
    OUTREG(index_reg,0x30101);
    OUTREG(data_reg,0xBFF880A0);
    OUTREG(index_reg,0x30200);
    OUTREG(data_reg,0x8F60BF40);
    OUTREG(index_reg,0x30201);
    OUTREG(data_reg,0xBFE88180);
    OUTREG(index_reg,0x30300);
    OUTREG(data_reg,0x8EC0BF00);
    OUTREG(index_reg,0x30301);
    OUTREG(data_reg,0xBFC88280);
    OUTREG(index_reg,0x30400);
    OUTREG(data_reg,0x8DE0BEE0);
    OUTREG(index_reg,0x30401);
    OUTREG(data_reg,0xBFA083A0);
    OUTREG(index_reg,0x30500);
    OUTREG(data_reg,0x8CE0BED0);
    OUTREG(index_reg,0x30501);
    OUTREG(data_reg,0xBF7884E0);
    OUTREG(index_reg,0x30600);
    OUTREG(data_reg,0x8BA0BED8);
    OUTREG(index_reg,0x30601);
    OUTREG(data_reg,0xBF508640);
    OUTREG(index_reg,0x30700);
    OUTREG(data_reg,0x8A60BEE8);
    OUTREG(index_reg,0x30701);
    OUTREG(data_reg,0xBF2087A0);
    OUTREG(index_reg,0x30800);
    OUTREG(data_reg,0x8900BF00);
    OUTREG(index_reg,0x30801);
    OUTREG(data_reg,0xBF008900);
}

static int
atombios_output_yuv_setup(xf86OutputPtr output, Bool enable)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    RADEONCrtcPrivatePtr radeon_crtc = output->crtc->driver_private;
    ENABLE_YUV_PS_ALLOCATION disp_data;
    AtomBiosArgRec data;
    unsigned char *space;
    unsigned char *RADEONMMIO = info->MMIO;
    uint32_t temp, reg;

    if (info->ChipFamily >= CHIP_FAMILY_R600)
	reg = R600_BIOS_3_SCRATCH;
    else
	reg = RADEON_BIOS_3_SCRATCH;

    //fix up scratch reg handling
    temp = INREG(reg);
    if (radeon_output->active_device & (ATOM_DEVICE_TV_SUPPORT))
	OUTREG(reg, (ATOM_S3_TV1_ACTIVE |
		     (radeon_crtc->crtc_id << 18)));
    else if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT))
	OUTREG(reg, (ATOM_S3_CV_ACTIVE |
		     (radeon_crtc->crtc_id << 24)));
    else
	OUTREG(reg, 0);

    memset(&disp_data, 0, sizeof(disp_data));

    if (enable)
	disp_data.ucEnable = ATOM_ENABLE;
    disp_data.ucCRTC = radeon_crtc->crtc_id;

    data.exec.index = GetIndexIntoMasterTable(COMMAND, EnableYUV);
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &disp_data;

    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {

	OUTREG(reg, temp);

/*	ErrorF("crtc %d YUV %s setup success\n", radeon_crtc->crtc_id, enable ? "enable" : "disable"); */
	return ATOM_SUCCESS;
    }

    OUTREG(reg, temp);

    ErrorF("crtc %d YUV %s setup failed\n", radeon_crtc->crtc_id, enable ? "enable" : "disable");
    return ATOM_NOT_IMPLEMENTED;

}

static int
atombios_output_overscan_setup(xf86OutputPtr output, DisplayModePtr mode, DisplayModePtr adjusted_mode)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONCrtcPrivatePtr radeon_crtc = output->crtc->driver_private;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    SET_CRTC_OVERSCAN_PS_ALLOCATION overscan_param;
    AtomBiosArgRec data;
    unsigned char *space;
    memset(&overscan_param, 0, sizeof(overscan_param));

    overscan_param.usOverscanRight = 0;
    overscan_param.usOverscanLeft = 0;
    overscan_param.usOverscanBottom = 0;
    overscan_param.usOverscanTop = 0;
    overscan_param.ucCRTC = radeon_crtc->crtc_id;

    if (radeon_output->Flags & RADEON_USE_RMX) {
	if (radeon_output->rmx_type == RMX_FULL) {
	    overscan_param.usOverscanRight = 0;
	    overscan_param.usOverscanLeft = 0;
	    overscan_param.usOverscanBottom = 0;
	    overscan_param.usOverscanTop = 0;
	} else if (radeon_output->rmx_type == RMX_CENTER) {
	    overscan_param.usOverscanTop = (adjusted_mode->CrtcVDisplay - mode->CrtcVDisplay) / 2;
	    overscan_param.usOverscanBottom = (adjusted_mode->CrtcVDisplay - mode->CrtcVDisplay) / 2;
	    overscan_param.usOverscanLeft = (adjusted_mode->CrtcHDisplay - mode->CrtcHDisplay) / 2;
	    overscan_param.usOverscanRight = (adjusted_mode->CrtcHDisplay - mode->CrtcHDisplay) / 2;
	} else if (radeon_output->rmx_type == RMX_ASPECT) {
	    int a1 = mode->CrtcVDisplay * adjusted_mode->CrtcHDisplay;
	    int a2 = adjusted_mode->CrtcVDisplay * mode->CrtcHDisplay;

	    if (a1 > a2) {
		overscan_param.usOverscanLeft = (adjusted_mode->CrtcHDisplay - (a2 / mode->CrtcVDisplay)) / 2;
		overscan_param.usOverscanRight = (adjusted_mode->CrtcHDisplay - (a2 / mode->CrtcVDisplay)) / 2;
	    } else if (a2 > a1) {
		overscan_param.usOverscanLeft = (adjusted_mode->CrtcVDisplay - (a1 / mode->CrtcHDisplay)) / 2;
		overscan_param.usOverscanRight = (adjusted_mode->CrtcVDisplay - (a1 / mode->CrtcHDisplay)) / 2;
	    }
	}
    }

    data.exec.index = GetIndexIntoMasterTable(COMMAND, SetCRTC_OverScan);
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &overscan_param;

    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
/*	ErrorF("Set CRTC %d Overscan success\n", radeon_crtc->crtc_id); */
	return ATOM_SUCCESS ;
    }

    ErrorF("Set CRTC %d Overscan failed\n", radeon_crtc->crtc_id);
    return ATOM_NOT_IMPLEMENTED;
}

static int
atombios_output_scaler_setup(xf86OutputPtr output)
{
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    radeon_tvout_ptr tvout = &radeon_output->tvout;
    RADEONCrtcPrivatePtr radeon_crtc = output->crtc->driver_private;
    ENABLE_SCALER_PS_ALLOCATION disp_data;
    AtomBiosArgRec data;
    unsigned char *space;

    if (!IS_AVIVO_VARIANT && radeon_crtc->crtc_id)
	return ATOM_SUCCESS;

    memset(&disp_data, 0, sizeof(disp_data));

    disp_data.ucScaler = radeon_crtc->crtc_id;

    if (radeon_output->active_device & (ATOM_DEVICE_TV_SUPPORT)) {
	switch (tvout->tvStd) {
	case TV_STD_NTSC:
	    disp_data.ucTVStandard = ATOM_TV_NTSC;
	    break;
	case TV_STD_PAL:
	    disp_data.ucTVStandard = ATOM_TV_PAL;
	    break;
	case TV_STD_PAL_M:
	    disp_data.ucTVStandard = ATOM_TV_PALM;
	    break;
	case TV_STD_PAL_60:
	    disp_data.ucTVStandard = ATOM_TV_PAL60;
	    break;
	case TV_STD_NTSC_J:
	    disp_data.ucTVStandard = ATOM_TV_NTSCJ;
	    break;
	case TV_STD_SCART_PAL:
	    disp_data.ucTVStandard = ATOM_TV_PAL; /* ??? */
	    break;
	case TV_STD_SECAM:
	    disp_data.ucTVStandard = ATOM_TV_SECAM;
	    break;
	case TV_STD_PAL_CN:
	    disp_data.ucTVStandard = ATOM_TV_PALCN;
	    break;
	default:
	    disp_data.ucTVStandard = ATOM_TV_NTSC;
	    break;
	}
	disp_data.ucEnable = SCALER_ENABLE_MULTITAP_MODE;
/*        ErrorF("Using TV scaler %x %x\n", disp_data.ucTVStandard, disp_data.ucEnable); */
    } else if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT)) {
	disp_data.ucTVStandard = ATOM_TV_CV;
	disp_data.ucEnable = SCALER_ENABLE_MULTITAP_MODE;
/*        ErrorF("Using CV scaler %x %x\n", disp_data.ucTVStandard, disp_data.ucEnable); */
    } else if (radeon_output->Flags & RADEON_USE_RMX) {
/*	ErrorF("Using RMX\n"); */
	if (radeon_output->rmx_type == RMX_FULL)
	    disp_data.ucEnable = ATOM_SCALER_EXPANSION;
	else if (radeon_output->rmx_type == RMX_CENTER)
	    disp_data.ucEnable = ATOM_SCALER_CENTER;
	else if (radeon_output->rmx_type == RMX_ASPECT)
	    disp_data.ucEnable = ATOM_SCALER_EXPANSION;
    } else {
/*	ErrorF("Not using RMX\n"); */
	if (IS_AVIVO_VARIANT)
	    disp_data.ucEnable = ATOM_SCALER_DISABLE;
	else
	    disp_data.ucEnable = ATOM_SCALER_CENTER;
    }

    data.exec.index = GetIndexIntoMasterTable(COMMAND, EnableScaler);
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &disp_data;

    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
	if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT | ATOM_DEVICE_TV_SUPPORT)
	    && info->ChipFamily >= CHIP_FAMILY_RV515 && info->ChipFamily <= CHIP_FAMILY_RV570) {
/*	    ErrorF("forcing TV scaler\n"); */
	    atom_rv515_force_tv_scaler(output->scrn, radeon_crtc);
	}
/*	ErrorF("scaler %d setup success\n", radeon_crtc->crtc_id); */
	return ATOM_SUCCESS;
    }

    ErrorF("scaler %d setup failed\n", radeon_crtc->crtc_id);
    return ATOM_NOT_IMPLEMENTED;

}

void
atombios_output_dpms(xf86OutputPtr output, int mode)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    radeon_encoder_ptr radeon_encoder = radeon_get_encoder(output);
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    DISPLAY_DEVICE_OUTPUT_CONTROL_PS_ALLOCATION disp_data;
    AtomBiosArgRec data;
    unsigned char *space;
    int index = 0;
    Bool is_dig = FALSE;
    unsigned char *RADEONMMIO = info->MMIO;
    uint32_t reg = 0;

    if (radeon_encoder == NULL)
        return;

    switch (radeon_encoder->encoder_id) {
    case ENCODER_OBJECT_ID_INTERNAL_TMDS1:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_TMDS1:
	index = GetIndexIntoMasterTable(COMMAND, TMDSAOutputControl);
	break;
    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
	is_dig = TRUE;
	break;
    case ENCODER_OBJECT_ID_INTERNAL_DVO1:
    case ENCODER_OBJECT_ID_INTERNAL_DDI:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1:
	index = GetIndexIntoMasterTable(COMMAND, DVOOutputControl);
	break;
    case ENCODER_OBJECT_ID_INTERNAL_LVDS:
	index = GetIndexIntoMasterTable(COMMAND, LCD1OutputControl);
	break;
    case ENCODER_OBJECT_ID_INTERNAL_LVTM1:
	if (radeon_output->active_device & (ATOM_DEVICE_LCD_SUPPORT))
	    index = GetIndexIntoMasterTable(COMMAND, LCD1OutputControl);
	else
	    index = GetIndexIntoMasterTable(COMMAND, LVTMAOutputControl);
	break;
    case ENCODER_OBJECT_ID_INTERNAL_DAC1:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1:
	if (IS_DCE32_VARIANT)
	    index = GetIndexIntoMasterTable(COMMAND, DAC1OutputControl);
	else {
	    if (radeon_output->active_device & (ATOM_DEVICE_TV_SUPPORT))
		index = GetIndexIntoMasterTable(COMMAND, TV1OutputControl);
	    else if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT))
		index = GetIndexIntoMasterTable(COMMAND, CV1OutputControl);
	    else
		index = GetIndexIntoMasterTable(COMMAND, DAC1OutputControl);
	}
	break;
    case ENCODER_OBJECT_ID_INTERNAL_DAC2:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC2:
	if (IS_DCE32_VARIANT)
	    index = GetIndexIntoMasterTable(COMMAND, DAC2OutputControl);
	else {
	    if (radeon_output->active_device & (ATOM_DEVICE_TV_SUPPORT))
		index = GetIndexIntoMasterTable(COMMAND, TV1OutputControl);
	    else if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT))
		index = GetIndexIntoMasterTable(COMMAND, CV1OutputControl);
	    else
		index = GetIndexIntoMasterTable(COMMAND, DAC2OutputControl);
	}
	break;
    }

    switch (mode) {
    case DPMSModeOn:
	radeon_encoder->devices |= radeon_output->active_device;
	if (is_dig) {
	    atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_ENABLE_OUTPUT, 0, 0);
	    if (((radeon_output->ConnectorType == CONNECTOR_DISPLAY_PORT) ||
		 (radeon_output->ConnectorType == CONNECTOR_EDP)) &&
		(radeon_output->MonType == MT_DP)) {
		do_displayport_link_train(output);
		if (IS_DCE4_VARIANT)
		    atombios_output_dig_encoder_setup(output, ATOM_ENCODER_CMD_DP_VIDEO_ON);
	    }
	}
	else {
	    disp_data.ucAction = ATOM_ENABLE;
	    data.exec.index = index;
	    data.exec.dataSpace = (void *)&space;
	    data.exec.pspace = &disp_data;

	    /* workaround for DVOOutputControl on some RS690 systems */
	    if (radeon_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_DDI) {
		reg = INREG(RADEON_BIOS_3_SCRATCH);
		OUTREG(RADEON_BIOS_3_SCRATCH, reg & ~ATOM_S3_DFP2I_ACTIVE);
	    }
	    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) != ATOM_SUCCESS)
/*
		ErrorF("Output %s enable success\n",
		       device_name[radeon_get_device_index(radeon_output->active_device)]);
	    else
*/
		ErrorF("Output %s enable failed\n",
		       device_name[radeon_get_device_index(radeon_output->active_device)]);
	    if (radeon_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_DDI)
		OUTREG(RADEON_BIOS_3_SCRATCH, reg);
	}
	/* at least for TV atom fails to reassociate the correct crtc source at dpms on */
	if (radeon_output->active_device & (ATOM_DEVICE_TV_SUPPORT))
		atombios_set_output_crtc_source(output);
	break;
    case DPMSModeStandby:
    case DPMSModeSuspend:
    case DPMSModeOff:
	radeon_encoder->devices &= ~(radeon_output->active_device);
	if (!radeon_encoder->devices) {
	    if (is_dig) {
		atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_DISABLE_OUTPUT, 0, 0);
		if (((radeon_output->ConnectorType == CONNECTOR_DISPLAY_PORT) ||
		     (radeon_output->ConnectorType == CONNECTOR_EDP)) &&
		    (radeon_output->MonType == MT_DP)) {
		    if (IS_DCE4_VARIANT)
			atombios_output_dig_encoder_setup(output, ATOM_ENCODER_CMD_DP_VIDEO_OFF);
		}
	    } else {
		disp_data.ucAction = ATOM_DISABLE;
		data.exec.index = index;
		data.exec.dataSpace = (void *)&space;
		data.exec.pspace = &disp_data;

		if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data)
		    != ATOM_SUCCESS)
/*
		    ErrorF("Output %s disable success\n",
			   device_name[radeon_get_device_index(radeon_output->active_device)]);
		else
*/
		    ErrorF("Output %s disable failed\n",
			   device_name[radeon_get_device_index(radeon_output->active_device)]);
	    }
	}
	break;
    }
}

union crtc_source_param {
    SELECT_CRTC_SOURCE_PS_ALLOCATION v1;
    SELECT_CRTC_SOURCE_PARAMETERS_V2 v2;
};

void
atombios_set_output_crtc_source(xf86OutputPtr output)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONCrtcPrivatePtr radeon_crtc = output->crtc->driver_private;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    radeon_encoder_ptr radeon_encoder = radeon_get_encoder(output);
    AtomBiosArgRec data;
    unsigned char *space;
    union crtc_source_param args;
    int index = GetIndexIntoMasterTable(COMMAND, SelectCRTC_Source);
    int major, minor;

    if (radeon_encoder == NULL)
	return;

    memset(&args, 0, sizeof(args));

    atombios_get_command_table_version(info->atomBIOS, index, &major, &minor);

    /*ErrorF("select crtc source table is %d %d\n", major, minor);*/

    switch(major) {
    case 1:
	switch(minor) {
	case 0:
	case 1:
	default:
	    if (IS_AVIVO_VARIANT)
		args.v1.ucCRTC = radeon_crtc->crtc_id;
	    else {
		if (radeon_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_DAC1)
		    args.v1.ucCRTC = radeon_crtc->crtc_id;
		else
		    args.v1.ucCRTC = radeon_crtc->crtc_id << 2;
	    }
	    switch (radeon_encoder->encoder_id) {
	    case ENCODER_OBJECT_ID_INTERNAL_TMDS1:
	    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_TMDS1:
		args.v1.ucDevice = ATOM_DEVICE_DFP1_INDEX;
		break;
	    case ENCODER_OBJECT_ID_INTERNAL_LVDS:
	    case ENCODER_OBJECT_ID_INTERNAL_LVTM1:
		if (radeon_output->active_device & ATOM_DEVICE_LCD1_SUPPORT)
		    args.v1.ucDevice = ATOM_DEVICE_LCD1_INDEX;
		else
		    args.v1.ucDevice = ATOM_DEVICE_DFP3_INDEX;
		break;
	    case ENCODER_OBJECT_ID_INTERNAL_DVO1:
	    case ENCODER_OBJECT_ID_INTERNAL_DDI:
	    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1:
		args.v1.ucDevice = ATOM_DEVICE_DFP2_INDEX;
		break;
	    case ENCODER_OBJECT_ID_INTERNAL_DAC1:
	    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1:
		if (radeon_output->active_device & (ATOM_DEVICE_TV_SUPPORT))
		    args.v1.ucDevice = ATOM_DEVICE_TV1_INDEX;
		else if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT))
		    args.v1.ucDevice = ATOM_DEVICE_CV_INDEX;
		else
		    args.v1.ucDevice = ATOM_DEVICE_CRT1_INDEX;
		break;
	    case ENCODER_OBJECT_ID_INTERNAL_DAC2:
	    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC2:
		if (radeon_output->active_device & (ATOM_DEVICE_TV_SUPPORT))
		    args.v1.ucDevice = ATOM_DEVICE_TV1_INDEX;
		else if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT))
		    args.v1.ucDevice = ATOM_DEVICE_CV_INDEX;
		else
		    args.v1.ucDevice = ATOM_DEVICE_CRT2_INDEX;
		break;
	    }
	    /*ErrorF("device sourced: 0x%x\n", args.v1.ucDevice);*/
	    break;
	case 2:
	    args.v2.ucCRTC = radeon_crtc->crtc_id;
	    args.v2.ucEncodeMode = atombios_get_encoder_mode(output);
	    switch (radeon_encoder->encoder_id) {
	    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
	    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
	    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
	    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
 		switch (radeon_output->dig_encoder) {
 		case 0:
 		    args.v2.ucEncoderID = ASIC_INT_DIG1_ENCODER_ID;
 		    break;
 		case 1:
 		    args.v2.ucEncoderID = ASIC_INT_DIG2_ENCODER_ID;
 		    break;
 		case 2:
 		    args.v2.ucEncoderID = ASIC_INT_DIG3_ENCODER_ID;
 		    break;
 		case 3:
 		    args.v2.ucEncoderID = ASIC_INT_DIG4_ENCODER_ID;
 		    break;
 		case 4:
 		    args.v2.ucEncoderID = ASIC_INT_DIG5_ENCODER_ID;
 		    break;
 		case 5:
 		    args.v2.ucEncoderID = ASIC_INT_DIG6_ENCODER_ID;
 		    break;
 		}
		break;
	    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1:
		args.v2.ucEncoderID = ASIC_INT_DVO_ENCODER_ID;
		break;
	    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1:
		if (radeon_output->active_device & (ATOM_DEVICE_TV_SUPPORT))
		    args.v2.ucEncoderID = ASIC_INT_TV_ENCODER_ID;
		else if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT))
		    args.v2.ucEncoderID = ASIC_INT_TV_ENCODER_ID;
		else
		    args.v2.ucEncoderID = ASIC_INT_DAC1_ENCODER_ID;
		break;
	    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC2:
		if (radeon_output->active_device & (ATOM_DEVICE_TV_SUPPORT))
		    args.v2.ucEncoderID = ASIC_INT_TV_ENCODER_ID;
		else if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT))
		    args.v2.ucEncoderID = ASIC_INT_TV_ENCODER_ID;
		else
		    args.v2.ucEncoderID = ASIC_INT_DAC2_ENCODER_ID;
		break;
	    }
	    /*ErrorF("device sourced: 0x%x\n", args.v2.ucEncoderID);*/
	    break;
	}
	break;
    default:
	ErrorF("Unknown table version\n");
	exit(-1);
    }

    data.exec.pspace = &args;
    data.exec.index = index;
    data.exec.dataSpace = (void *)&space;

    if (RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
/*	ErrorF("Set CRTC %d Source success\n", radeon_crtc->crtc_id); */
	return;
    }

    ErrorF("Set CRTC Source failed\n");
    return;
}

static void
atombios_apply_output_quirks(xf86OutputPtr output, DisplayModePtr mode)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONCrtcPrivatePtr radeon_crtc = output->crtc->driver_private;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    unsigned char *RADEONMMIO = info->MMIO;

    /* Funky macbooks */
    if ((info->Chipset == PCI_CHIP_RV530_71C5) &&
	(PCI_SUB_VENDOR_ID(info->PciInfo) == 0x106b) &&
	(PCI_SUB_DEVICE_ID(info->PciInfo) == 0x0080)) {
	if (radeon_output->MonType == MT_LCD) {
	    if (radeon_output->devices & ATOM_DEVICE_LCD1_SUPPORT) {
		uint32_t lvtma_bit_depth_control = INREG(AVIVO_LVTMA_BIT_DEPTH_CONTROL);

		lvtma_bit_depth_control &= ~AVIVO_LVTMA_BIT_DEPTH_CONTROL_TRUNCATE_EN;
		lvtma_bit_depth_control &= ~AVIVO_LVTMA_BIT_DEPTH_CONTROL_SPATIAL_DITHER_EN;

		OUTREG(AVIVO_LVTMA_BIT_DEPTH_CONTROL, lvtma_bit_depth_control);
	    }
	}
    }

    /* set scaler clears this on some chips */
    if (!(radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT | ATOM_DEVICE_TV_SUPPORT))) {
	if (IS_AVIVO_VARIANT && (mode->Flags & V_INTERLACE))
	    OUTREG(AVIVO_D1MODE_DATA_FORMAT + radeon_crtc->crtc_offset, AVIVO_D1MODE_INTERLEAVE_EN);
    }

    if (IS_DCE32_VARIANT &&
	(!IS_DCE4_VARIANT) &&
	(radeon_output->active_device & (ATOM_DEVICE_DFP_SUPPORT))) {
	radeon_encoder_ptr radeon_encoder = radeon_get_encoder(output);
	if (radeon_encoder == NULL)
	    return;
	/* XXX: need to sort out why transmitter control table sometimes sets this to a
	 * different golden value.
	 */
	if (radeon_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_UNIPHY2) {
	    OUTREG(0x7ec4, 0x00824002);
	}
    }
}

void
atombios_pick_dig_encoder(xf86OutputPtr output)
{
    xf86CrtcConfigPtr   xf86_config = XF86_CRTC_CONFIG_PTR(output->scrn);
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    radeon_encoder_ptr radeon_encoder = NULL;
    Bool is_lvtma = FALSE;
    int i, mode;
    uint32_t dig_enc_use_mask = 0;

    /* non digital encoders don't need a dig block */
    mode = atombios_get_encoder_mode(output);
    if (mode == ATOM_ENCODER_MODE_CRT ||
        mode == ATOM_ENCODER_MODE_TV ||
        mode == ATOM_ENCODER_MODE_CV)
        return;

    if (IS_DCE4_VARIANT) {
        radeon_encoder = radeon_get_encoder(output);

	if (IS_DCE41_VARIANT) {
	    if (radeon_output->linkb)
		radeon_output->dig_encoder = 1;
	    else
		radeon_output->dig_encoder = 0;
	} else {
	    switch (radeon_encoder->encoder_id) {
	    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
		if (radeon_output->linkb)
		    radeon_output->dig_encoder = 1;
		else
		    radeon_output->dig_encoder = 0;
		break;
	    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
		if (radeon_output->linkb)
		    radeon_output->dig_encoder = 3;
		else
		    radeon_output->dig_encoder = 2;
		break;
	    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
		if (radeon_output->linkb)
		    radeon_output->dig_encoder = 5;
		else
		    radeon_output->dig_encoder = 4;
		break;
	    default:
		ErrorF("Unknown encoder\n");
		break;
	    }
	}
	return;
    }

    if (IS_DCE32_VARIANT) {
        RADEONCrtcPrivatePtr radeon_crtc = output->crtc->driver_private;
        radeon_output->dig_encoder = radeon_crtc->crtc_id;
        return;
    }

    for (i = 0; i < xf86_config->num_output; i++) {
        xf86OutputPtr test = xf86_config->output[i];
        RADEONOutputPrivatePtr radeon_test = test->driver_private;
        radeon_encoder = radeon_get_encoder(test);

        if (!radeon_encoder || !test->crtc)
            continue;

        if (output == test && radeon_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA)
            is_lvtma = TRUE;
        if (output != test && (radeon_test->dig_encoder >= 0))
            dig_enc_use_mask |= (1 << radeon_test->dig_encoder);

    }
    if (is_lvtma) {
        if (dig_enc_use_mask & 0x2)
            ErrorF("Need digital encoder 2 for LVTMA and it isn't free - stealing\n");
        radeon_output->dig_encoder = 1;
        return;
    }
    if (!(dig_enc_use_mask & 1))
        radeon_output->dig_encoder = 0;
    else
        radeon_output->dig_encoder = 1;
}
void
atombios_output_mode_set(xf86OutputPtr output,
			 DisplayModePtr mode,
			 DisplayModePtr adjusted_mode)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    radeon_encoder_ptr radeon_encoder = radeon_get_encoder(output);
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    if (radeon_encoder == NULL)
	return;

    radeon_output->pixel_clock = adjusted_mode->Clock;
    atombios_output_overscan_setup(output, mode, adjusted_mode);
    atombios_output_scaler_setup(output);
    atombios_set_output_crtc_source(output);

    if (IS_AVIVO_VARIANT && !IS_DCE4_VARIANT) {
	if (radeon_output->active_device & (ATOM_DEVICE_CV_SUPPORT | ATOM_DEVICE_TV_SUPPORT))
	    atombios_output_yuv_setup(output, TRUE);
	else
	    atombios_output_yuv_setup(output, FALSE);
    }

    switch (radeon_encoder->encoder_id) {
    case ENCODER_OBJECT_ID_INTERNAL_TMDS1:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_TMDS1:
    case ENCODER_OBJECT_ID_INTERNAL_LVDS:
    case ENCODER_OBJECT_ID_INTERNAL_LVTM1:
	atombios_output_digital_setup(output, PANEL_ENCODER_ACTION_ENABLE);
	break;
    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1:
    case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
	/* disable encoder and transmitter */
	/* setup and enable the encoder and transmitter */
	if (IS_DCE4_VARIANT) {
	    atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_DISABLE, 0, 0);
	    atombios_output_dig_encoder_setup(output, ATOM_ENCODER_CMD_SETUP);
	    atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_INIT, 0, 0);
	    atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_ENABLE, 0, 0);
	} else {
	    atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_DISABLE, 0, 0);
	    atombios_output_dig_encoder_setup(output, ATOM_DISABLE);
	    atombios_output_dig_encoder_setup(output, ATOM_ENABLE);

	    atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_INIT, 0, 0);
	    atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_SETUP, 0, 0);
	    atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_ENABLE, 0, 0);
	}
	break;
    case ENCODER_OBJECT_ID_INTERNAL_DDI:
	atombios_output_ddia_setup(output, ATOM_ENABLE);
	break;
    case ENCODER_OBJECT_ID_INTERNAL_DVO1:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1:
	atombios_external_tmds_setup(output, ATOM_ENABLE);
	break;
    case ENCODER_OBJECT_ID_INTERNAL_DAC1:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1:
    case ENCODER_OBJECT_ID_INTERNAL_DAC2:
    case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC2:
	atombios_output_dac_setup(output, ATOM_ENABLE);
	if (radeon_output->devices & (ATOM_DEVICE_TV_SUPPORT | ATOM_DEVICE_CV_SUPPORT)) {
		if (radeon_output->active_device & (ATOM_DEVICE_TV_SUPPORT | ATOM_DEVICE_CV_SUPPORT))
			atombios_output_tv_setup(output, ATOM_ENABLE);
		else
			atombios_output_tv_setup(output, ATOM_DISABLE);
	}
	break;
    }
    atombios_apply_output_quirks(output, adjusted_mode);
}

static AtomBiosResult
atom_bios_dac_load_detect(atomBiosHandlePtr atomBIOS, xf86OutputPtr output)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    DAC_LOAD_DETECTION_PS_ALLOCATION dac_data;
    AtomBiosArgRec data;
    unsigned char *space;
    int major, minor;
    int index = GetIndexIntoMasterTable(COMMAND, DAC_LoadDetection);

    atombios_get_command_table_version(info->atomBIOS, index, &major, &minor);

    dac_data.sDacload.ucMisc = 0;

    if (radeon_output->devices & ATOM_DEVICE_CRT1_SUPPORT) {
	dac_data.sDacload.usDeviceID = cpu_to_le16(ATOM_DEVICE_CRT1_SUPPORT);
	if (info->encoders[ATOM_DEVICE_CRT1_INDEX] &&
	    ((info->encoders[ATOM_DEVICE_CRT1_INDEX]->encoder_id == ENCODER_OBJECT_ID_INTERNAL_DAC1) ||
	     (info->encoders[ATOM_DEVICE_CRT1_INDEX]->encoder_id == ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1)))
	    dac_data.sDacload.ucDacType = ATOM_DAC_A;
	else
	    dac_data.sDacload.ucDacType = ATOM_DAC_B;
    } else if (radeon_output->devices & ATOM_DEVICE_CRT2_SUPPORT) {
	dac_data.sDacload.usDeviceID = cpu_to_le16(ATOM_DEVICE_CRT2_SUPPORT);
	if (info->encoders[ATOM_DEVICE_CRT2_INDEX] &&
	    ((info->encoders[ATOM_DEVICE_CRT2_INDEX]->encoder_id == ENCODER_OBJECT_ID_INTERNAL_DAC1) ||
	     (info->encoders[ATOM_DEVICE_CRT2_INDEX]->encoder_id == ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1)))
	    dac_data.sDacload.ucDacType = ATOM_DAC_A;
	else
	    dac_data.sDacload.ucDacType = ATOM_DAC_B;
    } else if (radeon_output->devices & ATOM_DEVICE_CV_SUPPORT) {
	dac_data.sDacload.usDeviceID = cpu_to_le16(ATOM_DEVICE_CV_SUPPORT);
	if (info->encoders[ATOM_DEVICE_CV_INDEX] &&
	    ((info->encoders[ATOM_DEVICE_CV_INDEX]->encoder_id == ENCODER_OBJECT_ID_INTERNAL_DAC1) ||
	     (info->encoders[ATOM_DEVICE_CV_INDEX]->encoder_id == ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1)))
	    dac_data.sDacload.ucDacType = ATOM_DAC_A;
	else
	    dac_data.sDacload.ucDacType = ATOM_DAC_B;
	if (minor >= 3)
	    dac_data.sDacload.ucMisc = DAC_LOAD_MISC_YPrPb;
    } else if (radeon_output->devices & ATOM_DEVICE_TV1_SUPPORT) {
	dac_data.sDacload.usDeviceID = cpu_to_le16(ATOM_DEVICE_TV1_SUPPORT);
	if (info->encoders[ATOM_DEVICE_TV1_INDEX] &&
	    ((info->encoders[ATOM_DEVICE_TV1_INDEX]->encoder_id == ENCODER_OBJECT_ID_INTERNAL_DAC1) ||
	     (info->encoders[ATOM_DEVICE_TV1_INDEX]->encoder_id == ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1)))
	    dac_data.sDacload.ucDacType = ATOM_DAC_A;
	else
	    dac_data.sDacload.ucDacType = ATOM_DAC_B;
	if (minor >= 3)
	    dac_data.sDacload.ucMisc = DAC_LOAD_MISC_YPrPb;
    } else
	return ATOM_NOT_IMPLEMENTED;

    data.exec.index = index;
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &dac_data;

    if (RHDAtomBiosFunc(atomBIOS->pScrn, atomBIOS, ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
/*	ErrorF("Dac detection success\n"); */
	return ATOM_SUCCESS ;
    }

    ErrorF("DAC detection failed\n");
    return ATOM_NOT_IMPLEMENTED;
}

RADEONMonitorType
atombios_dac_detect(xf86OutputPtr output)
{
    ScrnInfoPtr pScrn = output->scrn;
    RADEONInfoPtr info       = RADEONPTR(pScrn);
    unsigned char *RADEONMMIO = info->MMIO;
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONMonitorType MonType = MT_NONE;
    AtomBiosResult ret;
    RADEONSavePtr save = info->ModeReg;

    if (radeon_output->devices & ATOM_DEVICE_TV1_SUPPORT) {
	if (xf86ReturnOptValBool(info->Options, OPTION_FORCE_TVOUT, FALSE)) {
	    if (radeon_output->ConnectorType == CONNECTOR_STV)
		return MT_STV;
	    else
		return MT_CTV;
	}
    }

    ret = atom_bios_dac_load_detect(info->atomBIOS, output);
    if (ret == ATOM_SUCCESS) {
	if (info->ChipFamily >= CHIP_FAMILY_R600)
	    save->bios_0_scratch = INREG(R600_BIOS_0_SCRATCH);
	else
	    save->bios_0_scratch = INREG(RADEON_BIOS_0_SCRATCH);
	/*ErrorF("DAC connect %08X\n", (unsigned int)save->bios_0_scratch);*/

	if (radeon_output->devices & ATOM_DEVICE_CRT1_SUPPORT) {
	    if (save->bios_0_scratch & ATOM_S0_CRT1_MASK)
		MonType = MT_CRT;
	} else if (radeon_output->devices & ATOM_DEVICE_CRT2_SUPPORT) {
	    if (save->bios_0_scratch & ATOM_S0_CRT2_MASK)
		MonType = MT_CRT;
	} else if (radeon_output->devices & ATOM_DEVICE_CV_SUPPORT) {
	    if (save->bios_0_scratch & (ATOM_S0_CV_MASK | ATOM_S0_CV_MASK_A))
		MonType = MT_CV;
	} else if (radeon_output->devices & ATOM_DEVICE_TV1_SUPPORT) {
	    if (save->bios_0_scratch & (ATOM_S0_TV1_COMPOSITE | ATOM_S0_TV1_COMPOSITE_A))
		MonType = MT_CTV;
	    else if (save->bios_0_scratch & (ATOM_S0_TV1_SVIDEO | ATOM_S0_TV1_SVIDEO_A))
		MonType = MT_STV;
	}
    }

    return MonType;
}


static inline int atom_dp_get_encoder_id(xf86OutputPtr output)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    int ret = 0;
    if (radeon_output->dig_encoder)
        ret |= ATOM_DP_CONFIG_DIG2_ENCODER;
    else
        ret |= ATOM_DP_CONFIG_DIG1_ENCODER;
    if (radeon_output->linkb)
        ret |= ATOM_DP_CONFIG_LINK_B;
    else
        ret |= ATOM_DP_CONFIG_LINK_A;
    return ret;
}

union aux_channel_transaction {
    PROCESS_AUX_CHANNEL_TRANSACTION_PS_ALLOCATION v1;
    PROCESS_AUX_CHANNEL_TRANSACTION_PARAMETERS_V2 v2;
};

Bool
RADEONProcessAuxCH(xf86OutputPtr output, uint8_t *req_bytes, uint8_t num_bytes,
		   uint8_t *read_byte, uint8_t read_buf_len, uint8_t delay)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    RADEONInfoPtr info       = RADEONPTR(output->scrn);
    union aux_channel_transaction args;
    AtomBiosArgRec data;
    unsigned char *space;
    unsigned char *base;
    int retry_count = 0;

    memset(&args, 0, sizeof(args));
    if (info->atomBIOS->fbBase)
	base = info->FB + info->atomBIOS->fbBase;
    else if (info->atomBIOS->scratchBase)
	base = (unsigned char *)info->atomBIOS->scratchBase;
    else
	return FALSE;

retry:
    memcpy(base, req_bytes, num_bytes);

    args.v1.lpAuxRequest = 0;
    args.v1.lpDataOut = 16;
    args.v1.ucDataOutLen = 0;
    args.v1.ucChannelID = radeon_output->ucI2cId;
    args.v1.ucDelay = delay / 10; /* 10 usec */
    if (IS_DCE4_VARIANT)
	args.v2.ucHPD_ID = radeon_output->hpd_id;

    data.exec.index = GetIndexIntoMasterTable(COMMAND, ProcessAuxChannelTransaction);
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &args;

    RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data);
    if (args.v1.ucReplyStatus && !args.v1.ucDataOutLen) {
	if (args.v1.ucReplyStatus == 0x20 && retry_count++ < 10)
		goto retry;
	ErrorF("failed to get auxch %02x%02x %02x %02x %02x after %d retries\n",
	       req_bytes[1], req_bytes[0], req_bytes[2], req_bytes[3], args.v1.ucReplyStatus, retry_count);
	return FALSE;
    }
    if (args.v1.ucDataOutLen && read_byte && read_buf_len) {
	if (read_buf_len < args.v1.ucDataOutLen) {
	    ErrorF("%s: Buffer too small for return answer %d %d\n", __func__, read_buf_len, args.v1.ucDataOutLen);
	    return FALSE;
	}
	{
	    int len = read_buf_len < args.v1.ucDataOutLen ? read_buf_len : args.v1.ucDataOutLen;
	    memcpy(read_byte, base+16, len);
	}
    }
    return TRUE;
}

static int
RADEONDPEncoderService(xf86OutputPtr output, int action, uint8_t ucconfig, uint8_t lane_num)
{
    RADEONInfoPtr info = RADEONPTR(output->scrn);
    DP_ENCODER_SERVICE_PARAMETERS args;
    AtomBiosArgRec data;
    unsigned char *space;

    memset(&args, 0, sizeof(args));

    args.ucLinkClock = 0;
    args.ucConfig = ucconfig;
    args.ucAction = action;
    args.ucLaneNum = lane_num;
    args.ucStatus = 0;

    data.exec.index = GetIndexIntoMasterTable(COMMAND, DPEncoderService);
    data.exec.dataSpace = (void *)&space;
    data.exec.pspace = &args;

    RHDAtomBiosFunc(info->atomBIOS->pScrn, info->atomBIOS, ATOMBIOS_EXEC, &data);

/*    ErrorF("%s: %d %d\n", __func__, action, args.ucStatus); */
    return args.ucStatus;
}

int RADEON_DP_GetSinkType(xf86OutputPtr output)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;

    return RADEONDPEncoderService(output, ATOM_DP_ACTION_GET_SINK_TYPE, radeon_output->ucI2cId, 0);
}

static Bool atom_dp_aux_native_write(xf86OutputPtr output, uint16_t address,
				     uint8_t send_bytes, uint8_t *send)
{
    uint8_t msg[20];
    uint8_t msg_len, dp_msg_len;
    int ret;

    dp_msg_len = 4;
    msg[0] = address;
    msg[1] = address >> 8;
    msg[2] = AUX_NATIVE_WRITE << 4;
    dp_msg_len += send_bytes;
    msg[3] = (dp_msg_len << 4)| (send_bytes - 1);

    if (0)
	ErrorF("writing %02x %02x %02x, %d, %d\n", msg[0], msg[1], msg[3], send_bytes, dp_msg_len);
    if (send_bytes > 16)
	return FALSE;

    memcpy(&msg[4], send, send_bytes);
    msg_len = 4 + send_bytes;
    ret = RADEONProcessAuxCH(output, msg, msg_len, NULL, 0, 0);
    return ret;
}

static Bool atom_dp_aux_native_read(xf86OutputPtr output, uint16_t address,
				    uint8_t delay,
				    uint8_t expected_bytes, uint8_t *read_p)
{
    uint8_t msg[20];
    uint8_t msg_len, dp_msg_len;
    int ret;

    msg_len = 4;
    dp_msg_len = 4;
    msg[0] = address;
    msg[1] = address >> 8;
    msg[2] = AUX_NATIVE_READ << 4;
    msg[3] = (dp_msg_len) << 4;
    msg[3] |= expected_bytes - 1;

    if (0)
	ErrorF("reading %02x %02x %02x, %d, %d\n", msg[0], msg[1], msg[3], expected_bytes, dp_msg_len);
    ret = RADEONProcessAuxCH(output, msg, msg_len, read_p, expected_bytes, delay);
    return ret;
}

/* fill out the DPCD structure */
void RADEON_DP_GetDPCD(xf86OutputPtr output)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    uint8_t msg[25];
    int ret;

    ret = atom_dp_aux_native_read(output, DP_DPCD_REV, 0, 8, msg);
    if (ret) {
	memcpy(radeon_output->dpcd, msg, 8);
	if (0) {
	    int i;
	    ErrorF("DPCD: ");
	    for (i = 0; i < 8; i++)
		ErrorF("%02x ", radeon_output->dpcd[i]);
	    ErrorF("\n");
	}
	ret = atom_dp_aux_native_read(output, DP_LINK_BW_SET, 0, 2, msg);
	if (0) {
	    ErrorF("0x200: %02x %02x\n", msg[0], msg[1]);
	}
	return;
    }
    radeon_output->dpcd[0] = 0;
    return;
}


enum dp_aux_i2c_mode {
    dp_aux_i2c_start,
    dp_aux_i2c_write,
    dp_aux_i2c_read,
    dp_aux_i2c_stop,
};


static Bool atom_dp_aux_i2c_transaction(xf86OutputPtr output, uint16_t address,
				       enum dp_aux_i2c_mode mode,
				       uint8_t write_byte, uint8_t *read_byte)
{
    uint8_t msg[8], msg_len, dp_msg_len;
    int ret;
    int auxch_cmd = 0;

    memset(msg, 0, 8);

    if (mode != dp_aux_i2c_stop)
	auxch_cmd = AUX_I2C_MOT;

    if (address & 1)
	auxch_cmd |= AUX_I2C_READ;
    else
    	auxch_cmd |= AUX_I2C_WRITE;

    msg[2] = auxch_cmd << 4;

    msg[4] = 0;
    msg[0] = (address >> 1);
    msg[1] = (address >> 9);

    msg_len = 4;
    dp_msg_len = 3;
    switch (mode) {
    case dp_aux_i2c_read:
	/* bottom bits is byte count - 1 so for 1 byte == 0 */
	dp_msg_len += 1;
	break;
    case dp_aux_i2c_write:
	dp_msg_len += 2;
	msg[4] = write_byte;
	msg_len++;
	break;
    default:
	break;
    }
    msg[3] = dp_msg_len << 4;

    ret = RADEONProcessAuxCH(output, msg, msg_len, read_byte, 1, 0);
    return ret;
}

static Bool
atom_dp_i2c_address(I2CDevPtr dev, I2CSlaveAddr addr)
{
    I2CBusPtr bus = dev->pI2CBus;
    xf86OutputPtr output = bus->DriverPrivate.ptr;
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    int ret;

    radeon_output->dp_i2c_addr = addr;
    radeon_output->dp_i2c_running = TRUE;

    /* call i2c start */
    ret = atom_dp_aux_i2c_transaction(output, radeon_output->dp_i2c_addr,
				      dp_aux_i2c_start, 0, NULL);

    return ret;
}
static Bool
atom_dp_i2c_start(I2CBusPtr bus, int timeout)
{
/*    ErrorF("%s\n", __func__); */
    return TRUE;
}

static void
atom_dp_i2c_stop(I2CDevPtr dev)
{
    I2CBusPtr bus = dev->pI2CBus;
    xf86OutputPtr output = bus->DriverPrivate.ptr;
    RADEONOutputPrivatePtr radeon_output = output->driver_private;

    if (radeon_output->dp_i2c_running)
	atom_dp_aux_i2c_transaction(output, radeon_output->dp_i2c_addr,
				    dp_aux_i2c_stop, 0, NULL);
    radeon_output->dp_i2c_running = FALSE;
}


static Bool
atom_dp_i2c_put_byte(I2CDevPtr dev, I2CByte byte)
{
    I2CBusPtr bus = dev->pI2CBus;
    xf86OutputPtr output = bus->DriverPrivate.ptr;
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    Bool ret;

    ret = (atom_dp_aux_i2c_transaction(output, radeon_output->dp_i2c_addr,
				       dp_aux_i2c_write, byte, NULL));
    return ret;
}

static Bool
atom_dp_i2c_get_byte(I2CDevPtr dev, I2CByte *byte_ret, Bool last)
{
    I2CBusPtr bus = dev->pI2CBus;
    xf86OutputPtr output = bus->DriverPrivate.ptr;
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    Bool ret;

    ret = (atom_dp_aux_i2c_transaction(output, radeon_output->dp_i2c_addr,
				       dp_aux_i2c_read, 0, byte_ret));
    return ret;
}

Bool
RADEON_DP_I2CInit(ScrnInfoPtr pScrn, I2CBusPtr *bus_ptr, char *name, xf86OutputPtr output)
{
    I2CBusPtr pI2CBus;

    pI2CBus = xf86CreateI2CBusRec();
    if (!pI2CBus) return FALSE;

    pI2CBus->BusName = name;
    pI2CBus->scrnIndex = pScrn->scrnIndex;
    pI2CBus->I2CGetByte = atom_dp_i2c_get_byte;
    pI2CBus->I2CPutByte = atom_dp_i2c_put_byte;
    pI2CBus->I2CAddress = atom_dp_i2c_address;
    pI2CBus->I2CStart = atom_dp_i2c_start;
    pI2CBus->I2CStop = atom_dp_i2c_stop;
    pI2CBus->DriverPrivate.ptr = output;

    /*
     * These were set incorrectly in the server pre-1.3, Having
     * duplicate settings is sub-optimal, but this lets the driver
     * work with older servers
     */
    pI2CBus->ByteTimeout = 2200; /* VESA DDC spec 3 p. 43 (+10 %) */
    pI2CBus->StartTimeout = 550;
    pI2CBus->BitTimeout = 40;
    pI2CBus->AcknTimeout = 40;
    pI2CBus->RiseFallTime = 20;

    if (!xf86I2CBusInit(pI2CBus))
	return FALSE;

    *bus_ptr = pI2CBus;
    return TRUE;
}


static uint8_t dp_link_status(uint8_t link_status[DP_LINK_STATUS_SIZE], int r)
{
    return link_status[r - DP_LANE0_1_STATUS];
}

static uint8_t dp_get_lane_status(uint8_t link_status[DP_LINK_STATUS_SIZE], int lane)
{
    int i = DP_LANE0_1_STATUS + (lane >> 1);
    int s = (lane & 1) * 4;
    uint8_t l = dp_link_status(link_status, i);
    return (l >> s) & 0xf;
}

static Bool dp_clock_recovery_ok(uint8_t link_status[DP_LINK_STATUS_SIZE], int lane_count)
{
    int lane;

    uint8_t lane_status;

    for (lane = 0; lane < lane_count; lane++) {
	lane_status = dp_get_lane_status(link_status, lane);
	if ((lane_status & DP_LANE_CR_DONE) == 0)
	    return FALSE;
    }
    return TRUE;
}


/* Check to see if channel eq is done on all channels */
#define CHANNEL_EQ_BITS (DP_LANE_CR_DONE|\
			 DP_LANE_CHANNEL_EQ_DONE|\
			 DP_LANE_SYMBOL_LOCKED)
static Bool
dp_channel_eq_ok(uint8_t link_status[DP_LINK_STATUS_SIZE], int lane_count)
{
    uint8_t lane_align;
    uint8_t lane_status;
    int lane;

    lane_align = dp_link_status(link_status,
				DP_LANE_ALIGN_STATUS_UPDATED);
    if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0)
	return FALSE;
    for (lane = 0; lane < lane_count; lane++) {
	lane_status = dp_get_lane_status(link_status, lane);
	if ((lane_status & CHANNEL_EQ_BITS) != CHANNEL_EQ_BITS)
	    return FALSE;
    }
    return TRUE;
}

/*
 * Fetch AUX CH registers 0x202 - 0x207 which contain
 * link status information
 */
static Bool
atom_dp_get_link_status(xf86OutputPtr output,
			  uint8_t link_status[DP_LINK_STATUS_SIZE])
{
    ScrnInfoPtr pScrn = output->scrn;
    int ret;
    ret = atom_dp_aux_native_read(output, DP_LANE0_1_STATUS, 100,
				  DP_LINK_STATUS_SIZE, link_status);
    if (!ret) {
	xf86DrvMsg(pScrn->scrnIndex, X_INFO, "dp link status failed\n");
	return FALSE;
    }
/*    ErrorF("link status %02x %02x %02x %02x %02x %02x\n", link_status[0], link_status[1],
	   link_status[2], link_status[3], link_status[4], link_status[5]); */

    return TRUE;
}

static uint8_t
dp_get_adjust_request_voltage(uint8_t link_status[DP_LINK_STATUS_SIZE],
			      int lane)

{
    int     i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
    int     s = ((lane & 1) ?
                 DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT :
                 DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT);
    uint8_t l = dp_link_status(link_status, i);

    return ((l >> s) & 3) << DP_TRAIN_VOLTAGE_SWING_SHIFT;
}

static uint8_t
dp_get_adjust_request_pre_emphasis(uint8_t link_status[DP_LINK_STATUS_SIZE],
				   int lane)
{
    int     i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
    int     s = ((lane & 1) ?
                 DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT :
                 DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT);
    uint8_t l = dp_link_status(link_status, i);

    return ((l >> s) & 3) << DP_TRAIN_PRE_EMPHASIS_SHIFT;
}

static char     *voltage_names[] = {
        "0.4V", "0.6V", "0.8V", "1.2V"
};
static char     *pre_emph_names[] = {
        "0dB", "3.5dB", "6dB", "9.5dB"
};

/*
 * These are source-specific values; current Intel hardware supports
 * a maximum voltage of 800mV and a maximum pre-emphasis of 6dB
 */
#define DP_VOLTAGE_MAX         DP_TRAIN_VOLTAGE_SWING_1200

static uint8_t
dp_pre_emphasis_max(uint8_t voltage_swing)
{
    switch (voltage_swing & DP_TRAIN_VOLTAGE_SWING_MASK) {
    case DP_TRAIN_VOLTAGE_SWING_400:
        return DP_TRAIN_PRE_EMPHASIS_6;
    case DP_TRAIN_VOLTAGE_SWING_600:
        return DP_TRAIN_PRE_EMPHASIS_6;
    case DP_TRAIN_VOLTAGE_SWING_800:
        return DP_TRAIN_PRE_EMPHASIS_3_5;
    case DP_TRAIN_VOLTAGE_SWING_1200:
    default:
        return DP_TRAIN_PRE_EMPHASIS_0;
    }
}

static void dp_set_training(xf86OutputPtr output, uint8_t training)
{
    atom_dp_aux_native_write(output, DP_TRAINING_PATTERN_SET, 1, &training);
}

static void dp_set_power(xf86OutputPtr output, uint8_t power_state)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;

    if (radeon_output->dpcd[0] >= 0x11) {
	atom_dp_aux_native_write(output, 0x600, 1, &power_state);
    }
}

static void
dp_get_adjust_train(xf86OutputPtr output,
		      uint8_t link_status[DP_LINK_STATUS_SIZE],
		      int lane_count,
		      uint8_t train_set[4])
{
    ScrnInfoPtr pScrn = output->scrn;
    uint8_t v = 0;
    uint8_t p = 0;
    int lane;

    for (lane = 0; lane < lane_count; lane++) {
	uint8_t this_v = dp_get_adjust_request_voltage(link_status, lane);
	uint8_t this_p = dp_get_adjust_request_pre_emphasis(link_status, lane);

	if (0) {
	    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
		       "requested signal parameters: lane %d voltage %s pre_emph %s\n",
		       lane,
		       voltage_names[this_v >> DP_TRAIN_VOLTAGE_SWING_SHIFT],
		       pre_emph_names[this_p >> DP_TRAIN_PRE_EMPHASIS_SHIFT]);
	}
	if (this_v > v)
	    v = this_v;
	if (this_p > p)
	    p = this_p;
    }

    if (v >= DP_VOLTAGE_MAX)
	v = DP_VOLTAGE_MAX | DP_TRAIN_MAX_SWING_REACHED;

    if (p >= dp_pre_emphasis_max(v))
	p = dp_pre_emphasis_max(v) | DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;

    if (0) {
	xf86DrvMsg(pScrn->scrnIndex, X_INFO,
		   "using signal parameters: voltage %s pre_emph %s\n",
		   voltage_names[(v & DP_TRAIN_VOLTAGE_SWING_MASK) >> DP_TRAIN_VOLTAGE_SWING_SHIFT],
		   pre_emph_names[(p & DP_TRAIN_PRE_EMPHASIS_MASK) >> DP_TRAIN_PRE_EMPHASIS_SHIFT]);
    }
    for (lane = 0; lane < 4; lane++)
	train_set[lane] = v | p;
}

static int radeon_dp_max_lane_count(xf86OutputPtr output)
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    int max_lane_count = 4;

    if (radeon_output->dpcd[0] >= 0x11) {
	max_lane_count = radeon_output->dpcd[2] & 0x1f;
	switch(max_lane_count) {
	case 1: case 2: case 4:
	    break;
	default:
	    max_lane_count = 4;
	}
    }
    return max_lane_count;
}

Bool radeon_dp_mode_fixup(xf86OutputPtr output, DisplayModePtr mode, DisplayModePtr adjusted_mode)
{
	RADEONOutputPrivatePtr radeon_output = output->driver_private;
	int clock = adjusted_mode->Clock;

	radeon_output->dp_lane_count = dp_lanes_for_mode_clock(output, clock);
	radeon_output->dp_clock = dp_link_clock_for_mode_clock(output, clock);
	if (!radeon_output->dp_lane_count || !radeon_output->dp_clock)
		return FALSE;
	return TRUE;
}

static void dp_update_dpvs_emph(xf86OutputPtr output, uint8_t train_set[4])
{
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    int i;
    for (i = 0; i < radeon_output->dp_lane_count; i++)
	atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH, i, train_set[i]);

    atom_dp_aux_native_write(output, DP_TRAINING_LANE0_SET, radeon_output->dp_lane_count, train_set);
}

static void do_displayport_link_train(xf86OutputPtr output)
{
    ScrnInfoPtr pScrn = output->scrn;
    RADEONInfoPtr info = RADEONPTR(pScrn);
    RADEONOutputPrivatePtr radeon_output = output->driver_private;
    int enc_id = atom_dp_get_encoder_id(output);
    Bool clock_recovery;
    uint8_t link_status[DP_LINK_STATUS_SIZE];
    uint8_t tries, voltage, ss_cntl;
    uint8_t train_set[4];
    int i;
    Bool channel_eq;
    uint8_t dp_link_configuration[DP_LINK_CONFIGURATION_SIZE];

    memset(train_set, 0, 4);

    /* set up link configuration */
    memset(dp_link_configuration, 0, DP_LINK_CONFIGURATION_SIZE);

    if (radeon_output->dp_clock == 27000)
	dp_link_configuration[0] = DP_LINK_BW_2_7;
    else
	dp_link_configuration[0] = DP_LINK_BW_1_62;
    dp_link_configuration[1] = radeon_output->dp_lane_count;

    if (radeon_output->dpcd[0] >= 0x11) {
	dp_link_configuration[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
    }

    /* power up to D0 */
    dp_set_power(output, DP_SET_POWER_D0);

    /* disable training */
    dp_set_training(output, DP_TRAINING_PATTERN_DISABLE);

    /* write link rate / num / eh framing */
    atom_dp_aux_native_write(output, DP_LINK_BW_SET, 2,
			     dp_link_configuration);

    /* write ss cntl */
    ss_cntl = 0;
    atom_dp_aux_native_write(output, DP_DOWNSPREAD_CTRL, 1,
			     &ss_cntl);

    /* start local training start */
    if (IS_DCE4_VARIANT) {
	atombios_output_dig_encoder_setup(output, ATOM_ENCODER_CMD_DP_LINK_TRAINING_START);
	atombios_output_dig_encoder_setup(output, ATOM_ENCODER_CMD_DP_LINK_TRAINING_PATTERN1);
    } else {
	RADEONDPEncoderService(output, ATOM_DP_ACTION_TRAINING_START, enc_id, 0);
	RADEONDPEncoderService(output, ATOM_DP_ACTION_TRAINING_PATTERN_SEL, enc_id, 0);
    }

    usleep(400);
    dp_set_training(output, DP_TRAINING_PATTERN_1);
    dp_update_dpvs_emph(output, train_set);

    /* loop around doing configuration reads and DP encoder setups */
    clock_recovery = FALSE;
    tries = 0;
    voltage = 0xff;
    for (;;) {
      	usleep(100);
	if (!atom_dp_get_link_status(output, link_status))
	    break;

	if (dp_clock_recovery_ok(link_status, radeon_output->dp_lane_count)) {
	    clock_recovery = TRUE;
	    break;
	}

	for (i = 0; i < radeon_output->dp_lane_count; i++)
	    if ((train_set[i] & DP_TRAIN_MAX_SWING_REACHED) == 0)
		break;
	if (i == radeon_output->dp_lane_count) {
	    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
		       "clock recovery reached max voltage\n");
	    break;
	}

	/* Check to see if we've tried the same voltage 5 times */
	if ((train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == voltage) {
	    ++tries;
	    if (tries == 5) {
		xf86DrvMsg(pScrn->scrnIndex, X_INFO,
			   "clock recovery tried 5 times\n");
		break;
	    }
	} else
	    tries = 0;

	voltage = train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK;

        dp_get_adjust_train(output, link_status, radeon_output->dp_lane_count, train_set);
	dp_update_dpvs_emph(output, train_set);

    }

    if (!clock_recovery)
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		   "clock recovery failed\n");

    /* channel equalization */
    tries = 0;
    channel_eq = FALSE;
    dp_set_training(output, DP_TRAINING_PATTERN_2);
    if (IS_DCE4_VARIANT)
	atombios_output_dig_encoder_setup(output, ATOM_ENCODER_CMD_DP_LINK_TRAINING_PATTERN2);
    else
	RADEONDPEncoderService(output, ATOM_DP_ACTION_TRAINING_PATTERN_SEL, enc_id, 1);

    for (;;) {
	usleep(400);
	if (!atom_dp_get_link_status(output, link_status))
	    break;

	if (dp_channel_eq_ok(link_status, radeon_output->dp_lane_count)) {
	    channel_eq = TRUE;
	    break;
	}

	/* Try 5 times */
	if (tries > 5) {
	    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
		       "channel eq failed: 5 tries\n");
	    break;
	}

	/* Compute new train_set as requested by target */
        dp_get_adjust_train(output, link_status, radeon_output->dp_lane_count, train_set);
	dp_update_dpvs_emph(output, train_set);

	++tries;
    }

    if (!channel_eq)
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
		   "channel eq failed\n");

    dp_set_training(output, DP_TRAINING_PATTERN_DISABLE);
    if (IS_DCE4_VARIANT)
	atombios_output_dig_encoder_setup(output, ATOM_ENCODER_CMD_DP_LINK_TRAINING_COMPLETE);
    else
	RADEONDPEncoderService(output, ATOM_DP_ACTION_TRAINING_COMPLETE, enc_id, 0);

}

