2009-08-09 14:22:20 -03:00
/*
* drivers / media / radio / si470x / radio - si470x - common . c
*
* Driver for radios with Silicon Labs Si470x FM Radio Receivers
*
* Copyright ( c ) 2009 Tobias Lorenz < tobias . lorenz @ gmx . net >
*
* 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 . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
/*
* History :
* 2008 - 01 - 12 Tobias Lorenz < tobias . lorenz @ gmx . net >
* Version 1.0 .0
* - First working version
* 2008 - 01 - 13 Tobias Lorenz < tobias . lorenz @ gmx . net >
* Version 1.0 .1
* - Improved error handling , every function now returns errno
* - Improved multi user access ( start / mute / stop )
* - Channel doesn ' t get lost anymore after start / mute / stop
* - RDS support added ( polling mode via interrupt EP 1 )
* - marked default module parameters with * value *
* - switched from bit structs to bit masks
* - header file cleaned and integrated
* 2008 - 01 - 14 Tobias Lorenz < tobias . lorenz @ gmx . net >
* Version 1.0 .2
* - hex values are now lower case
* - commented USB ID for ADS / Tech moved on todo list
* - blacklisted si470x in hid - quirks . c
* - rds buffer handling functions integrated into * _work , * _read
* - rds_command in si470x_poll exchanged against simple retval
* - check for firmware version 15
* - code order and prototypes still remain the same
* - spacing and bottom of band codes remain the same
* 2008 - 01 - 16 Tobias Lorenz < tobias . lorenz @ gmx . net >
* Version 1.0 .3
* - code reordered to avoid function prototypes
* - switch / case defaults are now more user - friendly
* - unified comment style
* - applied all checkpatch . pl v1 .12 suggestions
* except the warning about the too long lines with bit comments
* - renamed FMRADIO to RADIO to cut line length ( checkpatch . pl )
* 2008 - 01 - 22 Tobias Lorenz < tobias . lorenz @ gmx . net >
* Version 1.0 .4
* - avoid poss . locking when doing copy_to_user which may sleep
* - RDS is automatically activated on read now
* - code cleaned of unnecessary rds_commands
* - USB Vendor / Product ID for ADS / Tech FM Radio Receiver verified
* ( thanks to Guillaume RAMOUSSE )
* 2008 - 01 - 27 Tobias Lorenz < tobias . lorenz @ gmx . net >
* Version 1.0 .5
* - number of seek_retries changed to tune_timeout
* - fixed problem with incomplete tune operations by own buffers
* - optimization of variables and printf types
* - improved error logging
* 2008 - 01 - 31 Tobias Lorenz < tobias . lorenz @ gmx . net >
* Oliver Neukum < oliver @ neukum . org >
* Version 1.0 .6
* - fixed coverity checker warnings in * _usb_driver_disconnect
* - probe ( ) / open ( ) race by correct ordering in probe ( )
* - DMA coherency rules by separate allocation of all buffers
* - use of endianness macros
* - abuse of spinlock , replaced by mutex
* - racy handling of timer in disconnect ,
* replaced by delayed_work
* - racy interruptible_sleep_on ( ) ,
* replaced with wait_event_interruptible ( )
* - handle signals in read ( )
* 2008 - 02 - 08 Tobias Lorenz < tobias . lorenz @ gmx . net >
* Oliver Neukum < oliver @ neukum . org >
* Version 1.0 .7
* - usb autosuspend support
* - unplugging fixed
* 2008 - 05 - 07 Tobias Lorenz < tobias . lorenz @ gmx . net >
* Version 1.0 .8
* - hardware frequency seek support
* - afc indication
* - more safety checks , let si470x_get_freq return errno
* - vidioc behavior corrected according to v4l2 spec
* 2008 - 10 - 20 Alexey Klimov < klimov . linux @ gmail . com >
* - add support for KWorld USB FM Radio FM700
* - blacklisted KWorld radio in hid - core . c and hid - ids . h
* 2008 - 12 - 03 Mark Lord < mlord @ pobox . com >
* - add support for DealExtreme USB Radio
* 2009 - 01 - 31 Bob Ross < pigiron @ gmx . com >
* - correction of stereo detection / setting
* - correction of signal strength indicator scaling
* 2009 - 01 - 31 Rick Bronson < rick @ efn . org >
* Tobias Lorenz < tobias . lorenz @ gmx . net >
* - add LED status output
* - get HW / SW version from scratchpad
* 2009 - 06 - 16 Edouard Lafargue < edouard @ lafargue . name >
* Version 1.0 .10
* - add support for interrupt mode for RDS endpoint ,
* instead of polling .
* Improves RDS reception significantly
*/
/* kernel includes */
# include "radio-si470x.h"
/**************************************************************************
* Module Parameters
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Spacing (kHz) */
/* 0: 200 kHz (USA, Australia) */
/* 1: 100 kHz (Europe, Japan) */
/* 2: 50 kHz */
static unsigned short space = 2 ;
module_param ( space , ushort , 0444 ) ;
MODULE_PARM_DESC ( space , " Spacing: 0=200kHz 1=100kHz *2=50kHz* " ) ;
/* Bottom of Band (MHz) */
/* 0: 87.5 - 108 MHz (USA, Europe)*/
/* 1: 76 - 108 MHz (Japan wide band) */
/* 2: 76 - 90 MHz (Japan) */
static unsigned short band = 1 ;
module_param ( band , ushort , 0444 ) ;
MODULE_PARM_DESC ( band , " Band: 0=87.5..108MHz *1=76..108MHz* 2=76..90MHz " ) ;
/* De-emphasis */
/* 0: 75 us (USA) */
/* 1: 50 us (Europe, Australia, Japan) */
static unsigned short de = 1 ;
module_param ( de , ushort , 0444 ) ;
MODULE_PARM_DESC ( de , " De-emphasis: 0=75us *1=50us* " ) ;
/* Tune timeout */
static unsigned int tune_timeout = 3000 ;
module_param ( tune_timeout , uint , 0644 ) ;
MODULE_PARM_DESC ( tune_timeout , " Tune timeout: *3000* " ) ;
/* Seek timeout */
static unsigned int seek_timeout = 5000 ;
module_param ( seek_timeout , uint , 0644 ) ;
MODULE_PARM_DESC ( seek_timeout , " Seek timeout: *5000* " ) ;
/**************************************************************************
* Generic Functions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* si470x_set_chan - set the channel
*/
static int si470x_set_chan ( struct si470x_device * radio , unsigned short chan )
{
int retval ;
unsigned long timeout ;
bool timed_out = 0 ;
/* start tuning */
radio - > registers [ CHANNEL ] & = ~ CHANNEL_CHAN ;
radio - > registers [ CHANNEL ] | = CHANNEL_TUNE | chan ;
retval = si470x_set_register ( radio , CHANNEL ) ;
if ( retval < 0 )
goto done ;
/* wait till tune operation has completed */
timeout = jiffies + msecs_to_jiffies ( tune_timeout ) ;
do {
retval = si470x_get_register ( radio , STATUSRSSI ) ;
if ( retval < 0 )
goto stop ;
timed_out = time_after ( jiffies , timeout ) ;
} while ( ( ( radio - > registers [ STATUSRSSI ] & STATUSRSSI_STC ) = = 0 ) & &
( ! timed_out ) ) ;
if ( ( radio - > registers [ STATUSRSSI ] & STATUSRSSI_STC ) = = 0 )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev , " tune does not complete \n " ) ;
2009-08-09 14:22:20 -03:00
if ( timed_out )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev ,
" tune timed out after %u ms \n " , tune_timeout ) ;
2009-08-09 14:22:20 -03:00
stop :
/* stop tuning */
radio - > registers [ CHANNEL ] & = ~ CHANNEL_TUNE ;
retval = si470x_set_register ( radio , CHANNEL ) ;
done :
return retval ;
}
/*
* si470x_get_freq - get the frequency
*/
static int si470x_get_freq ( struct si470x_device * radio , unsigned int * freq )
{
unsigned int spacing , band_bottom ;
unsigned short chan ;
int retval ;
/* Spacing (kHz) */
switch ( ( radio - > registers [ SYSCONFIG2 ] & SYSCONFIG2_SPACE ) > > 4 ) {
/* 0: 200 kHz (USA, Australia) */
case 0 :
spacing = 0.200 * FREQ_MUL ; break ;
/* 1: 100 kHz (Europe, Japan) */
case 1 :
spacing = 0.100 * FREQ_MUL ; break ;
/* 2: 50 kHz */
default :
spacing = 0.050 * FREQ_MUL ; break ;
} ;
/* Bottom of Band (MHz) */
switch ( ( radio - > registers [ SYSCONFIG2 ] & SYSCONFIG2_BAND ) > > 6 ) {
/* 0: 87.5 - 108 MHz (USA, Europe) */
case 0 :
band_bottom = 87.5 * FREQ_MUL ; break ;
/* 1: 76 - 108 MHz (Japan wide band) */
default :
band_bottom = 76 * FREQ_MUL ; break ;
/* 2: 76 - 90 MHz (Japan) */
case 2 :
band_bottom = 76 * FREQ_MUL ; break ;
} ;
/* read channel */
retval = si470x_get_register ( radio , READCHAN ) ;
chan = radio - > registers [ READCHAN ] & READCHAN_READCHAN ;
/* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */
* freq = chan * spacing + band_bottom ;
return retval ;
}
/*
* si470x_set_freq - set the frequency
*/
int si470x_set_freq ( struct si470x_device * radio , unsigned int freq )
{
unsigned int spacing , band_bottom ;
unsigned short chan ;
/* Spacing (kHz) */
switch ( ( radio - > registers [ SYSCONFIG2 ] & SYSCONFIG2_SPACE ) > > 4 ) {
/* 0: 200 kHz (USA, Australia) */
case 0 :
spacing = 0.200 * FREQ_MUL ; break ;
/* 1: 100 kHz (Europe, Japan) */
case 1 :
spacing = 0.100 * FREQ_MUL ; break ;
/* 2: 50 kHz */
default :
spacing = 0.050 * FREQ_MUL ; break ;
} ;
/* Bottom of Band (MHz) */
switch ( ( radio - > registers [ SYSCONFIG2 ] & SYSCONFIG2_BAND ) > > 6 ) {
/* 0: 87.5 - 108 MHz (USA, Europe) */
case 0 :
band_bottom = 87.5 * FREQ_MUL ; break ;
/* 1: 76 - 108 MHz (Japan wide band) */
default :
band_bottom = 76 * FREQ_MUL ; break ;
/* 2: 76 - 90 MHz (Japan) */
case 2 :
band_bottom = 76 * FREQ_MUL ; break ;
} ;
/* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */
chan = ( freq - band_bottom ) / spacing ;
return si470x_set_chan ( radio , chan ) ;
}
/*
* si470x_set_seek - set seek
*/
static int si470x_set_seek ( struct si470x_device * radio ,
unsigned int wrap_around , unsigned int seek_upward )
{
int retval = 0 ;
unsigned long timeout ;
bool timed_out = 0 ;
/* start seeking */
radio - > registers [ POWERCFG ] | = POWERCFG_SEEK ;
if ( wrap_around = = 1 )
radio - > registers [ POWERCFG ] & = ~ POWERCFG_SKMODE ;
else
radio - > registers [ POWERCFG ] | = POWERCFG_SKMODE ;
if ( seek_upward = = 1 )
radio - > registers [ POWERCFG ] | = POWERCFG_SEEKUP ;
else
radio - > registers [ POWERCFG ] & = ~ POWERCFG_SEEKUP ;
retval = si470x_set_register ( radio , POWERCFG ) ;
if ( retval < 0 )
goto done ;
/* wait till seek operation has completed */
timeout = jiffies + msecs_to_jiffies ( seek_timeout ) ;
do {
retval = si470x_get_register ( radio , STATUSRSSI ) ;
if ( retval < 0 )
goto stop ;
timed_out = time_after ( jiffies , timeout ) ;
} while ( ( ( radio - > registers [ STATUSRSSI ] & STATUSRSSI_STC ) = = 0 ) & &
( ! timed_out ) ) ;
if ( ( radio - > registers [ STATUSRSSI ] & STATUSRSSI_STC ) = = 0 )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev , " seek does not complete \n " ) ;
2009-08-09 14:22:20 -03:00
if ( radio - > registers [ STATUSRSSI ] & STATUSRSSI_SF )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev ,
" seek failed / band limit reached \n " ) ;
2009-08-09 14:22:20 -03:00
if ( timed_out )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev ,
" seek timed out after %u ms \n " , seek_timeout ) ;
2009-08-09 14:22:20 -03:00
stop :
/* stop seeking */
radio - > registers [ POWERCFG ] & = ~ POWERCFG_SEEK ;
retval = si470x_set_register ( radio , POWERCFG ) ;
done :
/* try again, if timed out */
if ( ( retval = = 0 ) & & timed_out )
retval = - EAGAIN ;
return retval ;
}
/*
* si470x_start - switch on radio
*/
int si470x_start ( struct si470x_device * radio )
{
int retval ;
/* powercfg */
radio - > registers [ POWERCFG ] =
POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM ;
retval = si470x_set_register ( radio , POWERCFG ) ;
if ( retval < 0 )
goto done ;
/* sysconfig 1 */
radio - > registers [ SYSCONFIG1 ] = SYSCONFIG1_DE ;
retval = si470x_set_register ( radio , SYSCONFIG1 ) ;
if ( retval < 0 )
goto done ;
/* sysconfig 2 */
radio - > registers [ SYSCONFIG2 ] =
( 0x3f < < 8 ) | /* SEEKTH */
( ( band < < 6 ) & SYSCONFIG2_BAND ) | /* BAND */
( ( space < < 4 ) & SYSCONFIG2_SPACE ) | /* SPACE */
15 ; /* VOLUME (max) */
retval = si470x_set_register ( radio , SYSCONFIG2 ) ;
if ( retval < 0 )
goto done ;
/* reset last channel */
retval = si470x_set_chan ( radio ,
radio - > registers [ CHANNEL ] & CHANNEL_CHAN ) ;
done :
return retval ;
}
/*
* si470x_stop - switch off radio
*/
int si470x_stop ( struct si470x_device * radio )
{
int retval ;
/* sysconfig 1 */
radio - > registers [ SYSCONFIG1 ] & = ~ SYSCONFIG1_RDS ;
retval = si470x_set_register ( radio , SYSCONFIG1 ) ;
if ( retval < 0 )
goto done ;
/* powercfg */
radio - > registers [ POWERCFG ] & = ~ POWERCFG_DMUTE ;
/* POWERCFG_ENABLE has to automatically go low */
radio - > registers [ POWERCFG ] | = POWERCFG_ENABLE | POWERCFG_DISABLE ;
retval = si470x_set_register ( radio , POWERCFG ) ;
done :
return retval ;
}
/*
* si470x_rds_on - switch on rds reception
*/
int si470x_rds_on ( struct si470x_device * radio )
{
int retval ;
/* sysconfig 1 */
mutex_lock ( & radio - > lock ) ;
radio - > registers [ SYSCONFIG1 ] | = SYSCONFIG1_RDS ;
retval = si470x_set_register ( radio , SYSCONFIG1 ) ;
if ( retval < 0 )
radio - > registers [ SYSCONFIG1 ] & = ~ SYSCONFIG1_RDS ;
mutex_unlock ( & radio - > lock ) ;
return retval ;
}
2009-12-10 16:49:34 -03:00
/**************************************************************************
* File Operations Interface
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* si470x_fops_read - read RDS data
*/
static ssize_t si470x_fops_read ( struct file * file , char __user * buf ,
size_t count , loff_t * ppos )
{
struct si470x_device * radio = video_drvdata ( file ) ;
int retval = 0 ;
unsigned int block_count = 0 ;
/* switch on rds reception */
if ( ( radio - > registers [ SYSCONFIG1 ] & SYSCONFIG1_RDS ) = = 0 )
si470x_rds_on ( radio ) ;
/* block if no new data available */
while ( radio - > wr_index = = radio - > rd_index ) {
if ( file - > f_flags & O_NONBLOCK ) {
retval = - EWOULDBLOCK ;
goto done ;
}
if ( wait_event_interruptible ( radio - > read_queue ,
radio - > wr_index ! = radio - > rd_index ) < 0 ) {
retval = - EINTR ;
goto done ;
}
}
/* calculate block count from byte count */
count / = 3 ;
/* copy RDS block out of internal buffer and to user buffer */
mutex_lock ( & radio - > lock ) ;
while ( block_count < count ) {
if ( radio - > rd_index = = radio - > wr_index )
break ;
/* always transfer rds complete blocks */
if ( copy_to_user ( buf , & radio - > buffer [ radio - > rd_index ] , 3 ) )
/* retval = -EFAULT; */
break ;
/* increment and wrap read pointer */
radio - > rd_index + = 3 ;
if ( radio - > rd_index > = radio - > buf_size )
radio - > rd_index = 0 ;
/* increment counters */
block_count + + ;
buf + = 3 ;
retval + = 3 ;
}
mutex_unlock ( & radio - > lock ) ;
done :
return retval ;
}
/*
* si470x_fops_poll - poll RDS data
*/
static unsigned int si470x_fops_poll ( struct file * file ,
struct poll_table_struct * pts )
{
struct si470x_device * radio = video_drvdata ( file ) ;
int retval = 0 ;
/* switch on rds reception */
if ( ( radio - > registers [ SYSCONFIG1 ] & SYSCONFIG1_RDS ) = = 0 )
si470x_rds_on ( radio ) ;
poll_wait ( file , & radio - > read_queue , pts ) ;
if ( radio - > rd_index ! = radio - > wr_index )
retval = POLLIN | POLLRDNORM ;
return retval ;
}
/*
* si470x_fops - file operations interface
*/
static const struct v4l2_file_operations si470x_fops = {
. owner = THIS_MODULE ,
. read = si470x_fops_read ,
. poll = si470x_fops_poll ,
. ioctl = video_ioctl2 ,
. open = si470x_fops_open ,
. release = si470x_fops_release ,
} ;
2009-08-09 14:22:20 -03:00
/**************************************************************************
* Video4Linux Interface
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* si470x_vidioc_queryctrl - enumerate control items
*/
static int si470x_vidioc_queryctrl ( struct file * file , void * priv ,
struct v4l2_queryctrl * qc )
{
2009-08-09 14:22:41 -03:00
struct si470x_device * radio = video_drvdata ( file ) ;
2009-08-09 14:22:20 -03:00
int retval = - EINVAL ;
/* abort if qc->id is below V4L2_CID_BASE */
if ( qc - > id < V4L2_CID_BASE )
goto done ;
/* search video control */
switch ( qc - > id ) {
case V4L2_CID_AUDIO_VOLUME :
return v4l2_ctrl_query_fill ( qc , 0 , 15 , 1 , 15 ) ;
case V4L2_CID_AUDIO_MUTE :
return v4l2_ctrl_query_fill ( qc , 0 , 1 , 1 , 1 ) ;
}
/* disable unsupported base controls */
/* to satisfy kradio and such apps */
if ( ( retval = = - EINVAL ) & & ( qc - > id < V4L2_CID_LASTP1 ) ) {
qc - > flags = V4L2_CTRL_FLAG_DISABLED ;
retval = 0 ;
}
done :
if ( retval < 0 )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev ,
" query controls failed with %d \n " , retval ) ;
2009-08-09 14:22:20 -03:00
return retval ;
}
/*
* si470x_vidioc_g_ctrl - get the value of a control
*/
static int si470x_vidioc_g_ctrl ( struct file * file , void * priv ,
struct v4l2_control * ctrl )
{
struct si470x_device * radio = video_drvdata ( file ) ;
int retval = 0 ;
/* safety checks */
2009-08-09 14:23:02 -03:00
retval = si470x_disconnect_check ( radio ) ;
if ( retval )
2009-08-09 14:22:20 -03:00
goto done ;
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_VOLUME :
ctrl - > value = radio - > registers [ SYSCONFIG2 ] &
SYSCONFIG2_VOLUME ;
break ;
case V4L2_CID_AUDIO_MUTE :
ctrl - > value = ( ( radio - > registers [ POWERCFG ] &
POWERCFG_DMUTE ) = = 0 ) ? 1 : 0 ;
break ;
default :
retval = - EINVAL ;
}
done :
if ( retval < 0 )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev ,
" get control failed with %d \n " , retval ) ;
2009-08-09 14:22:20 -03:00
return retval ;
}
/*
* si470x_vidioc_s_ctrl - set the value of a control
*/
static int si470x_vidioc_s_ctrl ( struct file * file , void * priv ,
struct v4l2_control * ctrl )
{
struct si470x_device * radio = video_drvdata ( file ) ;
int retval = 0 ;
/* safety checks */
2009-08-09 14:23:02 -03:00
retval = si470x_disconnect_check ( radio ) ;
if ( retval )
2009-08-09 14:22:20 -03:00
goto done ;
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_VOLUME :
radio - > registers [ SYSCONFIG2 ] & = ~ SYSCONFIG2_VOLUME ;
radio - > registers [ SYSCONFIG2 ] | = ctrl - > value ;
retval = si470x_set_register ( radio , SYSCONFIG2 ) ;
break ;
case V4L2_CID_AUDIO_MUTE :
if ( ctrl - > value = = 1 )
radio - > registers [ POWERCFG ] & = ~ POWERCFG_DMUTE ;
else
radio - > registers [ POWERCFG ] | = POWERCFG_DMUTE ;
retval = si470x_set_register ( radio , POWERCFG ) ;
break ;
default :
retval = - EINVAL ;
}
done :
if ( retval < 0 )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev ,
" set control failed with %d \n " , retval ) ;
2009-08-09 14:22:20 -03:00
return retval ;
}
/*
* si470x_vidioc_g_audio - get audio attributes
*/
static int si470x_vidioc_g_audio ( struct file * file , void * priv ,
struct v4l2_audio * audio )
{
/* driver constants */
audio - > index = 0 ;
strcpy ( audio - > name , " Radio " ) ;
audio - > capability = V4L2_AUDCAP_STEREO ;
audio - > mode = 0 ;
return 0 ;
}
/*
* si470x_vidioc_g_tuner - get tuner attributes
*/
static int si470x_vidioc_g_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * tuner )
{
struct si470x_device * radio = video_drvdata ( file ) ;
int retval = 0 ;
/* safety checks */
2009-08-09 14:23:02 -03:00
retval = si470x_disconnect_check ( radio ) ;
if ( retval )
2009-08-09 14:22:20 -03:00
goto done ;
2009-08-09 14:23:02 -03:00
2009-08-09 14:22:20 -03:00
if ( tuner - > index ! = 0 ) {
retval = - EINVAL ;
goto done ;
}
retval = si470x_get_register ( radio , STATUSRSSI ) ;
if ( retval < 0 )
goto done ;
/* driver constants */
strcpy ( tuner - > name , " FM " ) ;
tuner - > type = V4L2_TUNER_RADIO ;
2009-08-09 14:23:35 -03:00
# if defined(CONFIG_USB_SI470X) || defined(CONFIG_USB_SI470X_MODULE)
2009-08-09 14:22:20 -03:00
tuner - > capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS ;
2009-08-09 14:23:35 -03:00
# else
tuner - > capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO ;
# endif
2009-08-09 14:22:20 -03:00
/* range limits */
switch ( ( radio - > registers [ SYSCONFIG2 ] & SYSCONFIG2_BAND ) > > 6 ) {
/* 0: 87.5 - 108 MHz (USA, Europe, default) */
default :
tuner - > rangelow = 87.5 * FREQ_MUL ;
tuner - > rangehigh = 108 * FREQ_MUL ;
break ;
/* 1: 76 - 108 MHz (Japan wide band) */
case 1 :
tuner - > rangelow = 76 * FREQ_MUL ;
tuner - > rangehigh = 108 * FREQ_MUL ;
break ;
/* 2: 76 - 90 MHz (Japan) */
case 2 :
tuner - > rangelow = 76 * FREQ_MUL ;
tuner - > rangehigh = 90 * FREQ_MUL ;
break ;
} ;
/* stereo indicator == stereo (instead of mono) */
if ( ( radio - > registers [ STATUSRSSI ] & STATUSRSSI_ST ) = = 0 )
tuner - > rxsubchans = V4L2_TUNER_SUB_MONO ;
else
tuner - > rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO ;
2009-08-09 14:23:35 -03:00
# if defined(CONFIG_USB_SI470X) || defined(CONFIG_USB_SI470X_MODULE)
2009-08-09 14:22:20 -03:00
/* If there is a reliable method of detecting an RDS channel,
then this code should check for that before setting this
RDS subchannel . */
tuner - > rxsubchans | = V4L2_TUNER_SUB_RDS ;
2009-08-09 14:23:35 -03:00
# endif
2009-08-09 14:22:20 -03:00
/* mono/stereo selector */
if ( ( radio - > registers [ POWERCFG ] & POWERCFG_MONO ) = = 0 )
tuner - > audmode = V4L2_TUNER_MODE_STEREO ;
else
tuner - > audmode = V4L2_TUNER_MODE_MONO ;
/* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */
2010-02-18 16:33:46 -03:00
/* measured in units of dbµV in 1 db increments (max at ~75 dbµV) */
2009-08-09 14:22:20 -03:00
tuner - > signal = ( radio - > registers [ STATUSRSSI ] & STATUSRSSI_RSSI ) ;
/* the ideal factor is 0xffff/75 = 873,8 */
tuner - > signal = ( tuner - > signal * 873 ) + ( 8 * tuner - > signal / 10 ) ;
/* automatic frequency control: -1: freq to low, 1 freq to high */
/* AFCRL does only indicate that freq. differs, not if too low/high */
tuner - > afc = ( radio - > registers [ STATUSRSSI ] & STATUSRSSI_AFCRL ) ? 1 : 0 ;
done :
if ( retval < 0 )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev ,
" get tuner failed with %d \n " , retval ) ;
2009-08-09 14:22:20 -03:00
return retval ;
}
/*
* si470x_vidioc_s_tuner - set tuner attributes
*/
static int si470x_vidioc_s_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * tuner )
{
struct si470x_device * radio = video_drvdata ( file ) ;
int retval = - EINVAL ;
/* safety checks */
2009-08-09 14:23:02 -03:00
retval = si470x_disconnect_check ( radio ) ;
if ( retval )
2009-08-09 14:22:20 -03:00
goto done ;
2009-08-09 14:23:02 -03:00
2009-08-09 14:22:20 -03:00
if ( tuner - > index ! = 0 )
goto done ;
/* mono/stereo selector */
switch ( tuner - > audmode ) {
case V4L2_TUNER_MODE_MONO :
radio - > registers [ POWERCFG ] | = POWERCFG_MONO ; /* force mono */
break ;
case V4L2_TUNER_MODE_STEREO :
radio - > registers [ POWERCFG ] & = ~ POWERCFG_MONO ; /* try stereo */
break ;
default :
goto done ;
}
retval = si470x_set_register ( radio , POWERCFG ) ;
done :
if ( retval < 0 )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev ,
" set tuner failed with %d \n " , retval ) ;
2009-08-09 14:22:20 -03:00
return retval ;
}
/*
* si470x_vidioc_g_frequency - get tuner or modulator radio frequency
*/
static int si470x_vidioc_g_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * freq )
{
struct si470x_device * radio = video_drvdata ( file ) ;
int retval = 0 ;
/* safety checks */
2009-08-09 14:23:02 -03:00
retval = si470x_disconnect_check ( radio ) ;
if ( retval )
2009-08-09 14:22:20 -03:00
goto done ;
2009-08-09 14:23:02 -03:00
2009-08-09 14:22:20 -03:00
if ( freq - > tuner ! = 0 ) {
retval = - EINVAL ;
goto done ;
}
freq - > type = V4L2_TUNER_RADIO ;
retval = si470x_get_freq ( radio , & freq - > frequency ) ;
done :
if ( retval < 0 )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev ,
" get frequency failed with %d \n " , retval ) ;
2009-08-09 14:22:20 -03:00
return retval ;
}
/*
* si470x_vidioc_s_frequency - set tuner or modulator radio frequency
*/
static int si470x_vidioc_s_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * freq )
{
struct si470x_device * radio = video_drvdata ( file ) ;
int retval = 0 ;
/* safety checks */
2009-08-09 14:23:02 -03:00
retval = si470x_disconnect_check ( radio ) ;
if ( retval )
2009-08-09 14:22:20 -03:00
goto done ;
2009-08-09 14:23:02 -03:00
2009-08-09 14:22:20 -03:00
if ( freq - > tuner ! = 0 ) {
retval = - EINVAL ;
goto done ;
}
retval = si470x_set_freq ( radio , freq - > frequency ) ;
done :
if ( retval < 0 )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev ,
" set frequency failed with %d \n " , retval ) ;
2009-08-09 14:22:20 -03:00
return retval ;
}
/*
* si470x_vidioc_s_hw_freq_seek - set hardware frequency seek
*/
static int si470x_vidioc_s_hw_freq_seek ( struct file * file , void * priv ,
struct v4l2_hw_freq_seek * seek )
{
struct si470x_device * radio = video_drvdata ( file ) ;
int retval = 0 ;
/* safety checks */
2009-08-09 14:23:02 -03:00
retval = si470x_disconnect_check ( radio ) ;
if ( retval )
2009-08-09 14:22:20 -03:00
goto done ;
2009-08-09 14:23:02 -03:00
2009-08-09 14:22:20 -03:00
if ( seek - > tuner ! = 0 ) {
retval = - EINVAL ;
goto done ;
}
retval = si470x_set_seek ( radio , seek - > wrap_around , seek - > seek_upward ) ;
done :
if ( retval < 0 )
2009-08-09 14:22:41 -03:00
dev_warn ( & radio - > videodev - > dev ,
" set hardware frequency seek failed with %d \n " , retval ) ;
2009-08-09 14:22:20 -03:00
return retval ;
}
/*
* si470x_ioctl_ops - video device ioctl operations
*/
static const struct v4l2_ioctl_ops si470x_ioctl_ops = {
. vidioc_querycap = si470x_vidioc_querycap ,
. vidioc_queryctrl = si470x_vidioc_queryctrl ,
. vidioc_g_ctrl = si470x_vidioc_g_ctrl ,
. vidioc_s_ctrl = si470x_vidioc_s_ctrl ,
. vidioc_g_audio = si470x_vidioc_g_audio ,
. vidioc_g_tuner = si470x_vidioc_g_tuner ,
. vidioc_s_tuner = si470x_vidioc_s_tuner ,
. vidioc_g_frequency = si470x_vidioc_g_frequency ,
. vidioc_s_frequency = si470x_vidioc_s_frequency ,
. vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek ,
} ;
/*
* si470x_viddev_template - video device interface
*/
struct video_device si470x_viddev_template = {
. fops = & si470x_fops ,
. name = DRIVER_NAME ,
. release = video_device_release ,
. ioctl_ops = & si470x_ioctl_ops ,
} ;