2016-10-14 19:56:49 +05:30
/*
* Copyright © 2016 Intel Corporation
*
* 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 .
*
*
*/
# include <drm/drm_edid.h>
# include <drm/drm_atomic_helper.h>
# include <drm/drm_dp_dual_mode_helper.h>
# include "intel_drv.h"
2016-10-24 19:33:30 +03:00
static struct intel_dp * lspcon_to_intel_dp ( struct intel_lspcon * lspcon )
{
struct intel_digital_port * dig_port =
container_of ( lspcon , struct intel_digital_port , lspcon ) ;
return & dig_port - > dp ;
}
2016-11-21 21:15:06 +02:00
static const char * lspcon_mode_name ( enum drm_lspcon_mode mode )
{
switch ( mode ) {
case DRM_LSPCON_MODE_PCON :
return " PCON " ;
case DRM_LSPCON_MODE_LS :
return " LS " ;
case DRM_LSPCON_MODE_INVALID :
return " INVALID " ;
default :
MISSING_CASE ( mode ) ;
return " INVALID " ;
}
}
2016-10-18 14:21:51 +03:00
static enum drm_lspcon_mode lspcon_get_current_mode ( struct intel_lspcon * lspcon )
2016-10-14 19:56:49 +05:30
{
2016-11-21 21:15:06 +02:00
enum drm_lspcon_mode current_mode ;
2016-10-24 19:33:30 +03:00
struct i2c_adapter * adapter = & lspcon_to_intel_dp ( lspcon ) - > aux . ddc ;
2016-10-14 19:56:49 +05:30
2016-11-21 21:15:06 +02:00
if ( drm_lspcon_get_mode ( adapter , & current_mode ) ) {
2017-10-10 15:37:43 +05:30
DRM_DEBUG_KMS ( " Error reading LSPCON mode \n " ) ;
2016-11-21 21:15:06 +02:00
return DRM_LSPCON_MODE_INVALID ;
}
return current_mode ;
}
static enum drm_lspcon_mode lspcon_wait_mode ( struct intel_lspcon * lspcon ,
enum drm_lspcon_mode mode )
{
enum drm_lspcon_mode current_mode ;
current_mode = lspcon_get_current_mode ( lspcon ) ;
2017-10-10 15:37:43 +05:30
if ( current_mode = = mode )
2016-11-21 21:15:06 +02:00
goto out ;
DRM_DEBUG_KMS ( " Waiting for LSPCON mode %s to settle \n " ,
lspcon_mode_name ( mode ) ) ;
2017-10-10 15:37:43 +05:30
wait_for ( ( current_mode = lspcon_get_current_mode ( lspcon ) ) = = mode , 100 ) ;
2016-11-21 21:15:06 +02:00
if ( current_mode ! = mode )
2017-10-10 15:37:43 +05:30
DRM_ERROR ( " LSPCON mode hasn't settled \n " ) ;
2016-11-21 21:15:06 +02:00
out :
DRM_DEBUG_KMS ( " Current LSPCON mode %s \n " ,
lspcon_mode_name ( current_mode ) ) ;
2016-10-14 19:56:49 +05:30
return current_mode ;
}
static int lspcon_change_mode ( struct intel_lspcon * lspcon ,
2016-11-21 21:15:07 +02:00
enum drm_lspcon_mode mode )
2016-10-14 19:56:49 +05:30
{
int err ;
enum drm_lspcon_mode current_mode ;
2016-10-24 19:33:30 +03:00
struct i2c_adapter * adapter = & lspcon_to_intel_dp ( lspcon ) - > aux . ddc ;
2016-10-14 19:56:49 +05:30
err = drm_lspcon_get_mode ( adapter , & current_mode ) ;
if ( err ) {
DRM_ERROR ( " Error reading LSPCON mode \n " ) ;
return err ;
}
if ( current_mode = = mode ) {
DRM_DEBUG_KMS ( " Current mode = desired LSPCON mode \n " ) ;
return 0 ;
}
err = drm_lspcon_set_mode ( adapter , mode ) ;
if ( err < 0 ) {
DRM_ERROR ( " LSPCON mode change failed \n " ) ;
return err ;
}
lspcon - > mode = mode ;
DRM_DEBUG_KMS ( " LSPCON mode changed done \n " ) ;
return 0 ;
}
2016-11-21 21:15:04 +02:00
static bool lspcon_wake_native_aux_ch ( struct intel_lspcon * lspcon )
{
uint8_t rev ;
if ( drm_dp_dpcd_readb ( & lspcon_to_intel_dp ( lspcon ) - > aux , DP_DPCD_REV ,
& rev ) ! = 1 ) {
DRM_DEBUG_KMS ( " Native AUX CH down \n " ) ;
return false ;
}
DRM_DEBUG_KMS ( " Native AUX CH up, DPCD version: %d.%d \n " ,
rev > > 4 , rev & 0xf ) ;
return true ;
}
2016-10-14 19:56:49 +05:30
static bool lspcon_probe ( struct intel_lspcon * lspcon )
{
2017-10-10 15:37:44 +05:30
int retry ;
2016-10-14 19:56:49 +05:30
enum drm_dp_dual_mode_type adaptor_type ;
2016-10-24 19:33:30 +03:00
struct i2c_adapter * adapter = & lspcon_to_intel_dp ( lspcon ) - > aux . ddc ;
2016-11-21 21:15:06 +02:00
enum drm_lspcon_mode expected_mode ;
2016-10-14 19:56:49 +05:30
2016-11-21 21:15:06 +02:00
expected_mode = lspcon_wake_native_aux_ch ( lspcon ) ?
DRM_LSPCON_MODE_PCON : DRM_LSPCON_MODE_LS ;
2016-11-21 21:15:04 +02:00
2016-10-14 19:56:49 +05:30
/* Lets probe the adaptor and check its type */
2017-10-10 15:37:44 +05:30
for ( retry = 0 ; retry < 6 ; retry + + ) {
if ( retry )
usleep_range ( 500 , 1000 ) ;
adaptor_type = drm_dp_dual_mode_detect ( adapter ) ;
if ( adaptor_type = = DRM_DP_DUAL_MODE_LSPCON )
break ;
}
2016-10-14 19:56:49 +05:30
if ( adaptor_type ! = DRM_DP_DUAL_MODE_LSPCON ) {
DRM_DEBUG_KMS ( " No LSPCON detected, found %s \n " ,
2017-10-10 15:37:44 +05:30
drm_dp_get_dual_mode_type_name ( adaptor_type ) ) ;
2016-10-14 19:56:49 +05:30
return false ;
}
/* Yay ... got a LSPCON device */
DRM_DEBUG_KMS ( " LSPCON detected \n " ) ;
2016-11-21 21:15:06 +02:00
lspcon - > mode = lspcon_wait_mode ( lspcon , expected_mode ) ;
2016-10-14 19:56:49 +05:30
lspcon - > active = true ;
return true ;
}
2016-10-24 19:33:31 +03:00
static void lspcon_resume_in_pcon_wa ( struct intel_lspcon * lspcon )
{
struct intel_dp * intel_dp = lspcon_to_intel_dp ( lspcon ) ;
2017-01-27 11:39:19 +02:00
struct intel_digital_port * dig_port = dp_to_dig_port ( intel_dp ) ;
2016-10-24 19:33:31 +03:00
unsigned long start = jiffies ;
while ( 1 ) {
2018-01-29 15:22:20 -08:00
if ( intel_digital_port_connected ( & dig_port - > base ) ) {
2016-10-24 19:33:31 +03:00
DRM_DEBUG_KMS ( " LSPCON recovering in PCON mode after %u ms \n " ,
jiffies_to_msecs ( jiffies - start ) ) ;
return ;
}
if ( time_after ( jiffies , start + msecs_to_jiffies ( 1000 ) ) )
break ;
usleep_range ( 10000 , 15000 ) ;
}
DRM_DEBUG_KMS ( " LSPCON DP descriptor mismatch after resume \n " ) ;
}
2016-10-14 19:56:52 +05:30
void lspcon_resume ( struct intel_lspcon * lspcon )
{
2016-11-21 21:15:06 +02:00
enum drm_lspcon_mode expected_mode ;
if ( lspcon_wake_native_aux_ch ( lspcon ) ) {
expected_mode = DRM_LSPCON_MODE_PCON ;
2016-11-21 21:15:04 +02:00
lspcon_resume_in_pcon_wa ( lspcon ) ;
2016-11-21 21:15:06 +02:00
} else {
expected_mode = DRM_LSPCON_MODE_LS ;
}
if ( lspcon_wait_mode ( lspcon , expected_mode ) = = DRM_LSPCON_MODE_PCON )
return ;
2016-10-24 19:33:31 +03:00
2016-11-21 21:15:07 +02:00
if ( lspcon_change_mode ( lspcon , DRM_LSPCON_MODE_PCON ) )
2016-10-14 19:56:52 +05:30
DRM_ERROR ( " LSPCON resume failed \n " ) ;
else
DRM_DEBUG_KMS ( " LSPCON resume success \n " ) ;
}
2016-11-21 21:15:06 +02:00
void lspcon_wait_pcon_mode ( struct intel_lspcon * lspcon )
{
lspcon_wait_mode ( lspcon , DRM_LSPCON_MODE_PCON ) ;
}
2016-10-14 19:56:49 +05:30
bool lspcon_init ( struct intel_digital_port * intel_dig_port )
{
struct intel_dp * dp = & intel_dig_port - > dp ;
struct intel_lspcon * lspcon = & intel_dig_port - > lspcon ;
struct drm_device * dev = intel_dig_port - > base . base . dev ;
struct drm_i915_private * dev_priv = to_i915 ( dev ) ;
2017-08-15 20:04:03 -07:00
if ( ! HAS_LSPCON ( dev_priv ) ) {
DRM_ERROR ( " LSPCON is not supported on this platform \n " ) ;
2016-10-14 19:56:49 +05:30
return false ;
}
lspcon - > active = false ;
lspcon - > mode = DRM_LSPCON_MODE_INVALID ;
if ( ! lspcon_probe ( lspcon ) ) {
DRM_ERROR ( " Failed to probe lspcon \n " ) ;
return false ;
}
/*
* In the SW state machine , lets Put LSPCON in PCON mode only .
* In this way , it will work with both HDMI 1.4 sinks as well as HDMI
* 2.0 sinks .
*/
if ( lspcon - > active & & lspcon - > mode ! = DRM_LSPCON_MODE_PCON ) {
2016-11-21 21:15:07 +02:00
if ( lspcon_change_mode ( lspcon , DRM_LSPCON_MODE_PCON ) < 0 ) {
2016-10-14 19:56:49 +05:30
DRM_ERROR ( " LSPCON mode change to PCON failed \n " ) ;
return false ;
}
}
2016-10-24 19:33:28 +03:00
if ( ! intel_dp_read_dpcd ( dp ) ) {
DRM_ERROR ( " LSPCON DPCD read failed \n " ) ;
return false ;
}
2017-05-18 14:10:23 +03:00
drm_dp_read_desc ( & dp - > aux , & dp - > desc , drm_dp_is_branch ( dp - > dpcd ) ) ;
2016-10-24 19:33:29 +03:00
2016-10-14 19:56:49 +05:30
DRM_DEBUG_KMS ( " Success: LSPCON init \n " ) ;
return true ;
}