2015-12-31 18:21:21 +01:00
/*
* Copyright ( C ) 2016 Atmel
* Bo Shen < voice . shen @ atmel . com >
*
* Authors : Bo Shen < voice . shen @ atmel . com >
* Boris Brezillon < boris . brezillon @ free - electrons . com >
* Wu , Songjun < Songjun . Wu @ atmel . com >
*
*
* Copyright ( C ) 2010 - 2011 Freescale Semiconductor , Inc . All Rights Reserved .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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/gpio/consumer.h>
# include <linux/i2c.h>
# include <linux/module.h>
# include <linux/regmap.h>
# include <drm/drmP.h>
# include <drm/drm_atomic_helper.h>
# include <drm/drm_crtc_helper.h>
# include <drm/drm_edid.h>
# define SII902X_TPI_VIDEO_DATA 0x0
# define SII902X_TPI_PIXEL_REPETITION 0x8
# define SII902X_TPI_AVI_PIXEL_REP_BUS_24BIT BIT(5)
# define SII902X_TPI_AVI_PIXEL_REP_RISING_EDGE BIT(4)
# define SII902X_TPI_AVI_PIXEL_REP_4X 3
# define SII902X_TPI_AVI_PIXEL_REP_2X 1
# define SII902X_TPI_AVI_PIXEL_REP_NONE 0
# define SII902X_TPI_CLK_RATIO_HALF (0 << 6)
# define SII902X_TPI_CLK_RATIO_1X (1 << 6)
# define SII902X_TPI_CLK_RATIO_2X (2 << 6)
# define SII902X_TPI_CLK_RATIO_4X (3 << 6)
# define SII902X_TPI_AVI_IN_FORMAT 0x9
# define SII902X_TPI_AVI_INPUT_BITMODE_12BIT BIT(7)
# define SII902X_TPI_AVI_INPUT_DITHER BIT(6)
# define SII902X_TPI_AVI_INPUT_RANGE_LIMITED (2 << 2)
# define SII902X_TPI_AVI_INPUT_RANGE_FULL (1 << 2)
# define SII902X_TPI_AVI_INPUT_RANGE_AUTO (0 << 2)
# define SII902X_TPI_AVI_INPUT_COLORSPACE_BLACK (3 << 0)
# define SII902X_TPI_AVI_INPUT_COLORSPACE_YUV422 (2 << 0)
# define SII902X_TPI_AVI_INPUT_COLORSPACE_YUV444 (1 << 0)
# define SII902X_TPI_AVI_INPUT_COLORSPACE_RGB (0 << 0)
# define SII902X_TPI_AVI_INFOFRAME 0x0c
# define SII902X_SYS_CTRL_DATA 0x1a
# define SII902X_SYS_CTRL_PWR_DWN BIT(4)
# define SII902X_SYS_CTRL_AV_MUTE BIT(3)
# define SII902X_SYS_CTRL_DDC_BUS_REQ BIT(2)
# define SII902X_SYS_CTRL_DDC_BUS_GRTD BIT(1)
# define SII902X_SYS_CTRL_OUTPUT_MODE BIT(0)
# define SII902X_SYS_CTRL_OUTPUT_HDMI 1
# define SII902X_SYS_CTRL_OUTPUT_DVI 0
# define SII902X_REG_CHIPID(n) (0x1b + (n))
# define SII902X_PWR_STATE_CTRL 0x1e
# define SII902X_AVI_POWER_STATE_MSK GENMASK(1, 0)
# define SII902X_AVI_POWER_STATE_D(l) ((l) & SII902X_AVI_POWER_STATE_MSK)
# define SII902X_INT_ENABLE 0x3c
# define SII902X_INT_STATUS 0x3d
# define SII902X_HOTPLUG_EVENT BIT(0)
# define SII902X_PLUGGED_STATUS BIT(2)
# define SII902X_REG_TPI_RQB 0xc7
# define SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS 500
struct sii902x {
struct i2c_client * i2c ;
struct regmap * regmap ;
struct drm_bridge bridge ;
struct drm_connector connector ;
struct gpio_desc * reset_gpio ;
} ;
static inline struct sii902x * bridge_to_sii902x ( struct drm_bridge * bridge )
{
return container_of ( bridge , struct sii902x , bridge ) ;
}
static inline struct sii902x * connector_to_sii902x ( struct drm_connector * con )
{
return container_of ( con , struct sii902x , connector ) ;
}
static void sii902x_reset ( struct sii902x * sii902x )
{
if ( ! sii902x - > reset_gpio )
return ;
gpiod_set_value ( sii902x - > reset_gpio , 1 ) ;
/* The datasheet says treset-min = 100us. Make it 150us to be sure. */
usleep_range ( 150 , 200 ) ;
gpiod_set_value ( sii902x - > reset_gpio , 0 ) ;
}
static enum drm_connector_status
sii902x_connector_detect ( struct drm_connector * connector , bool force )
{
struct sii902x * sii902x = connector_to_sii902x ( connector ) ;
unsigned int status ;
regmap_read ( sii902x - > regmap , SII902X_INT_STATUS , & status ) ;
return ( status & SII902X_PLUGGED_STATUS ) ?
connector_status_connected : connector_status_disconnected ;
}
static const struct drm_connector_funcs sii902x_connector_funcs = {
. detect = sii902x_connector_detect ,
. fill_modes = drm_helper_probe_single_connector_modes ,
. destroy = drm_connector_cleanup ,
. 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 int sii902x_get_modes ( struct drm_connector * connector )
{
struct sii902x * sii902x = connector_to_sii902x ( connector ) ;
struct regmap * regmap = sii902x - > regmap ;
u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24 ;
unsigned long timeout ;
unsigned int status ;
struct edid * edid ;
int num = 0 ;
int ret ;
ret = regmap_update_bits ( regmap , SII902X_SYS_CTRL_DATA ,
SII902X_SYS_CTRL_DDC_BUS_REQ ,
SII902X_SYS_CTRL_DDC_BUS_REQ ) ;
if ( ret )
return ret ;
timeout = jiffies +
msecs_to_jiffies ( SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS ) ;
do {
ret = regmap_read ( regmap , SII902X_SYS_CTRL_DATA , & status ) ;
if ( ret )
return ret ;
} while ( ! ( status & SII902X_SYS_CTRL_DDC_BUS_GRTD ) & &
time_before ( jiffies , timeout ) ) ;
if ( ! ( status & SII902X_SYS_CTRL_DDC_BUS_GRTD ) ) {
2017-04-11 10:22:19 +08:00
dev_err ( & sii902x - > i2c - > dev , " failed to acquire the i2c bus \n " ) ;
2015-12-31 18:21:21 +01:00
return - ETIMEDOUT ;
}
ret = regmap_write ( regmap , SII902X_SYS_CTRL_DATA , status ) ;
if ( ret )
return ret ;
edid = drm_get_edid ( connector , sii902x - > i2c - > adapter ) ;
drm_mode_connector_update_edid_property ( connector , edid ) ;
if ( edid ) {
num = drm_add_edid_modes ( connector , edid ) ;
kfree ( edid ) ;
}
ret = drm_display_info_set_bus_formats ( & connector - > display_info ,
& bus_format , 1 ) ;
if ( ret )
return ret ;
ret = regmap_read ( regmap , SII902X_SYS_CTRL_DATA , & status ) ;
if ( ret )
return ret ;
ret = regmap_update_bits ( regmap , SII902X_SYS_CTRL_DATA ,
SII902X_SYS_CTRL_DDC_BUS_REQ |
SII902X_SYS_CTRL_DDC_BUS_GRTD , 0 ) ;
if ( ret )
return ret ;
timeout = jiffies +
msecs_to_jiffies ( SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS ) ;
do {
ret = regmap_read ( regmap , SII902X_SYS_CTRL_DATA , & status ) ;
if ( ret )
return ret ;
} while ( status & ( SII902X_SYS_CTRL_DDC_BUS_REQ |
SII902X_SYS_CTRL_DDC_BUS_GRTD ) & &
time_before ( jiffies , timeout ) ) ;
if ( status & ( SII902X_SYS_CTRL_DDC_BUS_REQ |
SII902X_SYS_CTRL_DDC_BUS_GRTD ) ) {
2017-04-11 10:22:19 +08:00
dev_err ( & sii902x - > i2c - > dev , " failed to release the i2c bus \n " ) ;
2015-12-31 18:21:21 +01:00
return - ETIMEDOUT ;
}
return num ;
}
static enum drm_mode_status sii902x_mode_valid ( struct drm_connector * connector ,
struct drm_display_mode * mode )
{
/* TODO: check mode */
return MODE_OK ;
}
static const struct drm_connector_helper_funcs sii902x_connector_helper_funcs = {
. get_modes = sii902x_get_modes ,
. mode_valid = sii902x_mode_valid ,
} ;
static void sii902x_bridge_disable ( struct drm_bridge * bridge )
{
struct sii902x * sii902x = bridge_to_sii902x ( bridge ) ;
regmap_update_bits ( sii902x - > regmap , SII902X_SYS_CTRL_DATA ,
SII902X_SYS_CTRL_PWR_DWN ,
SII902X_SYS_CTRL_PWR_DWN ) ;
}
static void sii902x_bridge_enable ( struct drm_bridge * bridge )
{
struct sii902x * sii902x = bridge_to_sii902x ( bridge ) ;
regmap_update_bits ( sii902x - > regmap , SII902X_PWR_STATE_CTRL ,
SII902X_AVI_POWER_STATE_MSK ,
SII902X_AVI_POWER_STATE_D ( 0 ) ) ;
regmap_update_bits ( sii902x - > regmap , SII902X_SYS_CTRL_DATA ,
SII902X_SYS_CTRL_PWR_DWN , 0 ) ;
}
static void sii902x_bridge_mode_set ( struct drm_bridge * bridge ,
struct drm_display_mode * mode ,
struct drm_display_mode * adj )
{
struct sii902x * sii902x = bridge_to_sii902x ( bridge ) ;
struct regmap * regmap = sii902x - > regmap ;
u8 buf [ HDMI_INFOFRAME_SIZE ( AVI ) ] ;
struct hdmi_avi_infoframe frame ;
int ret ;
buf [ 0 ] = adj - > clock ;
buf [ 1 ] = adj - > clock > > 8 ;
buf [ 2 ] = adj - > vrefresh ;
buf [ 3 ] = 0x00 ;
buf [ 4 ] = adj - > hdisplay ;
buf [ 5 ] = adj - > hdisplay > > 8 ;
buf [ 6 ] = adj - > vdisplay ;
buf [ 7 ] = adj - > vdisplay > > 8 ;
buf [ 8 ] = SII902X_TPI_CLK_RATIO_1X | SII902X_TPI_AVI_PIXEL_REP_NONE |
SII902X_TPI_AVI_PIXEL_REP_BUS_24BIT ;
buf [ 9 ] = SII902X_TPI_AVI_INPUT_RANGE_AUTO |
SII902X_TPI_AVI_INPUT_COLORSPACE_RGB ;
ret = regmap_bulk_write ( regmap , SII902X_TPI_VIDEO_DATA , buf , 10 ) ;
if ( ret )
return ;
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
ret = drm_hdmi_avi_infoframe_from_display_mode ( & frame , adj , false ) ;
2015-12-31 18:21:21 +01:00
if ( ret < 0 ) {
DRM_ERROR ( " couldn't fill AVI infoframe \n " ) ;
return ;
}
ret = hdmi_avi_infoframe_pack ( & frame , buf , sizeof ( buf ) ) ;
if ( ret < 0 ) {
DRM_ERROR ( " failed to pack AVI infoframe: %d \n " , ret ) ;
return ;
}
/* Do not send the infoframe header, but keep the CRC field. */
regmap_bulk_write ( regmap , SII902X_TPI_AVI_INFOFRAME ,
buf + HDMI_INFOFRAME_HEADER_SIZE - 1 ,
HDMI_AVI_INFOFRAME_SIZE + 1 ) ;
}
static int sii902x_bridge_attach ( struct drm_bridge * bridge )
{
struct sii902x * sii902x = bridge_to_sii902x ( bridge ) ;
struct drm_device * drm = bridge - > dev ;
int ret ;
drm_connector_helper_add ( & sii902x - > connector ,
& sii902x_connector_helper_funcs ) ;
if ( ! drm_core_check_feature ( drm , DRIVER_ATOMIC ) ) {
dev_err ( & sii902x - > i2c - > dev ,
2017-04-11 10:22:19 +08:00
" sii902x driver is only compatible with DRM devices supporting atomic updates \n " ) ;
2015-12-31 18:21:21 +01:00
return - ENOTSUPP ;
}
ret = drm_connector_init ( drm , & sii902x - > connector ,
& sii902x_connector_funcs ,
DRM_MODE_CONNECTOR_HDMIA ) ;
if ( ret )
return ret ;
if ( sii902x - > i2c - > irq > 0 )
sii902x - > connector . polled = DRM_CONNECTOR_POLL_HPD ;
else
sii902x - > connector . polled = DRM_CONNECTOR_POLL_CONNECT ;
drm_mode_connector_attach_encoder ( & sii902x - > connector , bridge - > encoder ) ;
return 0 ;
}
static const struct drm_bridge_funcs sii902x_bridge_funcs = {
. attach = sii902x_bridge_attach ,
. mode_set = sii902x_bridge_mode_set ,
. disable = sii902x_bridge_disable ,
. enable = sii902x_bridge_enable ,
} ;
static const struct regmap_range sii902x_volatile_ranges [ ] = {
{ . range_min = 0 , . range_max = 0xff } ,
} ;
static const struct regmap_access_table sii902x_volatile_table = {
. yes_ranges = sii902x_volatile_ranges ,
. n_yes_ranges = ARRAY_SIZE ( sii902x_volatile_ranges ) ,
} ;
static const struct regmap_config sii902x_regmap_config = {
. reg_bits = 8 ,
. val_bits = 8 ,
. volatile_table = & sii902x_volatile_table ,
. cache_type = REGCACHE_NONE ,
} ;
static irqreturn_t sii902x_interrupt ( int irq , void * data )
{
struct sii902x * sii902x = data ;
unsigned int status = 0 ;
regmap_read ( sii902x - > regmap , SII902X_INT_STATUS , & status ) ;
regmap_write ( sii902x - > regmap , SII902X_INT_STATUS , status ) ;
if ( ( status & SII902X_HOTPLUG_EVENT ) & & sii902x - > bridge . dev )
drm_helper_hpd_irq_event ( sii902x - > bridge . dev ) ;
return IRQ_HANDLED ;
}
static int sii902x_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct device * dev = & client - > dev ;
unsigned int status = 0 ;
struct sii902x * sii902x ;
u8 chipid [ 4 ] ;
int ret ;
sii902x = devm_kzalloc ( dev , sizeof ( * sii902x ) , GFP_KERNEL ) ;
if ( ! sii902x )
return - ENOMEM ;
sii902x - > i2c = client ;
sii902x - > regmap = devm_regmap_init_i2c ( client , & sii902x_regmap_config ) ;
if ( IS_ERR ( sii902x - > regmap ) )
return PTR_ERR ( sii902x - > regmap ) ;
sii902x - > reset_gpio = devm_gpiod_get_optional ( dev , " reset " ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( sii902x - > reset_gpio ) ) {
dev_err ( dev , " Failed to retrieve/request reset gpio: %ld \n " ,
PTR_ERR ( sii902x - > reset_gpio ) ) ;
return PTR_ERR ( sii902x - > reset_gpio ) ;
}
sii902x_reset ( sii902x ) ;
ret = regmap_write ( sii902x - > regmap , SII902X_REG_TPI_RQB , 0x0 ) ;
if ( ret )
return ret ;
ret = regmap_bulk_read ( sii902x - > regmap , SII902X_REG_CHIPID ( 0 ) ,
& chipid , 4 ) ;
if ( ret ) {
dev_err ( dev , " regmap_read failed %d \n " , ret ) ;
return ret ;
}
if ( chipid [ 0 ] ! = 0xb0 ) {
dev_err ( dev , " Invalid chipid: %02x (expecting 0xb0) \n " ,
chipid [ 0 ] ) ;
return - EINVAL ;
}
/* Clear all pending interrupts */
regmap_read ( sii902x - > regmap , SII902X_INT_STATUS , & status ) ;
regmap_write ( sii902x - > regmap , SII902X_INT_STATUS , status ) ;
if ( client - > irq > 0 ) {
regmap_write ( sii902x - > regmap , SII902X_INT_ENABLE ,
SII902X_HOTPLUG_EVENT ) ;
ret = devm_request_threaded_irq ( dev , client - > irq , NULL ,
sii902x_interrupt ,
IRQF_ONESHOT , dev_name ( dev ) ,
sii902x ) ;
if ( ret )
return ret ;
}
sii902x - > bridge . funcs = & sii902x_bridge_funcs ;
sii902x - > bridge . of_node = dev - > of_node ;
2017-07-03 17:42:24 +09:00
drm_bridge_add ( & sii902x - > bridge ) ;
2015-12-31 18:21:21 +01:00
i2c_set_clientdata ( client , sii902x ) ;
return 0 ;
}
static int sii902x_remove ( struct i2c_client * client )
{
struct sii902x * sii902x = i2c_get_clientdata ( client ) ;
drm_bridge_remove ( & sii902x - > bridge ) ;
return 0 ;
}
static const struct of_device_id sii902x_dt_ids [ ] = {
{ . compatible = " sil,sii9022 " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , sii902x_dt_ids ) ;
static const struct i2c_device_id sii902x_i2c_ids [ ] = {
{ " sii9022 " , 0 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( i2c , sii902x_i2c_ids ) ;
static struct i2c_driver sii902x_driver = {
. probe = sii902x_probe ,
. remove = sii902x_remove ,
. driver = {
. name = " sii902x " ,
. of_match_table = sii902x_dt_ids ,
} ,
. id_table = sii902x_i2c_ids ,
} ;
module_i2c_driver ( sii902x_driver ) ;
MODULE_AUTHOR ( " Boris Brezillon <boris.brezillon@free-electrons.com> " ) ;
MODULE_DESCRIPTION ( " SII902x RGB -> HDMI bridges " ) ;
MODULE_LICENSE ( " GPL " ) ;