2005-04-17 02:20:36 +04:00
/*
* vpx3220a , vpx3216b & vpx3214c video decoder driver version 0.0 .1
*
* Copyright ( C ) 2001 Laurent Pinchart < lpinchart @ freegates . be >
*
* 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/types.h>
# include <linux/slab.h>
# include <linux/byteorder/swab.h>
# include <asm/io.h>
# include <asm/uaccess.h>
# include <linux/i2c.h>
# include <linux/i2c-dev.h>
# define I2C_NAME(x) (x)->name
# include <linux/videodev.h>
# include <linux/video_decoder.h>
# define I2C_VPX3220 0x86
# define VPX3220_DEBUG KERN_DEBUG "vpx3220: "
static int debug = 0 ;
module_param ( debug , int , 0 ) ;
MODULE_PARM_DESC ( debug , " Debug level (0-1) " ) ;
# define dprintk(num, format, args...) \
do { \
if ( debug > = num ) \
printk ( format , # # args ) ; \
} while ( 0 )
# define VPX_TIMEOUT_COUNT 10
/* ----------------------------------------------------------------------- */
struct vpx3220 {
unsigned char reg [ 255 ] ;
int norm ;
int input ;
int enable ;
int bright ;
int contrast ;
int hue ;
int sat ;
} ;
static char * inputs [ ] = { " internal " , " composite " , " svideo " } ;
/* ----------------------------------------------------------------------- */
static inline int
vpx3220_write ( struct i2c_client * client ,
u8 reg ,
u8 value )
{
struct vpx3220 * decoder = i2c_get_clientdata ( client ) ;
decoder - > reg [ reg ] = value ;
return i2c_smbus_write_byte_data ( client , reg , value ) ;
}
static inline int
vpx3220_read ( struct i2c_client * client ,
u8 reg )
{
return i2c_smbus_read_byte_data ( client , reg ) ;
}
static int
vpx3220_fp_status ( struct i2c_client * client )
{
unsigned char status ;
unsigned int i ;
for ( i = 0 ; i < VPX_TIMEOUT_COUNT ; i + + ) {
status = vpx3220_read ( client , 0x29 ) ;
if ( ! ( status & 4 ) )
return 0 ;
udelay ( 10 ) ;
if ( need_resched ( ) )
cond_resched ( ) ;
}
return - 1 ;
}
static int
vpx3220_fp_write ( struct i2c_client * client ,
u8 fpaddr ,
u16 data )
{
/* Write the 16-bit address to the FPWR register */
if ( i2c_smbus_write_word_data ( client , 0x27 , swab16 ( fpaddr ) ) = = - 1 ) {
dprintk ( 1 , VPX3220_DEBUG " %s: failed \n " , __func__ ) ;
return - 1 ;
}
if ( vpx3220_fp_status ( client ) < 0 )
return - 1 ;
/* Write the 16-bit data to the FPDAT register */
if ( i2c_smbus_write_word_data ( client , 0x28 , swab16 ( data ) ) = = - 1 ) {
dprintk ( 1 , VPX3220_DEBUG " %s: failed \n " , __func__ ) ;
return - 1 ;
}
return 0 ;
}
static u16
vpx3220_fp_read ( struct i2c_client * client ,
u16 fpaddr )
{
s16 data ;
/* Write the 16-bit address to the FPRD register */
if ( i2c_smbus_write_word_data ( client , 0x26 , swab16 ( fpaddr ) ) = = - 1 ) {
dprintk ( 1 , VPX3220_DEBUG " %s: failed \n " , __func__ ) ;
return - 1 ;
}
if ( vpx3220_fp_status ( client ) < 0 )
return - 1 ;
/* Read the 16-bit data from the FPDAT register */
data = i2c_smbus_read_word_data ( client , 0x28 ) ;
if ( data = = - 1 ) {
dprintk ( 1 , VPX3220_DEBUG " %s: failed \n " , __func__ ) ;
return - 1 ;
}
return swab16 ( data ) ;
}
static int
vpx3220_write_block ( struct i2c_client * client ,
const u8 * data ,
unsigned int len )
{
u8 reg ;
int ret = - 1 ;
while ( len > = 2 ) {
reg = * data + + ;
if ( ( ret =
vpx3220_write ( client , reg , * data + + ) ) < 0 )
break ;
len - = 2 ;
}
return ret ;
}
static int
vpx3220_write_fp_block ( struct i2c_client * client ,
const u16 * data ,
unsigned int len )
{
u8 reg ;
int ret = 0 ;
while ( len > 1 ) {
reg = * data + + ;
ret | = vpx3220_fp_write ( client , reg , * data + + ) ;
len - = 2 ;
}
return ret ;
}
/* ---------------------------------------------------------------------- */
static const unsigned short init_ntsc [ ] = {
0x1c , 0x00 , /* NTSC tint angle */
0x88 , 17 , /* Window 1 vertical */
0x89 , 240 , /* Vertical lines in */
0x8a , 240 , /* Vertical lines out */
0x8b , 000 , /* Horizontal begin */
0x8c , 640 , /* Horizontal length */
0x8d , 640 , /* Number of pixels */
0x8f , 0xc00 , /* Disable window 2 */
2005-10-17 07:29:24 +04:00
0xf0 , 0x73 , /* 13.5 MHz transport, Forced
2005-04-17 02:20:36 +04:00
* mode , latch windows */
0xf2 , 0x13 , /* NTSC M, composite input */
0xe7 , 0x1e1 , /* Enable vertical standard
* locking @ 240 lines */
} ;
static const unsigned short init_pal [ ] = {
0x88 , 23 , /* Window 1 vertical begin */
2005-10-17 07:29:24 +04:00
0x89 , 288 , /* Vertical lines in (16 lines
2005-04-17 02:20:36 +04:00
* skipped by the VFE ) */
2005-10-17 07:29:24 +04:00
0x8a , 288 , /* Vertical lines out (16 lines
2005-04-17 02:20:36 +04:00
* skipped by the VFE ) */
0x8b , 16 , /* Horizontal begin */
0x8c , 768 , /* Horizontal length */
0x8d , 784 , /* Number of pixels
* Must be > = Horizontal begin + Horizontal length */
0x8f , 0xc00 , /* Disable window 2 */
2005-10-17 07:29:24 +04:00
0xf0 , 0x77 , /* 13.5 MHz transport, Forced
2005-04-17 02:20:36 +04:00
* mode , latch windows */
0xf2 , 0x3d1 , /* PAL B,G,H,I, composite input */
2005-10-17 07:29:24 +04:00
0xe7 , 0x241 , /* PAL/SECAM set to 288 lines */
2005-04-17 02:20:36 +04:00
} ;
static const unsigned short init_secam [ ] = {
2005-10-17 07:29:24 +04:00
0x88 , 23 , /* Window 1 vertical begin */
0x89 , 288 , /* Vertical lines in (16 lines
2005-04-17 02:20:36 +04:00
* skipped by the VFE ) */
2005-10-17 07:29:24 +04:00
0x8a , 288 , /* Vertical lines out (16 lines
2005-04-17 02:20:36 +04:00
* skipped by the VFE ) */
0x8b , 16 , /* Horizontal begin */
0x8c , 768 , /* Horizontal length */
0x8d , 784 , /* Number of pixels
* Must be > = Horizontal begin + Horizontal length */
0x8f , 0xc00 , /* Disable window 2 */
2005-10-17 07:29:24 +04:00
0xf0 , 0x77 , /* 13.5 MHz transport, Forced
2005-04-17 02:20:36 +04:00
* mode , latch windows */
0xf2 , 0x3d5 , /* SECAM, composite input */
2005-10-17 07:29:24 +04:00
0xe7 , 0x241 , /* PAL/SECAM set to 288 lines */
2005-04-17 02:20:36 +04:00
} ;
static const unsigned char init_common [ ] = {
0xf2 , 0x00 , /* Disable all outputs */
0x33 , 0x0d , /* Luma : VIN2, Chroma : CIN
* ( clamp off ) */
0xd8 , 0xa8 , /* HREF/VREF active high, VREF
* pulse = 2 , Odd / Even flag */
0x20 , 0x03 , /* IF compensation 0dB/oct */
0xe0 , 0xff , /* Open up all comparators */
0xe1 , 0x00 ,
0xe2 , 0x7f ,
0xe3 , 0x80 ,
0xe4 , 0x7f ,
0xe5 , 0x80 ,
0xe6 , 0x00 , /* Brightness set to 0 */
0xe7 , 0xe0 , /* Contrast to 1.0, noise shaping
* 10 to 8 2 - bit error diffusion */
0xe8 , 0xf8 , /* YUV422, CbCr binary offset,
* . . . ( p .32 ) */
0xea , 0x18 , /* LLC2 connected, output FIFO
* reset with VACTintern */
0xf0 , 0x8a , /* Half full level to 10, bus
* shuffler [ 7 : 0 , 23 : 16 , 15 : 8 ] */
0xf1 , 0x18 , /* Single clock, sync mode, no
* FE delay , no HLEN counter */
0xf8 , 0x12 , /* Port A, PIXCLK, HF# & FE#
* strength to 2 */
0xf9 , 0x24 , /* Port B, HREF, VREF, PREF &
* ALPHA strength to 4 */
} ;
static const unsigned short init_fp [ ] = {
0x59 , 0 ,
0xa0 , 2070 , /* ACC reference */
0xa3 , 0 ,
0xa4 , 0 ,
0xa8 , 30 ,
0xb2 , 768 ,
0xbe , 27 ,
0x58 , 0 ,
0x26 , 0 ,
0x4b , 0x298 , /* PLL gain */
} ;
static void
vpx3220_dump_i2c ( struct i2c_client * client )
{
int len = sizeof ( init_common ) ;
const unsigned char * data = init_common ;
while ( len > 1 ) {
dprintk ( 1 ,
KERN_DEBUG " vpx3216b i2c reg 0x%02x data 0x%02x \n " ,
* data , vpx3220_read ( client , * data ) ) ;
data + = 2 ;
len - = 2 ;
}
}
static int
vpx3220_command ( struct i2c_client * client ,
unsigned int cmd ,
void * arg )
{
struct vpx3220 * decoder = i2c_get_clientdata ( client ) ;
switch ( cmd ) {
case 0 :
{
vpx3220_write_block ( client , init_common ,
sizeof ( init_common ) ) ;
vpx3220_write_fp_block ( client , init_fp ,
sizeof ( init_fp ) > > 1 ) ;
switch ( decoder - > norm ) {
case VIDEO_MODE_NTSC :
vpx3220_write_fp_block ( client , init_ntsc ,
sizeof ( init_ntsc ) > > 1 ) ;
break ;
case VIDEO_MODE_PAL :
vpx3220_write_fp_block ( client , init_pal ,
sizeof ( init_pal ) > > 1 ) ;
break ;
case VIDEO_MODE_SECAM :
vpx3220_write_fp_block ( client , init_secam ,
sizeof ( init_secam ) > > 1 ) ;
break ;
default :
vpx3220_write_fp_block ( client , init_pal ,
sizeof ( init_pal ) > > 1 ) ;
break ;
}
}
break ;
case DECODER_DUMP :
{
vpx3220_dump_i2c ( client ) ;
}
break ;
case DECODER_GET_CAPABILITIES :
{
struct video_decoder_capability * cap = arg ;
dprintk ( 1 , KERN_DEBUG " %s: DECODER_GET_CAPABILITIES \n " ,
I2C_NAME ( client ) ) ;
cap - > flags = VIDEO_DECODER_PAL |
VIDEO_DECODER_NTSC |
VIDEO_DECODER_SECAM |
VIDEO_DECODER_AUTO |
VIDEO_DECODER_CCIR ;
cap - > inputs = 3 ;
cap - > outputs = 1 ;
}
break ;
case DECODER_GET_STATUS :
{
int res = 0 , status ;
dprintk ( 1 , KERN_INFO " %s: DECODER_GET_STATUS \n " ,
I2C_NAME ( client ) ) ;
status = vpx3220_fp_read ( client , 0x0f3 ) ;
dprintk ( 1 , KERN_INFO " %s: status: 0x%04x \n " , I2C_NAME ( client ) ,
status ) ;
if ( status < 0 )
return status ;
if ( ( status & 0x20 ) = = 0 ) {
res | = DECODER_STATUS_GOOD | DECODER_STATUS_COLOR ;
switch ( status & 0x18 ) {
case 0x00 :
case 0x10 :
case 0x14 :
case 0x18 :
res | = DECODER_STATUS_PAL ;
break ;
case 0x08 :
res | = DECODER_STATUS_SECAM ;
break ;
case 0x04 :
case 0x0c :
case 0x1c :
res | = DECODER_STATUS_NTSC ;
break ;
}
}
* ( int * ) arg = res ;
}
break ;
case DECODER_SET_NORM :
{
int * iarg = arg , data ;
2005-10-17 07:29:25 +04:00
int temp_input ;
/* Here we back up the input selection because it gets
overwritten when we fill the registers with the
choosen video norm */
temp_input = vpx3220_fp_read ( client , 0xf2 ) ;
2005-04-17 02:20:36 +04:00
dprintk ( 1 , KERN_DEBUG " %s: DECODER_SET_NORM %d \n " ,
I2C_NAME ( client ) , * iarg ) ;
switch ( * iarg ) {
case VIDEO_MODE_NTSC :
vpx3220_write_fp_block ( client , init_ntsc ,
sizeof ( init_ntsc ) > > 1 ) ;
dprintk ( 1 , KERN_INFO " %s: norm switched to NTSC \n " ,
I2C_NAME ( client ) ) ;
break ;
case VIDEO_MODE_PAL :
vpx3220_write_fp_block ( client , init_pal ,
sizeof ( init_pal ) > > 1 ) ;
dprintk ( 1 , KERN_INFO " %s: norm switched to PAL \n " ,
I2C_NAME ( client ) ) ;
break ;
case VIDEO_MODE_SECAM :
vpx3220_write_fp_block ( client , init_secam ,
sizeof ( init_secam ) > > 1 ) ;
dprintk ( 1 , KERN_INFO " %s: norm switched to SECAM \n " ,
I2C_NAME ( client ) ) ;
break ;
case VIDEO_MODE_AUTO :
/* FIXME This is only preliminary support */
data = vpx3220_fp_read ( client , 0xf2 ) & 0x20 ;
vpx3220_fp_write ( client , 0xf2 , 0x00c0 | data ) ;
dprintk ( 1 , KERN_INFO " %s: norm switched to Auto \n " ,
I2C_NAME ( client ) ) ;
break ;
default :
return - EINVAL ;
}
decoder - > norm = * iarg ;
2005-10-17 07:29:25 +04:00
/* And here we set the backed up video input again */
vpx3220_fp_write ( client , 0xf2 , temp_input | 0x0010 ) ;
udelay ( 10 ) ;
2005-04-17 02:20:36 +04:00
}
break ;
case DECODER_SET_INPUT :
{
int * iarg = arg , data ;
/* RJ: *iarg = 0: ST8 (PCTV) input
* iarg = 1 : COMPOSITE input
* iarg = 2 : SVHS input */
const int input [ 3 ] [ 2 ] = {
{ 0x0c , 0 } ,
{ 0x0d , 0 } ,
{ 0x0e , 1 }
} ;
if ( * iarg < 0 | | * iarg > 2 )
return - EINVAL ;
dprintk ( 1 , KERN_INFO " %s: input switched to %s \n " ,
I2C_NAME ( client ) , inputs [ * iarg ] ) ;
vpx3220_write ( client , 0x33 , input [ * iarg ] [ 0 ] ) ;
data = vpx3220_fp_read ( client , 0xf2 ) & ~ ( 0x0020 ) ;
if ( data < 0 )
return data ;
/* 0x0010 is required to latch the setting */
vpx3220_fp_write ( client , 0xf2 ,
data | ( input [ * iarg ] [ 1 ] < < 5 ) | 0x0010 ) ;
udelay ( 10 ) ;
}
break ;
case DECODER_SET_OUTPUT :
{
int * iarg = arg ;
/* not much choice of outputs */
if ( * iarg ! = 0 ) {
return - EINVAL ;
}
}
break ;
case DECODER_ENABLE_OUTPUT :
{
int * iarg = arg ;
dprintk ( 1 , KERN_DEBUG " %s: DECODER_ENABLE_OUTPUT %d \n " ,
I2C_NAME ( client ) , * iarg ) ;
vpx3220_write ( client , 0xf2 , ( * iarg ? 0x1b : 0x00 ) ) ;
}
break ;
case DECODER_SET_PICTURE :
{
struct video_picture * pic = arg ;
if ( decoder - > bright ! = pic - > brightness ) {
/* We want -128 to 128 we get 0-65535 */
decoder - > bright = pic - > brightness ;
vpx3220_write ( client , 0xe6 ,
( decoder - > bright - 32768 ) > > 8 ) ;
}
if ( decoder - > contrast ! = pic - > contrast ) {
/* We want 0 to 64 we get 0-65535 */
/* Bit 7 and 8 is for noise shaping */
decoder - > contrast = pic - > contrast ;
vpx3220_write ( client , 0xe7 ,
( decoder - > contrast > > 10 ) + 192 ) ;
}
if ( decoder - > sat ! = pic - > colour ) {
/* We want 0 to 4096 we get 0-65535 */
decoder - > sat = pic - > colour ;
vpx3220_fp_write ( client , 0xa0 ,
decoder - > sat > > 4 ) ;
}
if ( decoder - > hue ! = pic - > hue ) {
/* We want -512 to 512 we get 0-65535 */
decoder - > hue = pic - > hue ;
vpx3220_fp_write ( client , 0x1c ,
( ( decoder - > hue - 32768 ) > > 6 ) & 0xFFF ) ;
}
}
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static int
vpx3220_init_client ( struct i2c_client * client )
{
vpx3220_write_block ( client , init_common , sizeof ( init_common ) ) ;
vpx3220_write_fp_block ( client , init_fp , sizeof ( init_fp ) > > 1 ) ;
/* Default to PAL */
vpx3220_write_fp_block ( client , init_pal , sizeof ( init_pal ) > > 1 ) ;
return 0 ;
}
/* -----------------------------------------------------------------------
* Client managment code
*/
/*
* Generic i2c probe
* concerning the addresses : i2c wants 7 bit ( without the r / w bit ) , so ' > > 1 '
*/
static unsigned short normal_i2c [ ] =
{ I2C_VPX3220 > > 1 , ( I2C_VPX3220 > > 1 ) + 4 ,
I2C_CLIENT_END
} ;
2005-04-02 22:04:41 +04:00
static unsigned short ignore = I2C_CLIENT_END ;
2005-04-17 02:20:36 +04:00
static struct i2c_client_address_data addr_data = {
. normal_i2c = normal_i2c ,
2005-04-02 22:04:41 +04:00
. probe = & ignore ,
. ignore = & ignore ,
2005-04-17 02:20:36 +04:00
} ;
static struct i2c_driver vpx3220_i2c_driver ;
static int
vpx3220_detach_client ( struct i2c_client * client )
{
struct vpx3220 * decoder = i2c_get_clientdata ( client ) ;
int err ;
err = i2c_detach_client ( client ) ;
if ( err ) {
return err ;
}
kfree ( decoder ) ;
kfree ( client ) ;
return 0 ;
}
static int
vpx3220_detect_client ( struct i2c_adapter * adapter ,
int address ,
int kind )
{
int err ;
struct i2c_client * client ;
struct vpx3220 * decoder ;
dprintk ( 1 , VPX3220_DEBUG " %s \n " , __func__ ) ;
/* Check if the adapter supports the needed features */
if ( ! i2c_check_functionality
( adapter , I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA ) )
return 0 ;
client = kmalloc ( sizeof ( struct i2c_client ) , GFP_KERNEL ) ;
if ( client = = NULL ) {
return - ENOMEM ;
}
memset ( client , 0 , sizeof ( struct i2c_client ) ) ;
client - > addr = address ;
client - > adapter = adapter ;
client - > driver = & vpx3220_i2c_driver ;
client - > flags = I2C_CLIENT_ALLOW_USE ;
/* Check for manufacture ID and part number */
if ( kind < 0 ) {
u8 id ;
u16 pn ;
id = vpx3220_read ( client , 0x00 ) ;
if ( id ! = 0xec ) {
dprintk ( 1 ,
KERN_INFO
" vpx3220_attach: Wrong manufacturer ID (0x%02x) \n " ,
id ) ;
kfree ( client ) ;
return 0 ;
}
pn = ( vpx3220_read ( client , 0x02 ) < < 8 ) +
vpx3220_read ( client , 0x01 ) ;
switch ( pn ) {
case 0x4680 :
strlcpy ( I2C_NAME ( client ) , " vpx3220a " ,
sizeof ( I2C_NAME ( client ) ) ) ;
break ;
case 0x4260 :
strlcpy ( I2C_NAME ( client ) , " vpx3216b " ,
sizeof ( I2C_NAME ( client ) ) ) ;
break ;
case 0x4280 :
strlcpy ( I2C_NAME ( client ) , " vpx3214c " ,
sizeof ( I2C_NAME ( client ) ) ) ;
break ;
default :
dprintk ( 1 ,
KERN_INFO
" %s: Wrong part number (0x%04x) \n " ,
__func__ , pn ) ;
kfree ( client ) ;
return 0 ;
}
} else {
strlcpy ( I2C_NAME ( client ) , " forced vpx32xx " ,
sizeof ( I2C_NAME ( client ) ) ) ;
}
decoder = kmalloc ( sizeof ( struct vpx3220 ) , GFP_KERNEL ) ;
if ( decoder = = NULL ) {
kfree ( client ) ;
return - ENOMEM ;
}
memset ( decoder , 0 , sizeof ( struct vpx3220 ) ) ;
decoder - > norm = VIDEO_MODE_PAL ;
decoder - > input = 0 ;
decoder - > enable = 1 ;
decoder - > bright = 32768 ;
decoder - > contrast = 32768 ;
decoder - > hue = 32768 ;
decoder - > sat = 32768 ;
i2c_set_clientdata ( client , decoder ) ;
err = i2c_attach_client ( client ) ;
if ( err ) {
kfree ( client ) ;
kfree ( decoder ) ;
return err ;
}
dprintk ( 1 , KERN_INFO " %s: vpx32xx client found at address 0x%02x \n " ,
I2C_NAME ( client ) , client - > addr < < 1 ) ;
vpx3220_init_client ( client ) ;
return 0 ;
}
static int
vpx3220_attach_adapter ( struct i2c_adapter * adapter )
{
int ret ;
ret = i2c_probe ( adapter , & addr_data , & vpx3220_detect_client ) ;
dprintk ( 1 , VPX3220_DEBUG " %s: i2c_probe returned %d \n " ,
__func__ , ret ) ;
return ret ;
}
/* -----------------------------------------------------------------------
* Driver initialization and cleanup code
*/
static struct i2c_driver vpx3220_i2c_driver = {
. owner = THIS_MODULE ,
. name = " vpx3220 " ,
. id = I2C_DRIVERID_VPX3220 ,
. flags = I2C_DF_NOTIFY ,
. attach_adapter = vpx3220_attach_adapter ,
. detach_client = vpx3220_detach_client ,
. command = vpx3220_command ,
} ;
static int __init
vpx3220_init ( void )
{
return i2c_add_driver ( & vpx3220_i2c_driver ) ;
}
static void __exit
vpx3220_cleanup ( void )
{
i2c_del_driver ( & vpx3220_i2c_driver ) ;
}
module_init ( vpx3220_init ) ;
module_exit ( vpx3220_cleanup ) ;
MODULE_DESCRIPTION ( " vpx3220a/vpx3216b/vpx3214c video encoder driver " ) ;
MODULE_AUTHOR ( " Laurent Pinchart " ) ;
MODULE_LICENSE ( " GPL " ) ;