summaryrefslogtreecommitdiff
path: root/src/atombios_output.c
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2009-12-02 14:30:36 +1000
committerDave Airlie <airlied@redhat.com>2009-12-02 14:30:40 +1000
commitefbc2c80ab02879edf3b7b3d65b16c45ddce5017 (patch)
tree2250add945ebcca4f52ae31615bd643ea5cc9cce /src/atombios_output.c
parent88a50a30df11a06263209340a42251851f8e2334 (diff)
parenta612813c5f329f00a9271e7bb69abd2630a49403 (diff)
Merge remote branch 'origin/displayport'
Diffstat (limited to 'src/atombios_output.c')
-rw-r--r--src/atombios_output.c1076
1 files changed, 997 insertions, 79 deletions
diff --git a/src/atombios_output.c b/src/atombios_output.c
index c9f1dfa3..8047b534 100644
--- a/src/atombios_output.c
+++ b/src/atombios_output.c
@@ -65,6 +65,8 @@ const char *device_name[12] = {
"DFP5",
};
+static void do_displayport_link_train(xf86OutputPtr output);
+
static void atombios_set_output_crtc_source(xf86OutputPtr output);
static int
@@ -466,41 +468,102 @@ atombios_get_encoder_mode(xf86OutputPtr output)
}
static const int dp_clocks[] = {
- 16200,
- 27000,
- 32400,
- 54000,
- 0,
- 0,
- 64800,
- 108000,
+ 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
-dp_lanes_for_mode_clock(int mode_clock)
+dp_lanes_for_mode_clock(RADEONOutputPrivatePtr radeon_output,
+ int mode_clock)
{
int i;
-
- for (i = 0; i < num_dp_clocks; i++)
- if (dp_clocks[i] > (mode_clock / 10))
- return (i / 2) + 1;
+ int max_link_bw = radeon_output->dpcd[1];
+
+ switch (max_link_bw) {
+ case DP_LINK_BW_1_62:
+ default:
+ for (i = 0; i < num_dp_clocks; i++) {
+ if (i % 2)
+ continue;
+ 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++) {
+ 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(int mode_clock)
+dp_link_clock_for_mode_clock(RADEONOutputPrivatePtr radeon_output,
+ int mode_clock)
{
int i;
+ int max_link_bw = radeon_output->dpcd[1];
- for (i = 0; i < num_dp_clocks; i++)
- if (dp_clocks[i] > (mode_clock / 10))
- return (dp_clocks[i % 2]);
+ switch (max_link_bw) {
+ case DP_LINK_BW_1_62:
+ default:
+ return 16200;
+ break;
+ case DP_LINK_BW_2_7:
+ for (i = 0; i < num_dp_clocks; i++)
+ 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
+ *
+ * Routing
+ * crtc -> dig encoder -> UNIPHY/LVTMA (1 or 2 links)
+ * Examples:
+ * crtc0 -> dig2 -> LVTMA links A+B
+ * crtc1 -> dig1 -> UNIPHY0 link B
+ */
static int
atombios_output_dig_encoder_setup(xf86OutputPtr output, int action)
{
@@ -528,10 +591,17 @@ atombios_output_dig_encoder_setup(xf86OutputPtr output, int action)
} else {
switch (radeon_encoder->encoder_id) {
case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
- index = GetIndexIntoMasterTable(COMMAND, DIG1EncoderControl);
+ /* doesn't really matter which dig encoder we pick as long as it's
+ * not already in use
+ */
+ if (radeon_output->linkb)
+ index = GetIndexIntoMasterTable(COMMAND, DIG2EncoderControl);
+ else
+ index = GetIndexIntoMasterTable(COMMAND, DIG1EncoderControl);
num = 1;
break;
case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
+ /* Only dig2 encoder can drive LVTMA */
index = GetIndexIntoMasterTable(COMMAND, DIG2EncoderControl);
num = 2;
break;
@@ -569,26 +639,18 @@ atombios_output_dig_encoder_setup(xf86OutputPtr output, int action)
disp_data.ucEncoderMode = atombios_get_encoder_mode(output);
if (disp_data.ucEncoderMode == ATOM_ENCODER_MODE_DP) {
- if (radeon_output->linkb)
- disp_data.ucConfig |= ATOM_ENCODER_CONFIG_LINKB;
- else
- disp_data.ucConfig |= ATOM_ENCODER_CONFIG_LINKA;
-
- if (dp_link_clock_for_mode_clock(clock) == 27000)
+ if (dp_link_clock_for_mode_clock(radeon_output, clock) == 27000)
disp_data.ucConfig |= ATOM_ENCODER_CONFIG_DPLINKRATE_2_70GHZ;
-
- disp_data.ucLaneNum = dp_lanes_for_mode_clock(clock);
- } else if (clock > 165000) {
- disp_data.ucConfig |= ATOM_ENCODER_CONFIG_LINKA_B;
+ disp_data.ucLaneNum = dp_lanes_for_mode_clock(radeon_output, clock);
+ } else if (clock > 165000)
disp_data.ucLaneNum = 8;
- } else {
- if (radeon_output->linkb)
- disp_data.ucConfig |= ATOM_ENCODER_CONFIG_LINKB;
- else
- disp_data.ucConfig |= ATOM_ENCODER_CONFIG_LINKA;
-
+ else
disp_data.ucLaneNum = 4;
- }
+
+ if (radeon_output->linkb)
+ disp_data.ucConfig |= ATOM_ENCODER_CONFIG_LINKB;
+ else
+ disp_data.ucConfig |= ATOM_ENCODER_CONFIG_LINKA;
data.exec.index = index;
data.exec.dataSpace = (void *)&space;
@@ -610,7 +672,7 @@ union dig_transmitter_control {
};
static int
-atombios_output_dig_transmitter_setup(xf86OutputPtr output, int action)
+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);
@@ -648,10 +710,13 @@ atombios_output_dig_transmitter_setup(xf86OutputPtr output, int action)
if (IS_DCE32_VARIANT) {
if (action == ATOM_TRANSMITTER_ACTION_INIT) {
disp_data.v2.usInitInfo = radeon_output->connector_object_id;
+ } else if (action == ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH) {
+ disp_data.v2.asMode.ucLaneSel = lane_num;
+ disp_data.v2.asMode.ucLaneSet = lane_set;
} else {
if (radeon_output->MonType == MT_DP) {
disp_data.v2.usPixelClock =
- cpu_to_le16(dp_link_clock_for_mode_clock(clock));
+ cpu_to_le16(dp_link_clock_for_mode_clock(radeon_output, clock));
disp_data.v2.acConfig.fDPConnector = 1;
} else if (clock > 165000) {
disp_data.v2.usPixelClock = cpu_to_le16((clock / 2) / 10);
@@ -663,6 +728,9 @@ atombios_output_dig_transmitter_setup(xf86OutputPtr output, int action)
if (dig_block)
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;
@@ -678,7 +746,9 @@ atombios_output_dig_transmitter_setup(xf86OutputPtr output, int action)
break;
}
- if (radeon_output->active_device & (ATOM_DEVICE_DFP_SUPPORT)) {
+ 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;
xf86DrvMsg(output->scrn->scrnIndex, X_INFO, "UNIPHY%d transmitter: Coherent Mode enabled\n",disp_data.v2.acConfig.ucTransmitterSel);
@@ -690,10 +760,13 @@ atombios_output_dig_transmitter_setup(xf86OutputPtr output, int 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.v2.asMode.ucLaneSel = lane_num;
+ disp_data.v2.asMode.ucLaneSet = lane_set;
} else {
if (radeon_output->MonType == MT_DP)
disp_data.v1.usPixelClock =
- cpu_to_le16(dp_link_clock_for_mode_clock(clock));
+ cpu_to_le16(dp_link_clock_for_mode_clock(radeon_output, clock));
else if (clock > 165000)
disp_data.v1.usPixelClock = cpu_to_le16((clock / 2) / 10);
else
@@ -702,18 +775,20 @@ atombios_output_dig_transmitter_setup(xf86OutputPtr output, int action)
switch (radeon_encoder->encoder_id) {
case ENCODER_OBJECT_ID_INTERNAL_UNIPHY:
- disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG1_ENCODER;
+ /* doesn't really matter which dig encoder we pick as long as it's
+ * not already in use
+ */
+ if (radeon_output->linkb)
+ disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG2_ENCODER;
+ else
+ disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG1_ENCODER;
if (info->IsIGP) {
if (clock > 165000) {
- disp_data.v1.ucConfig |= (ATOM_TRANSMITTER_CONFIG_8LANE_LINK |
- ATOM_TRANSMITTER_CONFIG_LINKA_B);
-
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 {
- disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA;
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)
@@ -723,39 +798,24 @@ atombios_output_dig_transmitter_setup(xf86OutputPtr output, int action)
else if (radeon_output->igp_lane_info & 0x8)
disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_12_15;
}
- } else {
- if (clock > 165000)
- disp_data.v1.ucConfig |= (ATOM_TRANSMITTER_CONFIG_8LANE_LINK |
- ATOM_TRANSMITTER_CONFIG_LINKA_B |
- ATOM_TRANSMITTER_CONFIG_LANE_0_7);
- else {
- /* XXX */
- if (radeon_output->linkb)
- disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKB | ATOM_TRANSMITTER_CONFIG_LANE_0_3;
- else
- disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA | ATOM_TRANSMITTER_CONFIG_LANE_0_3;
- }
}
break;
case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
+ /* Only dig2 encoder can drive LVTMA */
disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG2_ENCODER;
- if (clock > 165000)
- disp_data.v1.ucConfig |= (ATOM_TRANSMITTER_CONFIG_8LANE_LINK |
- ATOM_TRANSMITTER_CONFIG_LINKA_B |
- ATOM_TRANSMITTER_CONFIG_LANE_0_7);
- else {
- /* XXX */
- if (radeon_output->linkb)
- disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKB | ATOM_TRANSMITTER_CONFIG_LANE_0_3;
- else
- disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA | ATOM_TRANSMITTER_CONFIG_LANE_0_3;
- }
break;
}
+ if (clock > 165000)
+ disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_8LANE_LINK;
+ if (radeon_output->linkb)
+ disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKB;
+ else
+ disp_data.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA;
- if (radeon_output->active_device & (ATOM_DEVICE_DFP_SUPPORT)) {
- if (radeon_output->coherent_mode &&
- radeon_output->MonType != MT_DP) {
+ 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;
xf86DrvMsg(output->scrn->scrnIndex, X_INFO,
"DIG%d transmitter: Coherent Mode enabled\n", num);
@@ -1285,8 +1345,12 @@ atombios_output_dpms(xf86OutputPtr output, int mode)
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);
+ 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->MonType == MT_DP) {
+ do_displayport_link_train(output);
+ }
+ }
else {
disp_data.ucAction = ATOM_ENABLE;
data.exec.index = index;
@@ -1310,7 +1374,7 @@ atombios_output_dpms(xf86OutputPtr output, int mode)
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);
+ atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_DISABLE_OUTPUT, 0, 0);
else {
disp_data.ucAction = ATOM_DISABLE;
data.exec.index = index;
@@ -1418,10 +1482,18 @@ atombios_set_output_crtc_source(xf86OutputPtr output)
crtc_src_param2.ucEncoderID = ASIC_INT_DIG2_ENCODER_ID;
else
crtc_src_param2.ucEncoderID = ASIC_INT_DIG1_ENCODER_ID;
- } else
- crtc_src_param2.ucEncoderID = ASIC_INT_DIG1_ENCODER_ID;
+ } else {
+ /* doesn't really matter which dig encoder we pick as long as it's
+ * not already in use
+ */
+ if (radeon_output->linkb)
+ crtc_src_param2.ucEncoderID = ASIC_INT_DIG2_ENCODER_ID;
+ else
+ crtc_src_param2.ucEncoderID = ASIC_INT_DIG1_ENCODER_ID;
+ }
break;
case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
+ /* Only dig2 encoder can drive LVTMA */
crtc_src_param2.ucEncoderID = ASIC_INT_DIG2_ENCODER_ID;
break;
case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1:
@@ -1543,14 +1615,14 @@ atombios_output_mode_set(xf86OutputPtr output,
case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2:
case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA:
/* disable encoder and transmitter */
- atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_DISABLE);
+ atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_DISABLE, 0, 0);
atombios_output_dig_encoder_setup(output, ATOM_DISABLE);
/* setup and enable the encoder and transmitter */
atombios_output_dig_encoder_setup(output, ATOM_ENABLE);
- atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_INIT);
- atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_SETUP);
- atombios_output_dig_transmitter_setup(output, ATOM_TRANSMITTER_ACTION_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);
@@ -1686,3 +1758,849 @@ atombios_dac_detect(xf86OutputPtr output)
return MonType;
}
+#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 inline int atom_dp_get_encoder_id(xf86OutputPtr output)
+{
+ RADEONInfoPtr info = RADEONPTR(output->scrn);
+ RADEONOutputPrivatePtr radeon_output = output->driver_private;
+ int ret = 0;
+ if (IS_DCE32_VARIANT) {
+ if (radeon_output->dig_block)
+ 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;
+ } else {
+ if (radeon_output->linkb)
+ ret |= ATOM_DP_CONFIG_DIG2_ENCODER | ATOM_DP_CONFIG_LINK_B;
+ else
+ ret |= ATOM_DP_CONFIG_DIG1_ENCODER | ATOM_DP_CONFIG_LINK_A;
+ }
+ return ret;
+}
+
+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);
+ PROCESS_AUX_CHANNEL_TRANSACTION_PS_ALLOCATION args;
+ AtomBiosArgRec data;
+ unsigned char *space;
+ unsigned char *base;
+
+ 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;
+
+ memcpy(base, req_bytes, num_bytes);
+
+ args.lpAuxRequest = 0;
+ args.lpDataOut = 16;
+ args.ucDataOutLen = 0;
+ args.ucChannelID = radeon_output->ucI2cId;
+ args.ucDelay = delay / 10; /* 10 usec */
+
+ data.exec.index = GetIndexIntoMasterTable(COMMAND, ProcessAuxChannelTransaction);
+ data.exec.dataSpace = (void *)&space;
+ data.exec.pspace = &args;
+
+ RHDAtomBiosFunc(info->atomBIOS->scrnIndex, info->atomBIOS, ATOMBIOS_EXEC, &data);
+ if (args.ucReplyStatus) {
+ ErrorF("failed to get auxch %02x%02x %02x %02x %02x\n",
+ req_bytes[1], req_bytes[0], req_bytes[2], req_bytes[3],args.ucReplyStatus);
+ return FALSE;
+ }
+ if (args.ucDataOutLen && read_byte && read_buf_len) {
+ if (read_buf_len < args.ucDataOutLen) {
+ ErrorF("%s: Buffer too small for return answer %d %d\n", __func__, read_buf_len, args.ucDataOutLen);
+ return FALSE;
+ }
+ {
+ int len = read_buf_len < args.ucDataOutLen ? read_buf_len : args.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->scrnIndex, info->atomBIOS, ATOMBIOS_EXEC, &data);
+
+ ErrorF("%s: %d\n", __func__, 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;
+}
+
+static int radeon_dp_max_link_bw(xf86OutputPtr output)
+{
+ RADEONOutputPrivatePtr radeon_output = output->driver_private;
+ int max_link_bw = radeon_output->dpcd[1];
+ switch(max_link_bw) {
+ case DP_LINK_BW_1_62:
+ case DP_LINK_BW_2_7:
+ break;
+ default:
+ max_link_bw = DP_LINK_BW_1_62;
+ break;
+ }
+ return max_link_bw;
+}
+
+static int radeon_dp_link_clock(uint8_t link_bw)
+{
+ if (link_bw == DP_LINK_BW_2_7)
+ return 270000;
+ else
+ return 162000;
+}
+
+
+/* I think this is a fiction */
+static int radeon_dp_link_required(int pixel_clock)
+{
+ return pixel_clock * 3;
+}
+
+Bool radeon_dp_mode_fixup(xf86OutputPtr output, DisplayModePtr mode, DisplayModePtr adjusted_mode)
+{
+ RADEONOutputPrivatePtr radeon_output = output->driver_private;
+ int lane_count, clock;
+ int max_lane_count = radeon_dp_max_lane_count(output);
+ int max_clock = radeon_dp_max_link_bw(output) == DP_LINK_BW_2_7 ? 1 : 0;
+ static int bws[2] = { DP_LINK_BW_1_62, DP_LINK_BW_2_7 };
+
+ for (lane_count = 1; lane_count <= max_lane_count; lane_count <<= 1) {
+ for (clock = 0; clock <= max_clock; clock++) {
+ int link_avail = radeon_dp_link_clock(bws[clock]) * lane_count;
+
+ if (radeon_dp_link_required(mode->Clock) <= link_avail) {
+ radeon_output->dp_lane_count = lane_count;
+ radeon_output->dp_clock = radeon_dp_link_clock(bws[clock]);
+ if (0)
+ xf86DrvMsg(0, X_INFO,
+ "lane_count %d clock %d\n",
+ radeon_output->dp_lane_count,
+ radeon_output->dp_clock);
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+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;
+ 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 == 270000)
+ 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 */
+ 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);
+ 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);
+ RADEONDPEncoderService(output, ATOM_DP_ACTION_TRAINING_COMPLETE, enc_id, 0);
+
+}
+