2005-09-10 00:04:03 +04:00
/*
Driver for SAA6588 RDS decoder
( c ) 2005 Hans J . Koch
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/kernel.h>
# include <linux/i2c.h>
# include <linux/types.h>
2009-02-19 23:31:17 +03:00
# include <linux/videodev2.h>
2005-09-10 00:04:03 +04:00
# include <linux/init.h>
# include <linux/errno.h>
# include <linux/slab.h>
# include <linux/poll.h>
# include <linux/wait.h>
# include <asm/uaccess.h>
2006-03-24 03:45:24 +03:00
# include <media/rds.h>
2009-02-12 01:23:57 +03:00
# include <media/v4l2-device.h>
2009-02-12 01:28:30 +03:00
# include <media/v4l2-chip-ident.h>
2009-03-29 13:29:30 +04:00
# include <media/v4l2-i2c-drv.h>
2005-09-10 00:04:03 +04:00
/* insmod options */
2008-04-22 21:41:48 +04:00
static unsigned int debug ;
static unsigned int xtal ;
2009-06-20 13:09:14 +04:00
static unsigned int mmbs ;
2008-04-22 21:41:48 +04:00
static unsigned int plvl ;
2005-09-10 00:04:03 +04:00
static unsigned int bufblocks = 100 ;
2006-01-13 19:10:23 +03:00
module_param ( debug , int , 0644 ) ;
2005-09-10 00:04:03 +04:00
MODULE_PARM_DESC ( debug , " enable debug messages " ) ;
2006-01-13 19:10:23 +03:00
module_param ( xtal , int , 0 ) ;
2005-09-10 00:04:03 +04:00
MODULE_PARM_DESC ( xtal , " select oscillator frequency (0..3), default 0 " ) ;
2009-06-20 13:09:14 +04:00
module_param ( mmbs , int , 0 ) ;
MODULE_PARM_DESC ( mmbs , " enable MMBS mode: 0=off (default), 1=on " ) ;
2006-01-13 19:10:23 +03:00
module_param ( plvl , int , 0 ) ;
2005-09-10 00:04:03 +04:00
MODULE_PARM_DESC ( plvl , " select pause level (0..3), default 0 " ) ;
2006-01-13 19:10:23 +03:00
module_param ( bufblocks , int , 0 ) ;
2005-09-10 00:04:03 +04:00
MODULE_PARM_DESC ( bufblocks , " number of buffered blocks, default 100 " ) ;
MODULE_DESCRIPTION ( " v4l2 driver module for SAA6588 RDS decoder " ) ;
MODULE_AUTHOR ( " Hans J. Koch <koch@hjk-az.de> " ) ;
MODULE_LICENSE ( " GPL " ) ;
/* ---------------------------------------------------------------------- */
# define UNSET (-1U)
# define PREFIX "saa6588: "
# define dprintk if (debug) printk
struct saa6588 {
2009-02-12 01:23:57 +03:00
struct v4l2_subdev sd ;
2009-03-07 13:44:12 +03:00
struct delayed_work work ;
2005-09-10 00:04:03 +04:00
spinlock_t lock ;
unsigned char * buffer ;
unsigned int buf_size ;
unsigned int rd_index ;
unsigned int wr_index ;
unsigned int block_count ;
unsigned char last_blocknum ;
wait_queue_head_t read_queue ;
int data_available_for_read ;
2009-06-20 13:09:14 +04:00
u8 sync ;
2005-09-10 00:04:03 +04:00
} ;
2009-02-12 01:23:57 +03:00
static inline struct saa6588 * to_saa6588 ( struct v4l2_subdev * sd )
{
return container_of ( sd , struct saa6588 , sd ) ;
}
2005-09-10 00:04:03 +04:00
/* ---------------------------------------------------------------------- */
/*
* SAA6588 defines
*/
/* Initialization and mode control byte (0w) */
/* bit 0+1 (DAC0/DAC1) */
# define cModeStandard 0x00
# define cModeFastPI 0x01
# define cModeReducedRequest 0x02
# define cModeInvalid 0x03
/* bit 2 (RBDS) */
# define cProcessingModeRDS 0x00
# define cProcessingModeRBDS 0x04
/* bit 3+4 (SYM0/SYM1) */
# define cErrCorrectionNone 0x00
# define cErrCorrection2Bits 0x08
# define cErrCorrection5Bits 0x10
# define cErrCorrectionNoneRBDS 0x18
/* bit 5 (NWSY) */
# define cSyncNormal 0x00
# define cSyncRestart 0x20
/* bit 6 (TSQD) */
# define cSigQualityDetectOFF 0x00
# define cSigQualityDetectON 0x40
/* bit 7 (SQCM) */
# define cSigQualityTriggered 0x00
# define cSigQualityContinous 0x80
/* Pause level and flywheel control byte (1w) */
/* bits 0..5 (FEB0..FEB5) */
# define cFlywheelMaxBlocksMask 0x3F
# define cFlywheelDefault 0x20
/* bits 6+7 (PL0/PL1) */
# define cPauseLevel_11mV 0x00
# define cPauseLevel_17mV 0x40
# define cPauseLevel_27mV 0x80
# define cPauseLevel_43mV 0xC0
/* Pause time/oscillator frequency/quality detector control byte (1w) */
/* bits 0..4 (SQS0..SQS4) */
# define cQualityDetectSensMask 0x1F
# define cQualityDetectDefault 0x0F
/* bit 5 (SOSC) */
# define cSelectOscFreqOFF 0x00
# define cSelectOscFreqON 0x20
/* bit 6+7 (PTF0/PTF1) */
# define cOscFreq_4332kHz 0x00
# define cOscFreq_8664kHz 0x40
# define cOscFreq_12996kHz 0x80
# define cOscFreq_17328kHz 0xC0
/* ---------------------------------------------------------------------- */
2005-09-29 03:37:34 +04:00
static int block_to_user_buf ( struct saa6588 * s , unsigned char __user * user_buf )
2005-09-10 00:04:03 +04:00
{
int i ;
if ( s - > rd_index = = s - > wr_index ) {
if ( debug > 2 )
dprintk ( PREFIX " Read: buffer empty. \n " ) ;
return 0 ;
}
if ( debug > 2 ) {
dprintk ( PREFIX " Read: " ) ;
for ( i = s - > rd_index ; i < s - > rd_index + 3 ; i + + )
dprintk ( " 0x%02x " , s - > buffer [ i ] ) ;
}
if ( copy_to_user ( user_buf , & s - > buffer [ s - > rd_index ] , 3 ) )
return - EFAULT ;
s - > rd_index + = 3 ;
if ( s - > rd_index > = s - > buf_size )
s - > rd_index = 0 ;
s - > block_count - - ;
if ( debug > 2 )
dprintk ( " %d blocks total. \n " , s - > block_count ) ;
return 1 ;
}
static void read_from_buf ( struct saa6588 * s , struct rds_command * a )
{
unsigned long flags ;
2005-09-29 03:37:34 +04:00
unsigned char __user * buf_ptr = a - > buffer ;
2005-09-10 00:04:03 +04:00
unsigned int i ;
unsigned int rd_blocks ;
a - > result = 0 ;
if ( ! a - > buffer )
return ;
while ( ! s - > data_available_for_read ) {
int ret = wait_event_interruptible ( s - > read_queue ,
s - > data_available_for_read ) ;
if ( ret = = - ERESTARTSYS ) {
a - > result = - EINTR ;
return ;
}
}
spin_lock_irqsave ( & s - > lock , flags ) ;
rd_blocks = a - > block_count ;
if ( rd_blocks > s - > block_count )
rd_blocks = s - > block_count ;
2006-11-20 13:20:48 +03:00
if ( ! rd_blocks ) {
spin_unlock_irqrestore ( & s - > lock , flags ) ;
2005-09-10 00:04:03 +04:00
return ;
2006-11-20 13:20:48 +03:00
}
2005-09-10 00:04:03 +04:00
for ( i = 0 ; i < rd_blocks ; i + + ) {
if ( block_to_user_buf ( s , buf_ptr ) ) {
buf_ptr + = 3 ;
a - > result + + ;
} else
break ;
}
a - > result * = 3 ;
s - > data_available_for_read = ( s - > block_count > 0 ) ;
spin_unlock_irqrestore ( & s - > lock , flags ) ;
}
static void block_to_buf ( struct saa6588 * s , unsigned char * blockbuf )
{
unsigned int i ;
if ( debug > 3 )
dprintk ( PREFIX " New block: " ) ;
for ( i = 0 ; i < 3 ; + + i ) {
if ( debug > 3 )
dprintk ( " 0x%02x " , blockbuf [ i ] ) ;
s - > buffer [ s - > wr_index ] = blockbuf [ i ] ;
s - > wr_index + + ;
}
if ( s - > wr_index > = s - > buf_size )
s - > wr_index = 0 ;
if ( s - > wr_index = = s - > rd_index ) {
2005-11-09 08:38:39 +03:00
s - > rd_index + = 3 ;
2005-09-10 00:04:03 +04:00
if ( s - > rd_index > = s - > buf_size )
s - > rd_index = 0 ;
} else
s - > block_count + + ;
if ( debug > 3 )
dprintk ( " %d blocks total. \n " , s - > block_count ) ;
}
static void saa6588_i2c_poll ( struct saa6588 * s )
{
2009-02-12 01:23:57 +03:00
struct i2c_client * client = v4l2_get_subdevdata ( & s - > sd ) ;
2005-09-10 00:04:03 +04:00
unsigned long flags ;
unsigned char tmpbuf [ 6 ] ;
unsigned char blocknum ;
unsigned char tmp ;
/* Although we only need 3 bytes, we have to read at least 6.
2009-06-20 13:09:14 +04:00
SAA6588 returns garbage otherwise . */
2009-02-12 01:23:57 +03:00
if ( 6 ! = i2c_master_recv ( client , & tmpbuf [ 0 ] , 6 ) ) {
2005-09-10 00:04:03 +04:00
if ( debug > 1 )
dprintk ( PREFIX " read error! \n " ) ;
return ;
}
2009-06-20 13:09:14 +04:00
s - > sync = tmpbuf [ 0 ] & 0x10 ;
if ( ! s - > sync )
return ;
2005-09-10 00:04:03 +04:00
blocknum = tmpbuf [ 0 ] > > 5 ;
if ( blocknum = = s - > last_blocknum ) {
if ( debug > 3 )
dprintk ( " Saw block %d again. \n " , blocknum ) ;
return ;
}
s - > last_blocknum = blocknum ;
/*
Byte order according to v4l2 specification :
Byte 0 : Least Significant Byte of RDS Block
Byte 1 : Most Significant Byte of RDS Block
Byte 2 Bit 7 : Error bit . Indicates that an uncorrectable error
occurred during reception of this block .
Bit 6 : Corrected bit . Indicates that an error was
corrected for this data block .
2009-06-20 13:09:14 +04:00
Bits 5 - 3 : Same as bits 0 - 2.
Bits 2 - 0 : Block number .
2005-09-10 00:04:03 +04:00
SAA6588 byte order is Status - MSB - LSB , so we have to swap the
first and the last of the 3 bytes block .
*/
tmp = tmpbuf [ 2 ] ;
tmpbuf [ 2 ] = tmpbuf [ 0 ] ;
tmpbuf [ 0 ] = tmp ;
2009-06-20 13:09:14 +04:00
/* Map 'Invalid block E' to 'Invalid Block' */
if ( blocknum = = 6 )
blocknum = V4L2_RDS_BLOCK_INVALID ;
/* And if are not in mmbs mode, then 'Block E' is also mapped
to ' Invalid Block ' . As far as I can tell MMBS is discontinued ,
and if there is ever a need to support E blocks , then please
contact the linux - media mailinglist . */
else if ( ! mmbs & & blocknum = = 5 )
blocknum = V4L2_RDS_BLOCK_INVALID ;
2005-09-10 00:04:03 +04:00
tmp = blocknum ;
tmp | = blocknum < < 3 ; /* Received offset == Offset Name (OK ?) */
if ( ( tmpbuf [ 2 ] & 0x03 ) = = 0x03 )
2009-06-20 13:09:14 +04:00
tmp | = V4L2_RDS_BLOCK_ERROR ; /* uncorrectable error */
2005-09-10 00:04:03 +04:00
else if ( ( tmpbuf [ 2 ] & 0x03 ) ! = 0x00 )
2009-06-20 13:09:14 +04:00
tmp | = V4L2_RDS_BLOCK_CORRECTED ; /* corrected error */
2005-09-10 00:04:03 +04:00
tmpbuf [ 2 ] = tmp ; /* Is this enough ? Should we also check other bits ? */
spin_lock_irqsave ( & s - > lock , flags ) ;
block_to_buf ( s , tmpbuf ) ;
spin_unlock_irqrestore ( & s - > lock , flags ) ;
s - > data_available_for_read = 1 ;
wake_up_interruptible ( & s - > read_queue ) ;
}
2006-11-22 17:57:56 +03:00
static void saa6588_work ( struct work_struct * work )
2005-09-10 00:04:03 +04:00
{
2009-03-07 13:44:12 +03:00
struct saa6588 * s = container_of ( work , struct saa6588 , work . work ) ;
2005-09-10 00:04:03 +04:00
saa6588_i2c_poll ( s ) ;
2009-03-07 13:44:12 +03:00
schedule_delayed_work ( & s - > work , msecs_to_jiffies ( 20 ) ) ;
2005-09-10 00:04:03 +04:00
}
2009-06-20 13:09:14 +04:00
static void saa6588_configure ( struct saa6588 * s )
2005-09-10 00:04:03 +04:00
{
2009-02-12 01:23:57 +03:00
struct i2c_client * client = v4l2_get_subdevdata ( & s - > sd ) ;
2005-09-10 00:04:03 +04:00
unsigned char buf [ 3 ] ;
int rc ;
buf [ 0 ] = cSyncRestart ;
2009-06-20 13:09:14 +04:00
if ( mmbs )
2005-09-10 00:04:03 +04:00
buf [ 0 ] | = cProcessingModeRBDS ;
buf [ 1 ] = cFlywheelDefault ;
switch ( plvl ) {
case 0 :
buf [ 1 ] | = cPauseLevel_11mV ;
break ;
case 1 :
buf [ 1 ] | = cPauseLevel_17mV ;
break ;
case 2 :
buf [ 1 ] | = cPauseLevel_27mV ;
break ;
case 3 :
buf [ 1 ] | = cPauseLevel_43mV ;
break ;
default : /* nothing */
break ;
}
buf [ 2 ] = cQualityDetectDefault | cSelectOscFreqON ;
switch ( xtal ) {
case 0 :
buf [ 2 ] | = cOscFreq_4332kHz ;
break ;
case 1 :
buf [ 2 ] | = cOscFreq_8664kHz ;
break ;
case 2 :
buf [ 2 ] | = cOscFreq_12996kHz ;
break ;
case 3 :
buf [ 2 ] | = cOscFreq_17328kHz ;
break ;
default : /* nothing */
break ;
}
dprintk ( PREFIX " writing: 0w=0x%02x 1w=0x%02x 2w=0x%02x \n " ,
buf [ 0 ] , buf [ 1 ] , buf [ 2 ] ) ;
2009-02-12 01:23:57 +03:00
rc = i2c_master_send ( client , buf , 3 ) ;
2009-02-12 01:14:09 +03:00
if ( rc ! = 3 )
2005-09-10 00:04:03 +04:00
printk ( PREFIX " i2c i/o error: rc == %d (should be 3) \n " , rc ) ;
}
/* ---------------------------------------------------------------------- */
2009-02-12 01:23:57 +03:00
static long saa6588_ioctl ( struct v4l2_subdev * sd , unsigned int cmd , void * arg )
{
struct saa6588 * s = to_saa6588 ( sd ) ;
struct rds_command * a = arg ;
switch ( cmd ) {
/* --- open() for /dev/radio --- */
case RDS_CMD_OPEN :
a - > result = 0 ; /* return error if chip doesn't work ??? */
break ;
/* --- close() for /dev/radio --- */
case RDS_CMD_CLOSE :
s - > data_available_for_read = 1 ;
wake_up_interruptible ( & s - > read_queue ) ;
a - > result = 0 ;
break ;
/* --- read() for /dev/radio --- */
case RDS_CMD_READ :
read_from_buf ( s , a ) ;
break ;
/* --- poll() for /dev/radio --- */
case RDS_CMD_POLL :
a - > result = 0 ;
if ( s - > data_available_for_read ) {
a - > result | = POLLIN | POLLRDNORM ;
}
poll_wait ( a - > instance , & s - > read_queue , a - > event_list ) ;
break ;
default :
/* nothing */
return - ENOIOCTLCMD ;
}
return 0 ;
}
2009-06-20 13:09:14 +04:00
static int saa6588_g_tuner ( struct v4l2_subdev * sd , struct v4l2_tuner * vt )
{
struct saa6588 * s = to_saa6588 ( sd ) ;
vt - > capability | = V4L2_TUNER_CAP_RDS ;
if ( s - > sync )
vt - > rxsubchans | = V4L2_TUNER_SUB_RDS ;
return 0 ;
}
static int saa6588_s_tuner ( struct v4l2_subdev * sd , struct v4l2_tuner * vt )
{
struct saa6588 * s = to_saa6588 ( sd ) ;
saa6588_configure ( s ) ;
return 0 ;
}
2009-02-12 01:28:30 +03:00
static int saa6588_g_chip_ident ( struct v4l2_subdev * sd , struct v4l2_dbg_chip_ident * chip )
{
struct i2c_client * client = v4l2_get_subdevdata ( sd ) ;
return v4l2_chip_ident_i2c_client ( client , chip , V4L2_IDENT_SAA6588 , 0 ) ;
}
2009-02-12 01:23:57 +03:00
/* ----------------------------------------------------------------------- */
static const struct v4l2_subdev_core_ops saa6588_core_ops = {
2009-02-12 01:28:30 +03:00
. g_chip_ident = saa6588_g_chip_ident ,
2009-02-12 01:23:57 +03:00
. ioctl = saa6588_ioctl ,
} ;
2009-06-20 13:09:14 +04:00
static const struct v4l2_subdev_tuner_ops saa6588_tuner_ops = {
. g_tuner = saa6588_g_tuner ,
. s_tuner = saa6588_s_tuner ,
} ;
2009-02-12 01:23:57 +03:00
static const struct v4l2_subdev_ops saa6588_ops = {
. core = & saa6588_core_ops ,
2009-06-20 13:09:14 +04:00
. tuner = & saa6588_tuner_ops ,
2009-02-12 01:23:57 +03:00
} ;
/* ---------------------------------------------------------------------- */
2009-02-12 01:14:09 +03:00
static int saa6588_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
2005-09-10 00:04:03 +04:00
{
struct saa6588 * s ;
2009-02-12 01:23:57 +03:00
struct v4l2_subdev * sd ;
2005-09-10 00:04:03 +04:00
2009-02-12 01:14:09 +03:00
v4l_info ( client , " saa6588 found @ 0x%x (%s) \n " ,
client - > addr < < 1 , client - > adapter - > name ) ;
2005-09-10 00:04:03 +04:00
2009-02-12 01:14:09 +03:00
s = kzalloc ( sizeof ( * s ) , GFP_KERNEL ) ;
if ( s = = NULL )
2005-09-10 00:04:03 +04:00
return - ENOMEM ;
s - > buf_size = bufblocks * 3 ;
2009-02-12 01:14:09 +03:00
s - > buffer = kmalloc ( s - > buf_size , GFP_KERNEL ) ;
if ( s - > buffer = = NULL ) {
2005-09-10 00:04:03 +04:00
kfree ( s ) ;
return - ENOMEM ;
}
2009-02-12 01:23:57 +03:00
sd = & s - > sd ;
v4l2_i2c_subdev_init ( sd , client , & saa6588_ops ) ;
2007-09-12 23:20:43 +04:00
spin_lock_init ( & s - > lock ) ;
2005-09-10 00:04:03 +04:00
s - > block_count = 0 ;
s - > wr_index = 0 ;
s - > rd_index = 0 ;
s - > last_blocknum = 0xff ;
init_waitqueue_head ( & s - > read_queue ) ;
s - > data_available_for_read = 0 ;
saa6588_configure ( s ) ;
/* start polling via eventd */
2009-03-07 13:44:12 +03:00
INIT_DELAYED_WORK ( & s - > work , saa6588_work ) ;
schedule_delayed_work ( & s - > work , 0 ) ;
2005-09-10 00:04:03 +04:00
return 0 ;
}
2009-02-12 01:14:09 +03:00
static int saa6588_remove ( struct i2c_client * client )
2005-09-10 00:04:03 +04:00
{
2009-02-12 01:23:57 +03:00
struct v4l2_subdev * sd = i2c_get_clientdata ( client ) ;
struct saa6588 * s = to_saa6588 ( sd ) ;
v4l2_device_unregister_subdev ( sd ) ;
2005-09-10 00:04:03 +04:00
2009-03-07 13:44:12 +03:00
cancel_delayed_work_sync ( & s - > work ) ;
2005-09-10 00:04:03 +04:00
kfree ( s - > buffer ) ;
kfree ( s ) ;
return 0 ;
}
/* ----------------------------------------------------------------------- */
2009-02-12 01:14:09 +03:00
static const struct i2c_device_id saa6588_id [ ] = {
{ " saa6588 " , 0 } ,
{ }
2005-09-10 00:04:03 +04:00
} ;
2009-02-12 01:14:09 +03:00
MODULE_DEVICE_TABLE ( i2c , saa6588_id ) ;
2005-09-10 00:04:03 +04:00
2009-02-12 01:14:09 +03:00
static struct v4l2_i2c_driver_data v4l2_i2c_data = {
2005-09-10 00:04:03 +04:00
. name = " saa6588 " ,
2009-02-12 01:14:09 +03:00
. probe = saa6588_probe ,
. remove = saa6588_remove ,
. id_table = saa6588_id ,
2005-09-10 00:04:03 +04:00
} ;