iBoot/drivers/displayport/controller.c

692 lines
25 KiB
C

/*
* Copyright (C) 2010, 2015 Apple Inc. All rights reserved.
*
* This document is the property of Apple Inc.
* It is considered confidential and proprietary.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Apple Inc.
*/
#include <debug.h>
#include <drivers/displayport/displayport.h>
#include <drivers/displayport.h>
#include <sys/task.h>
#if WITH_HW_MCU
#include <drivers/process_edid.h>
#endif
#include "dpcd.h"
/////////////////////////////////////////
////////// debug support
#define DP_DEBUG_MASK ( \
DP_DEBUG_INIT | \
DP_DEBUG_ERROR | \
DP_DEBUG_INFO | \
DP_DEBUG_TRAINING | \
0)
#undef DP_DEBUG_MASK
#define DP_DEBUG_MASK (DP_DEBUG_INIT | DP_DEBUG_ERROR)
#define DP_DEBUG_INIT (1<<16) // initialisation
#define DP_DEBUG_ERROR (1<<17) // errors
#define DP_DEBUG_INFO (1<<18) // info
#define DP_DEBUG_TRAINING (1<<19) // link training
#define DP_DEBUG_ALWAYS (1<<31) // unconditional output
#define debug(_fac, _fmt, _args...) \
do { \
if ((DP_DEBUG_ ## _fac) & (DP_DEBUG_MASK | DP_DEBUG_ALWAYS)) \
dprintf(DEBUG_CRITICAL, "DPC: %s, %d: " _fmt, __FUNCTION__, __LINE__, ##_args); \
} while(0)
/////////////////////////////////////////
////////// consts, macros
#define kMaxRetry 5
#define kClockRecoveryDelay 100
#define kEQDelay 400
#define kMaxClockRecoveryIterations 100
#define require(assertion, exception_label) \
do { \
if (__builtin_expect(!(assertion), 0)) \
{ \
goto exception_label; \
} \
} while (0)
#define require_action(assertion, exception_label, action) \
do { \
if (__builtin_expect(!(assertion), 0)) \
{ \
{ \
action; \
} \
goto exception_label; \
} \
} while (0)
#define require_noerr(error_code, exception_label) \
do { \
if (__builtin_expect(0 != (error_code), 0)) \
{ \
goto exception_label; \
} \
} while (0)
#define require_noerr_action(error_code, exception_label, action) \
do { \
if (__builtin_expect(0 != (error_code), 0)) \
{ \
{ \
action; \
} \
goto exception_label; \
} \
} while (0)
/////////////////////////////////////////
////////// typedefs, enums, structs
enum {
kLinkTrainingStateIdle,
kLinkTrainingStateStart,
kLinkTrainingStateClockRecovery,
kLinkTrainingStateEQTraining
};
/////////////////////////////////////////
////////// local variables
static u_int8_t dp_link_clock_recovery_retry[kDPMaxLanes];
static bool dp_link_voltage_swing_max[kDPMaxLanes];
static bool dp_link_eq_max[kDPMaxLanes];
static u_int8_t dp_link_eq_retry;
static u_int32_t dp_link_clock_recovery_iterations;
static u_int8_t dp_link_eq_iterations;
static struct video_link_data dp_video_data;
static int eq_pattern = kDPTrainingPattern2;
static dp_t dp_link_config = {
.mode = kDPControllerMode_Master,
.type = kDPControllerType_DP,
.min_link_rate = 0x6,
.max_link_rate = 0x6,
.lanes = 4,
.ssc = 0,
.alpm = 0,
.vrr_enable = 0,
.vrr_on = 0,
};
/////////////////////////////////////////
////////// local functions declaration
static int run_link_training_state_machine(struct dp_link_train_data *data);
static int process_link_state_start(struct dp_link_train_data *data, u_int32_t *state);
static int process_link_state_clock_recovery(struct dp_link_train_data *data, u_int32_t *state);
static int process_link_state_eq_training(struct dp_link_train_data *data, u_int32_t *state);
static int process_link_state_eq_training_internal(struct dp_link_train_data *data, uint32_t lane, bool *done);
static int process_link_state_clock_recovery_internal(struct dp_link_train_data * data, uint32_t lane, bool *p_done);
/////////////////////////////////////////
////////// DisplayPort global functions
int displayport_init(dp_t *dp)
{
// defaults to External display, and Master mode
if ( dp_controller_start(dp) != 0 )
return -1;
return 0;
}
int displayport_init_with_timing_info(struct display_timing *timing_info)
{
memcpy(&dp_link_config, timing_info->display_config, sizeof(dp_t));
displayport_set_timings(timing_info);
if ( dp_controller_start(&dp_link_config) != 0 )
return -1;
return 0;
}
int displayport_set_timings(struct display_timing *timing_info)
{
u_int8_t mode;
memcpy(&dp_link_config, timing_info->display_config, sizeof(dp_t));
mode = dp_link_config.mode;
bzero(&dp_video_data, sizeof(struct video_link_data));
dp_video_data.mirror_mode = (mode == kDPControllerMode_Slave) ? true : false;
dp_video_data.test_mode = 0;
dp_video_data.color.depth = 8;
dp_video_data.color.space = kDisplayColorSpacesRGB;
dp_video_data.color.range = kDisplayColorDynamicRangeVESA;
dp_video_data.color.coefficient = kDisplayColorCoefficientITU601;
dp_video_data.timing.axis[0].total = timing_info->h_active + timing_info->h_back_porch +
timing_info->h_front_porch + timing_info->h_pulse_width;
dp_video_data.timing.axis[0].active = timing_info->h_active;
dp_video_data.timing.axis[0].sync_width = timing_info->h_pulse_width;
dp_video_data.timing.axis[0].back_porch = timing_info->h_back_porch;
dp_video_data.timing.axis[0].front_porch = timing_info->h_front_porch;
dp_video_data.timing.axis[0].sync_rate = 0;
// Hsync polarity: 0->negative, 1->positive.
dp_video_data.timing.axis[0].sync_polarity = timing_info->neg_hsync ? 0 : 1;
dp_video_data.timing.axis[1].total = timing_info->v_active + timing_info->v_back_porch +
timing_info->v_front_porch + timing_info->v_pulse_width;
dp_video_data.timing.axis[1].active = timing_info->v_active;
dp_video_data.timing.axis[1].sync_width = timing_info->v_pulse_width;
dp_video_data.timing.axis[1].back_porch = timing_info->v_back_porch;
dp_video_data.timing.axis[1].front_porch = timing_info->v_front_porch;
dp_video_data.timing.axis[1].sync_rate = (60 << 16);
// Vsync polarity: 0->negative, 1->positive.
dp_video_data.timing.axis[1].sync_polarity = timing_info->neg_vsync ? 0 : 1;
debug(INFO, "vTotal:%d hTotal:%d\n", dp_video_data.timing.axis[1].total, dp_video_data.timing.axis[0].total);
#if WITH_HW_MCU
// Restrict EDID choices to timings matching these, otherwise the
// framebuffer dimensions will mismatch.
restrict_edid(dp_video_data.timing.axis[0].active,
dp_video_data.timing.axis[1].active);
#endif
return 0;
}
void displayport_quiesce()
{
dp_controller_stop();
}
int displayport_start_video(void)
{
#if WITH_HW_MCU && WITH_HW_HOOVER
int downstream_type = get_edid_downstream_type();
if (downstream_type == kDPDownstreamTypeDVI) {
uint8_t data;
dp_controller_read_bytes_dpcd(DPCD_ADDR_HDMI_DVI_MODE_SELECT, &data, sizeof(data));
data |= DPCD_ADDR_HDMI_DVI_MODE_SELECT_DVI;
debug(INFO, "Reprogramming Hoover to be in DVI mode: data 0x%x\n", data);
dp_controller_write_bytes_dpcd(DPCD_ADDR_HDMI_DVI_MODE_SELECT, &data, sizeof(data));
}
#endif
return dp_controller_start_video(&dp_video_data);
}
int displayport_enable_alpm(bool enable)
{
return dp_controller_enable_alpm(enable, &dp_video_data);
}
#if WITH_HW_DISPLAY_EDP
int displayport_get_raw_panel_id(u_int8_t *raw_panel_id)
{
//caller is responsible for waiting for HPD
return dp_device_get_raw_panel_id(raw_panel_id);
}
#endif // WITH_HW_DISPLAY_EDP
bool displayport_video_configured()
{
return dp_controller_video_configured();
}
/////////////////////////////////////////
////////// controller global functions
int dp_controller_train_link(struct dp_link_train_data *data)
{
int ret;
u_int32_t lc;
u_int32_t lr;
u_int32_t retry = 0;
ret = -1;
lc = data->lane_count;
lr = data->link_rate;
do {
data->lane_count = __min((dp_controller_get_max_lane_count()), lc);
data->link_rate = __min((dp_controller_get_max_link_rate()), lr);
do {
// check if the device has sufficient bandwidth
if ( data->lane_count < dp_controller_get_min_lane_count() ) {
ret = -1;
break;
}
ret = run_link_training_state_machine(data);
if ( ret != 0 ) {
if ( !data->fast ) {
data->link_rate = __min((dp_controller_get_max_link_rate()), lr);
data->lane_count = data->lane_count/2;
} else {
data->fast = false;
}
}
} while ( ret != 0 );
} while ( ret != 0 && ++retry < 5 );
return 0;
}
/////////////////////////////////////////
////////// controller local functions
static int run_link_training_state_machine(struct dp_link_train_data *data)
{
u_int32_t state = kLinkTrainingStateStart;
int ret;
debug(TRAINING, "Preparing to establish new link\n");
// establish link training
do {
switch(state) {
case kLinkTrainingStateStart:
ret = process_link_state_start(data, &state);
break;
case kLinkTrainingStateClockRecovery:
ret = process_link_state_clock_recovery(data, &state);
break;
case kLinkTrainingStateEQTraining:
ret = process_link_state_eq_training(data, &state);
break;
default:
break;
}
debug(TRAINING, "Performing Link training state=%d result=%d\n", state, ret);
if ( ret != 0 )
state = kLinkTrainingStateIdle;
} while ( state != kLinkTrainingStateIdle );
debug(TRAINING, "Link training result=%d\n", ret);
return ret;
}
static int process_link_state_start(struct dp_link_train_data *data, u_int32_t *state)
{
int ret;
u_int32_t index;
#if DISPLAYPORT_VERSION > 2
eq_pattern = dp_device_get_supports_training_pattern3() ? kDPTrainingPattern3 : kDPTrainingPattern2;
#endif
ret = -1;
debug(TRAINING, "Begining START phase of link traing. Initial lanecount=%d linkRate=%u bps\n",
data->lane_count, data->link_rate);
bzero(dp_link_clock_recovery_retry, sizeof(dp_link_clock_recovery_retry));
bzero(dp_link_voltage_swing_max, sizeof(dp_link_voltage_swing_max));
bzero(dp_link_eq_max, sizeof(dp_link_eq_max));
dp_link_eq_retry = 0;
dp_link_clock_recovery_iterations = 0;
dp_link_eq_iterations = 0;
// Set link rate and count as you want to establish
// set device
ret = dp_device_set_enhanced_mode(data->enhanced_mode);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set device enhanced mode\n"));
ret = dp_device_set_ASSR(data->assr);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set device assr\n"));
ret = dp_device_set_downspread(data->downspread);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set device downspread\n"));
ret = dp_device_set_link_rate(data->link_rate);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set device link rate\n"));
ret = dp_device_set_lane_count(data->lane_count);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set device lane count\n"));
// set controller
ret = dp_controller_set_link_rate(data->link_rate);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set controller link rate\n"));
ret = dp_controller_set_enhanced_mode(data->enhanced_mode);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set controller enhanced mode\n"));
ret = dp_controller_set_ASSR(data->assr);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set controller assr\n"));
ret = dp_controller_set_downspread(data->downspread);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set controller downspread\n"));
ret = dp_controller_set_lane_count(data->lane_count);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set controller lane count\n"));
for ( index=0; index<data->lane_count; index++) {
ret = dp_controller_set_adjustment_levels(index, kDPVoltageLevelMin, kDPEQLevelMin, NULL, NULL);
if ( ret != 0 ) {
debug(ERROR, "Failed to set controller adjustment\n");
goto exit;
}
ret = dp_device_set_adjustment_levels(index, kDPVoltageLevelMin, kDPEQLevelMin, false, false);
if ( ret != 0 ) {
debug(ERROR, "Failed to set device adjustment\n");
goto exit;
}
}
require_noerr_action(ret, exit, debug(ERROR, "Failed to adjustment levels\n"));
ret = dp_controller_set_training_pattern(kDPTrainingPattern1, false);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set controller training pattern\n"));
ret = dp_device_set_training_pattern(kDPTrainingPattern1, false);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set device training pattern\n"));
require(!data->fast, train_fast);
*state = kLinkTrainingStateClockRecovery;
return ret;
train_fast:
debug(TRAINING, "Skipping to fast link training\n");
task_sleep(1 * 1000);
ret = dp_controller_set_training_pattern(eq_pattern, false);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set controller training pattern\n"));
ret = dp_device_set_training_pattern(eq_pattern, false);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set device training pattern\n"));
task_sleep(1 * 1000);
ret = dp_controller_set_training_pattern(kDPTrainingPatternNone, true);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set controller training pattern\n"));
ret = dp_device_set_training_pattern(kDPTrainingPatternNone, true);
require_noerr_action(ret, exit, debug(ERROR, "Failed to set device training pattern\n"));
exit:
*state = kLinkTrainingStateIdle;
return ret;
}
static int process_link_state_clock_recovery(struct dp_link_train_data *data, u_int32_t *state)
{
uint32_t lane;
uint32_t alignment_status;
bool done = true;
bool succeed = true;
int ret = 0;
spin(kClockRecoveryDelay);
ret = dp_device_get_alignment_status_mask(&alignment_status);
require_noerr(ret, exit);
for ( lane=0; lane<data->lane_count; lane++ ) {
ret = process_link_state_clock_recovery_internal(data, lane, &done);
succeed &= ( ret == 0 );
}
if ( !done ) {
ret = dp_device_set_training_pattern(kDPTrainingPattern1, false);
require_noerr(ret, exit);
}
if ( !succeed ) {
debug(TRAINING, "loop failed: linkRate=%u bps\n", data->link_rate);
// traing pattern : Set to Normal
ret = dp_controller_set_training_pattern(kDPTrainingPatternNone, true);
require_noerr(ret, exit);
ret = dp_device_set_training_pattern(kDPTrainingPatternNone, true);
require_noerr(ret, exit);
// reduce bit rate
if ( (data->link_rate > dp_controller_get_min_link_rate()) && (data->link_rate == kLinkRate270Gbps) ) {
data->link_rate = kLinkRate162Gbps;
debug(TRAINING, "retry @ linkRate=%d\n", data->link_rate);
*state = kLinkTrainingStateStart;
}
// already in reduced bit-rate
else {
ret = -1;
}
}
else if ( done ) {
// set training pattern 2 for EQ
ret = dp_controller_set_training_pattern(eq_pattern, false);
require_noerr(ret, exit);
// set the training pattern
ret = dp_device_set_training_pattern(eq_pattern, false);
require_noerr(ret, exit);
*state = kLinkTrainingStateEQTraining;
}
exit:
if (++dp_link_clock_recovery_iterations > kMaxClockRecoveryIterations) {
debug(TRAINING, "Max clock recovery iterations exceeded\n");
ret = -1;
}
return ret;
}
static int process_link_state_clock_recovery_internal(struct dp_link_train_data * data, uint32_t lane,
bool *p_done)
{
int ret = 0;
bool done = false;
uint32_t lane_status = 0;
uint32_t eq = kDPEQLevelMin;
uint32_t voltage_swing = kDPVoltageLevelMin;
uint32_t phy_eq;
uint32_t phy_voltage_swing;
// might as well read the values for lane
ret = dp_device_get_lane_status_mask(lane, &lane_status);
require_noerr(ret, exit);
debug(TRAINING, "Reading lane status: lane=%d status=0x%08x\n",lane, lane_status);
require_action(!(lane_status & kDPLaneStatusFlagClockRecoveryDone), exit, done = true);
ret = dp_device_get_requested_adjustment_levels(lane, &voltage_swing, &eq);
require_noerr(ret, exit);
ret = dp_controller_get_adjustment_levels(lane, &phy_voltage_swing, &phy_eq);
require_noerr(ret, exit);
debug(TRAINING, "Reading lane adjust request: Lane%d: voltage=%d eq=%d\n",lane, voltage_swing, eq);
debug(TRAINING, "Current phy lane properties: Lane%d: voltage=%d eq=%d\n",lane, phy_voltage_swing, phy_eq);
data->lane[lane].voltage = voltage_swing;
data->lane[lane].eq = eq;
// lane same voltage count
if (phy_voltage_swing == voltage_swing)
dp_link_clock_recovery_retry[lane]++;
else
dp_link_clock_recovery_retry[lane] = 0;
debug(TRAINING, "_linkClockRecoveryRetry[%d]=%d\n", lane, dp_link_clock_recovery_retry[lane]);
require_action((dp_link_clock_recovery_retry[lane] < kMaxRetry) && !dp_link_voltage_swing_max[lane],
exit, ret=-1);
// increase voltage swing as requested,write an updated value
debug(TRAINING, "Setting lane adjust request: Lane%d: voltage=%d eq=%d\n",lane, voltage_swing, eq);
// set voltage drive ONLY on the PHY
ret = dp_controller_set_adjustment_levels(lane, voltage_swing, eq, &dp_link_voltage_swing_max[lane],
&dp_link_eq_max[lane]);
require_noerr(ret, exit);
// set voltage drive and EQ on the SINK
ret = dp_device_set_adjustment_levels(lane, voltage_swing, eq, dp_link_voltage_swing_max[lane],
dp_link_eq_max[lane]);
exit:
*p_done &= done;
return ret;
}
static int process_link_state_eq_training(struct dp_link_train_data *data, u_int32_t *state)
{
uint32_t lane;
uint32_t alignment_status;
bool done = true;
bool succeed = true;
bool abort = false;
bool clear = false;
bool reduce = false;
bool restart = false;
int ret = 0;
debug(TRAINING, "Begining EQ phase of link traing\n");
spin(kEQDelay);
dp_link_eq_retry++;
ret = dp_device_get_alignment_status_mask(&alignment_status);
require_noerr(ret, exit);
debug(TRAINING, "alignment_status=0x%08x\n", alignment_status);
// process the eq state for each lane
for ( lane=0; lane<data->lane_count; lane++ ) {
ret = process_link_state_eq_training_internal(data, lane, &done);
if ( ret == 0 )
continue;
abort = (ret == -1);
succeed = false;
break;
}
require(!abort, exit);
done &= (alignment_status & kDPAlignmentStatusFlagsDone) != 0;
// reapply the training pattern
// this does a bulk write
if ( !done ) {
ret = dp_device_set_training_pattern(eq_pattern, false);
require_noerr(ret, exit);
}
if ( done ) {
debug(TRAINING, "Link trainking success @ %d lanes and %u Gbps\n", data->lane_count,
data->link_rate);
clear = true;
*state = kLinkTrainingStateIdle;
} else if ( !abort && (!succeed || dp_link_eq_retry > kMaxRetry) ) {
reduce = true;
}
if ( reduce ) {
debug(TRAINING, "loop failed: laneCount=%d linkRate=%u Gbps\n", data->lane_count, data->link_rate);
clear = true;
// reduce bit rate and restart
if ( (data->link_rate > dp_controller_get_min_link_rate()) && (data->link_rate == kLinkRate270Gbps) ) {
data->link_rate = kLinkRate162Gbps;
debug(TRAINING, "retry @ linkRate=%d\n", data->link_rate);
restart = true;
}
// already in reduced bit-rate
else {
ret = -1;
}
}
if ( restart ) {
*state = kLinkTrainingStateStart;
}
if ( clear ) {
// traing pattern : Set to Normal
dp_controller_set_training_pattern(kDPTrainingPatternNone, true);
dp_device_set_training_pattern(kDPTrainingPatternNone, true);
}
exit:
return ret;
}
static int process_link_state_eq_training_internal(struct dp_link_train_data *data, uint32_t lane, bool *p_done)
{
int ret = 0;
bool done = false;
uint32_t lane_status = 0;
uint32_t eq;
uint32_t voltage_swing;
uint32_t phy_eq;
uint32_t phy_voltage_swing;
// might as well read the values for lane
ret = dp_device_get_lane_status_mask(lane, &lane_status);
require_noerr(ret, exit);
debug(TRAINING, "Reading lane status: lane=%d lane_status=0x%08x\n",lane, lane_status);
require_action(lane_status & kDPLaneStatusFlagClockRecoveryDone, exit, ret = -1);
done = (lane_status & (kDPLaneStatusFlagsEQDone|kDPLaneStatusFlagsSymbolLocked))==(kDPLaneStatusFlagsEQDone|kDPLaneStatusFlagsSymbolLocked);
require(!done, exit);
ret = dp_device_get_requested_adjustment_levels(lane, &voltage_swing, &eq);
require_noerr(ret, exit);
ret = dp_controller_get_adjustment_levels(lane, &phy_voltage_swing, &phy_eq);
require_noerr(ret, exit);
debug(TRAINING, "Reading lane adjust request: Lane%d: voltage=%d eq=%d\n",lane, voltage_swing, eq);
debug(TRAINING, "Current phy lane properties: Lane%d: voltage=%d eq=%d\n",lane, phy_voltage_swing, phy_eq);
data->lane[lane].voltage = voltage_swing;
data->lane[lane].eq = eq;
// set voltage drive and EQ on the PHY
ret = dp_controller_set_adjustment_levels(lane, voltage_swing, eq,
&dp_link_voltage_swing_max[lane], &dp_link_eq_max[lane]);
require_noerr(ret, exit);
// set voltage drive and EQ on the SINK
ret = dp_device_set_adjustment_levels(lane, voltage_swing, eq,
dp_link_voltage_swing_max[lane], dp_link_eq_max[lane]);
exit:
*p_done &= done;
return ret;
}