2013-06-03 13:26:17 -03:00
/*
* ths8200 - Texas Instruments THS8200 video encoder driver
*
* Copyright 2013 Cisco Systems , Inc . and / or its affiliates .
*
* This program is free software ; you may redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; version 2 of the License .
*
* 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 version 2.
*
* This program is distributed . as is . WITHOUT ANY WARRANTY of any
* kind , whether express or implied ; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/i2c.h>
# include <linux/module.h>
2013-10-18 00:07:12 -03:00
# include <linux/of.h>
2013-06-03 13:26:17 -03:00
# include <linux/v4l2-dv-timings.h>
2013-07-29 08:40:56 -03:00
# include <media/v4l2-dv-timings.h>
2013-06-22 05:46:34 -03:00
# include <media/v4l2-async.h>
2013-06-03 13:26:17 -03:00
# include <media/v4l2-device.h>
# include "ths8200_regs.h"
static int debug ;
module_param ( debug , int , 0644 ) ;
MODULE_PARM_DESC ( debug , " debug level (0-2) " ) ;
MODULE_DESCRIPTION ( " Texas Instruments THS8200 video encoder driver " ) ;
MODULE_AUTHOR ( " Mats Randgaard <mats.randgaard@cisco.com> " ) ;
MODULE_AUTHOR ( " Martin Bugge <martin.bugge@cisco.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
struct ths8200_state {
struct v4l2_subdev sd ;
uint8_t chip_version ;
/* Is the ths8200 powered on? */
bool power_on ;
struct v4l2_dv_timings dv_timings ;
} ;
2013-07-29 08:41:01 -03:00
static const struct v4l2_dv_timings_cap ths8200_timings_cap = {
. type = V4L2_DV_BT_656_1120 ,
2013-08-30 08:29:25 -03:00
/* keep this initialization for compatibility with GCC < 4.4.6 */
. reserved = { 0 } ,
V4L2_INIT_BT_TIMINGS ( 0 , 1920 , 0 , 1080 , 25000000 , 148500000 ,
V4L2_DV_BT_STD_CEA861 , V4L2_DV_BT_CAP_PROGRESSIVE )
2013-06-03 13:26:17 -03:00
} ;
static inline struct ths8200_state * to_state ( struct v4l2_subdev * sd )
{
return container_of ( sd , struct ths8200_state , sd ) ;
}
static inline unsigned htotal ( const struct v4l2_bt_timings * t )
{
2013-07-29 08:40:59 -03:00
return V4L2_DV_BT_FRAME_WIDTH ( t ) ;
2013-06-03 13:26:17 -03:00
}
static inline unsigned vtotal ( const struct v4l2_bt_timings * t )
{
2013-07-29 08:40:59 -03:00
return V4L2_DV_BT_FRAME_HEIGHT ( t ) ;
2013-06-03 13:26:17 -03:00
}
static int ths8200_read ( struct v4l2_subdev * sd , u8 reg )
{
struct i2c_client * client = v4l2_get_subdevdata ( sd ) ;
return i2c_smbus_read_byte_data ( client , reg ) ;
}
static int ths8200_write ( struct v4l2_subdev * sd , u8 reg , u8 val )
{
struct i2c_client * client = v4l2_get_subdevdata ( sd ) ;
int ret ;
int i ;
for ( i = 0 ; i < 3 ; i + + ) {
ret = i2c_smbus_write_byte_data ( client , reg , val ) ;
if ( ret = = 0 )
return 0 ;
}
v4l2_err ( sd , " I2C Write Problem \n " ) ;
return ret ;
}
/* To set specific bits in the register, a clear-mask is given (to be AND-ed),
* and then the value - mask ( to be OR - ed ) .
*/
static inline void
ths8200_write_and_or ( struct v4l2_subdev * sd , u8 reg ,
uint8_t clr_mask , uint8_t val_mask )
{
ths8200_write ( sd , reg , ( ths8200_read ( sd , reg ) & clr_mask ) | val_mask ) ;
}
# ifdef CONFIG_VIDEO_ADV_DEBUG
static int ths8200_g_register ( struct v4l2_subdev * sd ,
struct v4l2_dbg_register * reg )
{
reg - > val = ths8200_read ( sd , reg - > reg & 0xff ) ;
reg - > size = 1 ;
return 0 ;
}
static int ths8200_s_register ( struct v4l2_subdev * sd ,
const struct v4l2_dbg_register * reg )
{
ths8200_write ( sd , reg - > reg & 0xff , reg - > val & 0xff ) ;
return 0 ;
}
# endif
static int ths8200_log_status ( struct v4l2_subdev * sd )
{
struct ths8200_state * state = to_state ( sd ) ;
uint8_t reg_03 = ths8200_read ( sd , THS8200_CHIP_CTL ) ;
v4l2_info ( sd , " ----- Chip status ----- \n " ) ;
v4l2_info ( sd , " version: %u \n " , state - > chip_version ) ;
v4l2_info ( sd , " power: %s \n " , ( reg_03 & 0x0c ) ? " off " : " on " ) ;
v4l2_info ( sd , " reset: %s \n " , ( reg_03 & 0x01 ) ? " off " : " on " ) ;
v4l2_info ( sd , " test pattern: %s \n " ,
( reg_03 & 0x20 ) ? " enabled " : " disabled " ) ;
v4l2_info ( sd , " format: %ux%u \n " ,
ths8200_read ( sd , THS8200_DTG2_PIXEL_CNT_MSB ) * 256 +
ths8200_read ( sd , THS8200_DTG2_PIXEL_CNT_LSB ) ,
( ths8200_read ( sd , THS8200_DTG2_LINE_CNT_MSB ) & 0x07 ) * 256 +
ths8200_read ( sd , THS8200_DTG2_LINE_CNT_LSB ) ) ;
2013-08-15 08:05:59 -03:00
v4l2_print_dv_timings ( sd - > name , " Configured format: " ,
& state - > dv_timings , true ) ;
2013-06-03 13:26:17 -03:00
return 0 ;
}
/* Power up/down ths8200 */
static int ths8200_s_power ( struct v4l2_subdev * sd , int on )
{
struct ths8200_state * state = to_state ( sd ) ;
v4l2_dbg ( 1 , debug , sd , " %s: power %s \n " , __func__ , on ? " on " : " off " ) ;
state - > power_on = on ;
/* Power up/down - leave in reset state until input video is present */
ths8200_write_and_or ( sd , THS8200_CHIP_CTL , 0xf2 , ( on ? 0x00 : 0x0c ) ) ;
return 0 ;
}
static const struct v4l2_subdev_core_ops ths8200_core_ops = {
. log_status = ths8200_log_status ,
. s_power = ths8200_s_power ,
# ifdef CONFIG_VIDEO_ADV_DEBUG
. g_register = ths8200_g_register ,
. s_register = ths8200_s_register ,
# endif
} ;
/* -----------------------------------------------------------------------------
* V4L2 subdev video operations
*/
static int ths8200_s_stream ( struct v4l2_subdev * sd , int enable )
{
struct ths8200_state * state = to_state ( sd ) ;
if ( enable & & ! state - > power_on )
ths8200_s_power ( sd , true ) ;
ths8200_write_and_or ( sd , THS8200_CHIP_CTL , 0xfe ,
( enable ? 0x01 : 0x00 ) ) ;
v4l2_dbg ( 1 , debug , sd , " %s: %sable \n " ,
__func__ , ( enable ? " en " : " dis " ) ) ;
return 0 ;
}
static void ths8200_core_init ( struct v4l2_subdev * sd )
{
/* setup clocks */
ths8200_write_and_or ( sd , THS8200_CHIP_CTL , 0x3f , 0xc0 ) ;
/**** Data path control (DATA) ****/
/* Set FSADJ 700 mV,
* bypass 422 - 444 interpolation ,
* input format 30 bit RGB444
*/
ths8200_write ( sd , THS8200_DATA_CNTL , 0x70 ) ;
/* DTG Mode (Video blocked during blanking
* VESA slave
*/
ths8200_write ( sd , THS8200_DTG1_MODE , 0x87 ) ;
/**** Display Timing Generator Control, Part 1 (DTG1). ****/
/* Disable embedded syncs on the output by setting
* the amplitude to zero for all channels .
*/
2014-02-07 05:11:03 -03:00
ths8200_write ( sd , THS8200_DTG1_Y_SYNC_MSB , 0x00 ) ;
ths8200_write ( sd , THS8200_DTG1_CBCR_SYNC_MSB , 0x00 ) ;
2013-06-03 13:26:17 -03:00
}
static void ths8200_setup ( struct v4l2_subdev * sd , struct v4l2_bt_timings * bt )
{
uint8_t polarity = 0 ;
uint16_t line_start_active_video = ( bt - > vsync + bt - > vbackporch ) ;
uint16_t line_start_front_porch = ( vtotal ( bt ) - bt - > vfrontporch ) ;
/*** System ****/
/* Set chip in reset while it is configured */
ths8200_s_stream ( sd , false ) ;
/* configure video output timings */
ths8200_write ( sd , THS8200_DTG1_SPEC_A , bt - > hsync ) ;
ths8200_write ( sd , THS8200_DTG1_SPEC_B , bt - > hfrontporch ) ;
/* Zero for progressive scan formats.*/
if ( ! bt - > interlaced )
ths8200_write ( sd , THS8200_DTG1_SPEC_C , 0x00 ) ;
/* Distance from leading edge of h sync to start of active video.
* MSB in 0x2b
*/
ths8200_write ( sd , THS8200_DTG1_SPEC_D_LSB ,
( bt - > hbackporch + bt - > hsync ) & 0xff ) ;
/* Zero for SDTV-mode. MSB in 0x2b */
ths8200_write ( sd , THS8200_DTG1_SPEC_E_LSB , 0x00 ) ;
/*
* MSB for dtg1_spec ( d / e / h ) . See comment for
* corresponding LSB registers .
*/
ths8200_write ( sd , THS8200_DTG1_SPEC_DEH_MSB ,
( ( bt - > hbackporch + bt - > hsync ) & 0x100 ) > > 1 ) ;
/* h front porch */
ths8200_write ( sd , THS8200_DTG1_SPEC_K_LSB , ( bt - > hfrontporch ) & 0xff ) ;
ths8200_write ( sd , THS8200_DTG1_SPEC_K_MSB ,
( ( bt - > hfrontporch ) & 0x700 ) > > 8 ) ;
/* Half the line length. Used to calculate SDTV line types. */
ths8200_write ( sd , THS8200_DTG1_SPEC_G_LSB , ( htotal ( bt ) / 2 ) & 0xff ) ;
ths8200_write ( sd , THS8200_DTG1_SPEC_G_MSB ,
( ( htotal ( bt ) / 2 ) > > 8 ) & 0x0f ) ;
/* Total pixels per line (ex. 720p: 1650) */
ths8200_write ( sd , THS8200_DTG1_TOT_PIXELS_MSB , htotal ( bt ) > > 8 ) ;
ths8200_write ( sd , THS8200_DTG1_TOT_PIXELS_LSB , htotal ( bt ) & 0xff ) ;
/* Frame height and field height */
/* Field height should be programmed higher than frame_size for
* progressive scan formats
*/
ths8200_write ( sd , THS8200_DTG1_FRAME_FIELD_SZ_MSB ,
( ( vtotal ( bt ) > > 4 ) & 0xf0 ) + 0x7 ) ;
ths8200_write ( sd , THS8200_DTG1_FRAME_SZ_LSB , vtotal ( bt ) & 0xff ) ;
/* Should be programmed higher than frame_size
* for progressive formats
*/
if ( ! bt - > interlaced )
ths8200_write ( sd , THS8200_DTG1_FIELD_SZ_LSB , 0xff ) ;
/**** Display Timing Generator Control, Part 2 (DTG2). ****/
/* Set breakpoint line numbers and types
* THS8200 generates line types with different properties . A line type
* that sets all the RGB - outputs to zero is used in the blanking areas ,
* while a line type that enable the RGB - outputs is used in active video
* area . The line numbers for start of active video , start of front
* porch and after the last line in the frame must be set with the
* corresponding line types .
*
* Line types :
* 0x9 - Full normal sync pulse : Blocks data when dtg1_pass is off .
* Used in blanking area .
* 0x0 - Active video : Video data is always passed . Used in active
* video area .
*/
ths8200_write_and_or ( sd , THS8200_DTG2_BP1_2_MSB , 0x88 ,
( ( line_start_active_video > > 4 ) & 0x70 ) +
( ( line_start_front_porch > > 8 ) & 0x07 ) ) ;
ths8200_write ( sd , THS8200_DTG2_BP3_4_MSB , ( ( vtotal ( bt ) ) > > 4 ) & 0x70 ) ;
ths8200_write ( sd , THS8200_DTG2_BP1_LSB , line_start_active_video & 0xff ) ;
ths8200_write ( sd , THS8200_DTG2_BP2_LSB , line_start_front_porch & 0xff ) ;
ths8200_write ( sd , THS8200_DTG2_BP3_LSB , ( vtotal ( bt ) ) & 0xff ) ;
/* line types */
ths8200_write ( sd , THS8200_DTG2_LINETYPE1 , 0x90 ) ;
ths8200_write ( sd , THS8200_DTG2_LINETYPE2 , 0x90 ) ;
/* h sync width transmitted */
ths8200_write ( sd , THS8200_DTG2_HLENGTH_LSB , bt - > hsync & 0xff ) ;
ths8200_write_and_or ( sd , THS8200_DTG2_HLENGTH_LSB_HDLY_MSB , 0x3f ,
( bt - > hsync > > 2 ) & 0xc0 ) ;
/* The pixel value h sync is asserted on */
ths8200_write_and_or ( sd , THS8200_DTG2_HLENGTH_LSB_HDLY_MSB , 0xe0 ,
( htotal ( bt ) > > 8 ) & 0x1f ) ;
ths8200_write ( sd , THS8200_DTG2_HLENGTH_HDLY_LSB , htotal ( bt ) ) ;
2014-02-07 05:11:05 -03:00
/* v sync width transmitted (must add 1 to get correct output) */
ths8200_write ( sd , THS8200_DTG2_VLENGTH1_LSB , ( bt - > vsync + 1 ) & 0xff ) ;
2013-06-03 13:26:17 -03:00
ths8200_write_and_or ( sd , THS8200_DTG2_VLENGTH1_MSB_VDLY1_MSB , 0x3f ,
2014-02-07 05:11:05 -03:00
( ( bt - > vsync + 1 ) > > 2 ) & 0xc0 ) ;
2013-06-03 13:26:17 -03:00
2014-02-07 05:11:05 -03:00
/* The pixel value v sync is asserted on (must add 1 to get correct output) */
2013-06-03 13:26:17 -03:00
ths8200_write_and_or ( sd , THS8200_DTG2_VLENGTH1_MSB_VDLY1_MSB , 0xf8 ,
2014-02-07 05:11:05 -03:00
( ( vtotal ( bt ) + 1 ) > > 8 ) & 0x7 ) ;
ths8200_write ( sd , THS8200_DTG2_VDLY1_LSB , vtotal ( bt ) + 1 ) ;
2013-06-03 13:26:17 -03:00
/* For progressive video vlength2 must be set to all 0 and vdly2 must
* be set to all 1.
*/
ths8200_write ( sd , THS8200_DTG2_VLENGTH2_LSB , 0x00 ) ;
ths8200_write ( sd , THS8200_DTG2_VLENGTH2_MSB_VDLY2_MSB , 0x07 ) ;
ths8200_write ( sd , THS8200_DTG2_VDLY2_LSB , 0xff ) ;
/* Internal delay factors to synchronize the sync pulses and the data */
2014-02-07 05:11:05 -03:00
/* Experimental values delays (hor 0, ver 0) */
ths8200_write ( sd , THS8200_DTG2_HS_IN_DLY_MSB , 0 ) ;
ths8200_write ( sd , THS8200_DTG2_HS_IN_DLY_LSB , 0 ) ;
2013-06-03 13:26:17 -03:00
ths8200_write ( sd , THS8200_DTG2_VS_IN_DLY_MSB , 0 ) ;
2014-02-07 05:11:05 -03:00
ths8200_write ( sd , THS8200_DTG2_VS_IN_DLY_LSB , 0 ) ;
2013-06-03 13:26:17 -03:00
/* Polarity of received and transmitted sync signals */
if ( bt - > polarities & V4L2_DV_HSYNC_POS_POL ) {
polarity | = 0x01 ; /* HS_IN */
polarity | = 0x08 ; /* HS_OUT */
}
if ( bt - > polarities & V4L2_DV_VSYNC_POS_POL ) {
polarity | = 0x02 ; /* VS_IN */
polarity | = 0x10 ; /* VS_OUT */
}
/* RGB mode, no embedded timings */
/* Timing of video input bus is derived from HS, VS, and FID dedicated
* inputs
*/
2014-02-07 05:11:04 -03:00
ths8200_write ( sd , THS8200_DTG2_CNTL , 0x44 | polarity ) ;
2013-06-03 13:26:17 -03:00
/* leave reset */
ths8200_s_stream ( sd , true ) ;
v4l2_dbg ( 1 , debug , sd , " %s: frame %dx%d, polarity %d \n "
" horizontal: front porch %d, back porch %d, sync %d \n "
" vertical: sync %d \n " , __func__ , htotal ( bt ) , vtotal ( bt ) ,
polarity , bt - > hfrontporch , bt - > hbackporch ,
bt - > hsync , bt - > vsync ) ;
}
static int ths8200_s_dv_timings ( struct v4l2_subdev * sd ,
struct v4l2_dv_timings * timings )
{
struct ths8200_state * state = to_state ( sd ) ;
v4l2_dbg ( 1 , debug , sd , " %s: \n " , __func__ ) ;
2013-08-19 11:21:50 -03:00
if ( ! v4l2_valid_dv_timings ( timings , & ths8200_timings_cap ,
NULL , NULL ) )
2013-06-03 13:26:17 -03:00
return - EINVAL ;
2013-08-19 11:21:50 -03:00
if ( ! v4l2_find_dv_timings_cap ( timings , & ths8200_timings_cap , 10 ,
NULL , NULL ) ) {
2013-06-03 13:26:17 -03:00
v4l2_dbg ( 1 , debug , sd , " Unsupported format \n " ) ;
return - EINVAL ;
}
timings - > bt . flags & = ~ V4L2_DV_FL_REDUCED_FPS ;
/* save timings */
state - > dv_timings = * timings ;
ths8200_setup ( sd , & timings - > bt ) ;
return 0 ;
}
static int ths8200_g_dv_timings ( struct v4l2_subdev * sd ,
struct v4l2_dv_timings * timings )
{
struct ths8200_state * state = to_state ( sd ) ;
v4l2_dbg ( 1 , debug , sd , " %s: \n " , __func__ ) ;
* timings = state - > dv_timings ;
return 0 ;
}
static int ths8200_enum_dv_timings ( struct v4l2_subdev * sd ,
struct v4l2_enum_dv_timings * timings )
{
2014-01-31 08:51:18 -03:00
if ( timings - > pad ! = 0 )
return - EINVAL ;
2013-08-19 11:21:50 -03:00
return v4l2_enum_dv_timings_cap ( timings , & ths8200_timings_cap ,
NULL , NULL ) ;
2013-06-03 13:26:17 -03:00
}
static int ths8200_dv_timings_cap ( struct v4l2_subdev * sd ,
struct v4l2_dv_timings_cap * cap )
{
2014-01-31 08:51:18 -03:00
if ( cap - > pad ! = 0 )
return - EINVAL ;
2013-07-29 08:41:01 -03:00
* cap = ths8200_timings_cap ;
2013-06-03 13:26:17 -03:00
return 0 ;
}
/* Specific video subsystem operation handlers */
static const struct v4l2_subdev_video_ops ths8200_video_ops = {
. s_stream = ths8200_s_stream ,
. s_dv_timings = ths8200_s_dv_timings ,
. g_dv_timings = ths8200_g_dv_timings ,
} ;
2014-01-31 08:51:18 -03:00
static const struct v4l2_subdev_pad_ops ths8200_pad_ops = {
. enum_dv_timings = ths8200_enum_dv_timings ,
. dv_timings_cap = ths8200_dv_timings_cap ,
} ;
2013-06-03 13:26:17 -03:00
/* V4L2 top level operation handlers */
static const struct v4l2_subdev_ops ths8200_ops = {
. core = & ths8200_core_ops ,
. video = & ths8200_video_ops ,
2014-01-31 08:51:18 -03:00
. pad = & ths8200_pad_ops ,
2013-06-03 13:26:17 -03:00
} ;
static int ths8200_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct ths8200_state * state ;
struct v4l2_subdev * sd ;
2013-06-22 05:46:34 -03:00
int error ;
2013-06-03 13:26:17 -03:00
/* Check if the adapter supports the needed features */
if ( ! i2c_check_functionality ( client - > adapter , I2C_FUNC_SMBUS_BYTE_DATA ) )
return - EIO ;
state = devm_kzalloc ( & client - > dev , sizeof ( * state ) , GFP_KERNEL ) ;
if ( ! state )
return - ENOMEM ;
sd = & state - > sd ;
v4l2_i2c_subdev_init ( sd , client , & ths8200_ops ) ;
state - > chip_version = ths8200_read ( sd , THS8200_VERSION ) ;
v4l2_dbg ( 1 , debug , sd , " chip version 0x%x \n " , state - > chip_version ) ;
ths8200_core_init ( sd ) ;
2013-06-22 05:46:34 -03:00
error = v4l2_async_register_subdev ( & state - > sd ) ;
if ( error )
return error ;
2013-06-03 13:26:17 -03:00
v4l2_info ( sd , " %s found @ 0x%x (%s) \n " , client - > name ,
client - > addr < < 1 , client - > adapter - > name ) ;
return 0 ;
}
static int ths8200_remove ( struct i2c_client * client )
{
struct v4l2_subdev * sd = i2c_get_clientdata ( client ) ;
2013-06-22 05:46:34 -03:00
struct ths8200_state * decoder = to_state ( sd ) ;
2013-06-03 13:26:17 -03:00
v4l2_dbg ( 1 , debug , sd , " %s removed @ 0x%x (%s) \n " , client - > name ,
client - > addr < < 1 , client - > adapter - > name ) ;
ths8200_s_power ( sd , false ) ;
2013-06-22 05:46:34 -03:00
v4l2_async_unregister_subdev ( & decoder - > sd ) ;
2013-06-03 13:26:17 -03:00
return 0 ;
}
static struct i2c_device_id ths8200_id [ ] = {
{ " ths8200 " , 0 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( i2c , ths8200_id ) ;
2013-06-22 05:46:35 -03:00
# if IS_ENABLED(CONFIG_OF)
static const struct of_device_id ths8200_of_match [ ] = {
{ . compatible = " ti,ths8200 " , } ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , ths8200_of_match ) ;
# endif
2013-06-03 13:26:17 -03:00
static struct i2c_driver ths8200_driver = {
. driver = {
. owner = THIS_MODULE ,
. name = " ths8200 " ,
2013-06-22 05:46:35 -03:00
. of_match_table = of_match_ptr ( ths8200_of_match ) ,
2013-06-03 13:26:17 -03:00
} ,
. probe = ths8200_probe ,
. remove = ths8200_remove ,
. id_table = ths8200_id ,
} ;
module_i2c_driver ( ths8200_driver ) ;