2019-10-21 17:34:37 +03:00
// SPDX-License-Identifier: MIT
/*
* Copyright ( C ) 2013 - 2019 NVIDIA Corporation
* Copyright ( C ) 2015 Rob Clark
*/
2015-07-21 17:38:11 +03:00
# include <drm/drm_crtc.h>
2019-10-21 17:34:37 +03:00
# include <drm/drm_dp_helper.h>
2015-07-07 21:59:22 +03:00
# include <drm/drm_print.h>
2019-10-21 17:34:37 +03:00
# include "dp.h"
2015-07-07 21:59:22 +03:00
static const u8 drm_dp_edp_revisions [ ] = { 0x11 , 0x12 , 0x13 , 0x14 } ;
2015-12-03 14:45:45 +03:00
static void drm_dp_link_caps_reset ( struct drm_dp_link_caps * caps )
{
caps - > enhanced_framing = false ;
2015-07-07 21:52:07 +03:00
caps - > tps3_supported = false ;
2015-12-03 15:07:43 +03:00
caps - > fast_training = false ;
2018-02-05 16:07:57 +03:00
caps - > channel_coding = false ;
2018-02-05 17:16:18 +03:00
caps - > alternate_scrambler_reset = false ;
2015-12-03 14:45:45 +03:00
}
void drm_dp_link_caps_copy ( struct drm_dp_link_caps * dest ,
const struct drm_dp_link_caps * src )
{
dest - > enhanced_framing = src - > enhanced_framing ;
2015-07-07 21:52:07 +03:00
dest - > tps3_supported = src - > tps3_supported ;
2015-12-03 15:07:43 +03:00
dest - > fast_training = src - > fast_training ;
2018-02-05 16:07:57 +03:00
dest - > channel_coding = src - > channel_coding ;
2018-02-05 17:16:18 +03:00
dest - > alternate_scrambler_reset = src - > alternate_scrambler_reset ;
2015-12-03 14:45:45 +03:00
}
2015-12-03 13:44:17 +03:00
static void drm_dp_link_reset ( struct drm_dp_link * link )
{
2018-02-01 19:46:42 +03:00
unsigned int i ;
2015-12-03 13:44:17 +03:00
if ( ! link )
return ;
link - > revision = 0 ;
2015-07-21 17:33:48 +03:00
link - > max_rate = 0 ;
link - > max_lanes = 0 ;
2015-12-03 14:45:45 +03:00
drm_dp_link_caps_reset ( & link - > caps ) ;
2015-07-07 22:01:26 +03:00
link - > aux_rd_interval . cr = 0 ;
link - > aux_rd_interval . ce = 0 ;
2015-07-07 21:59:22 +03:00
link - > edp = 0 ;
2015-07-21 17:33:48 +03:00
link - > rate = 0 ;
link - > lanes = 0 ;
2018-02-01 19:46:42 +03:00
for ( i = 0 ; i < DP_MAX_SUPPORTED_RATES ; i + + )
link - > rates [ i ] = 0 ;
link - > num_rates = 0 ;
}
/**
* drm_dp_link_add_rate ( ) - add a rate to the list of supported rates
* @ link : the link to add the rate to
* @ rate : the rate to add
*
* Add a link rate to the list of supported link rates .
*
* Returns :
* 0 on success or one of the following negative error codes on failure :
* - ENOSPC if the maximum number of supported rates has been reached
* - EEXISTS if the link already supports this rate
*
* See also :
* drm_dp_link_remove_rate ( )
*/
int drm_dp_link_add_rate ( struct drm_dp_link * link , unsigned long rate )
{
unsigned int i , pivot ;
if ( link - > num_rates = = DP_MAX_SUPPORTED_RATES )
return - ENOSPC ;
for ( pivot = 0 ; pivot < link - > num_rates ; pivot + + )
if ( rate < = link - > rates [ pivot ] )
break ;
if ( pivot ! = link - > num_rates & & rate = = link - > rates [ pivot ] )
return - EEXIST ;
for ( i = link - > num_rates ; i > pivot ; i - - )
link - > rates [ i ] = link - > rates [ i - 1 ] ;
link - > rates [ pivot ] = rate ;
link - > num_rates + + ;
return 0 ;
}
/**
* drm_dp_link_remove_rate ( ) - remove a rate from the list of supported rates
* @ link : the link from which to remove the rate
* @ rate : the rate to remove
*
* Removes a link rate from the list of supported link rates .
*
* Returns :
* 0 on success or one of the following negative error codes on failure :
* - EINVAL if the specified rate is not among the supported rates
*
* See also :
* drm_dp_link_add_rate ( )
*/
int drm_dp_link_remove_rate ( struct drm_dp_link * link , unsigned long rate )
{
unsigned int i ;
for ( i = 0 ; i < link - > num_rates ; i + + )
if ( rate = = link - > rates [ i ] )
break ;
if ( i = = link - > num_rates )
return - EINVAL ;
link - > num_rates - - ;
while ( i < link - > num_rates ) {
link - > rates [ i ] = link - > rates [ i + 1 ] ;
i + + ;
}
return 0 ;
}
/**
* drm_dp_link_update_rates ( ) - normalize the supported link rates array
* @ link : the link for which to normalize the supported link rates
*
* Users should call this function after they ' ve manually modified the array
* of supported link rates . This function removes any stale entries , compacts
* the array and updates the supported link rate count . Note that calling the
* drm_dp_link_remove_rate ( ) function already does this janitorial work .
*
* See also :
* drm_dp_link_add_rate ( ) , drm_dp_link_remove_rate ( )
*/
void drm_dp_link_update_rates ( struct drm_dp_link * link )
{
unsigned int i , count = 0 ;
for ( i = 0 ; i < link - > num_rates ; i + + ) {
if ( link - > rates [ i ] ! = 0 )
link - > rates [ count + + ] = link - > rates [ i ] ;
}
for ( i = count ; i < link - > num_rates ; i + + )
link - > rates [ i ] = 0 ;
link - > num_rates = count ;
2015-12-03 13:44:17 +03:00
}
2019-10-21 17:34:37 +03:00
/**
* drm_dp_link_probe ( ) - probe a DisplayPort link for capabilities
* @ aux : DisplayPort AUX channel
* @ link : pointer to structure in which to return link capabilities
*
* The structure filled in by this function can usually be passed directly
* into drm_dp_link_power_up ( ) and drm_dp_link_configure ( ) to power up and
* configure the link based on the link ' s capabilities .
*
* Returns 0 on success or a negative error code on failure .
*/
int drm_dp_link_probe ( struct drm_dp_aux * aux , struct drm_dp_link * link )
{
2015-07-07 21:59:22 +03:00
u8 dpcd [ DP_RECEIVER_CAP_SIZE ] , value ;
2015-07-07 22:01:26 +03:00
unsigned int rd_interval ;
2019-10-21 17:34:37 +03:00
int err ;
2015-12-03 13:44:17 +03:00
drm_dp_link_reset ( link ) ;
2019-10-21 17:34:37 +03:00
2015-12-03 15:02:52 +03:00
err = drm_dp_dpcd_read ( aux , DP_DPCD_REV , dpcd , sizeof ( dpcd ) ) ;
2019-10-21 17:34:37 +03:00
if ( err < 0 )
return err ;
2015-12-03 15:02:52 +03:00
link - > revision = dpcd [ DP_DPCD_REV ] ;
link - > max_rate = drm_dp_max_link_rate ( dpcd ) ;
link - > max_lanes = drm_dp_max_lane_count ( dpcd ) ;
2019-10-21 17:34:37 +03:00
2015-12-03 15:02:52 +03:00
link - > caps . enhanced_framing = drm_dp_enhanced_frame_cap ( dpcd ) ;
2015-07-07 21:52:07 +03:00
link - > caps . tps3_supported = drm_dp_tps3_supported ( dpcd ) ;
2015-12-03 15:07:43 +03:00
link - > caps . fast_training = drm_dp_fast_training_cap ( dpcd ) ;
2018-02-05 16:07:57 +03:00
link - > caps . channel_coding = drm_dp_channel_coding_supported ( dpcd ) ;
2019-10-21 17:34:37 +03:00
2015-07-07 21:59:22 +03:00
if ( drm_dp_alternate_scrambler_reset_cap ( dpcd ) ) {
2018-02-05 17:16:18 +03:00
link - > caps . alternate_scrambler_reset = true ;
2015-07-07 21:59:22 +03:00
err = drm_dp_dpcd_readb ( aux , DP_EDP_DPCD_REV , & value ) ;
if ( err < 0 )
return err ;
if ( value > = ARRAY_SIZE ( drm_dp_edp_revisions ) )
DRM_ERROR ( " unsupported eDP version: %02x \n " , value ) ;
else
link - > edp = drm_dp_edp_revisions [ value ] ;
}
2015-07-07 22:01:26 +03:00
/*
* The DPCD stores the AUX read interval in units of 4 ms . There are
* two special cases :
*
* 1 ) if the TRAINING_AUX_RD_INTERVAL field is 0 , the clock recovery
* and channel equalization should use 100 us or 400 us AUX read
* intervals , respectively
*
* 2 ) for DP v1 .4 and above , clock recovery should always use 100 us
* AUX read intervals
*/
rd_interval = dpcd [ DP_TRAINING_AUX_RD_INTERVAL ] &
DP_TRAINING_AUX_RD_MASK ;
if ( rd_interval > 4 ) {
DRM_DEBUG_KMS ( " AUX interval %u out of range (max. 4) \n " ,
rd_interval ) ;
rd_interval = 4 ;
}
rd_interval * = 4 * USEC_PER_MSEC ;
if ( rd_interval = = 0 | | link - > revision > = DP_DPCD_REV_14 )
link - > aux_rd_interval . cr = 100 ;
if ( rd_interval = = 0 )
link - > aux_rd_interval . ce = 400 ;
2015-07-21 17:33:48 +03:00
link - > rate = link - > max_rate ;
link - > lanes = link - > max_lanes ;
2018-02-01 19:46:42 +03:00
/* Parse SUPPORTED_LINK_RATES from eDP 1.4 */
if ( link - > edp > = 0x14 ) {
u8 supported_rates [ DP_MAX_SUPPORTED_RATES * 2 ] ;
unsigned int i ;
u16 rate ;
err = drm_dp_dpcd_read ( aux , DP_SUPPORTED_LINK_RATES ,
supported_rates ,
sizeof ( supported_rates ) ) ;
if ( err < 0 )
return err ;
for ( i = 0 ; i < DP_MAX_SUPPORTED_RATES ; i + + ) {
rate = supported_rates [ i * 2 + 1 ] < < 8 |
supported_rates [ i * 2 + 0 ] ;
drm_dp_link_add_rate ( link , rate * 200 ) ;
}
}
2019-10-21 17:34:37 +03:00
return 0 ;
}
/**
* drm_dp_link_power_up ( ) - power up a DisplayPort link
* @ aux : DisplayPort AUX channel
* @ link : pointer to a structure containing the link configuration
*
* Returns 0 on success or a negative error code on failure .
*/
int drm_dp_link_power_up ( struct drm_dp_aux * aux , struct drm_dp_link * link )
{
u8 value ;
int err ;
/* DP_SET_POWER register is only available on DPCD v1.1 and later */
if ( link - > revision < 0x11 )
return 0 ;
err = drm_dp_dpcd_readb ( aux , DP_SET_POWER , & value ) ;
if ( err < 0 )
return err ;
value & = ~ DP_SET_POWER_MASK ;
value | = DP_SET_POWER_D0 ;
err = drm_dp_dpcd_writeb ( aux , DP_SET_POWER , value ) ;
if ( err < 0 )
return err ;
/*
* According to the DP 1.1 specification , a " Sink Device must exit the
* power saving state within 1 ms " (Section 2.5.3.1, Table 5-52, " Sink
* Control Field " (register 0x600).
*/
usleep_range ( 1000 , 2000 ) ;
return 0 ;
}
/**
* drm_dp_link_power_down ( ) - power down a DisplayPort link
* @ aux : DisplayPort AUX channel
* @ link : pointer to a structure containing the link configuration
*
* Returns 0 on success or a negative error code on failure .
*/
int drm_dp_link_power_down ( struct drm_dp_aux * aux , struct drm_dp_link * link )
{
u8 value ;
int err ;
/* DP_SET_POWER register is only available on DPCD v1.1 and later */
if ( link - > revision < 0x11 )
return 0 ;
err = drm_dp_dpcd_readb ( aux , DP_SET_POWER , & value ) ;
if ( err < 0 )
return err ;
value & = ~ DP_SET_POWER_MASK ;
value | = DP_SET_POWER_D3 ;
err = drm_dp_dpcd_writeb ( aux , DP_SET_POWER , value ) ;
if ( err < 0 )
return err ;
return 0 ;
}
/**
* drm_dp_link_configure ( ) - configure a DisplayPort link
* @ aux : DisplayPort AUX channel
* @ link : pointer to a structure containing the link configuration
*
* Returns 0 on success or a negative error code on failure .
*/
int drm_dp_link_configure ( struct drm_dp_aux * aux , struct drm_dp_link * link )
{
2015-06-10 17:35:44 +03:00
u8 values [ 2 ] , value ;
2019-10-21 17:34:37 +03:00
int err ;
2015-07-07 22:21:48 +03:00
if ( link - > ops & & link - > ops - > configure ) {
err = link - > ops - > configure ( link ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to configure DP link: %d \n " , err ) ;
return err ;
}
}
2019-10-21 17:34:37 +03:00
values [ 0 ] = drm_dp_link_rate_to_bw_code ( link - > rate ) ;
2015-07-21 17:33:48 +03:00
values [ 1 ] = link - > lanes ;
2019-10-21 17:34:37 +03:00
2015-12-03 14:45:45 +03:00
if ( link - > caps . enhanced_framing )
2019-10-21 17:34:37 +03:00
values [ 1 ] | = DP_LANE_COUNT_ENHANCED_FRAME_EN ;
err = drm_dp_dpcd_write ( aux , DP_LINK_BW_SET , values , sizeof ( values ) ) ;
if ( err < 0 )
return err ;
2015-06-10 17:35:44 +03:00
if ( link - > caps . channel_coding )
value = DP_SET_ANSI_8B10B ;
else
value = 0 ;
err = drm_dp_dpcd_writeb ( aux , DP_MAIN_LINK_CHANNEL_CODING_SET , value ) ;
if ( err < 0 )
return err ;
2015-07-07 22:14:12 +03:00
if ( link - > caps . alternate_scrambler_reset ) {
err = drm_dp_dpcd_writeb ( aux , DP_EDP_CONFIGURATION_SET ,
DP_ALTERNATE_SCRAMBLER_RESET_ENABLE ) ;
if ( err < 0 )
return err ;
}
2019-10-21 17:34:37 +03:00
return 0 ;
}
2015-07-21 17:38:11 +03:00
/**
* drm_dp_link_choose ( ) - choose the lowest possible configuration for a mode
* @ link : DRM DP link object
* @ mode : DRM display mode
* @ info : DRM display information
*
* According to the eDP specification , a source should select a configuration
* with the lowest number of lanes and the lowest possible link rate that can
* match the bitrate requirements of a video mode . However it must ensure not
* to exceed the capabilities of the sink .
*
* Returns : 0 on success or a negative error code on failure .
*/
int drm_dp_link_choose ( struct drm_dp_link * link ,
const struct drm_display_mode * mode ,
const struct drm_display_info * info )
{
/* available link symbol clock rates */
static const unsigned int rates [ 3 ] = { 162000 , 270000 , 540000 } ;
/* available number of lanes */
static const unsigned int lanes [ 3 ] = { 1 , 2 , 4 } ;
unsigned long requirement , capacity ;
unsigned int rate = link - > max_rate ;
unsigned int i , j ;
/* bandwidth requirement */
requirement = mode - > clock * info - > bpc * 3 ;
for ( i = 0 ; i < ARRAY_SIZE ( lanes ) & & lanes [ i ] < = link - > max_lanes ; i + + ) {
for ( j = 0 ; j < ARRAY_SIZE ( rates ) & & rates [ j ] < = rate ; j + + ) {
/*
* Capacity for this combination of lanes and rate ,
* factoring in the ANSI 8 B / 10 B encoding .
*
* Link rates in the DRM DP helpers are really link
* symbol frequencies , so a tenth of the actual rate
* of the link .
*/
capacity = lanes [ i ] * ( rates [ j ] * 10 ) * 8 / 10 ;
if ( capacity > = requirement ) {
DRM_DEBUG_KMS ( " using %u lanes at %u kHz (%lu/%lu kbps) \n " ,
lanes [ i ] , rates [ j ] , requirement ,
capacity ) ;
link - > lanes = lanes [ i ] ;
link - > rate = rates [ j ] ;
return 0 ;
}
}
}
return - ERANGE ;
}
2015-07-07 22:21:48 +03:00
/**
* DOC : Link training
*
* These functions contain common logic and helpers to implement DisplayPort
* link training .
*/
/**
* drm_dp_link_train_init ( ) - initialize DisplayPort link training state
* @ train : DisplayPort link training state
*/
void drm_dp_link_train_init ( struct drm_dp_link_train * train )
{
struct drm_dp_link_train_set * request = & train - > request ;
struct drm_dp_link_train_set * adjust = & train - > adjust ;
unsigned int i ;
for ( i = 0 ; i < 4 ; i + + ) {
request - > voltage_swing [ i ] = 0 ;
adjust - > voltage_swing [ i ] = 0 ;
request - > pre_emphasis [ i ] = 0 ;
adjust - > pre_emphasis [ i ] = 0 ;
request - > post_cursor [ i ] = 0 ;
adjust - > post_cursor [ i ] = 0 ;
}
train - > pattern = DP_TRAINING_PATTERN_DISABLE ;
train - > clock_recovered = false ;
train - > channel_equalized = false ;
}
static bool drm_dp_link_train_valid ( const struct drm_dp_link_train * train )
{
return train - > clock_recovered & & train - > channel_equalized ;
}
static int drm_dp_link_apply_training ( struct drm_dp_link * link )
{
struct drm_dp_link_train_set * request = & link - > train . request ;
unsigned int lanes = link - > lanes , * vs , * pe , * pc , i ;
struct drm_dp_aux * aux = link - > aux ;
u8 values [ 4 ] , pattern = 0 ;
int err ;
err = link - > ops - > apply_training ( link ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to apply link training: %d \n " , err ) ;
return err ;
}
vs = request - > voltage_swing ;
pe = request - > pre_emphasis ;
pc = request - > post_cursor ;
/* write currently selected voltage-swing and pre-emphasis levels */
for ( i = 0 ; i < lanes ; i + + )
values [ i ] = DP_TRAIN_VOLTAGE_SWING_LEVEL ( vs [ i ] ) |
DP_TRAIN_PRE_EMPHASIS_LEVEL ( pe [ i ] ) ;
err = drm_dp_dpcd_write ( aux , DP_TRAINING_LANE0_SET , values , lanes ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to set training parameters: %d \n " , err ) ;
return err ;
}
/* write currently selected post-cursor level (if supported) */
if ( link - > revision > = 0x12 & & link - > rate = = 540000 ) {
values [ 0 ] = values [ 1 ] = 0 ;
for ( i = 0 ; i < lanes ; i + + )
values [ i / 2 ] | = DP_LANE_POST_CURSOR ( i , pc [ i ] ) ;
err = drm_dp_dpcd_write ( aux , DP_TRAINING_LANE0_1_SET2 , values ,
DIV_ROUND_UP ( lanes , 2 ) ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to set post-cursor: %d \n " , err ) ;
return err ;
}
}
/* write link pattern */
if ( link - > train . pattern ! = DP_TRAINING_PATTERN_DISABLE )
pattern | = DP_LINK_SCRAMBLING_DISABLE ;
pattern | = link - > train . pattern ;
err = drm_dp_dpcd_writeb ( aux , DP_TRAINING_PATTERN_SET , pattern ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to set training pattern: %d \n " , err ) ;
return err ;
}
return 0 ;
}
static void drm_dp_link_train_wait ( struct drm_dp_link * link )
{
unsigned long min = 0 ;
switch ( link - > train . pattern ) {
case DP_TRAINING_PATTERN_1 :
min = link - > aux_rd_interval . cr ;
break ;
case DP_TRAINING_PATTERN_2 :
case DP_TRAINING_PATTERN_3 :
min = link - > aux_rd_interval . ce ;
break ;
default :
break ;
}
if ( min > 0 )
usleep_range ( min , 2 * min ) ;
}
static void drm_dp_link_get_adjustments ( struct drm_dp_link * link ,
u8 status [ DP_LINK_STATUS_SIZE ] )
{
struct drm_dp_link_train_set * adjust = & link - > train . adjust ;
unsigned int i ;
for ( i = 0 ; i < link - > lanes ; i + + ) {
adjust - > voltage_swing [ i ] =
drm_dp_get_adjust_request_voltage ( status , i ) > >
DP_TRAIN_VOLTAGE_SWING_SHIFT ;
adjust - > pre_emphasis [ i ] =
drm_dp_get_adjust_request_pre_emphasis ( status , i ) > >
DP_TRAIN_PRE_EMPHASIS_SHIFT ;
adjust - > post_cursor [ i ] =
drm_dp_get_adjust_request_post_cursor ( status , i ) ;
}
}
static void drm_dp_link_train_adjust ( struct drm_dp_link_train * train )
{
struct drm_dp_link_train_set * request = & train - > request ;
struct drm_dp_link_train_set * adjust = & train - > adjust ;
unsigned int i ;
for ( i = 0 ; i < 4 ; i + + )
if ( request - > voltage_swing [ i ] ! = adjust - > voltage_swing [ i ] )
request - > voltage_swing [ i ] = adjust - > voltage_swing [ i ] ;
for ( i = 0 ; i < 4 ; i + + )
if ( request - > pre_emphasis [ i ] ! = adjust - > pre_emphasis [ i ] )
request - > pre_emphasis [ i ] = adjust - > pre_emphasis [ i ] ;
for ( i = 0 ; i < 4 ; i + + )
if ( request - > post_cursor [ i ] ! = adjust - > post_cursor [ i ] )
request - > post_cursor [ i ] = adjust - > post_cursor [ i ] ;
}
static int drm_dp_link_recover_clock ( struct drm_dp_link * link )
{
u8 status [ DP_LINK_STATUS_SIZE ] ;
int err ;
err = drm_dp_link_apply_training ( link ) ;
if ( err < 0 )
return err ;
drm_dp_link_train_wait ( link ) ;
err = drm_dp_dpcd_read_link_status ( link - > aux , status ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to read link status: %d \n " , err ) ;
return err ;
}
if ( ! drm_dp_clock_recovery_ok ( status , link - > lanes ) )
drm_dp_link_get_adjustments ( link , status ) ;
else
link - > train . clock_recovered = true ;
return 0 ;
}
static int drm_dp_link_clock_recovery ( struct drm_dp_link * link )
{
unsigned int repeat ;
int err ;
/* start clock recovery using training pattern 1 */
link - > train . pattern = DP_TRAINING_PATTERN_1 ;
for ( repeat = 1 ; repeat < 5 ; repeat + + ) {
err = drm_dp_link_recover_clock ( link ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to recover clock: %d \n " , err ) ;
return err ;
}
if ( link - > train . clock_recovered )
break ;
2019-10-15 15:59:37 +03:00
drm_dp_link_train_adjust ( & link - > train ) ;
2015-07-07 22:21:48 +03:00
}
return 0 ;
}
static int drm_dp_link_equalize_channel ( struct drm_dp_link * link )
{
struct drm_dp_aux * aux = link - > aux ;
u8 status [ DP_LINK_STATUS_SIZE ] ;
int err ;
err = drm_dp_link_apply_training ( link ) ;
if ( err < 0 )
return err ;
drm_dp_link_train_wait ( link ) ;
err = drm_dp_dpcd_read_link_status ( aux , status ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to read link status: %d \n " , err ) ;
return err ;
}
if ( ! drm_dp_clock_recovery_ok ( status , link - > lanes ) ) {
DRM_ERROR ( " clock recovery lost while equalizing channel \n " ) ;
link - > train . clock_recovered = false ;
return 0 ;
}
if ( ! drm_dp_channel_eq_ok ( status , link - > lanes ) )
drm_dp_link_get_adjustments ( link , status ) ;
else
link - > train . channel_equalized = true ;
return 0 ;
}
static int drm_dp_link_channel_equalization ( struct drm_dp_link * link )
{
unsigned int repeat ;
int err ;
/* start channel equalization using pattern 2 or 3 */
if ( link - > caps . tps3_supported )
link - > train . pattern = DP_TRAINING_PATTERN_3 ;
else
link - > train . pattern = DP_TRAINING_PATTERN_2 ;
for ( repeat = 1 ; repeat < 5 ; repeat + + ) {
err = drm_dp_link_equalize_channel ( link ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to equalize channel: %d \n " , err ) ;
return err ;
}
if ( link - > train . channel_equalized )
break ;
2019-10-15 15:59:37 +03:00
drm_dp_link_train_adjust ( & link - > train ) ;
2015-07-07 22:21:48 +03:00
}
return 0 ;
}
static int drm_dp_link_downgrade ( struct drm_dp_link * link )
{
switch ( link - > rate ) {
case 162000 :
return - EINVAL ;
case 270000 :
link - > rate = 162000 ;
break ;
case 540000 :
link - > rate = 270000 ;
return 0 ;
}
return 0 ;
}
static void drm_dp_link_train_disable ( struct drm_dp_link * link )
{
int err ;
link - > train . pattern = DP_TRAINING_PATTERN_DISABLE ;
err = drm_dp_link_apply_training ( link ) ;
if ( err < 0 )
DRM_ERROR ( " failed to disable link training: %d \n " , err ) ;
}
static int drm_dp_link_train_full ( struct drm_dp_link * link )
{
int err ;
retry :
DRM_DEBUG_KMS ( " full-training link: %u lane%s at %u MHz \n " ,
link - > lanes , ( link - > lanes > 1 ) ? " s " : " " ,
link - > rate / 100 ) ;
err = drm_dp_link_configure ( link - > aux , link ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to configure DP link: %d \n " , err ) ;
return err ;
}
err = drm_dp_link_clock_recovery ( link ) ;
if ( err < 0 ) {
DRM_ERROR ( " clock recovery failed: %d \n " , err ) ;
goto out ;
}
if ( ! link - > train . clock_recovered ) {
DRM_ERROR ( " clock recovery failed, downgrading link \n " ) ;
err = drm_dp_link_downgrade ( link ) ;
if ( err < 0 )
goto out ;
goto retry ;
}
DRM_DEBUG_KMS ( " clock recovery succeeded \n " ) ;
err = drm_dp_link_channel_equalization ( link ) ;
if ( err < 0 ) {
DRM_ERROR ( " channel equalization failed: %d \n " , err ) ;
goto out ;
}
if ( ! link - > train . channel_equalized ) {
DRM_ERROR ( " channel equalization failed, downgrading link \n " ) ;
err = drm_dp_link_downgrade ( link ) ;
if ( err < 0 )
goto out ;
goto retry ;
}
DRM_DEBUG_KMS ( " channel equalization succeeded \n " ) ;
out :
drm_dp_link_train_disable ( link ) ;
return err ;
}
static int drm_dp_link_train_fast ( struct drm_dp_link * link )
{
u8 status [ DP_LINK_STATUS_SIZE ] ;
int err ;
DRM_DEBUG_KMS ( " fast-training link: %u lane%s at %u MHz \n " ,
link - > lanes , ( link - > lanes > 1 ) ? " s " : " " ,
link - > rate / 100 ) ;
err = drm_dp_link_configure ( link - > aux , link ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to configure DP link: %d \n " , err ) ;
return err ;
}
/* transmit training pattern 1 for 500 microseconds */
link - > train . pattern = DP_TRAINING_PATTERN_1 ;
err = drm_dp_link_apply_training ( link ) ;
if ( err < 0 )
goto out ;
usleep_range ( 500 , 1000 ) ;
/* transmit training pattern 2 or 3 for 500 microseconds */
if ( link - > caps . tps3_supported )
link - > train . pattern = DP_TRAINING_PATTERN_3 ;
else
link - > train . pattern = DP_TRAINING_PATTERN_2 ;
err = drm_dp_link_apply_training ( link ) ;
if ( err < 0 )
goto out ;
usleep_range ( 500 , 1000 ) ;
err = drm_dp_dpcd_read_link_status ( link - > aux , status ) ;
if ( err < 0 ) {
DRM_ERROR ( " failed to read link status: %d \n " , err ) ;
goto out ;
}
if ( ! drm_dp_clock_recovery_ok ( status , link - > lanes ) ) {
DRM_ERROR ( " clock recovery failed \n " ) ;
err = - EIO ;
}
if ( ! drm_dp_channel_eq_ok ( status , link - > lanes ) ) {
DRM_ERROR ( " channel equalization failed \n " ) ;
err = - EIO ;
}
out :
drm_dp_link_train_disable ( link ) ;
return err ;
}
/**
* drm_dp_link_train ( ) - perform DisplayPort link training
* @ link : a DP link object
*
* Uses the context stored in the DP link object to perform link training . It
* is expected that drivers will call drm_dp_link_probe ( ) to obtain the link
* capabilities before performing link training .
*
* If the sink supports fast link training ( no AUX CH handshake ) and valid
* training settings are available , this function will try to perform fast
* link training and fall back to full link training on failure .
*
* Returns : 0 on success or a negative error code on failure .
*/
int drm_dp_link_train ( struct drm_dp_link * link )
{
int err ;
2019-10-15 15:59:37 +03:00
drm_dp_link_train_init ( & link - > train ) ;
2015-07-07 22:21:48 +03:00
if ( link - > caps . fast_training ) {
if ( drm_dp_link_train_valid ( & link - > train ) ) {
err = drm_dp_link_train_fast ( link ) ;
if ( err < 0 )
DRM_ERROR ( " fast link training failed: %d \n " ,
err ) ;
else
return 0 ;
} else {
DRM_DEBUG_KMS ( " training parameters not available \n " ) ;
}
} else {
DRM_DEBUG_KMS ( " fast link training not supported \n " ) ;
}
err = drm_dp_link_train_full ( link ) ;
if ( err < 0 )
DRM_ERROR ( " full link training failed: %d \n " , err ) ;
return err ;
}