2005-04-17 02:20:36 +04:00
/* SF16FMI radio driver for Linux radio support
* heavily based on rtrack driver . . .
* ( c ) 1997 M . Kirkwood
* ( c ) 1998 Petr Vandrovec , vandrove @ vc . cvut . cz
*
2008-10-27 21:13:47 +03:00
* Fitted to new interface by Alan Cox < alan @ lxorguk . ukuu . org . uk >
2005-04-17 02:20:36 +04:00
* Made working and cleaned up functions < mikael . hedin @ irf . se >
* Support for ISAPnP by Ladislav Michl < ladis @ psi . cz >
*
* Notes on the hardware
*
* Frequency control is done digitally - - ie out ( port , encodefreq ( 95.8 ) ) ;
* No volume control - only mute / unmute - you have to use line volume
* control on SB - part of SF16FMI
2006-04-08 23:06:16 +04:00
*
2006-08-08 16:10:02 +04:00
* Converted to V4L2 API by Mauro Carvalho Chehab < mchehab @ infradead . org >
2005-04-17 02:20:36 +04:00
*/
2006-08-08 16:10:09 +04:00
# include <linux/version.h>
2005-04-17 02:20:36 +04:00
# include <linux/kernel.h> /* __setup */
# include <linux/module.h> /* Modules */
# 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 */
# include <linux/isapnp.h>
2006-02-07 11:49:14 +03:00
# include <linux/mutex.h>
2009-03-06 19:52:34 +03:00
# include <linux/videodev2.h> /* kernel radio structs */
# include <linux/io.h> /* outb, outb_p */
# include <media/v4l2-device.h>
# include <media/v4l2-ioctl.h>
2005-04-17 02:20:36 +04:00
2009-03-06 19:52:34 +03:00
MODULE_AUTHOR ( " Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood " ) ;
MODULE_DESCRIPTION ( " A driver for the SF16MI radio. " ) ;
MODULE_LICENSE ( " GPL " ) ;
2006-08-08 16:10:02 +04:00
2009-03-06 19:52:34 +03:00
static int io = - 1 ;
static int radio_nr = - 1 ;
module_param ( io , int , 0 ) ;
MODULE_PARM_DESC ( io , " I/O address of the SF16MI card (0x284 or 0x384) " ) ;
module_param ( radio_nr , int , 0 ) ;
# define RADIO_VERSION KERNEL_VERSION(0, 0, 2)
2006-08-08 16:10:02 +04:00
2009-03-06 19:52:34 +03:00
struct fmi
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:52:34 +03:00
struct v4l2_device v4l2_dev ;
struct video_device vdev ;
int io ;
2006-04-08 23:06:16 +04:00
int curvol ; /* 1 or 0 */
unsigned long curfreq ; /* freq in kHz */
2009-03-06 19:52:34 +03:00
struct mutex lock ;
2005-04-17 02:20:36 +04:00
} ;
2009-03-06 19:52:34 +03:00
static struct fmi fmi_card ;
static struct pnp_dev * dev ;
2005-04-17 02:20:36 +04:00
/* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */
2009-05-02 19:04:13 +04:00
/* It is only useful to give freq in interval of 800 (=0.05Mhz),
2006-04-08 23:06:16 +04:00
* other bits will be truncated , e . g 92.7400016 - > 92.7 , but
2005-04-17 02:20:36 +04:00
* 92.7400017 - > 92.75
*/
2009-03-06 19:52:34 +03:00
# define RSF16_ENCODE(x) ((x) / 800 + 214)
# define RSF16_MINFREQ (87 * 16000)
# define RSF16_MAXFREQ (108 * 16000)
2005-04-17 02:20:36 +04:00
2009-03-06 19:52:34 +03:00
static void outbits ( int bits , unsigned int data , int io )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:52:34 +03:00
while ( bits - - ) {
if ( data & 1 ) {
outb ( 5 , io ) ;
2005-04-17 02:20:36 +04:00
udelay ( 6 ) ;
2009-03-06 19:52:34 +03:00
outb ( 7 , io ) ;
2005-04-17 02:20:36 +04:00
udelay ( 6 ) ;
} else {
2009-03-06 19:52:34 +03:00
outb ( 1 , io ) ;
2005-04-17 02:20:36 +04:00
udelay ( 6 ) ;
2009-03-06 19:52:34 +03:00
outb ( 3 , io ) ;
2005-04-17 02:20:36 +04:00
udelay ( 6 ) ;
}
2009-03-06 19:52:34 +03:00
data > > = 1 ;
2005-04-17 02:20:36 +04:00
}
}
2009-03-06 19:52:34 +03:00
static inline void fmi_mute ( struct fmi * fmi )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:52:34 +03:00
mutex_lock ( & fmi - > lock ) ;
outb ( 0x00 , fmi - > io ) ;
mutex_unlock ( & fmi - > lock ) ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:52:34 +03:00
static inline void fmi_unmute ( struct fmi * fmi )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:52:34 +03:00
mutex_lock ( & fmi - > lock ) ;
outb ( 0x08 , fmi - > io ) ;
mutex_unlock ( & fmi - > lock ) ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:52:34 +03:00
static inline int fmi_setfreq ( struct fmi * fmi , unsigned long freq )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:52:34 +03:00
mutex_lock ( & fmi - > lock ) ;
fmi - > curfreq = freq ;
2005-04-17 02:20:36 +04:00
2009-03-06 19:52:34 +03:00
outbits ( 16 , RSF16_ENCODE ( freq ) , fmi - > io ) ;
outbits ( 8 , 0xC0 , fmi - > io ) ;
2005-04-17 02:20:36 +04:00
msleep ( 143 ) ; /* was schedule_timeout(HZ/7) */
2009-03-06 19:52:34 +03:00
mutex_unlock ( & fmi - > lock ) ;
if ( fmi - > curvol )
fmi_unmute ( fmi ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2009-03-06 19:52:34 +03:00
static inline int fmi_getsigstr ( struct fmi * fmi )
2005-04-17 02:20:36 +04:00
{
int val ;
int res ;
2006-04-08 23:06:16 +04:00
2009-03-06 19:52:34 +03:00
mutex_lock ( & fmi - > lock ) ;
val = fmi - > curvol ? 0x08 : 0x00 ; /* unmute/mute */
outb ( val , fmi - > io ) ;
outb ( val | 0x10 , fmi - > io ) ;
2005-04-17 02:20:36 +04:00
msleep ( 143 ) ; /* was schedule_timeout(HZ/7) */
2009-03-06 19:52:34 +03:00
res = ( int ) inb ( fmi - > io + 1 ) ;
outb ( val , fmi - > io ) ;
2006-04-08 23:06:16 +04:00
2009-03-06 19:52:34 +03:00
mutex_unlock ( & fmi - > lock ) ;
2005-04-17 02:20:36 +04:00
return ( res & 2 ) ? 0 : 0xFFFF ;
}
2007-04-24 00:52:12 +04:00
static int vidioc_querycap ( struct file * file , void * priv ,
struct v4l2_capability * v )
2005-04-17 02:20:36 +04:00
{
2007-04-24 00:52:12 +04:00
strlcpy ( v - > driver , " radio-sf16fmi " , sizeof ( v - > driver ) ) ;
strlcpy ( v - > card , " SF16-FMx radio " , sizeof ( v - > card ) ) ;
2009-03-06 19:52:34 +03:00
strlcpy ( v - > bus_info , " ISA " , sizeof ( v - > bus_info ) ) ;
2007-04-24 00:52:12 +04:00
v - > version = RADIO_VERSION ;
2009-03-06 19:52:34 +03:00
v - > capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO ;
2007-04-24 00:52:12 +04:00
return 0 ;
}
static int vidioc_g_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * v )
{
2009-03-06 19:52:34 +03:00
struct fmi * fmi = video_drvdata ( file ) ;
2007-04-24 00:52:12 +04:00
if ( v - > index > 0 )
return - EINVAL ;
2009-03-06 19:52:34 +03:00
strlcpy ( v - > name , " FM " , sizeof ( v - > name ) ) ;
2007-04-24 00:52:12 +04:00
v - > type = V4L2_TUNER_RADIO ;
2009-05-02 19:04:13 +04:00
v - > rangelow = RSF16_MINFREQ ;
v - > rangehigh = RSF16_MAXFREQ ;
2009-05-02 18:52:35 +04:00
v - > rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO ;
2009-05-02 19:06:15 +04:00
v - > capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW ;
2007-04-24 00:52:12 +04:00
v - > audmode = V4L2_TUNER_MODE_STEREO ;
v - > signal = fmi_getsigstr ( fmi ) ;
return 0 ;
}
2006-04-08 23:06:16 +04:00
2007-04-24 00:52:12 +04:00
static int vidioc_s_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * v )
{
2009-03-06 19:52:34 +03:00
return v - > index ? - EINVAL : 0 ;
2007-04-24 00:52:12 +04:00
}
2006-08-08 16:10:02 +04:00
2007-04-24 00:52:12 +04:00
static int vidioc_s_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * f )
{
2009-03-06 19:52:34 +03:00
struct fmi * fmi = video_drvdata ( file ) ;
2007-04-24 00:52:12 +04:00
2009-11-27 10:33:25 +03:00
if ( f - > tuner ! = 0 | | f - > type ! = V4L2_TUNER_RADIO )
return - EINVAL ;
2007-04-24 00:52:12 +04:00
if ( f - > frequency < RSF16_MINFREQ | |
2009-03-06 19:52:34 +03:00
f - > frequency > RSF16_MAXFREQ )
2007-04-24 00:52:12 +04:00
return - EINVAL ;
2009-03-06 19:52:34 +03:00
/* rounding in steps of 800 to match the freq
that will be used */
fmi_setfreq ( fmi , ( f - > frequency / 800 ) * 800 ) ;
2007-04-24 00:52:12 +04:00
return 0 ;
}
2006-08-08 16:10:02 +04:00
2007-04-24 00:52:12 +04:00
static int vidioc_g_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * f )
{
2009-03-06 19:52:34 +03:00
struct fmi * fmi = video_drvdata ( file ) ;
2006-08-08 16:10:02 +04:00
2009-11-27 10:33:25 +03:00
if ( f - > tuner ! = 0 )
return - EINVAL ;
2007-04-24 00:52:12 +04:00
f - > type = V4L2_TUNER_RADIO ;
f - > frequency = fmi - > curfreq ;
return 0 ;
}
2006-08-08 16:10:02 +04:00
2007-04-24 00:52:12 +04:00
static int vidioc_queryctrl ( struct file * file , void * priv ,
struct v4l2_queryctrl * qc )
{
2009-03-06 19:52:34 +03:00
switch ( qc - > id ) {
case V4L2_CID_AUDIO_MUTE :
return v4l2_ctrl_query_fill ( qc , 0 , 1 , 1 , 1 ) ;
2007-04-24 00:52:12 +04:00
}
return - EINVAL ;
}
2006-08-08 16:10:02 +04:00
2007-04-24 00:52:12 +04:00
static int vidioc_g_ctrl ( struct file * file , void * priv ,
struct v4l2_control * ctrl )
{
2009-03-06 19:52:34 +03:00
struct fmi * fmi = video_drvdata ( file ) ;
2006-08-08 16:10:02 +04:00
2007-04-24 00:52:12 +04:00
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_MUTE :
ctrl - > value = fmi - > curvol ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
2007-04-24 00:52:12 +04:00
return - EINVAL ;
}
static int vidioc_s_ctrl ( struct file * file , void * priv ,
struct v4l2_control * ctrl )
{
2009-03-06 19:52:34 +03:00
struct fmi * fmi = video_drvdata ( file ) ;
2007-04-24 00:52:12 +04:00
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_MUTE :
if ( ctrl - > value )
2009-03-06 19:52:34 +03:00
fmi_mute ( fmi ) ;
2007-04-24 00:52:12 +04:00
else
2009-03-06 19:52:34 +03:00
fmi_unmute ( fmi ) ;
2007-04-24 00:52:12 +04:00
fmi - > curvol = ctrl - > value ;
return 0 ;
}
return - EINVAL ;
}
static int vidioc_g_input ( struct file * filp , void * priv , unsigned int * i )
2005-04-17 02:20:36 +04:00
{
2007-04-24 00:52:12 +04:00
* i = 0 ;
return 0 ;
}
static int vidioc_s_input ( struct file * filp , void * priv , unsigned int i )
{
2009-03-06 19:52:34 +03:00
return i ? - EINVAL : 0 ;
2007-04-24 00:52:12 +04:00
}
2009-03-06 19:52:34 +03:00
static int vidioc_g_audio ( struct file * file , void * priv ,
2007-04-24 00:52:12 +04:00
struct v4l2_audio * a )
{
2009-03-06 19:52:34 +03:00
a - > index = 0 ;
strlcpy ( a - > name , " Radio " , sizeof ( a - > name ) ) ;
a - > capability = V4L2_AUDCAP_STEREO ;
2007-04-24 00:52:12 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2009-03-06 19:52:34 +03:00
static int vidioc_s_audio ( struct file * file , void * priv ,
struct v4l2_audio * a )
{
return a - > index ? - EINVAL : 0 ;
}
2005-04-17 02:20:36 +04:00
2008-12-30 12:58:20 +03:00
static const struct v4l2_file_operations fmi_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
2007-04-24 00:52:12 +04:00
. ioctl = video_ioctl2 ,
2005-04-17 02:20:36 +04:00
} ;
2008-07-21 09:57:38 +04:00
static const struct v4l2_ioctl_ops fmi_ioctl_ops = {
2007-04-24 00:52:12 +04:00
. vidioc_querycap = vidioc_querycap ,
. vidioc_g_tuner = vidioc_g_tuner ,
. vidioc_s_tuner = vidioc_s_tuner ,
. vidioc_g_audio = vidioc_g_audio ,
. vidioc_s_audio = vidioc_s_audio ,
. vidioc_g_input = vidioc_g_input ,
. vidioc_s_input = vidioc_s_input ,
. vidioc_g_frequency = vidioc_g_frequency ,
. vidioc_s_frequency = vidioc_s_frequency ,
. vidioc_queryctrl = vidioc_queryctrl ,
. vidioc_g_ctrl = vidioc_g_ctrl ,
. vidioc_s_ctrl = vidioc_s_ctrl ,
2005-04-17 02:20:36 +04:00
} ;
/* ladis: this is my card. does any other types exist? */
static struct isapnp_device_id id_table [ ] __devinitdata = {
{ ISAPNP_ANY_ID , ISAPNP_ANY_ID ,
ISAPNP_VENDOR ( ' M ' , ' F ' , ' R ' ) , ISAPNP_FUNCTION ( 0xad10 ) , 0 } ,
{ ISAPNP_CARD_END , } ,
} ;
MODULE_DEVICE_TABLE ( isapnp , id_table ) ;
2008-01-23 08:39:39 +03:00
static int __init isapnp_fmi_probe ( void )
2005-04-17 02:20:36 +04:00
{
int i = 0 ;
while ( id_table [ i ] . card_vendor ! = 0 & & dev = = NULL ) {
dev = pnp_find_dev ( NULL , id_table [ i ] . vendor ,
id_table [ i ] . function , NULL ) ;
i + + ;
}
if ( ! dev )
return - ENODEV ;
if ( pnp_device_attach ( dev ) < 0 )
return - EAGAIN ;
if ( pnp_activate_dev ( dev ) < 0 ) {
2009-03-06 19:52:34 +03:00
printk ( KERN_ERR " radio-sf16fmi: PnP configure failed (out of resources?) \n " ) ;
2005-04-17 02:20:36 +04:00
pnp_device_detach ( dev ) ;
return - ENOMEM ;
}
if ( ! pnp_port_valid ( dev , 0 ) ) {
pnp_device_detach ( dev ) ;
return - ENODEV ;
}
i = pnp_port_start ( dev , 0 ) ;
2009-03-06 19:52:34 +03:00
printk ( KERN_INFO " radio-sf16fmi: PnP reports card at %#x \n " , i ) ;
2005-04-17 02:20:36 +04:00
return i ;
}
static int __init fmi_init ( void )
{
2009-03-06 19:52:34 +03:00
struct fmi * fmi = & fmi_card ;
struct v4l2_device * v4l2_dev = & fmi - > v4l2_dev ;
int res ;
2005-04-17 02:20:36 +04:00
if ( io < 0 )
io = isapnp_fmi_probe ( ) ;
2009-03-06 19:52:34 +03:00
strlcpy ( v4l2_dev - > name , " sf16fmi " , sizeof ( v4l2_dev - > name ) ) ;
fmi - > io = io ;
if ( fmi - > io < 0 ) {
v4l2_err ( v4l2_dev , " No PnP card found. \n " ) ;
return fmi - > io ;
2005-04-17 02:20:36 +04:00
}
if ( ! request_region ( io , 2 , " radio-sf16fmi " ) ) {
2009-03-06 19:52:34 +03:00
v4l2_err ( v4l2_dev , " port 0x%x already in use \n " , fmi - > io ) ;
2008-01-27 20:43:20 +03:00
pnp_device_detach ( dev ) ;
2005-04-17 02:20:36 +04:00
return - EBUSY ;
}
2009-03-06 19:52:34 +03:00
res = v4l2_device_register ( NULL , v4l2_dev ) ;
if ( res < 0 ) {
release_region ( fmi - > io , 2 ) ;
pnp_device_detach ( dev ) ;
v4l2_err ( v4l2_dev , " Could not register v4l2_device \n " ) ;
return res ;
}
strlcpy ( fmi - > vdev . name , v4l2_dev - > name , sizeof ( fmi - > vdev . name ) ) ;
fmi - > vdev . v4l2_dev = v4l2_dev ;
fmi - > vdev . fops = & fmi_fops ;
fmi - > vdev . ioctl_ops = & fmi_ioctl_ops ;
fmi - > vdev . release = video_device_release_empty ;
video_set_drvdata ( & fmi - > vdev , fmi ) ;
2006-04-08 23:06:16 +04:00
2009-03-06 19:52:34 +03:00
mutex_init ( & fmi - > lock ) ;
2006-04-08 23:06:16 +04:00
2009-03-06 19:52:34 +03:00
if ( video_register_device ( & fmi - > vdev , VFL_TYPE_RADIO , radio_nr ) < 0 ) {
v4l2_device_unregister ( v4l2_dev ) ;
release_region ( fmi - > io , 2 ) ;
pnp_device_detach ( dev ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
2006-04-08 23:06:16 +04:00
2009-03-06 19:52:34 +03:00
v4l2_info ( v4l2_dev , " card driver at 0x%x \n " , fmi - > io ) ;
2005-04-17 02:20:36 +04:00
/* mute card - prevents noisy bootups */
2009-03-06 19:52:34 +03:00
fmi_mute ( fmi ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2009-03-06 19:52:34 +03:00
static void __exit fmi_exit ( void )
2005-04-17 02:20:36 +04:00
{
2009-03-06 19:52:34 +03:00
struct fmi * fmi = & fmi_card ;
video_unregister_device ( & fmi - > vdev ) ;
v4l2_device_unregister ( & fmi - > v4l2_dev ) ;
release_region ( fmi - > io , 2 ) ;
2005-04-17 02:20:36 +04:00
if ( dev )
pnp_device_detach ( dev ) ;
}
module_init ( fmi_init ) ;
2009-03-06 19:52:34 +03:00
module_exit ( fmi_exit ) ;