2019-10-21 17:34:37 +03:00
// SPDX-License-Identifier: MIT
/*
* Copyright ( C ) 2013 - 2019 NVIDIA Corporation
* Copyright ( C ) 2015 Rob Clark
*/
# 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 )
{
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 ;
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 ;
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 ;
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 ;
}