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 >
2012-07-12 16:55:48 -03:00
* Copyright ( c ) 2012 Hans de Goede < hdegoede @ redhat . com >
2009-08-09 14:22:20 -03:00
*
* 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 .
*/
/*
* 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 >
2018-01-04 06:47:28 -05:00
* 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
2009-08-09 14:22:20 -03:00
* 2008 - 01 - 16 Tobias Lorenz < tobias . lorenz @ gmx . net >
* Version 1.0 .3
2018-01-04 06:47:28 -05:00
* - code reordered to avoid function prototypes
2009-08-09 14:22:20 -03:00
* - 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 >
2018-01-04 06:47:28 -05:00
* - add support for KWorld USB FM Radio FM700
* - blacklisted KWorld radio in hid - core . c and hid - ids . h
2009-08-09 14:22:20 -03:00
* 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* " ) ;
/* 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* " ) ;
2012-07-12 16:55:48 -03:00
static const struct v4l2_frequency_band bands [ ] = {
{
. type = V4L2_TUNER_RADIO ,
. index = 0 ,
. capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
2012-08-03 11:16:59 -03:00
V4L2_TUNER_CAP_FREQ_BANDS |
2012-07-12 16:55:48 -03:00
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP ,
. rangelow = 87500 * 16 ,
. rangehigh = 108000 * 16 ,
. modulation = V4L2_BAND_MODULATION_FM ,
} ,
{
. type = V4L2_TUNER_RADIO ,
. index = 1 ,
. capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
2012-08-03 11:16:59 -03:00
V4L2_TUNER_CAP_FREQ_BANDS |
2012-07-12 16:55:48 -03:00
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP ,
. rangelow = 76000 * 16 ,
. rangehigh = 108000 * 16 ,
. modulation = V4L2_BAND_MODULATION_FM ,
} ,
{
. type = V4L2_TUNER_RADIO ,
. index = 2 ,
. capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
2012-08-03 11:16:59 -03:00
V4L2_TUNER_CAP_FREQ_BANDS |
2012-07-12 16:55:48 -03:00
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP ,
. rangelow = 76000 * 16 ,
. rangehigh = 90000 * 16 ,
. modulation = V4L2_BAND_MODULATION_FM ,
} ,
} ;
2009-08-09 14:22:20 -03:00
/**************************************************************************
* Generic Functions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2012-07-12 16:55:48 -03:00
/*
* si470x_set_band - set the band
*/
static int si470x_set_band ( struct si470x_device * radio , int band )
{
if ( radio - > band = = band )
return 0 ;
radio - > band = band ;
radio - > registers [ SYSCONFIG2 ] & = ~ SYSCONFIG2_BAND ;
radio - > registers [ SYSCONFIG2 ] | = radio - > band < < 6 ;
2018-04-05 15:08:12 -04:00
return radio - > set_register ( radio , SYSCONFIG2 ) ;
2012-07-12 16:55:48 -03:00
}
2009-08-09 14:22:20 -03:00
/*
* si470x_set_chan - set the channel
*/
static int si470x_set_chan ( struct si470x_device * radio , unsigned short chan )
{
int retval ;
2015-02-11 11:20:54 -03:00
unsigned long time_left ;
2014-09-03 15:54:17 -03:00
bool timed_out = false ;
2009-08-09 14:22:20 -03:00
2018-04-05 15:08:12 -04:00
retval = radio - > get_register ( radio , POWERCFG ) ;
2018-02-25 21:24:06 -05:00
if ( retval )
return retval ;
if ( ( radio - > registers [ POWERCFG ] & ( POWERCFG_ENABLE | POWERCFG_DMUTE ) )
! = ( POWERCFG_ENABLE | POWERCFG_DMUTE ) ) {
return 0 ;
}
2009-08-09 14:22:20 -03:00
/* start tuning */
radio - > registers [ CHANNEL ] & = ~ CHANNEL_CHAN ;
radio - > registers [ CHANNEL ] | = CHANNEL_TUNE | chan ;
2018-04-05 15:08:12 -04:00
retval = radio - > set_register ( radio , CHANNEL ) ;
2009-08-09 14:22:20 -03:00
if ( retval < 0 )
goto done ;
2012-06-14 09:43:12 -03:00
/* wait till tune operation has completed */
2013-11-14 14:32:02 -08:00
reinit_completion ( & radio - > completion ) ;
2015-02-11 11:20:54 -03:00
time_left = wait_for_completion_timeout ( & radio - > completion ,
msecs_to_jiffies ( tune_timeout ) ) ;
if ( time_left = = 0 )
2012-06-14 09:43:12 -03:00
timed_out = true ;
2011-03-11 03:54:46 -03:00
2009-08-09 14:22:20 -03:00
if ( ( radio - > registers [ STATUSRSSI ] & STATUSRSSI_STC ) = = 0 )
2012-05-04 09:16:57 -03:00
dev_warn ( & radio - > videodev . dev , " tune does not complete \n " ) ;
2009-08-09 14:22:20 -03:00
if ( timed_out )
2012-05-04 09:16:57 -03:00
dev_warn ( & radio - > videodev . dev ,
2009-08-09 14:22:41 -03:00
" tune timed out after %u ms \n " , tune_timeout ) ;
2009-08-09 14:22:20 -03:00
/* stop tuning */
radio - > registers [ CHANNEL ] & = ~ CHANNEL_TUNE ;
2018-04-05 15:08:12 -04:00
retval = radio - > set_register ( radio , CHANNEL ) ;
2009-08-09 14:22:20 -03:00
done :
return retval ;
}
/*
2012-07-12 16:55:48 -03:00
* si470x_get_step - get channel spacing
2009-08-09 14:22:20 -03:00
*/
2012-07-12 16:55:48 -03:00
static unsigned int si470x_get_step ( struct si470x_device * radio )
2009-08-09 14:22:20 -03:00
{
/* Spacing (kHz) */
switch ( ( radio - > registers [ SYSCONFIG2 ] & SYSCONFIG2_SPACE ) > > 4 ) {
/* 0: 200 kHz (USA, Australia) */
case 0 :
2012-07-12 16:55:48 -03:00
return 200 * 16 ;
2009-08-09 14:22:20 -03:00
/* 1: 100 kHz (Europe, Japan) */
case 1 :
2012-07-12 16:55:48 -03:00
return 100 * 16 ;
2009-08-09 14:22:20 -03:00
/* 2: 50 kHz */
default :
2012-07-12 16:55:48 -03:00
return 50 * 16 ;
2013-10-08 20:29:08 -03:00
}
2012-07-12 16:55:48 -03:00
}
2009-08-09 14:22:20 -03:00
2012-07-12 16:55:48 -03:00
/*
* si470x_get_freq - get the frequency
*/
static int si470x_get_freq ( struct si470x_device * radio , unsigned int * freq )
{
int chan , retval ;
2009-08-09 14:22:20 -03:00
/* read channel */
2018-04-05 15:08:12 -04:00
retval = radio - > get_register ( radio , READCHAN ) ;
2009-08-09 14:22:20 -03:00
chan = radio - > registers [ READCHAN ] & READCHAN_READCHAN ;
/* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */
2012-07-12 16:55:48 -03:00
* freq = chan * si470x_get_step ( radio ) + bands [ radio - > band ] . rangelow ;
2009-08-09 14:22:20 -03:00
return retval ;
}
/*
* si470x_set_freq - set the frequency
*/
int si470x_set_freq ( struct si470x_device * radio , unsigned int freq )
{
unsigned short chan ;
2012-07-12 16:55:48 -03:00
freq = clamp ( freq , bands [ radio - > band ] . rangelow ,
bands [ radio - > band ] . rangehigh ) ;
2009-08-09 14:22:20 -03:00
/* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */
2012-07-12 16:55:48 -03:00
chan = ( freq - bands [ radio - > band ] . rangelow ) / si470x_get_step ( radio ) ;
2009-08-09 14:22:20 -03:00
return si470x_set_chan ( radio , chan ) ;
}
2018-04-05 15:08:12 -04:00
EXPORT_SYMBOL_GPL ( si470x_set_freq ) ;
2009-08-09 14:22:20 -03:00
/*
* si470x_set_seek - set seek
*/
static int si470x_set_seek ( struct si470x_device * radio ,
2012-09-14 07:41:18 -03:00
const struct v4l2_hw_freq_seek * seek )
2009-08-09 14:22:20 -03:00
{
2012-07-12 16:55:48 -03:00
int band , retval ;
unsigned int freq ;
2014-09-03 15:54:17 -03:00
bool timed_out = false ;
2015-02-11 11:20:54 -03:00
unsigned long time_left ;
2009-08-09 14:22:20 -03:00
2012-07-12 16:55:48 -03:00
/* set band */
if ( seek - > rangelow | | seek - > rangehigh ) {
for ( band = 0 ; band < ARRAY_SIZE ( bands ) ; band + + ) {
if ( bands [ band ] . rangelow = = seek - > rangelow & &
bands [ band ] . rangehigh = = seek - > rangehigh )
break ;
}
if ( band = = ARRAY_SIZE ( bands ) )
return - EINVAL ; /* No matching band found */
} else
band = 1 ; /* If nothing is specified seek 76 - 108 Mhz */
if ( radio - > band ! = band ) {
retval = si470x_get_freq ( radio , & freq ) ;
if ( retval )
return retval ;
retval = si470x_set_band ( radio , band ) ;
if ( retval )
return retval ;
retval = si470x_set_freq ( radio , freq ) ;
if ( retval )
return retval ;
}
2009-08-09 14:22:20 -03:00
/* start seeking */
radio - > registers [ POWERCFG ] | = POWERCFG_SEEK ;
2012-07-12 16:55:48 -03:00
if ( seek - > wrap_around )
2009-08-09 14:22:20 -03:00
radio - > registers [ POWERCFG ] & = ~ POWERCFG_SKMODE ;
else
radio - > registers [ POWERCFG ] | = POWERCFG_SKMODE ;
2012-07-12 16:55:48 -03:00
if ( seek - > seek_upward )
2009-08-09 14:22:20 -03:00
radio - > registers [ POWERCFG ] | = POWERCFG_SEEKUP ;
else
radio - > registers [ POWERCFG ] & = ~ POWERCFG_SEEKUP ;
2018-04-05 15:08:12 -04:00
retval = radio - > set_register ( radio , POWERCFG ) ;
2009-08-09 14:22:20 -03:00
if ( retval < 0 )
2012-04-30 19:49:27 -03:00
return retval ;
2009-08-09 14:22:20 -03:00
2012-06-14 09:43:12 -03:00
/* wait till tune operation has completed */
2013-11-14 14:32:02 -08:00
reinit_completion ( & radio - > completion ) ;
2015-02-11 11:20:54 -03:00
time_left = wait_for_completion_timeout ( & radio - > completion ,
msecs_to_jiffies ( seek_timeout ) ) ;
if ( time_left = = 0 )
2012-06-14 09:43:12 -03:00
timed_out = true ;
2011-03-11 03:54:46 -03:00
2009-08-09 14:22:20 -03:00
if ( ( radio - > registers [ STATUSRSSI ] & STATUSRSSI_STC ) = = 0 )
2012-05-04 09:16:57 -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 )
2012-05-04 09:16:57 -03:00
dev_warn ( & radio - > videodev . dev ,
2009-08-09 14:22:41 -03:00
" seek failed / band limit reached \n " ) ;
2009-08-09 14:22:20 -03:00
/* stop seeking */
radio - > registers [ POWERCFG ] & = ~ POWERCFG_SEEK ;
2018-04-05 15:08:12 -04:00
retval = radio - > set_register ( radio , POWERCFG ) ;
2009-08-09 14:22:20 -03:00
/* try again, if timed out */
2012-04-30 19:49:27 -03:00
if ( retval = = 0 & & timed_out )
2012-05-27 07:25:06 -03:00
return - ENODATA ;
2009-08-09 14:22:20 -03:00
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 ;
2018-04-05 15:08:12 -04:00
retval = radio - > set_register ( radio , POWERCFG ) ;
2009-08-09 14:22:20 -03:00
if ( retval < 0 )
goto done ;
/* sysconfig 1 */
2018-03-02 10:25:43 -05:00
radio - > registers [ SYSCONFIG1 ] | = SYSCONFIG1_RDSIEN | SYSCONFIG1_STCIEN |
SYSCONFIG1_RDS ;
radio - > registers [ SYSCONFIG1 ] & = ~ SYSCONFIG1_GPIO2 ;
radio - > registers [ SYSCONFIG1 ] | = SYSCONFIG1_GPIO2_INT ;
if ( de )
radio - > registers [ SYSCONFIG1 ] | = SYSCONFIG1_DE ;
2018-04-05 15:08:12 -04:00
retval = radio - > set_register ( radio , SYSCONFIG1 ) ;
2009-08-09 14:22:20 -03:00
if ( retval < 0 )
goto done ;
/* sysconfig 2 */
radio - > registers [ SYSCONFIG2 ] =
2012-06-14 09:43:13 -03:00
( 0x1f < < 8 ) | /* SEEKTH */
2012-07-12 16:55:48 -03:00
( ( radio - > band < < 6 ) & SYSCONFIG2_BAND ) | /* BAND */
2009-08-09 14:22:20 -03:00
( ( space < < 4 ) & SYSCONFIG2_SPACE ) | /* SPACE */
15 ; /* VOLUME (max) */
2018-04-05 15:08:12 -04:00
retval = radio - > set_register ( radio , SYSCONFIG2 ) ;
2009-08-09 14:22:20 -03:00
if ( retval < 0 )
goto done ;
/* reset last channel */
retval = si470x_set_chan ( radio ,
radio - > registers [ CHANNEL ] & CHANNEL_CHAN ) ;
done :
return retval ;
}
2018-04-05 15:08:12 -04:00
EXPORT_SYMBOL_GPL ( si470x_start ) ;
2009-08-09 14:22:20 -03:00
/*
* si470x_stop - switch off radio
*/
int si470x_stop ( struct si470x_device * radio )
{
int retval ;
/* sysconfig 1 */
radio - > registers [ SYSCONFIG1 ] & = ~ SYSCONFIG1_RDS ;
2018-04-05 15:08:12 -04:00
retval = radio - > set_register ( radio , SYSCONFIG1 ) ;
2009-08-09 14:22:20 -03:00
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 ;
2018-04-05 15:08:12 -04:00
retval = radio - > set_register ( radio , POWERCFG ) ;
2009-08-09 14:22:20 -03:00
done :
return retval ;
}
2018-04-05 15:08:12 -04:00
EXPORT_SYMBOL_GPL ( si470x_stop ) ;
2009-08-09 14:22:20 -03:00
/*
* si470x_rds_on - switch on rds reception
*/
2010-09-15 08:35:45 -03:00
static int si470x_rds_on ( struct si470x_device * radio )
2009-08-09 14:22:20 -03:00
{
int retval ;
/* sysconfig 1 */
radio - > registers [ SYSCONFIG1 ] | = SYSCONFIG1_RDS ;
2018-04-05 15:08:12 -04:00
retval = radio - > set_register ( radio , SYSCONFIG1 ) ;
2009-08-09 14:22:20 -03:00
if ( retval < 0 )
radio - > registers [ SYSCONFIG1 ] & = ~ SYSCONFIG1_RDS ;
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 */
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 ;
}
done :
return retval ;
}
/*
* si470x_fops_poll - poll RDS data
*/
2017-07-03 03:02:56 -04:00
static __poll_t si470x_fops_poll ( struct file * file ,
2009-12-10 16:49:34 -03:00
struct poll_table_struct * pts )
{
struct si470x_device * radio = video_drvdata ( file ) ;
2017-07-03 03:14:15 -04:00
__poll_t req_events = poll_requested_events ( pts ) ;
2017-07-03 03:02:56 -04:00
__poll_t retval = v4l2_ctrl_poll ( file , pts ) ;
2010-09-15 08:35:45 -03:00
2018-02-11 14:34:03 -08:00
if ( req_events & ( EPOLLIN | EPOLLRDNORM ) ) {
2012-05-04 09:20:53 -03:00
/* switch on rds reception */
if ( ( radio - > registers [ SYSCONFIG1 ] & SYSCONFIG1_RDS ) = = 0 )
si470x_rds_on ( radio ) ;
2009-12-10 16:49:34 -03:00
2012-05-04 09:20:53 -03:00
poll_wait ( file , & radio - > read_queue , pts ) ;
2009-12-10 16:49:34 -03:00
2012-05-04 09:20:53 -03:00
if ( radio - > rd_index ! = radio - > wr_index )
2018-02-11 14:34:03 -08:00
retval | = EPOLLIN | EPOLLRDNORM ;
2012-05-04 09:20:53 -03:00
}
2009-12-10 16:49:34 -03:00
return retval ;
}
2018-04-05 15:08:12 -04:00
static int si470x_fops_open ( struct file * file )
{
struct si470x_device * radio = video_drvdata ( file ) ;
return radio - > fops_open ( file ) ;
}
/*
* si470x_fops_release - file release
*/
static int si470x_fops_release ( struct file * file )
{
struct si470x_device * radio = video_drvdata ( file ) ;
return radio - > fops_release ( file ) ;
}
2009-12-10 16:49:34 -03:00
/*
* si470x_fops - file operations interface
*/
static const struct v4l2_file_operations si470x_fops = {
. owner = THIS_MODULE ,
. read = si470x_fops_read ,
. poll = si470x_fops_poll ,
2010-09-15 08:35:45 -03:00
. unlocked_ioctl = video_ioctl2 ,
2009-12-10 16:49:34 -03:00
. open = si470x_fops_open ,
. release = si470x_fops_release ,
} ;
2009-08-09 14:22:20 -03:00
/**************************************************************************
* Video4Linux Interface
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2012-05-04 09:16:57 -03:00
static int si470x_s_ctrl ( struct v4l2_ctrl * ctrl )
2009-08-09 14:22:20 -03:00
{
2012-05-04 09:16:57 -03:00
struct si470x_device * radio =
container_of ( ctrl - > handler , struct si470x_device , hdl ) ;
2009-08-09 14:22:20 -03:00
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_VOLUME :
radio - > registers [ SYSCONFIG2 ] & = ~ SYSCONFIG2_VOLUME ;
2012-05-04 09:16:57 -03:00
radio - > registers [ SYSCONFIG2 ] | = ctrl - > val ;
2018-04-05 15:08:12 -04:00
return radio - > set_register ( radio , SYSCONFIG2 ) ;
2009-08-09 14:22:20 -03:00
case V4L2_CID_AUDIO_MUTE :
2012-05-04 09:16:57 -03:00
if ( ctrl - > val )
2009-08-09 14:22:20 -03:00
radio - > registers [ POWERCFG ] & = ~ POWERCFG_DMUTE ;
else
radio - > registers [ POWERCFG ] | = POWERCFG_DMUTE ;
2018-04-05 15:08:12 -04:00
return radio - > set_register ( radio , POWERCFG ) ;
2009-08-09 14:22:20 -03:00
default :
2012-05-04 09:16:57 -03:00
return - EINVAL ;
2009-08-09 14:22:20 -03:00
}
}
/*
* 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 ) ;
2012-06-14 09:43:11 -03:00
int retval = 0 ;
2009-08-09 14:22:20 -03:00
2012-04-30 19:49:27 -03:00
if ( tuner - > index ! = 0 )
return - EINVAL ;
2009-08-09 14:22:20 -03:00
2012-06-14 09:43:11 -03:00
if ( ! radio - > status_rssi_auto_update ) {
2018-04-05 15:08:12 -04:00
retval = radio - > get_register ( radio , STATUSRSSI ) ;
2012-06-14 09:43:11 -03:00
if ( retval < 0 )
return retval ;
}
2009-08-09 14:22:20 -03:00
/* driver constants */
2018-09-10 16:20:42 -04:00
strscpy ( tuner - > name , " FM " , sizeof ( tuner - > name ) ) ;
2009-08-09 14:22:20 -03:00
tuner - > type = V4L2_TUNER_RADIO ;
tuner - > capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
2012-05-27 07:25:06 -03:00
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP ;
2012-07-12 16:55:48 -03:00
tuner - > rangelow = 76 * FREQ_MUL ;
tuner - > rangehigh = 108 * FREQ_MUL ;
2009-08-09 14:22:20 -03:00
/* stereo indicator == stereo (instead of mono) */
if ( ( radio - > registers [ STATUSRSSI ] & STATUSRSSI_ST ) = = 0 )
tuner - > rxsubchans = V4L2_TUNER_SUB_MONO ;
else
2012-05-04 09:16:57 -03:00
tuner - > rxsubchans = V4L2_TUNER_SUB_STEREO ;
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 ;
/* 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 ) ;
2012-05-04 09:20:53 -03:00
if ( tuner - > signal > 0xffff )
tuner - > signal = 0xffff ;
2009-08-09 14:22:20 -03:00
/* 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 ;
return retval ;
}
/*
* si470x_vidioc_s_tuner - set tuner attributes
*/
static int si470x_vidioc_s_tuner ( struct file * file , void * priv ,
2013-03-15 06:10:06 -03:00
const struct v4l2_tuner * tuner )
2009-08-09 14:22:20 -03:00
{
struct si470x_device * radio = video_drvdata ( file ) ;
if ( tuner - > index ! = 0 )
2012-05-04 09:20:53 -03:00
return - EINVAL ;
2009-08-09 14:22:20 -03:00
/* mono/stereo selector */
switch ( tuner - > audmode ) {
case V4L2_TUNER_MODE_MONO :
radio - > registers [ POWERCFG ] | = POWERCFG_MONO ; /* force mono */
break ;
case V4L2_TUNER_MODE_STEREO :
2012-05-04 09:20:53 -03:00
default :
2009-08-09 14:22:20 -03:00
radio - > registers [ POWERCFG ] & = ~ POWERCFG_MONO ; /* try stereo */
break ;
}
2018-04-05 15:08:12 -04:00
return radio - > set_register ( radio , POWERCFG ) ;
2009-08-09 14:22:20 -03:00
}
/*
* 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 ) ;
2012-04-30 19:49:27 -03:00
if ( freq - > tuner ! = 0 )
return - EINVAL ;
2009-08-09 14:22:20 -03:00
freq - > type = V4L2_TUNER_RADIO ;
2012-04-30 19:49:27 -03:00
return si470x_get_freq ( radio , & freq - > frequency ) ;
2009-08-09 14:22:20 -03:00
}
/*
* si470x_vidioc_s_frequency - set tuner or modulator radio frequency
*/
static int si470x_vidioc_s_frequency ( struct file * file , void * priv ,
2013-03-19 04:09:26 -03:00
const struct v4l2_frequency * freq )
2009-08-09 14:22:20 -03:00
{
struct si470x_device * radio = video_drvdata ( file ) ;
2012-07-12 16:55:48 -03:00
int retval ;
2009-08-09 14:22:20 -03:00
2012-04-30 19:49:27 -03:00
if ( freq - > tuner ! = 0 )
return - EINVAL ;
2009-08-09 14:22:20 -03:00
2012-07-12 16:55:48 -03:00
if ( freq - > frequency < bands [ radio - > band ] . rangelow | |
freq - > frequency > bands [ radio - > band ] . rangehigh ) {
/* Switch to band 1 which covers everything we support */
retval = si470x_set_band ( radio , 1 ) ;
if ( retval )
return retval ;
}
2012-04-30 19:49:27 -03:00
return si470x_set_freq ( radio , freq - > frequency ) ;
2009-08-09 14:22:20 -03:00
}
/*
* si470x_vidioc_s_hw_freq_seek - set hardware frequency seek
*/
static int si470x_vidioc_s_hw_freq_seek ( struct file * file , void * priv ,
2012-09-14 07:41:18 -03:00
const struct v4l2_hw_freq_seek * seek )
2009-08-09 14:22:20 -03:00
{
struct si470x_device * radio = video_drvdata ( file ) ;
2012-04-30 19:49:27 -03:00
if ( seek - > tuner ! = 0 )
return - EINVAL ;
2009-08-09 14:22:20 -03:00
2012-09-21 09:33:35 -03:00
if ( file - > f_flags & O_NONBLOCK )
return - EWOULDBLOCK ;
2012-07-12 16:55:48 -03:00
return si470x_set_seek ( radio , seek ) ;
}
/*
* si470x_vidioc_enum_freq_bands - enumerate supported bands
*/
static int si470x_vidioc_enum_freq_bands ( struct file * file , void * priv ,
struct v4l2_frequency_band * band )
{
if ( band - > tuner ! = 0 )
return - EINVAL ;
if ( band - > index > = ARRAY_SIZE ( bands ) )
return - EINVAL ;
* band = bands [ band - > index ] ;
return 0 ;
2009-08-09 14:22:20 -03:00
}
2012-05-04 09:16:57 -03:00
const struct v4l2_ctrl_ops si470x_ctrl_ops = {
. s_ctrl = si470x_s_ctrl ,
} ;
2018-04-05 15:08:12 -04:00
EXPORT_SYMBOL_GPL ( si470x_ctrl_ops ) ;
static int si470x_vidioc_querycap ( struct file * file , void * priv ,
struct v4l2_capability * capability )
{
struct si470x_device * radio = video_drvdata ( file ) ;
return radio - > vidioc_querycap ( file , priv , capability ) ;
} ;
2009-08-09 14:22:20 -03:00
/*
* si470x_ioctl_ops - video device ioctl operations
*/
static const struct v4l2_ioctl_ops si470x_ioctl_ops = {
. vidioc_querycap = si470x_vidioc_querycap ,
. 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 ,
2012-07-12 16:55:48 -03:00
. vidioc_enum_freq_bands = si470x_vidioc_enum_freq_bands ,
2012-05-04 09:20:53 -03:00
. vidioc_subscribe_event = v4l2_ctrl_subscribe_event ,
. vidioc_unsubscribe_event = v4l2_event_unsubscribe ,
2009-08-09 14:22:20 -03:00
} ;
/*
* si470x_viddev_template - video device interface
*/
2017-09-27 04:38:29 -04:00
const struct video_device si470x_viddev_template = {
2009-08-09 14:22:20 -03:00
. fops = & si470x_fops ,
. name = DRIVER_NAME ,
2012-05-04 09:16:57 -03:00
. release = video_device_release_empty ,
2009-08-09 14:22:20 -03:00
. ioctl_ops = & si470x_ioctl_ops ,
} ;
2018-04-05 15:08:12 -04:00
EXPORT_SYMBOL_GPL ( si470x_viddev_template ) ;
MODULE_LICENSE ( " GPL " ) ;