2019-05-19 15:08:20 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2005-04-17 02:20:36 +04:00
/* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card
*
* by Fred Gleason < fredg @ wava . com >
* Version 0.3 .3
*
* ( Loosely ) based on code for the Aztech radio card by
*
* Russell Kroll ( rkroll @ exploits . org )
* Quay Ly
* Donald Song
2006-04-08 23:06:16 +04:00
* Jason Lewis ( jlewis @ twilight . vtc . vsc . edu )
2005-04-17 02:20:36 +04:00
* Scott McGrath ( smcgrath @ twilight . vtc . vsc . edu )
* William McGrath ( wmcgrath @ twilight . vtc . vsc . edu )
*
* History :
* 2000 - 04 - 29 Russell Kroll < rkroll @ exploits . org >
* Added ISAPnP detection for Linux 2.3 / 2.4
*
* 2001 - 01 - 10 Russell Kroll < rkroll @ exploits . org >
* Removed dead CONFIG_RADIO_CADET_PORT code
* PnP detection on load is now default ( no args necessary )
*
* 2002 - 01 - 17 Adam Belay < ambx1 @ neo . rr . com >
* Updated to latest pnp code
*
2008-10-27 21:13:47 +03:00
* 2003 - 01 - 31 Alan Cox < alan @ lxorguk . ukuu . org . uk >
2005-04-17 02:20:36 +04:00
* Cleaned up locking , delay code , general odds and ends
2006-08-08 16:10:12 +04:00
*
* 2006 - 07 - 30 Hans J . Koch < koch @ hjk - az . de >
* Changed API to V4L2
2005-04-17 02:20:36 +04:00
*/
2018-01-04 21:08:56 +03:00
# include <linux/module.h> /* Modules */
2005-04-17 02:20:36 +04:00
# include <linux/init.h> /* Initdata */
2005-09-13 12:25:15 +04:00
# include <linux/ioport.h> /* request_region */
2005-04-17 02:20:36 +04:00
# include <linux/delay.h> /* udelay */
2006-08-08 16:10:12 +04:00
# include <linux/videodev2.h> /* V4L2 API defs */
2005-04-17 02:20:36 +04:00
# include <linux/param.h>
# include <linux/pnp.h>
2009-10-04 16:11:37 +04:00
# include <linux/sched.h>
2009-03-06 19:48:47 +03:00
# include <linux/io.h> /* outb, outb_p */
# include <media/v4l2-device.h>
# include <media/v4l2-ioctl.h>
2012-07-02 16:36:39 +04:00
# include <media/v4l2-ctrls.h>
# include <media/v4l2-fh.h>
# include <media/v4l2-event.h>
2009-03-06 19:48:47 +03:00
MODULE_AUTHOR ( " Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath " ) ;
MODULE_DESCRIPTION ( " A driver for the ADS Cadet AM/FM/RDS radio card. " ) ;
MODULE_LICENSE ( " GPL " ) ;
2011-06-25 17:15:42 +04:00
MODULE_VERSION ( " 0.3.4 " ) ;
2009-03-06 19:48:47 +03:00
static int io = - 1 ; /* default to isapnp activation */
static int radio_nr = - 1 ;
module_param ( io , int , 0 ) ;
MODULE_PARM_DESC ( io , " I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e) " ) ;
module_param ( radio_nr , int , 0 ) ;
2005-04-17 02:20:36 +04:00
# define RDS_BUFFER 256
2006-08-08 16:10:12 +04:00
# define RDS_RX_FLAG 1
# define MBS_RX_FLAG 2
2009-03-06 19:48:47 +03:00
struct cadet {
struct v4l2_device v4l2_dev ;
struct video_device vdev ;
2012-07-02 16:36:39 +04:00
struct v4l2_ctrl_handler ctrl_handler ;
2009-03-06 19:48:47 +03:00
int io ;
2012-07-10 15:26:04 +04:00
bool is_fm_band ;
u32 curfreq ;
2009-03-06 19:48:47 +03:00
int tunestat ;
int sigstrength ;
wait_queue_head_t read_queue ;
struct timer_list readtimer ;
2012-07-02 16:46:46 +04:00
u8 rdsin , rdsout , rdsstat ;
2009-03-06 19:48:47 +03:00
unsigned char rdsbuf [ RDS_BUFFER ] ;
struct mutex lock ;
int reading ;
2007-05-07 23:17:16 +04:00
} ;
2009-03-06 19:48:47 +03:00
static struct cadet cadet_card ;
2005-04-17 02:20:36 +04:00
/*
* Signal Strength Threshold Values
2006-04-08 23:06:16 +04:00
* The V4L API spec does not define any particular unit for the signal
2005-04-17 02:20:36 +04:00
* strength value . These values are in microvolts of RF at the tuner ' s input .
*/
2012-07-10 15:26:04 +04:00
static u16 sigtable [ 2 ] [ 4 ] = {
{ 1835 , 2621 , 4128 , 65535 } ,
2012-07-02 16:46:46 +04:00
{ 2185 , 4369 , 13107 , 65535 } ,
2009-03-06 19:48:47 +03:00
} ;
2005-04-17 02:20:36 +04:00
2013-03-19 11:09:26 +04:00
static const struct v4l2_frequency_band bands [ ] = {
{
. index = 0 ,
. type = V4L2_TUNER_RADIO ,
. capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS ,
. rangelow = 8320 , /* 520 kHz */
. rangehigh = 26400 , /* 1650 kHz */
. modulation = V4L2_BAND_MODULATION_AM ,
} , {
. index = 1 ,
. type = V4L2_TUNER_RADIO ,
. capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS |
V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_LOW |
V4L2_TUNER_CAP_FREQ_BANDS ,
. rangelow = 1400000 , /* 87.5 MHz */
. rangehigh = 1728000 , /* 108.0 MHz */
. modulation = V4L2_BAND_MODULATION_FM ,
} ,
} ;
2005-04-17 02:20:36 +04:00
2009-03-06 19:48:47 +03:00
static int cadet_getstereo ( struct cadet * dev )
2005-04-17 02:20:36 +04:00
{
2006-08-08 16:10:12 +04:00
int ret = V4L2_TUNER_SUB_MONO ;
2009-03-06 19:48:47 +03:00
2012-07-10 15:26:04 +04:00
if ( ! dev - > is_fm_band ) /* Only FM has stereo capability! */
2006-08-08 16:10:12 +04:00
return V4L2_TUNER_SUB_MONO ;
2005-04-17 02:20:36 +04:00
2009-03-06 19:48:47 +03:00
outb ( 7 , dev - > io ) ; /* Select tuner control */
if ( ( inb ( dev - > io + 1 ) & 0x40 ) = = 0 )
2006-08-08 16:10:12 +04:00
ret = V4L2_TUNER_SUB_STEREO ;
2006-04-08 23:06:16 +04:00
return ret ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:48:47 +03:00
static unsigned cadet_gettune ( struct cadet * dev )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:48:47 +03:00
int curvol , i ;
unsigned fifo = 0 ;
2005-04-17 02:20:36 +04:00
2006-04-08 23:06:16 +04:00
/*
* Prepare for read
*/
2005-04-17 02:20:36 +04:00
2009-03-06 19:48:47 +03:00
outb ( 7 , dev - > io ) ; /* Select tuner control */
curvol = inb ( dev - > io + 1 ) ; /* Save current volume/mute setting */
outb ( 0x00 , dev - > io + 1 ) ; /* Ensure WRITE-ENABLE is LOW */
dev - > tunestat = 0xffff ;
2005-04-17 02:20:36 +04:00
2006-04-08 23:06:16 +04:00
/*
* Read the shift register
*/
2009-03-06 19:48:47 +03:00
for ( i = 0 ; i < 25 ; i + + ) {
fifo = ( fifo < < 1 ) | ( ( inb ( dev - > io + 1 ) > > 7 ) & 0x01 ) ;
if ( i < 24 ) {
outb ( 0x01 , dev - > io + 1 ) ;
dev - > tunestat & = inb ( dev - > io + 1 ) ;
outb ( 0x00 , dev - > io + 1 ) ;
2006-04-08 23:06:16 +04:00
}
}
/*
* Restore volume / mute setting
*/
2009-03-06 19:48:47 +03:00
outb ( curvol , dev - > io + 1 ) ;
2005-04-17 02:20:36 +04:00
return fifo ;
}
2009-03-06 19:48:47 +03:00
static unsigned cadet_getfreq ( struct cadet * dev )
2005-04-17 02:20:36 +04:00
{
2006-04-08 23:06:16 +04:00
int i ;
2009-03-06 19:48:47 +03:00
unsigned freq = 0 , test , fifo = 0 ;
2005-04-17 02:20:36 +04:00
/*
* Read current tuning
*/
2009-03-06 19:48:47 +03:00
fifo = cadet_gettune ( dev ) ;
2005-04-17 02:20:36 +04:00
2006-04-08 23:06:16 +04:00
/*
* Convert to actual frequency
*/
2012-07-10 15:26:04 +04:00
if ( ! dev - > is_fm_band ) /* AM */
return ( ( fifo & 0x7fff ) - 450 ) * 16 ;
test = 12500 ;
for ( i = 0 ; i < 14 ; i + + ) {
if ( ( fifo & 0x01 ) ! = 0 )
freq + = test ;
test = test < < 1 ;
fifo = fifo > > 1 ;
2005-04-17 02:20:36 +04:00
}
2012-07-10 15:26:04 +04:00
freq - = 10700000 ; /* IF frequency is 10.7 MHz */
freq = ( freq * 16 ) / 1000 ; /* Make it 1/16 kHz */
2006-04-08 23:06:16 +04:00
return freq ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:48:47 +03:00
static void cadet_settune ( struct cadet * dev , unsigned fifo )
2005-04-17 02:20:36 +04:00
{
2006-04-08 23:06:16 +04:00
int i ;
unsigned test ;
2005-04-17 02:20:36 +04:00
2009-03-06 19:48:47 +03:00
outb ( 7 , dev - > io ) ; /* Select tuner control */
2005-04-17 02:20:36 +04:00
/*
* Write the shift register
*/
2009-03-06 19:48:47 +03:00
test = 0 ;
test = ( fifo > > 23 ) & 0x02 ; /* Align data for SDO */
test | = 0x1c ; /* SDM=1, SWE=1, SEN=1, SCK=0 */
outb ( 7 , dev - > io ) ; /* Select tuner control */
outb ( test , dev - > io + 1 ) ; /* Initialize for write */
for ( i = 0 ; i < 25 ; i + + ) {
test | = 0x01 ; /* Toggle SCK High */
outb ( test , dev - > io + 1 ) ;
test & = 0xfe ; /* Toggle SCK Low */
outb ( test , dev - > io + 1 ) ;
fifo = fifo < < 1 ; /* Prepare the next bit */
test = 0x1c | ( ( fifo > > 23 ) & 0x02 ) ;
outb ( test , dev - > io + 1 ) ;
2005-04-17 02:20:36 +04:00
}
}
2009-03-06 19:48:47 +03:00
static void cadet_setfreq ( struct cadet * dev , unsigned freq )
2005-04-17 02:20:36 +04:00
{
2006-04-08 23:06:16 +04:00
unsigned fifo ;
2009-03-06 19:48:47 +03:00
int i , j , test ;
2006-04-08 23:06:16 +04:00
int curvol ;
2005-04-17 02:20:36 +04:00
2013-03-19 11:09:26 +04:00
freq = clamp ( freq , bands [ dev - > is_fm_band ] . rangelow ,
bands [ dev - > is_fm_band ] . rangehigh ) ;
2012-07-10 15:26:04 +04:00
dev - > curfreq = freq ;
2006-04-08 23:06:16 +04:00
/*
* Formulate a fifo command
*/
2009-03-06 19:48:47 +03:00
fifo = 0 ;
2012-07-10 15:26:04 +04:00
if ( dev - > is_fm_band ) { /* FM */
2009-03-06 19:48:47 +03:00
test = 102400 ;
2012-07-02 16:36:39 +04:00
freq = freq / 16 ; /* Make it kHz */
2009-03-06 19:48:47 +03:00
freq + = 10700 ; /* IF is 10700 kHz */
for ( i = 0 ; i < 14 ; i + + ) {
fifo = fifo < < 1 ;
if ( freq > = test ) {
fifo | = 0x01 ;
freq - = test ;
2006-04-08 23:06:16 +04:00
}
2009-03-06 19:48:47 +03:00
test = test > > 1 ;
2006-04-08 23:06:16 +04:00
}
2012-07-10 15:26:04 +04:00
} else { /* AM */
fifo = ( freq / 16 ) + 450 ; /* Make it kHz */
fifo | = 0x100000 ; /* Select AM Band */
2005-04-17 02:20:36 +04:00
}
2006-04-08 23:06:16 +04:00
/*
* Save current volume / mute setting
*/
2005-04-17 02:20:36 +04:00
2009-03-06 19:48:47 +03:00
outb ( 7 , dev - > io ) ; /* Select tuner control */
curvol = inb ( dev - > io + 1 ) ;
2005-04-17 02:20:36 +04:00
/*
* Tune the card
*/
2009-03-06 19:48:47 +03:00
for ( j = 3 ; j > - 1 ; j - - ) {
cadet_settune ( dev , fifo | ( j < < 16 ) ) ;
2006-04-08 23:06:16 +04:00
2009-03-06 19:48:47 +03:00
outb ( 7 , dev - > io ) ; /* Select tuner control */
outb ( curvol , dev - > io + 1 ) ;
2006-04-08 23:06:16 +04:00
2005-04-17 02:20:36 +04:00
msleep ( 100 ) ;
2009-03-06 19:48:47 +03:00
cadet_gettune ( dev ) ;
if ( ( dev - > tunestat & 0x40 ) = = 0 ) { /* Tuned */
2012-07-10 15:26:04 +04:00
dev - > sigstrength = sigtable [ dev - > is_fm_band ] [ j ] ;
2012-07-02 16:46:46 +04:00
goto reset_rds ;
2005-04-17 02:20:36 +04:00
}
}
2009-03-06 19:48:47 +03:00
dev - > sigstrength = 0 ;
2012-07-02 16:46:46 +04:00
reset_rds :
outb ( 3 , dev - > io ) ;
outb ( inb ( dev - > io + 1 ) & 0x7f , dev - > io + 1 ) ;
2005-04-17 02:20:36 +04:00
}
2014-02-10 14:21:36 +04:00
static bool cadet_has_rds_data ( struct cadet * dev )
{
bool result ;
mutex_lock ( & dev - > lock ) ;
result = dev - > rdsin ! = dev - > rdsout ;
mutex_unlock ( & dev - > lock ) ;
return result ;
}
2005-04-17 02:20:36 +04:00
2017-10-24 18:23:03 +03:00
static void cadet_handler ( struct timer_list * t )
2005-04-17 02:20:36 +04:00
{
2017-10-24 18:23:03 +03:00
struct cadet * dev = from_timer ( dev , t , readtimer ) ;
2005-04-17 02:20:36 +04:00
2009-03-06 19:48:47 +03:00
/* Service the RDS fifo */
if ( mutex_trylock ( & dev - > lock ) ) {
outb ( 0x3 , dev - > io ) ; /* Select RDS Decoder Control */
if ( ( inb ( dev - > io + 1 ) & 0x20 ) ! = 0 )
2014-02-10 14:21:36 +04:00
pr_err ( " cadet: RDS fifo overflow \n " ) ;
2009-03-06 19:48:47 +03:00
outb ( 0x80 , dev - > io ) ; /* Select RDS fifo */
2014-02-10 14:21:36 +04:00
2009-03-06 19:48:47 +03:00
while ( ( inb ( dev - > io ) & 0x80 ) ! = 0 ) {
dev - > rdsbuf [ dev - > rdsin ] = inb ( dev - > io + 1 ) ;
2014-02-10 14:21:36 +04:00
if ( dev - > rdsin + 1 ! = dev - > rdsout )
2009-03-06 19:48:47 +03:00
dev - > rdsin + + ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:48:47 +03:00
mutex_unlock ( & dev - > lock ) ;
2005-04-17 02:20:36 +04:00
}
/*
* Service pending read
*/
2014-02-10 14:21:36 +04:00
if ( cadet_has_rds_data ( dev ) )
2009-03-06 19:48:47 +03:00
wake_up_interruptible ( & dev - > read_queue ) ;
2005-04-17 02:20:36 +04:00
2006-04-08 23:06:16 +04:00
/*
2005-04-17 02:20:36 +04:00
* Clean up and exit
*/
2009-03-06 19:48:47 +03:00
dev - > readtimer . expires = jiffies + msecs_to_jiffies ( 50 ) ;
add_timer ( & dev - > readtimer ) ;
2005-04-17 02:20:36 +04:00
}
2012-07-02 16:46:46 +04:00
static void cadet_start_rds ( struct cadet * dev )
{
dev - > rdsstat = 1 ;
outb ( 0x80 , dev - > io ) ; /* Select RDS fifo */
2017-10-24 18:23:03 +03:00
timer_setup ( & dev - > readtimer , cadet_handler , 0 ) ;
2012-07-02 16:46:46 +04:00
dev - > readtimer . expires = jiffies + msecs_to_jiffies ( 50 ) ;
add_timer ( & dev - > readtimer ) ;
}
2005-04-17 02:20:36 +04:00
2009-03-06 19:48:47 +03:00
static ssize_t cadet_read ( struct file * file , char __user * data , size_t count , loff_t * ppos )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:48:47 +03:00
struct cadet * dev = video_drvdata ( file ) ;
2005-04-17 02:20:36 +04:00
unsigned char readbuf [ RDS_BUFFER ] ;
2009-03-06 19:48:47 +03:00
int i = 0 ;
2010-11-14 15:43:52 +03:00
mutex_lock ( & dev - > lock ) ;
2012-07-02 16:46:46 +04:00
if ( dev - > rdsstat = = 0 )
cadet_start_rds ( dev ) ;
2014-02-10 14:21:36 +04:00
mutex_unlock ( & dev - > lock ) ;
if ( ! cadet_has_rds_data ( dev ) & & ( file - > f_flags & O_NONBLOCK ) )
return - EWOULDBLOCK ;
i = wait_event_interruptible ( dev - > read_queue , cadet_has_rds_data ( dev ) ) ;
if ( i )
return i ;
mutex_lock ( & dev - > lock ) ;
2009-03-06 19:48:47 +03:00
while ( i < count & & dev - > rdsin ! = dev - > rdsout )
readbuf [ i + + ] = dev - > rdsbuf [ dev - > rdsout + + ] ;
2014-02-10 14:21:36 +04:00
mutex_unlock ( & dev - > lock ) ;
2005-04-17 02:20:36 +04:00
2012-07-02 16:46:46 +04:00
if ( i & & copy_to_user ( data , readbuf , i ) )
2014-02-10 14:21:36 +04:00
return - EFAULT ;
2005-04-17 02:20:36 +04:00
return i ;
}
2007-05-07 23:17:16 +04:00
static int vidioc_querycap ( struct file * file , void * priv ,
struct v4l2_capability * v )
{
2018-09-10 15:19:14 +03:00
strscpy ( v - > driver , " ADS Cadet " , sizeof ( v - > driver ) ) ;
strscpy ( v - > card , " ADS Cadet " , sizeof ( v - > card ) ) ;
strscpy ( v - > bus_info , " ISA:radio-cadet " , sizeof ( v - > bus_info ) ) ;
2012-07-02 16:36:39 +04:00
v - > device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
2009-06-20 13:19:09 +04:00
V4L2_CAP_READWRITE | V4L2_CAP_RDS_CAPTURE ;
2012-07-02 16:36:39 +04:00
v - > capabilities = v - > device_caps | V4L2_CAP_DEVICE_CAPS ;
2007-05-07 23:17:16 +04:00
return 0 ;
}
2005-04-17 02:20:36 +04:00
2007-05-07 23:17:16 +04:00
static int vidioc_g_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * v )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:48:47 +03:00
struct cadet * dev = video_drvdata ( file ) ;
2012-07-10 15:26:04 +04:00
if ( v - > index )
return - EINVAL ;
2007-05-07 23:17:16 +04:00
v - > type = V4L2_TUNER_RADIO ;
2018-09-10 15:19:14 +03:00
strscpy ( v - > name , " Radio " , sizeof ( v - > name ) ) ;
2012-07-10 15:26:04 +04:00
v - > capability = bands [ 0 ] . capability | bands [ 1 ] . capability ;
v - > rangelow = bands [ 0 ] . rangelow ; /* 520 kHz (start of AM band) */
v - > rangehigh = bands [ 1 ] . rangehigh ; /* 108.0 MHz (end of FM band) */
if ( dev - > is_fm_band ) {
2009-03-06 19:48:47 +03:00
v - > rxsubchans = cadet_getstereo ( dev ) ;
2012-07-02 16:46:46 +04:00
outb ( 3 , dev - > io ) ;
outb ( inb ( dev - > io + 1 ) & 0x7f , dev - > io + 1 ) ;
mdelay ( 100 ) ;
outb ( 3 , dev - > io ) ;
if ( inb ( dev - > io + 1 ) & 0x80 )
v - > rxsubchans | = V4L2_TUNER_SUB_RDS ;
2012-07-10 15:26:04 +04:00
} else {
2007-05-07 23:17:16 +04:00
v - > rangelow = 8320 ; /* 520 kHz */
v - > rangehigh = 26400 ; /* 1650 kHz */
v - > rxsubchans = V4L2_TUNER_SUB_MONO ;
}
2012-07-10 15:26:04 +04:00
v - > audmode = V4L2_TUNER_MODE_STEREO ;
2009-03-06 19:48:47 +03:00
v - > signal = dev - > sigstrength ; /* We might need to modify scaling of this */
2007-05-07 23:17:16 +04:00
return 0 ;
}
2006-08-08 16:10:12 +04:00
2007-05-07 23:17:16 +04:00
static int vidioc_s_tuner ( struct file * file , void * priv ,
2013-03-15 13:10:06 +04:00
const struct v4l2_tuner * v )
2007-05-07 23:17:16 +04:00
{
2012-07-10 15:26:04 +04:00
return v - > index ? - EINVAL : 0 ;
}
static int vidioc_enum_freq_bands ( struct file * file , void * priv ,
struct v4l2_frequency_band * band )
{
if ( band - > tuner )
return - EINVAL ;
if ( band - > index > = ARRAY_SIZE ( bands ) )
2007-05-07 23:17:16 +04:00
return - EINVAL ;
2012-07-10 15:26:04 +04:00
* band = bands [ band - > index ] ;
2007-05-07 23:17:16 +04:00
return 0 ;
}
2006-08-08 16:10:12 +04:00
2007-05-07 23:17:16 +04:00
static int vidioc_g_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * f )
{
2009-03-06 19:48:47 +03:00
struct cadet * dev = video_drvdata ( file ) ;
2012-07-10 15:26:04 +04:00
if ( f - > tuner )
2012-07-02 16:36:39 +04:00
return - EINVAL ;
2007-05-07 23:17:16 +04:00
f - > type = V4L2_TUNER_RADIO ;
2012-07-10 15:26:04 +04:00
f - > frequency = dev - > curfreq ;
2007-05-07 23:17:16 +04:00
return 0 ;
}
static int vidioc_s_frequency ( struct file * file , void * priv ,
2013-03-19 11:09:26 +04:00
const struct v4l2_frequency * f )
2007-05-07 23:17:16 +04:00
{
2009-03-06 19:48:47 +03:00
struct cadet * dev = video_drvdata ( file ) ;
2012-07-10 15:26:04 +04:00
if ( f - > tuner )
2007-05-07 23:17:16 +04:00
return - EINVAL ;
2012-07-10 15:26:04 +04:00
dev - > is_fm_band =
f - > frequency > = ( bands [ 0 ] . rangehigh + bands [ 1 ] . rangelow ) / 2 ;
2009-03-06 19:48:47 +03:00
cadet_setfreq ( dev , f - > frequency ) ;
2007-05-07 23:17:16 +04:00
return 0 ;
}
2012-07-02 16:36:39 +04:00
static int cadet_s_ctrl ( struct v4l2_ctrl * ctrl )
2007-05-07 23:17:16 +04:00
{
2012-07-02 16:36:39 +04:00
struct cadet * dev = container_of ( ctrl - > handler , struct cadet , ctrl_handler ) ;
2009-03-06 19:48:47 +03:00
switch ( ctrl - > id ) {
2012-07-02 16:36:39 +04:00
case V4L2_CID_AUDIO_MUTE :
outb ( 7 , dev - > io ) ; /* Select tuner control */
if ( ctrl - > val )
outb ( 0x00 , dev - > io + 1 ) ;
2007-05-07 23:17:16 +04:00
else
2012-07-02 16:36:39 +04:00
outb ( 0x20 , dev - > io + 1 ) ;
return 0 ;
2007-05-07 23:17:16 +04:00
}
2012-07-02 16:36:39 +04:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:48:47 +03:00
static int cadet_open ( struct file * file )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:48:47 +03:00
struct cadet * dev = video_drvdata ( file ) ;
2012-07-02 16:36:39 +04:00
int err ;
2009-03-06 19:48:47 +03:00
2010-11-14 15:43:52 +03:00
mutex_lock ( & dev - > lock ) ;
2012-07-02 16:36:39 +04:00
err = v4l2_fh_open ( file ) ;
if ( err )
goto fail ;
if ( v4l2_fh_is_singular_file ( file ) )
2009-03-06 19:48:47 +03:00
init_waitqueue_head ( & dev - > read_queue ) ;
2012-07-02 16:36:39 +04:00
fail :
2010-11-14 15:43:52 +03:00
mutex_unlock ( & dev - > lock ) ;
2012-07-02 16:36:39 +04:00
return err ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:48:47 +03:00
static int cadet_release ( struct file * file )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:48:47 +03:00
struct cadet * dev = video_drvdata ( file ) ;
2010-11-14 15:43:52 +03:00
mutex_lock ( & dev - > lock ) ;
2012-07-02 16:36:39 +04:00
if ( v4l2_fh_is_singular_file ( file ) & & dev - > rdsstat ) {
2009-03-06 19:48:47 +03:00
del_timer_sync ( & dev - > readtimer ) ;
dev - > rdsstat = 0 ;
2006-08-08 16:10:12 +04:00
}
2012-07-02 16:36:39 +04:00
v4l2_fh_release ( file ) ;
2010-11-14 15:43:52 +03:00
mutex_unlock ( & dev - > lock ) ;
2006-08-08 16:10:12 +04:00
return 0 ;
}
2017-07-03 10:02:56 +03:00
static __poll_t cadet_poll ( struct file * file , struct poll_table_struct * wait )
2006-08-08 16:10:12 +04:00
{
2009-03-06 19:48:47 +03:00
struct cadet * dev = video_drvdata ( file ) ;
2017-07-03 10:14:15 +03:00
__poll_t req_events = poll_requested_events ( wait ) ;
2017-07-03 10:02:56 +03:00
__poll_t res = v4l2_ctrl_poll ( file , wait ) ;
2009-03-06 19:48:47 +03:00
poll_wait ( file , & dev - > read_queue , wait ) ;
2018-02-12 01:34:03 +03:00
if ( dev - > rdsstat = = 0 & & ( req_events & ( EPOLLIN | EPOLLRDNORM ) ) ) {
2012-07-02 16:46:46 +04:00
mutex_lock ( & dev - > lock ) ;
if ( dev - > rdsstat = = 0 )
cadet_start_rds ( dev ) ;
mutex_unlock ( & dev - > lock ) ;
}
2014-02-10 14:21:36 +04:00
if ( cadet_has_rds_data ( dev ) )
2018-02-12 01:34:03 +03:00
res | = EPOLLIN | EPOLLRDNORM ;
2012-07-02 16:36:39 +04:00
return res ;
2005-04-17 02:20:36 +04:00
}
2008-12-30 12:58:20 +03:00
static const struct v4l2_file_operations cadet_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. open = cadet_open ,
2018-01-04 21:08:56 +03:00
. release = cadet_release ,
2005-04-17 02:20:36 +04:00
. read = cadet_read ,
2010-11-14 15:43:52 +03:00
. unlocked_ioctl = video_ioctl2 ,
2006-08-08 16:10:12 +04:00
. poll = cadet_poll ,
2005-04-17 02:20:36 +04:00
} ;
2008-07-21 09:57:38 +04:00
static const struct v4l2_ioctl_ops cadet_ioctl_ops = {
2007-05-07 23:17:16 +04:00
. vidioc_querycap = vidioc_querycap ,
. vidioc_g_tuner = vidioc_g_tuner ,
. vidioc_s_tuner = vidioc_s_tuner ,
. vidioc_g_frequency = vidioc_g_frequency ,
. vidioc_s_frequency = vidioc_s_frequency ,
2012-07-10 15:26:04 +04:00
. vidioc_enum_freq_bands = vidioc_enum_freq_bands ,
2012-07-02 16:36:39 +04:00
. vidioc_log_status = v4l2_ctrl_log_status ,
. vidioc_subscribe_event = v4l2_ctrl_subscribe_event ,
. vidioc_unsubscribe_event = v4l2_event_unsubscribe ,
} ;
static const struct v4l2_ctrl_ops cadet_ctrl_ops = {
. s_ctrl = cadet_s_ctrl ,
2005-04-17 02:20:36 +04:00
} ;
2008-04-01 04:21:48 +04:00
# ifdef CONFIG_PNP
2017-08-16 08:30:10 +03:00
static const struct pnp_device_id cadet_pnp_devices [ ] = {
2005-04-17 02:20:36 +04:00
/* ADS Cadet AM/FM Radio Card */
{ . id = " MSM0c24 " , . driver_data = 0 } ,
{ . id = " " }
} ;
MODULE_DEVICE_TABLE ( pnp , cadet_pnp_devices ) ;
2009-03-06 19:48:47 +03:00
static int cadet_pnp_probe ( struct pnp_dev * dev , const struct pnp_device_id * dev_id )
2005-04-17 02:20:36 +04:00
{
if ( ! dev )
return - ENODEV ;
/* only support one device */
if ( io > 0 )
return - EBUSY ;
2009-03-06 19:48:47 +03:00
if ( ! pnp_port_valid ( dev , 0 ) )
2005-04-17 02:20:36 +04:00
return - ENODEV ;
io = pnp_port_start ( dev , 0 ) ;
2009-03-06 19:48:47 +03:00
printk ( KERN_INFO " radio-cadet: PnP reports device at %#x \n " , io ) ;
2005-04-17 02:20:36 +04:00
return io ;
}
static struct pnp_driver cadet_pnp_driver = {
. name = " radio-cadet " ,
. id_table = cadet_pnp_devices ,
. probe = cadet_pnp_probe ,
. remove = NULL ,
} ;
2008-04-01 04:21:48 +04:00
# else
static struct pnp_driver cadet_pnp_driver ;
# endif
2009-03-06 19:48:47 +03:00
static void cadet_probe ( struct cadet * dev )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:48:47 +03:00
static int iovals [ 8 ] = { 0x330 , 0x332 , 0x334 , 0x336 , 0x338 , 0x33a , 0x33c , 0x33e } ;
2005-04-17 02:20:36 +04:00
int i ;
2009-03-06 19:48:47 +03:00
for ( i = 0 ; i < 8 ; i + + ) {
dev - > io = iovals [ i ] ;
if ( request_region ( dev - > io , 2 , " cadet-probe " ) ) {
2012-07-10 15:26:04 +04:00
cadet_setfreq ( dev , bands [ 1 ] . rangelow ) ;
if ( cadet_getfreq ( dev ) = = bands [ 1 ] . rangelow ) {
2009-03-06 19:48:47 +03:00
release_region ( dev - > io , 2 ) ;
return ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:48:47 +03:00
release_region ( dev - > io , 2 ) ;
2005-04-17 02:20:36 +04:00
}
}
2009-03-06 19:48:47 +03:00
dev - > io = - 1 ;
2005-04-17 02:20:36 +04:00
}
2006-04-08 23:06:16 +04:00
/*
2005-04-17 02:20:36 +04:00
* io should only be set if the user has used something like
* isapnp ( the userspace program ) to initialize this card for us
*/
static int __init cadet_init ( void )
{
2009-03-06 19:48:47 +03:00
struct cadet * dev = & cadet_card ;
struct v4l2_device * v4l2_dev = & dev - > v4l2_dev ;
2012-07-02 16:36:39 +04:00
struct v4l2_ctrl_handler * hdl ;
int res = - ENODEV ;
2006-04-08 23:06:16 +04:00
2018-09-10 15:19:14 +03:00
strscpy ( v4l2_dev - > name , " cadet " , sizeof ( v4l2_dev - > name ) ) ;
2009-03-06 19:48:47 +03:00
mutex_init ( & dev - > lock ) ;
/* If a probe was requested then probe ISAPnP first (safest) */
2005-04-17 02:20:36 +04:00
if ( io < 0 )
pnp_register_driver ( & cadet_pnp_driver ) ;
2009-03-06 19:48:47 +03:00
dev - > io = io ;
2005-04-17 02:20:36 +04:00
2009-03-06 19:48:47 +03:00
/* If that fails then probe unsafely if probe is requested */
if ( dev - > io < 0 )
cadet_probe ( dev ) ;
2006-04-08 23:06:16 +04:00
2009-03-06 19:48:47 +03:00
/* Else we bail out */
if ( dev - > io < 0 ) {
2006-04-08 23:06:16 +04:00
# ifdef MODULE
2009-03-09 14:11:21 +03:00
v4l2_err ( v4l2_dev , " you must set an I/O address with io=0x330, 0x332, 0x334, \n " ) ;
v4l2_err ( v4l2_dev , " 0x336, 0x338, 0x33a, 0x33c or 0x33e \n " ) ;
2005-04-17 02:20:36 +04:00
# endif
2006-04-08 23:06:16 +04:00
goto fail ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:48:47 +03:00
if ( ! request_region ( dev - > io , 2 , " cadet " ) )
goto fail ;
res = v4l2_device_register ( NULL , v4l2_dev ) ;
if ( res < 0 ) {
release_region ( dev - > io , 2 ) ;
v4l2_err ( v4l2_dev , " could not register v4l2_device \n " ) ;
2005-04-17 02:20:36 +04:00
goto fail ;
2009-03-06 19:48:47 +03:00
}
2012-07-02 16:36:39 +04:00
hdl = & dev - > ctrl_handler ;
v4l2_ctrl_handler_init ( hdl , 2 ) ;
v4l2_ctrl_new_std ( hdl , & cadet_ctrl_ops ,
V4L2_CID_AUDIO_MUTE , 0 , 1 , 1 , 1 ) ;
v4l2_dev - > ctrl_handler = hdl ;
if ( hdl - > error ) {
res = hdl - > error ;
v4l2_err ( v4l2_dev , " Could not register controls \n " ) ;
goto err_hdl ;
}
2012-07-10 15:26:04 +04:00
dev - > is_fm_band = true ;
dev - > curfreq = bands [ dev - > is_fm_band ] . rangelow ;
cadet_setfreq ( dev , dev - > curfreq ) ;
2018-09-10 15:19:14 +03:00
strscpy ( dev - > vdev . name , v4l2_dev - > name , sizeof ( dev - > vdev . name ) ) ;
2009-03-06 19:48:47 +03:00
dev - > vdev . v4l2_dev = v4l2_dev ;
dev - > vdev . fops = & cadet_fops ;
dev - > vdev . ioctl_ops = & cadet_ioctl_ops ;
dev - > vdev . release = video_device_release_empty ;
2012-07-02 16:36:39 +04:00
dev - > vdev . lock = & dev - > lock ;
2009-03-06 19:48:47 +03:00
video_set_drvdata ( & dev - > vdev , dev ) ;
2012-09-06 18:23:52 +04:00
res = video_register_device ( & dev - > vdev , VFL_TYPE_RADIO , radio_nr ) ;
if ( res < 0 )
2012-07-02 16:36:39 +04:00
goto err_hdl ;
2009-03-06 19:48:47 +03:00
v4l2_info ( v4l2_dev , " ADS Cadet Radio Card at 0x%x \n " , dev - > io ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
2012-07-02 16:36:39 +04:00
err_hdl :
v4l2_ctrl_handler_free ( hdl ) ;
v4l2_device_unregister ( v4l2_dev ) ;
release_region ( dev - > io , 2 ) ;
2005-04-17 02:20:36 +04:00
fail :
pnp_unregister_driver ( & cadet_pnp_driver ) ;
2012-07-02 16:36:39 +04:00
return res ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:48:47 +03:00
static void __exit cadet_exit ( void )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:48:47 +03:00
struct cadet * dev = & cadet_card ;
video_unregister_device ( & dev - > vdev ) ;
2012-07-02 16:36:39 +04:00
v4l2_ctrl_handler_free ( & dev - > ctrl_handler ) ;
2009-03-06 19:48:47 +03:00
v4l2_device_unregister ( & dev - > v4l2_dev ) ;
2012-07-02 16:46:46 +04:00
outb ( 7 , dev - > io ) ; /* Mute */
outb ( 0x00 , dev - > io + 1 ) ;
2009-03-06 19:48:47 +03:00
release_region ( dev - > io , 2 ) ;
2005-04-17 02:20:36 +04:00
pnp_unregister_driver ( & cadet_pnp_driver ) ;
}
module_init ( cadet_init ) ;
2009-03-06 19:48:47 +03:00
module_exit ( cadet_exit ) ;
2005-04-17 02:20:36 +04:00