2016-02-03 17:44:57 +08:00
/*
* Copyright ( C ) Fuzhou Rockchip Electronics Co . Ltd
* Zheng Yang < zhengyang @ rock - chips . com >
* Yakir Yang < ykk @ rock - chips . com >
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/irq.h>
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/err.h>
# include <linux/hdmi.h>
# include <linux/mfd/syscon.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/of_device.h>
# include <drm/drm_of.h>
# include <drm/drmP.h>
# include <drm/drm_atomic_helper.h>
# include <drm/drm_crtc_helper.h>
# include <drm/drm_edid.h>
# include "rockchip_drm_drv.h"
# include "rockchip_drm_vop.h"
# include "inno_hdmi.h"
# define to_inno_hdmi(x) container_of(x, struct inno_hdmi, x)
struct hdmi_data_info {
int vic ;
bool sink_is_hdmi ;
bool sink_has_audio ;
unsigned int enc_in_format ;
unsigned int enc_out_format ;
unsigned int colorimetry ;
} ;
struct inno_hdmi_i2c {
struct i2c_adapter adap ;
u8 ddc_addr ;
u8 segment_addr ;
struct mutex lock ;
struct completion cmp ;
} ;
struct inno_hdmi {
struct device * dev ;
struct drm_device * drm_dev ;
int irq ;
struct clk * pclk ;
void __iomem * regs ;
struct drm_connector connector ;
struct drm_encoder encoder ;
struct inno_hdmi_i2c * i2c ;
struct i2c_adapter * ddc ;
unsigned int tmds_rate ;
struct hdmi_data_info hdmi_data ;
struct drm_display_mode previous_mode ;
} ;
enum {
CSC_ITU601_16_235_TO_RGB_0_255_8BIT ,
CSC_ITU601_0_255_TO_RGB_0_255_8BIT ,
CSC_ITU709_16_235_TO_RGB_0_255_8BIT ,
CSC_RGB_0_255_TO_ITU601_16_235_8BIT ,
CSC_RGB_0_255_TO_ITU709_16_235_8BIT ,
CSC_RGB_0_255_TO_RGB_16_235_8BIT ,
} ;
static const char coeff_csc [ ] [ 24 ] = {
/*
* YUV2RGB : 601 SD mode ( Y [ 16 : 235 ] , UV [ 16 : 240 ] , RGB [ 0 : 255 ] ) :
* R = 1.164 * Y + 1.596 * V - 204
* G = 1.164 * Y - 0.391 * U - 0.813 * V + 154
* B = 1.164 * Y + 2.018 * U - 258
*/
{
0x04 , 0xa7 , 0x00 , 0x00 , 0x06 , 0x62 , 0x02 , 0xcc ,
0x04 , 0xa7 , 0x11 , 0x90 , 0x13 , 0x40 , 0x00 , 0x9a ,
0x04 , 0xa7 , 0x08 , 0x12 , 0x00 , 0x00 , 0x03 , 0x02
} ,
/*
* YUV2RGB : 601 SD mode ( YUV [ 0 : 255 ] , RGB [ 0 : 255 ] ) :
* R = Y + 1.402 * V - 248
* G = Y - 0.344 * U - 0.714 * V + 135
* B = Y + 1.772 * U - 227
*/
{
0x04 , 0x00 , 0x00 , 0x00 , 0x05 , 0x9b , 0x02 , 0xf8 ,
0x04 , 0x00 , 0x11 , 0x60 , 0x12 , 0xdb , 0x00 , 0x87 ,
0x04 , 0x00 , 0x07 , 0x16 , 0x00 , 0x00 , 0x02 , 0xe3
} ,
/*
* YUV2RGB : 709 HD mode ( Y [ 16 : 235 ] , UV [ 16 : 240 ] , RGB [ 0 : 255 ] ) :
* R = 1.164 * Y + 1.793 * V - 248
* G = 1.164 * Y - 0.213 * U - 0.534 * V + 77
* B = 1.164 * Y + 2.115 * U - 289
*/
{
0x04 , 0xa7 , 0x00 , 0x00 , 0x07 , 0x2c , 0x02 , 0xf8 ,
0x04 , 0xa7 , 0x10 , 0xda , 0x12 , 0x22 , 0x00 , 0x4d ,
0x04 , 0xa7 , 0x08 , 0x74 , 0x00 , 0x00 , 0x03 , 0x21
} ,
/*
* RGB2YUV : 601 SD mode :
* Cb = - 0.291 G - 0.148 R + 0.439 B + 128
* Y = 0.504 G + 0.257 R + 0.098 B + 16
* Cr = - 0.368 G + 0.439 R - 0.071 B + 128
*/
{
0x11 , 0x5f , 0x01 , 0x82 , 0x10 , 0x23 , 0x00 , 0x80 ,
0x02 , 0x1c , 0x00 , 0xa1 , 0x00 , 0x36 , 0x00 , 0x1e ,
0x11 , 0x29 , 0x10 , 0x59 , 0x01 , 0x82 , 0x00 , 0x80
} ,
/*
* RGB2YUV : 709 HD mode :
* Cb = - 0.338 G - 0.101 R + 0.439 B + 128
* Y = 0.614 G + 0.183 R + 0.062 B + 16
* Cr = - 0.399 G + 0.439 R - 0.040 B + 128
*/
{
0x11 , 0x98 , 0x01 , 0xc1 , 0x10 , 0x28 , 0x00 , 0x80 ,
0x02 , 0x74 , 0x00 , 0xbb , 0x00 , 0x3f , 0x00 , 0x10 ,
0x11 , 0x5a , 0x10 , 0x67 , 0x01 , 0xc1 , 0x00 , 0x80
} ,
/*
* RGB [ 0 : 255 ] 2 RGB [ 16 : 235 ] :
* R ' = R x ( 235 - 16 ) / 255 + 16 ;
* G ' = G x ( 235 - 16 ) / 255 + 16 ;
* B ' = B x ( 235 - 16 ) / 255 + 16 ;
*/
{
0x00 , 0x00 , 0x03 , 0x6F , 0x00 , 0x00 , 0x00 , 0x10 ,
0x03 , 0x6F , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x10 ,
0x00 , 0x00 , 0x00 , 0x00 , 0x03 , 0x6F , 0x00 , 0x10
} ,
} ;
static inline u8 hdmi_readb ( struct inno_hdmi * hdmi , u16 offset )
{
return readl_relaxed ( hdmi - > regs + ( offset ) * 0x04 ) ;
}
static inline void hdmi_writeb ( struct inno_hdmi * hdmi , u16 offset , u32 val )
{
writel_relaxed ( val , hdmi - > regs + ( offset ) * 0x04 ) ;
}
static inline void hdmi_modb ( struct inno_hdmi * hdmi , u16 offset ,
u32 msk , u32 val )
{
u8 temp = hdmi_readb ( hdmi , offset ) & ~ msk ;
temp | = val & msk ;
hdmi_writeb ( hdmi , offset , temp ) ;
}
static void inno_hdmi_i2c_init ( struct inno_hdmi * hdmi )
{
int ddc_bus_freq ;
ddc_bus_freq = ( hdmi - > tmds_rate > > 2 ) / HDMI_SCL_RATE ;
hdmi_writeb ( hdmi , DDC_BUS_FREQ_L , ddc_bus_freq & 0xFF ) ;
hdmi_writeb ( hdmi , DDC_BUS_FREQ_H , ( ddc_bus_freq > > 8 ) & 0xFF ) ;
/* Clear the EDID interrupt flag and mute the interrupt */
hdmi_writeb ( hdmi , HDMI_INTERRUPT_MASK1 , 0 ) ;
hdmi_writeb ( hdmi , HDMI_INTERRUPT_STATUS1 , m_INT_EDID_READY ) ;
}
static void inno_hdmi_sys_power ( struct inno_hdmi * hdmi , bool enable )
{
if ( enable )
hdmi_modb ( hdmi , HDMI_SYS_CTRL , m_POWER , v_PWR_ON ) ;
else
hdmi_modb ( hdmi , HDMI_SYS_CTRL , m_POWER , v_PWR_OFF ) ;
}
static void inno_hdmi_set_pwr_mode ( struct inno_hdmi * hdmi , int mode )
{
switch ( mode ) {
case NORMAL :
inno_hdmi_sys_power ( hdmi , false ) ;
hdmi_writeb ( hdmi , HDMI_PHY_PRE_EMPHASIS , 0x6f ) ;
hdmi_writeb ( hdmi , HDMI_PHY_DRIVER , 0xbb ) ;
hdmi_writeb ( hdmi , HDMI_PHY_SYS_CTL , 0x15 ) ;
hdmi_writeb ( hdmi , HDMI_PHY_SYS_CTL , 0x14 ) ;
hdmi_writeb ( hdmi , HDMI_PHY_SYS_CTL , 0x10 ) ;
hdmi_writeb ( hdmi , HDMI_PHY_CHG_PWR , 0x0f ) ;
hdmi_writeb ( hdmi , HDMI_PHY_SYNC , 0x00 ) ;
hdmi_writeb ( hdmi , HDMI_PHY_SYNC , 0x01 ) ;
inno_hdmi_sys_power ( hdmi , true ) ;
break ;
case LOWER_PWR :
inno_hdmi_sys_power ( hdmi , false ) ;
hdmi_writeb ( hdmi , HDMI_PHY_DRIVER , 0x00 ) ;
hdmi_writeb ( hdmi , HDMI_PHY_PRE_EMPHASIS , 0x00 ) ;
hdmi_writeb ( hdmi , HDMI_PHY_CHG_PWR , 0x00 ) ;
hdmi_writeb ( hdmi , HDMI_PHY_SYS_CTL , 0x15 ) ;
break ;
default :
2017-09-15 02:36:03 -06:00
DRM_DEV_ERROR ( hdmi - > dev , " Unknown power mode %d \n " , mode ) ;
2016-02-03 17:44:57 +08:00
}
}
static void inno_hdmi_reset ( struct inno_hdmi * hdmi )
{
u32 val ;
u32 msk ;
hdmi_modb ( hdmi , HDMI_SYS_CTRL , m_RST_DIGITAL , v_NOT_RST_DIGITAL ) ;
udelay ( 100 ) ;
hdmi_modb ( hdmi , HDMI_SYS_CTRL , m_RST_ANALOG , v_NOT_RST_ANALOG ) ;
udelay ( 100 ) ;
msk = m_REG_CLK_INV | m_REG_CLK_SOURCE | m_POWER | m_INT_POL ;
val = v_REG_CLK_INV | v_REG_CLK_SOURCE_SYS | v_PWR_ON | v_INT_POL_HIGH ;
hdmi_modb ( hdmi , HDMI_SYS_CTRL , msk , val ) ;
inno_hdmi_set_pwr_mode ( hdmi , NORMAL ) ;
}
static int inno_hdmi_upload_frame ( struct inno_hdmi * hdmi , int setup_rc ,
union hdmi_infoframe * frame , u32 frame_index ,
u32 mask , u32 disable , u32 enable )
{
if ( mask )
hdmi_modb ( hdmi , HDMI_PACKET_SEND_AUTO , mask , disable ) ;
hdmi_writeb ( hdmi , HDMI_CONTROL_PACKET_BUF_INDEX , frame_index ) ;
if ( setup_rc > = 0 ) {
u8 packed_frame [ HDMI_MAXIMUM_INFO_FRAME_SIZE ] ;
ssize_t rc , i ;
rc = hdmi_infoframe_pack ( frame , packed_frame ,
sizeof ( packed_frame ) ) ;
if ( rc < 0 )
return rc ;
for ( i = 0 ; i < rc ; i + + )
hdmi_writeb ( hdmi , HDMI_CONTROL_PACKET_ADDR + i ,
packed_frame [ i ] ) ;
if ( mask )
hdmi_modb ( hdmi , HDMI_PACKET_SEND_AUTO , mask , enable ) ;
}
return setup_rc ;
}
static int inno_hdmi_config_video_vsi ( struct inno_hdmi * hdmi ,
struct drm_display_mode * mode )
{
union hdmi_infoframe frame ;
int rc ;
rc = drm_hdmi_vendor_infoframe_from_display_mode ( & frame . vendor . hdmi ,
2017-11-13 19:04:19 +02:00
& hdmi - > connector ,
2016-02-03 17:44:57 +08:00
mode ) ;
return inno_hdmi_upload_frame ( hdmi , rc , & frame , INFOFRAME_VSI ,
m_PACKET_VSI_EN , v_PACKET_VSI_EN ( 0 ) , v_PACKET_VSI_EN ( 1 ) ) ;
}
static int inno_hdmi_config_video_avi ( struct inno_hdmi * hdmi ,
struct drm_display_mode * mode )
{
union hdmi_infoframe frame ;
int rc ;
drm: handle HDMI 2.0 VICs in AVI info-frames
HDMI 1.4b support the CEA video modes as per range of CEA-861-D (VIC 1-64).
For any other mode, the VIC filed in AVI infoframes should be 0.
HDMI 2.0 sinks, support video modes range as per CEA-861-F spec, which is
extended to (VIC 1-107).
This patch adds a bool input variable, which indicates if the connected
sink is a HDMI 2.0 sink or not. This will make sure that we don't pass a
HDMI 2.0 VIC to a HDMI 1.4 sink.
This patch touches all drm drivers, who are callers of this function
drm_hdmi_avi_infoframe_from_display_mode but to make sure there is
no change in current behavior, is_hdmi2 is kept as false.
In case of I915 driver, this patch:
- checks if the connected display is HDMI 2.0.
- HDMI infoframes carry one of this two type of information:
- VIC for 4K modes for HDMI 1.4 sinks
- S3D information for S3D modes
As CEA-861-F has already defined VICs for 4K videomodes, this
patch doesn't allow sending HDMI infoframes for HDMI 2.0 sinks,
until the mode is 3D.
Cc: Ville Syrjala <ville.syrjala@linux.intel.com>
Cc: Jose Abreu <jose.abreu@synopsys.com>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Alex Deucher <alexander.deucher@amd.com>
Cc: Daniel Vetter <daniel.vetter@intel.com>
PS: This patch touches a few lines in few files, which were
already above 80 char, so checkpatch gives 80 char warning again.
- gpu/drm/omapdrm/omap_encoder.c
- gpu/drm/i915/intel_sdvo.c
V2: Rebase, Added r-b from Andrzej
V3: Addressed review comment from Ville:
- Do not send VICs in both AVI-IF and HDMI-IF
send only one of it.
V4: Rebase
V5: Added r-b from Neil.
Addressed review comments from Ville
- Do not block HDMI vendor IF, instead check for VIC while
handling AVI infoframes
V6: Rebase
V7: Rebase
Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
Signed-off-by: Shashank Sharma <shashank.sharma@intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/1499960000-9232-2-git-send-email-shashank.sharma@intel.com
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
2017-07-13 21:03:07 +05:30
rc = drm_hdmi_avi_infoframe_from_display_mode ( & frame . avi , mode , false ) ;
2016-02-03 17:44:57 +08:00
if ( hdmi - > hdmi_data . enc_out_format = = HDMI_COLORSPACE_YUV444 )
frame . avi . colorspace = HDMI_COLORSPACE_YUV444 ;
else if ( hdmi - > hdmi_data . enc_out_format = = HDMI_COLORSPACE_YUV422 )
frame . avi . colorspace = HDMI_COLORSPACE_YUV422 ;
else
frame . avi . colorspace = HDMI_COLORSPACE_RGB ;
return inno_hdmi_upload_frame ( hdmi , rc , & frame , INFOFRAME_AVI , 0 , 0 , 0 ) ;
}
static int inno_hdmi_config_video_csc ( struct inno_hdmi * hdmi )
{
struct hdmi_data_info * data = & hdmi - > hdmi_data ;
int c0_c2_change = 0 ;
int csc_enable = 0 ;
int csc_mode = 0 ;
int auto_csc = 0 ;
int value ;
int i ;
/* Input video mode is SDR RGB24bit, data enable signal from external */
hdmi_writeb ( hdmi , HDMI_VIDEO_CONTRL1 , v_DE_EXTERNAL |
v_VIDEO_INPUT_FORMAT ( VIDEO_INPUT_SDR_RGB444 ) ) ;
/* Input color hardcode to RGB, and output color hardcode to RGB888 */
value = v_VIDEO_INPUT_BITS ( VIDEO_INPUT_8BITS ) |
v_VIDEO_OUTPUT_COLOR ( 0 ) |
v_VIDEO_INPUT_CSP ( 0 ) ;
hdmi_writeb ( hdmi , HDMI_VIDEO_CONTRL2 , value ) ;
if ( data - > enc_in_format = = data - > enc_out_format ) {
if ( ( data - > enc_in_format = = HDMI_COLORSPACE_RGB ) | |
( data - > enc_in_format > = HDMI_COLORSPACE_YUV444 ) ) {
value = v_SOF_DISABLE | v_COLOR_DEPTH_NOT_INDICATED ( 1 ) ;
hdmi_writeb ( hdmi , HDMI_VIDEO_CONTRL3 , value ) ;
hdmi_modb ( hdmi , HDMI_VIDEO_CONTRL ,
m_VIDEO_AUTO_CSC | m_VIDEO_C0_C2_SWAP ,
v_VIDEO_AUTO_CSC ( AUTO_CSC_DISABLE ) |
v_VIDEO_C0_C2_SWAP ( C0_C2_CHANGE_DISABLE ) ) ;
return 0 ;
}
}
if ( data - > colorimetry = = HDMI_COLORIMETRY_ITU_601 ) {
if ( ( data - > enc_in_format = = HDMI_COLORSPACE_RGB ) & &
( data - > enc_out_format = = HDMI_COLORSPACE_YUV444 ) ) {
csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT ;
auto_csc = AUTO_CSC_DISABLE ;
c0_c2_change = C0_C2_CHANGE_DISABLE ;
csc_enable = v_CSC_ENABLE ;
} else if ( ( data - > enc_in_format = = HDMI_COLORSPACE_YUV444 ) & &
( data - > enc_out_format = = HDMI_COLORSPACE_RGB ) ) {
csc_mode = CSC_ITU601_16_235_TO_RGB_0_255_8BIT ;
auto_csc = AUTO_CSC_ENABLE ;
c0_c2_change = C0_C2_CHANGE_DISABLE ;
csc_enable = v_CSC_DISABLE ;
}
} else {
if ( ( data - > enc_in_format = = HDMI_COLORSPACE_RGB ) & &
( data - > enc_out_format = = HDMI_COLORSPACE_YUV444 ) ) {
csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT ;
auto_csc = AUTO_CSC_DISABLE ;
c0_c2_change = C0_C2_CHANGE_DISABLE ;
csc_enable = v_CSC_ENABLE ;
} else if ( ( data - > enc_in_format = = HDMI_COLORSPACE_YUV444 ) & &
( data - > enc_out_format = = HDMI_COLORSPACE_RGB ) ) {
csc_mode = CSC_ITU709_16_235_TO_RGB_0_255_8BIT ;
auto_csc = AUTO_CSC_ENABLE ;
c0_c2_change = C0_C2_CHANGE_DISABLE ;
csc_enable = v_CSC_DISABLE ;
}
}
for ( i = 0 ; i < 24 ; i + + )
hdmi_writeb ( hdmi , HDMI_VIDEO_CSC_COEF + i ,
coeff_csc [ csc_mode ] [ i ] ) ;
value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED ( 1 ) ;
hdmi_writeb ( hdmi , HDMI_VIDEO_CONTRL3 , value ) ;
hdmi_modb ( hdmi , HDMI_VIDEO_CONTRL , m_VIDEO_AUTO_CSC |
m_VIDEO_C0_C2_SWAP , v_VIDEO_AUTO_CSC ( auto_csc ) |
v_VIDEO_C0_C2_SWAP ( c0_c2_change ) ) ;
return 0 ;
}
static int inno_hdmi_config_video_timing ( struct inno_hdmi * hdmi ,
struct drm_display_mode * mode )
{
int value ;
/* Set detail external video timing polarity and interlace mode */
value = v_EXTERANL_VIDEO ( 1 ) ;
value | = mode - > flags & DRM_MODE_FLAG_PHSYNC ?
v_HSYNC_POLARITY ( 1 ) : v_HSYNC_POLARITY ( 0 ) ;
value | = mode - > flags & DRM_MODE_FLAG_PVSYNC ?
v_VSYNC_POLARITY ( 1 ) : v_VSYNC_POLARITY ( 0 ) ;
value | = mode - > flags & DRM_MODE_FLAG_INTERLACE ?
v_INETLACE ( 1 ) : v_INETLACE ( 0 ) ;
hdmi_writeb ( hdmi , HDMI_VIDEO_TIMING_CTL , value ) ;
/* Set detail external video timing */
value = mode - > htotal ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_HTOTAL_L , value & 0xFF ) ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_HTOTAL_H , ( value > > 8 ) & 0xFF ) ;
value = mode - > htotal - mode - > hdisplay ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_HBLANK_L , value & 0xFF ) ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_HBLANK_H , ( value > > 8 ) & 0xFF ) ;
value = mode - > hsync_start - mode - > hdisplay ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_HDELAY_L , value & 0xFF ) ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_HDELAY_H , ( value > > 8 ) & 0xFF ) ;
value = mode - > hsync_end - mode - > hsync_start ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_HDURATION_L , value & 0xFF ) ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_HDURATION_H , ( value > > 8 ) & 0xFF ) ;
value = mode - > vtotal ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_VTOTAL_L , value & 0xFF ) ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_VTOTAL_H , ( value > > 8 ) & 0xFF ) ;
value = mode - > vtotal - mode - > vdisplay ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_VBLANK , value & 0xFF ) ;
value = mode - > vsync_start - mode - > vdisplay ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_VDELAY , value & 0xFF ) ;
value = mode - > vsync_end - mode - > vsync_start ;
hdmi_writeb ( hdmi , HDMI_VIDEO_EXT_VDURATION , value & 0xFF ) ;
hdmi_writeb ( hdmi , HDMI_PHY_PRE_DIV_RATIO , 0x1e ) ;
hdmi_writeb ( hdmi , HDMI_PHY_FEEDBACK_DIV_RATIO_LOW , 0x2c ) ;
hdmi_writeb ( hdmi , HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH , 0x01 ) ;
return 0 ;
}
static int inno_hdmi_setup ( struct inno_hdmi * hdmi ,
struct drm_display_mode * mode )
{
hdmi - > hdmi_data . vic = drm_match_cea_mode ( mode ) ;
hdmi - > hdmi_data . enc_in_format = HDMI_COLORSPACE_RGB ;
hdmi - > hdmi_data . enc_out_format = HDMI_COLORSPACE_RGB ;
if ( ( hdmi - > hdmi_data . vic = = 6 ) | | ( hdmi - > hdmi_data . vic = = 7 ) | |
( hdmi - > hdmi_data . vic = = 21 ) | | ( hdmi - > hdmi_data . vic = = 22 ) | |
( hdmi - > hdmi_data . vic = = 2 ) | | ( hdmi - > hdmi_data . vic = = 3 ) | |
( hdmi - > hdmi_data . vic = = 17 ) | | ( hdmi - > hdmi_data . vic = = 18 ) )
hdmi - > hdmi_data . colorimetry = HDMI_COLORIMETRY_ITU_601 ;
else
hdmi - > hdmi_data . colorimetry = HDMI_COLORIMETRY_ITU_709 ;
/* Mute video and audio output */
hdmi_modb ( hdmi , HDMI_AV_MUTE , m_AUDIO_MUTE | m_VIDEO_BLACK ,
v_AUDIO_MUTE ( 1 ) | v_VIDEO_MUTE ( 1 ) ) ;
/* Set HDMI Mode */
hdmi_writeb ( hdmi , HDMI_HDCP_CTRL ,
v_HDMI_DVI ( hdmi - > hdmi_data . sink_is_hdmi ) ) ;
inno_hdmi_config_video_timing ( hdmi , mode ) ;
inno_hdmi_config_video_csc ( hdmi ) ;
if ( hdmi - > hdmi_data . sink_is_hdmi ) {
inno_hdmi_config_video_avi ( hdmi , mode ) ;
inno_hdmi_config_video_vsi ( hdmi , mode ) ;
}
/*
* When IP controller have configured to an accurate video
* timing , then the TMDS clock source would be switched to
* DCLK_LCDC , so we need to init the TMDS rate to mode pixel
* clock rate , and reconfigure the DDC clock .
*/
hdmi - > tmds_rate = mode - > clock * 1000 ;
inno_hdmi_i2c_init ( hdmi ) ;
/* Unmute video and audio output */
hdmi_modb ( hdmi , HDMI_AV_MUTE , m_AUDIO_MUTE | m_VIDEO_BLACK ,
v_AUDIO_MUTE ( 0 ) | v_VIDEO_MUTE ( 0 ) ) ;
return 0 ;
}
static void inno_hdmi_encoder_mode_set ( struct drm_encoder * encoder ,
struct drm_display_mode * mode ,
struct drm_display_mode * adj_mode )
{
struct inno_hdmi * hdmi = to_inno_hdmi ( encoder ) ;
inno_hdmi_setup ( hdmi , adj_mode ) ;
/* Store the display mode for plugin/DPMS poweron events */
memcpy ( & hdmi - > previous_mode , adj_mode , sizeof ( hdmi - > previous_mode ) ) ;
}
static void inno_hdmi_encoder_enable ( struct drm_encoder * encoder )
{
struct inno_hdmi * hdmi = to_inno_hdmi ( encoder ) ;
inno_hdmi_set_pwr_mode ( hdmi , NORMAL ) ;
}
static void inno_hdmi_encoder_disable ( struct drm_encoder * encoder )
{
struct inno_hdmi * hdmi = to_inno_hdmi ( encoder ) ;
inno_hdmi_set_pwr_mode ( hdmi , LOWER_PWR ) ;
}
static bool inno_hdmi_encoder_mode_fixup ( struct drm_encoder * encoder ,
const struct drm_display_mode * mode ,
struct drm_display_mode * adj_mode )
{
return true ;
}
2016-04-20 10:41:42 +08:00
static int
inno_hdmi_encoder_atomic_check ( struct drm_encoder * encoder ,
struct drm_crtc_state * crtc_state ,
struct drm_connector_state * conn_state )
{
struct rockchip_crtc_state * s = to_rockchip_crtc_state ( crtc_state ) ;
s - > output_mode = ROCKCHIP_OUT_MODE_P888 ;
s - > output_type = DRM_MODE_CONNECTOR_HDMIA ;
return 0 ;
}
2016-02-03 17:44:57 +08:00
static struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = {
. enable = inno_hdmi_encoder_enable ,
. disable = inno_hdmi_encoder_disable ,
. mode_fixup = inno_hdmi_encoder_mode_fixup ,
. mode_set = inno_hdmi_encoder_mode_set ,
2016-04-20 10:41:42 +08:00
. atomic_check = inno_hdmi_encoder_atomic_check ,
2016-02-03 17:44:57 +08:00
} ;
static struct drm_encoder_funcs inno_hdmi_encoder_funcs = {
. destroy = drm_encoder_cleanup ,
} ;
static enum drm_connector_status
inno_hdmi_connector_detect ( struct drm_connector * connector , bool force )
{
struct inno_hdmi * hdmi = to_inno_hdmi ( connector ) ;
return ( hdmi_readb ( hdmi , HDMI_STATUS ) & m_HOTPLUG ) ?
connector_status_connected : connector_status_disconnected ;
}
static int inno_hdmi_connector_get_modes ( struct drm_connector * connector )
{
struct inno_hdmi * hdmi = to_inno_hdmi ( connector ) ;
struct edid * edid ;
int ret = 0 ;
if ( ! hdmi - > ddc )
return 0 ;
edid = drm_get_edid ( connector , hdmi - > ddc ) ;
if ( edid ) {
hdmi - > hdmi_data . sink_is_hdmi = drm_detect_hdmi_monitor ( edid ) ;
hdmi - > hdmi_data . sink_has_audio = drm_detect_monitor_audio ( edid ) ;
2018-07-09 10:40:06 +02:00
drm_connector_update_edid_property ( connector , edid ) ;
2016-02-03 17:44:57 +08:00
ret = drm_add_edid_modes ( connector , edid ) ;
kfree ( edid ) ;
}
return ret ;
}
static enum drm_mode_status
inno_hdmi_connector_mode_valid ( struct drm_connector * connector ,
struct drm_display_mode * mode )
{
return MODE_OK ;
}
static int
inno_hdmi_probe_single_connector_modes ( struct drm_connector * connector ,
uint32_t maxX , uint32_t maxY )
{
return drm_helper_probe_single_connector_modes ( connector , 1920 , 1080 ) ;
}
static void inno_hdmi_connector_destroy ( struct drm_connector * connector )
{
drm_connector_unregister ( connector ) ;
drm_connector_cleanup ( connector ) ;
}
2017-08-08 16:58:32 +05:30
static const struct drm_connector_funcs inno_hdmi_connector_funcs = {
2016-02-03 17:44:57 +08:00
. fill_modes = inno_hdmi_probe_single_connector_modes ,
. detect = inno_hdmi_connector_detect ,
. destroy = inno_hdmi_connector_destroy ,
. reset = drm_atomic_helper_connector_reset ,
. atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_connector_destroy_state ,
} ;
static struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = {
. get_modes = inno_hdmi_connector_get_modes ,
. mode_valid = inno_hdmi_connector_mode_valid ,
} ;
static int inno_hdmi_register ( struct drm_device * drm , struct inno_hdmi * hdmi )
{
struct drm_encoder * encoder = & hdmi - > encoder ;
struct device * dev = hdmi - > dev ;
encoder - > possible_crtcs = drm_of_find_possible_crtcs ( drm , dev - > of_node ) ;
/*
* If we failed to find the CRTC ( s ) which this encoder is
* supposed to be connected to , it ' s because the CRTC has
* not been registered yet . Defer probing , and hope that
* the required CRTC is added later .
*/
if ( encoder - > possible_crtcs = = 0 )
return - EPROBE_DEFER ;
drm_encoder_helper_add ( encoder , & inno_hdmi_encoder_helper_funcs ) ;
drm_encoder_init ( drm , encoder , & inno_hdmi_encoder_funcs ,
DRM_MODE_ENCODER_TMDS , NULL ) ;
hdmi - > connector . polled = DRM_CONNECTOR_POLL_HPD ;
drm_connector_helper_add ( & hdmi - > connector ,
& inno_hdmi_connector_helper_funcs ) ;
drm_connector_init ( drm , & hdmi - > connector , & inno_hdmi_connector_funcs ,
DRM_MODE_CONNECTOR_HDMIA ) ;
2018-07-09 10:40:07 +02:00
drm_connector_attach_encoder ( & hdmi - > connector , encoder ) ;
2016-02-03 17:44:57 +08:00
return 0 ;
}
static irqreturn_t inno_hdmi_i2c_irq ( struct inno_hdmi * hdmi )
{
struct inno_hdmi_i2c * i2c = hdmi - > i2c ;
u8 stat ;
stat = hdmi_readb ( hdmi , HDMI_INTERRUPT_STATUS1 ) ;
if ( ! ( stat & m_INT_EDID_READY ) )
return IRQ_NONE ;
/* Clear HDMI EDID interrupt flag */
hdmi_writeb ( hdmi , HDMI_INTERRUPT_STATUS1 , m_INT_EDID_READY ) ;
complete ( & i2c - > cmp ) ;
return IRQ_HANDLED ;
}
static irqreturn_t inno_hdmi_hardirq ( int irq , void * dev_id )
{
struct inno_hdmi * hdmi = dev_id ;
irqreturn_t ret = IRQ_NONE ;
u8 interrupt ;
if ( hdmi - > i2c )
ret = inno_hdmi_i2c_irq ( hdmi ) ;
interrupt = hdmi_readb ( hdmi , HDMI_STATUS ) ;
if ( interrupt & m_INT_HOTPLUG ) {
hdmi_modb ( hdmi , HDMI_STATUS , m_INT_HOTPLUG , m_INT_HOTPLUG ) ;
ret = IRQ_WAKE_THREAD ;
}
return ret ;
}
static irqreturn_t inno_hdmi_irq ( int irq , void * dev_id )
{
struct inno_hdmi * hdmi = dev_id ;
drm_helper_hpd_irq_event ( hdmi - > connector . dev ) ;
return IRQ_HANDLED ;
}
static int inno_hdmi_i2c_read ( struct inno_hdmi * hdmi , struct i2c_msg * msgs )
{
int length = msgs - > len ;
u8 * buf = msgs - > buf ;
int ret ;
ret = wait_for_completion_timeout ( & hdmi - > i2c - > cmp , HZ / 10 ) ;
if ( ! ret )
return - EAGAIN ;
while ( length - - )
* buf + + = hdmi_readb ( hdmi , HDMI_EDID_FIFO_ADDR ) ;
return 0 ;
}
static int inno_hdmi_i2c_write ( struct inno_hdmi * hdmi , struct i2c_msg * msgs )
{
/*
* The DDC module only support read EDID message , so
* we assume that each word write to this i2c adapter
* should be the offset of EDID word address .
*/
if ( ( msgs - > len ! = 1 ) | |
( ( msgs - > addr ! = DDC_ADDR ) & & ( msgs - > addr ! = DDC_SEGMENT_ADDR ) ) )
return - EINVAL ;
reinit_completion ( & hdmi - > i2c - > cmp ) ;
if ( msgs - > addr = = DDC_SEGMENT_ADDR )
hdmi - > i2c - > segment_addr = msgs - > buf [ 0 ] ;
if ( msgs - > addr = = DDC_ADDR )
hdmi - > i2c - > ddc_addr = msgs - > buf [ 0 ] ;
/* Set edid fifo first addr */
hdmi_writeb ( hdmi , HDMI_EDID_FIFO_OFFSET , 0x00 ) ;
/* Set edid word address 0x00/0x80 */
hdmi_writeb ( hdmi , HDMI_EDID_WORD_ADDR , hdmi - > i2c - > ddc_addr ) ;
/* Set edid segment pointer */
hdmi_writeb ( hdmi , HDMI_EDID_SEGMENT_POINTER , hdmi - > i2c - > segment_addr ) ;
return 0 ;
}
static int inno_hdmi_i2c_xfer ( struct i2c_adapter * adap ,
struct i2c_msg * msgs , int num )
{
struct inno_hdmi * hdmi = i2c_get_adapdata ( adap ) ;
struct inno_hdmi_i2c * i2c = hdmi - > i2c ;
int i , ret = 0 ;
mutex_lock ( & i2c - > lock ) ;
/* Clear the EDID interrupt flag and unmute the interrupt */
hdmi_writeb ( hdmi , HDMI_INTERRUPT_MASK1 , m_INT_EDID_READY ) ;
hdmi_writeb ( hdmi , HDMI_INTERRUPT_STATUS1 , m_INT_EDID_READY ) ;
for ( i = 0 ; i < num ; i + + ) {
2017-09-15 02:36:03 -06:00
DRM_DEV_DEBUG ( hdmi - > dev ,
" xfer: num: %d/%d, len: %d, flags: %#x \n " ,
i + 1 , num , msgs [ i ] . len , msgs [ i ] . flags ) ;
2016-02-03 17:44:57 +08:00
if ( msgs [ i ] . flags & I2C_M_RD )
ret = inno_hdmi_i2c_read ( hdmi , & msgs [ i ] ) ;
else
ret = inno_hdmi_i2c_write ( hdmi , & msgs [ i ] ) ;
if ( ret < 0 )
break ;
}
if ( ! ret )
ret = num ;
/* Mute HDMI EDID interrupt */
hdmi_writeb ( hdmi , HDMI_INTERRUPT_MASK1 , 0 ) ;
mutex_unlock ( & i2c - > lock ) ;
return ret ;
}
static u32 inno_hdmi_i2c_func ( struct i2c_adapter * adapter )
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL ;
}
static const struct i2c_algorithm inno_hdmi_algorithm = {
. master_xfer = inno_hdmi_i2c_xfer ,
. functionality = inno_hdmi_i2c_func ,
} ;
static struct i2c_adapter * inno_hdmi_i2c_adapter ( struct inno_hdmi * hdmi )
{
struct i2c_adapter * adap ;
struct inno_hdmi_i2c * i2c ;
int ret ;
i2c = devm_kzalloc ( hdmi - > dev , sizeof ( * i2c ) , GFP_KERNEL ) ;
if ( ! i2c )
return ERR_PTR ( - ENOMEM ) ;
mutex_init ( & i2c - > lock ) ;
init_completion ( & i2c - > cmp ) ;
adap = & i2c - > adap ;
adap - > class = I2C_CLASS_DDC ;
adap - > owner = THIS_MODULE ;
adap - > dev . parent = hdmi - > dev ;
adap - > dev . of_node = hdmi - > dev - > of_node ;
adap - > algo = & inno_hdmi_algorithm ;
strlcpy ( adap - > name , " Inno HDMI " , sizeof ( adap - > name ) ) ;
i2c_set_adapdata ( adap , hdmi ) ;
ret = i2c_add_adapter ( adap ) ;
if ( ret ) {
dev_warn ( hdmi - > dev , " cannot add %s I2C adapter \n " , adap - > name ) ;
devm_kfree ( hdmi - > dev , i2c ) ;
return ERR_PTR ( ret ) ;
}
hdmi - > i2c = i2c ;
2017-09-15 02:36:03 -06:00
DRM_DEV_INFO ( hdmi - > dev , " registered %s I2C bus driver \n " , adap - > name ) ;
2016-02-03 17:44:57 +08:00
return adap ;
}
static int inno_hdmi_bind ( struct device * dev , struct device * master ,
void * data )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct drm_device * drm = data ;
struct inno_hdmi * hdmi ;
struct resource * iores ;
int irq ;
int ret ;
hdmi = devm_kzalloc ( dev , sizeof ( * hdmi ) , GFP_KERNEL ) ;
if ( ! hdmi )
return - ENOMEM ;
hdmi - > dev = dev ;
hdmi - > drm_dev = drm ;
iores = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
hdmi - > regs = devm_ioremap_resource ( dev , iores ) ;
if ( IS_ERR ( hdmi - > regs ) )
return PTR_ERR ( hdmi - > regs ) ;
hdmi - > pclk = devm_clk_get ( hdmi - > dev , " pclk " ) ;
if ( IS_ERR ( hdmi - > pclk ) ) {
2017-09-15 02:36:03 -06:00
DRM_DEV_ERROR ( hdmi - > dev , " Unable to get HDMI pclk clk \n " ) ;
2016-02-03 17:44:57 +08:00
return PTR_ERR ( hdmi - > pclk ) ;
}
ret = clk_prepare_enable ( hdmi - > pclk ) ;
if ( ret ) {
2017-09-15 02:36:03 -06:00
DRM_DEV_ERROR ( hdmi - > dev ,
" Cannot enable HDMI pclk clock: %d \n " , ret ) ;
2016-02-03 17:44:57 +08:00
return ret ;
}
irq = platform_get_irq ( pdev , 0 ) ;
2018-03-02 18:57:54 +01:00
if ( irq < 0 ) {
ret = irq ;
goto err_disable_clk ;
}
2016-02-03 17:44:57 +08:00
inno_hdmi_reset ( hdmi ) ;
hdmi - > ddc = inno_hdmi_i2c_adapter ( hdmi ) ;
if ( IS_ERR ( hdmi - > ddc ) ) {
2016-02-26 00:30:29 +03:00
ret = PTR_ERR ( hdmi - > ddc ) ;
2016-02-03 17:44:57 +08:00
hdmi - > ddc = NULL ;
2018-03-02 18:57:54 +01:00
goto err_disable_clk ;
2016-02-03 17:44:57 +08:00
}
/*
* When IP controller haven ' t configured to an accurate video
* timing , then the TMDS clock source would be switched to
* PCLK_HDMI , so we need to init the TMDS rate to PCLK rate ,
* and reconfigure the DDC clock .
*/
hdmi - > tmds_rate = clk_get_rate ( hdmi - > pclk ) ;
inno_hdmi_i2c_init ( hdmi ) ;
ret = inno_hdmi_register ( drm , hdmi ) ;
if ( ret )
2018-03-02 18:57:54 +01:00
goto err_put_adapter ;
2016-02-03 17:44:57 +08:00
dev_set_drvdata ( dev , hdmi ) ;
/* Unmute hotplug interrupt */
hdmi_modb ( hdmi , HDMI_STATUS , m_MASK_INT_HOTPLUG , v_MASK_INT_HOTPLUG ( 1 ) ) ;
ret = devm_request_threaded_irq ( dev , irq , inno_hdmi_hardirq ,
inno_hdmi_irq , IRQF_SHARED ,
dev_name ( dev ) , hdmi ) ;
2018-03-02 18:57:54 +01:00
if ( ret < 0 )
goto err_cleanup_hdmi ;
2016-02-03 17:44:57 +08:00
2018-03-02 18:57:54 +01:00
return 0 ;
err_cleanup_hdmi :
hdmi - > connector . funcs - > destroy ( & hdmi - > connector ) ;
hdmi - > encoder . funcs - > destroy ( & hdmi - > encoder ) ;
err_put_adapter :
i2c_put_adapter ( hdmi - > ddc ) ;
err_disable_clk :
clk_disable_unprepare ( hdmi - > pclk ) ;
2016-02-03 17:44:57 +08:00
return ret ;
}
static void inno_hdmi_unbind ( struct device * dev , struct device * master ,
void * data )
{
struct inno_hdmi * hdmi = dev_get_drvdata ( dev ) ;
hdmi - > connector . funcs - > destroy ( & hdmi - > connector ) ;
hdmi - > encoder . funcs - > destroy ( & hdmi - > encoder ) ;
i2c_put_adapter ( hdmi - > ddc ) ;
2018-03-02 18:57:55 +01:00
clk_disable_unprepare ( hdmi - > pclk ) ;
2016-02-03 17:44:57 +08:00
}
static const struct component_ops inno_hdmi_ops = {
. bind = inno_hdmi_bind ,
. unbind = inno_hdmi_unbind ,
} ;
static int inno_hdmi_probe ( struct platform_device * pdev )
{
return component_add ( & pdev - > dev , & inno_hdmi_ops ) ;
}
static int inno_hdmi_remove ( struct platform_device * pdev )
{
component_del ( & pdev - > dev , & inno_hdmi_ops ) ;
return 0 ;
}
static const struct of_device_id inno_hdmi_dt_ids [ ] = {
{ . compatible = " rockchip,rk3036-inno-hdmi " ,
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , inno_hdmi_dt_ids ) ;
2017-03-22 11:21:20 +08:00
struct platform_driver inno_hdmi_driver = {
2016-02-03 17:44:57 +08:00
. probe = inno_hdmi_probe ,
. remove = inno_hdmi_remove ,
. driver = {
. name = " innohdmi-rockchip " ,
. of_match_table = inno_hdmi_dt_ids ,
} ,
} ;