2017-03-13 14:23:59 +03:00
/*
* Copyright ( c ) 2015 NVIDIA Corporation . All rights reserved .
*
* 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 , sub license ,
* 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 NON - INFRINGEMENT . 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 <linux/slab.h>
2017-03-13 14:24:02 +03:00
# include <linux/delay.h>
2017-03-13 14:23:59 +03:00
2019-05-26 20:35:35 +03:00
# include <drm/drm_print.h>
2017-03-13 14:23:59 +03:00
# include <drm/drm_scdc_helper.h>
/**
* DOC : scdc helpers
*
* Status and Control Data Channel ( SCDC ) is a mechanism introduced by the
* HDMI 2.0 specification . It is a point - to - point protocol that allows the
* HDMI source and HDMI sink to exchange data . The same I2C interface that
* is used to access EDID serves as the transport mechanism for SCDC .
*/
# define SCDC_I2C_SLAVE_ADDRESS 0x54
/**
* drm_scdc_read - read a block of data from SCDC
* @ adapter : I2C controller
* @ offset : start offset of block to read
* @ buffer : return location for the block to read
* @ size : size of the block to read
*
* Reads a block of data from SCDC , starting at a given offset .
*
* Returns :
* 0 on success , negative error code on failure .
*/
ssize_t drm_scdc_read ( struct i2c_adapter * adapter , u8 offset , void * buffer ,
size_t size )
{
int ret ;
struct i2c_msg msgs [ 2 ] = {
{
. addr = SCDC_I2C_SLAVE_ADDRESS ,
. flags = 0 ,
. len = 1 ,
. buf = & offset ,
} , {
. addr = SCDC_I2C_SLAVE_ADDRESS ,
. flags = I2C_M_RD ,
. len = size ,
. buf = buffer ,
}
} ;
ret = i2c_transfer ( adapter , msgs , ARRAY_SIZE ( msgs ) ) ;
if ( ret < 0 )
return ret ;
if ( ret ! = ARRAY_SIZE ( msgs ) )
return - EPROTO ;
return 0 ;
}
EXPORT_SYMBOL ( drm_scdc_read ) ;
/**
* drm_scdc_write - write a block of data to SCDC
* @ adapter : I2C controller
* @ offset : start offset of block to write
* @ buffer : block of data to write
* @ size : size of the block to write
*
* Writes a block of data to SCDC , starting at a given offset .
*
* Returns :
* 0 on success , negative error code on failure .
*/
ssize_t drm_scdc_write ( struct i2c_adapter * adapter , u8 offset ,
const void * buffer , size_t size )
{
struct i2c_msg msg = {
. addr = SCDC_I2C_SLAVE_ADDRESS ,
. flags = 0 ,
. len = 1 + size ,
. buf = NULL ,
} ;
void * data ;
int err ;
2017-09-14 02:28:29 +03:00
data = kmalloc ( 1 + size , GFP_KERNEL ) ;
2017-03-13 14:23:59 +03:00
if ( ! data )
return - ENOMEM ;
msg . buf = data ;
memcpy ( data , & offset , sizeof ( offset ) ) ;
memcpy ( data + 1 , buffer , size ) ;
err = i2c_transfer ( adapter , & msg , 1 ) ;
kfree ( data ) ;
if ( err < 0 )
return err ;
if ( err ! = 1 )
return - EPROTO ;
return 0 ;
}
EXPORT_SYMBOL ( drm_scdc_write ) ;
2017-03-13 14:24:02 +03:00
/**
2020-11-16 13:18:01 +03:00
* drm_scdc_get_scrambling_status - what is status of scrambling ?
2017-03-13 14:24:02 +03:00
* @ adapter : I2C adapter for DDC channel
*
* Reads the scrambler status over SCDC , and checks the
* scrambling status .
*
* Returns :
* True if the scrambling is enabled , false otherwise .
*/
bool drm_scdc_get_scrambling_status ( struct i2c_adapter * adapter )
{
u8 status ;
int ret ;
ret = drm_scdc_readb ( adapter , SCDC_SCRAMBLER_STATUS , & status ) ;
if ( ret < 0 ) {
2018-03-23 21:25:37 +03:00
DRM_DEBUG_KMS ( " Failed to read scrambling status: %d \n " , ret ) ;
2017-03-13 14:24:02 +03:00
return false ;
}
return status & SCDC_SCRAMBLING_STATUS ;
}
EXPORT_SYMBOL ( drm_scdc_get_scrambling_status ) ;
/**
* drm_scdc_set_scrambling - enable scrambling
* @ adapter : I2C adapter for DDC channel
* @ enable : bool to indicate if scrambling is to be enabled / disabled
*
* Writes the TMDS config register over SCDC channel , and :
* enables scrambling when enable = 1
* disables scrambling when enable = 0
*
* Returns :
* True if scrambling is set / reset successfully , false otherwise .
*/
bool drm_scdc_set_scrambling ( struct i2c_adapter * adapter , bool enable )
{
u8 config ;
int ret ;
ret = drm_scdc_readb ( adapter , SCDC_TMDS_CONFIG , & config ) ;
if ( ret < 0 ) {
2018-03-23 21:25:37 +03:00
DRM_DEBUG_KMS ( " Failed to read TMDS config: %d \n " , ret ) ;
2017-03-13 14:24:02 +03:00
return false ;
}
if ( enable )
config | = SCDC_SCRAMBLING_ENABLE ;
else
config & = ~ SCDC_SCRAMBLING_ENABLE ;
ret = drm_scdc_writeb ( adapter , SCDC_TMDS_CONFIG , config ) ;
if ( ret < 0 ) {
2018-03-23 21:25:37 +03:00
DRM_DEBUG_KMS ( " Failed to enable scrambling: %d \n " , ret ) ;
2017-03-13 14:24:02 +03:00
return false ;
}
return true ;
}
EXPORT_SYMBOL ( drm_scdc_set_scrambling ) ;
/**
* drm_scdc_set_high_tmds_clock_ratio - set TMDS clock ratio
* @ adapter : I2C adapter for DDC channel
* @ set : ret or reset the high clock ratio
*
2017-07-20 23:09:14 +03:00
*
* TMDS clock ratio calculations go like this :
* TMDS character = 10 bit TMDS encoded value
*
* TMDS character rate = The rate at which TMDS characters are
* transmitted ( Mcsc )
*
* TMDS bit rate = 10 x TMDS character rate
*
* As per the spec :
* TMDS clock rate for pixel clock < 340 MHz = 1 x the character
* rate = 1 / 10 pixel clock rate
*
* TMDS clock rate for pixel clock > 340 MHz = 0.25 x the character
* rate = 1 / 40 pixel clock rate
*
* Writes to the TMDS config register over SCDC channel , and :
* sets TMDS clock ratio to 1 / 40 when set = 1
*
* sets TMDS clock ratio to 1 / 10 when set = 0
2017-03-13 14:24:02 +03:00
*
* Returns :
* True if write is successful , false otherwise .
*/
bool drm_scdc_set_high_tmds_clock_ratio ( struct i2c_adapter * adapter , bool set )
{
u8 config ;
int ret ;
ret = drm_scdc_readb ( adapter , SCDC_TMDS_CONFIG , & config ) ;
if ( ret < 0 ) {
2018-03-23 21:25:37 +03:00
DRM_DEBUG_KMS ( " Failed to read TMDS config: %d \n " , ret ) ;
2017-03-13 14:24:02 +03:00
return false ;
}
if ( set )
config | = SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 ;
else
config & = ~ SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 ;
ret = drm_scdc_writeb ( adapter , SCDC_TMDS_CONFIG , config ) ;
if ( ret < 0 ) {
2018-03-23 21:25:37 +03:00
DRM_DEBUG_KMS ( " Failed to set TMDS clock ratio: %d \n " , ret ) ;
2017-03-13 14:24:02 +03:00
return false ;
}
/*
* The spec says that a source should wait minimum 1 ms and maximum
* 100 ms after writing the TMDS config for clock ratio . Lets allow a
* wait of upto 2 ms here .
*/
usleep_range ( 1000 , 2000 ) ;
return true ;
}
EXPORT_SYMBOL ( drm_scdc_set_high_tmds_clock_ratio ) ;