2012-05-19 13:18:26 -03:00
/* SF16-FMI, SF16-FMP and SF16-FMD radio driver for Linux radio support
2005-04-16 15:20:36 -07:00
* heavily based on rtrack driver . . .
* ( c ) 1997 M . Kirkwood
* ( c ) 1998 Petr Vandrovec , vandrove @ vc . cvut . cz
*
2008-10-27 15:13:47 -03:00
* Fitted to new interface by Alan Cox < alan @ lxorguk . ukuu . org . uk >
2005-04-16 15:20:36 -07: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
2012-05-19 13:18:26 -03:00
* control on SB - part of SF16 - FMI / SF16 - FMP / SF16 - FMD
2006-04-08 16:06:16 -03:00
*
2006-08-08 09:10:02 -03:00
* Converted to V4L2 API by Mauro Carvalho Chehab < mchehab @ infradead . org >
2005-04-16 15:20:36 -07:00
*/
# include <linux/kernel.h> /* __setup */
# include <linux/module.h> /* Modules */
# include <linux/init.h> /* Initdata */
2005-09-13 01:25:15 -07:00
# include <linux/ioport.h> /* request_region */
2005-04-16 15:20:36 -07:00
# include <linux/delay.h> /* udelay */
# include <linux/isapnp.h>
2006-02-07 06:49:14 -02:00
# include <linux/mutex.h>
2009-03-06 13: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>
2013-02-03 09:51:06 -03:00
# include <media/v4l2-ctrls.h>
2013-02-03 09:51:59 -03:00
# include <media/v4l2-event.h>
2012-06-13 17:25:44 -03:00
# include "lm7000.h"
2005-04-16 15:20:36 -07:00
2009-03-06 13:52:34 -03:00
MODULE_AUTHOR ( " Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood " ) ;
2012-05-19 13:18:26 -03:00
MODULE_DESCRIPTION ( " A driver for the SF16-FMI, SF16-FMP and SF16-FMD radio. " ) ;
2009-03-06 13:52:34 -03:00
MODULE_LICENSE ( " GPL " ) ;
2011-06-25 10:15:42 -03:00
MODULE_VERSION ( " 0.0.3 " ) ;
2006-08-08 09:10:02 -03:00
2009-03-06 13:52:34 -03:00
static int io = - 1 ;
static int radio_nr = - 1 ;
module_param ( io , int , 0 ) ;
2012-05-19 13:18:26 -03:00
MODULE_PARM_DESC ( io , " I/O address of the SF16-FMI/SF16-FMP/SF16-FMD card (0x284 or 0x384) " ) ;
2009-03-06 13:52:34 -03:00
module_param ( radio_nr , int , 0 ) ;
struct fmi
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:52:34 -03:00
struct v4l2_device v4l2_dev ;
2013-02-03 09:51:06 -03:00
struct v4l2_ctrl_handler hdl ;
2009-03-06 13:52:34 -03:00
struct video_device vdev ;
int io ;
2009-12-10 17:06:44 -03:00
bool mute ;
2013-06-14 17:01:39 -03:00
u32 curfreq ; /* freq in kHz */
2009-03-06 13:52:34 -03:00
struct mutex lock ;
2005-04-16 15:20:36 -07:00
} ;
2009-03-06 13:52:34 -03:00
static struct fmi fmi_card ;
static struct pnp_dev * dev ;
2014-09-24 15:42:11 -03:00
static bool pnp_attached ;
2005-04-16 15:20:36 -07:00
2013-05-31 06:21:03 -03:00
# define RSF16_MINFREQ (87U * 16000)
# define RSF16_MAXFREQ (108U * 16000)
2005-04-16 15:20:36 -07:00
2012-06-13 17:25:44 -03:00
# define FMI_BIT_TUN_CE (1 << 0)
# define FMI_BIT_TUN_CLK (1 << 1)
# define FMI_BIT_TUN_DATA (1 << 2)
# define FMI_BIT_VOL_SW (1 << 3)
# define FMI_BIT_TUN_STRQ (1 << 4)
2012-10-27 13:43:05 -03:00
static void fmi_set_pins ( void * handle , u8 pins )
2005-04-16 15:20:36 -07:00
{
2012-06-13 17:25:44 -03:00
struct fmi * fmi = handle ;
u8 bits = FMI_BIT_TUN_STRQ ;
if ( ! fmi - > mute )
bits | = FMI_BIT_VOL_SW ;
if ( pins & LM7000_DATA )
bits | = FMI_BIT_TUN_DATA ;
if ( pins & LM7000_CLK )
bits | = FMI_BIT_TUN_CLK ;
if ( pins & LM7000_CE )
bits | = FMI_BIT_TUN_CE ;
mutex_lock ( & fmi - > lock ) ;
outb_p ( bits , fmi - > io ) ;
mutex_unlock ( & fmi - > lock ) ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:52:34 -03:00
static inline void fmi_mute ( struct fmi * fmi )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:52:34 -03:00
mutex_lock ( & fmi - > lock ) ;
outb ( 0x00 , fmi - > io ) ;
mutex_unlock ( & fmi - > lock ) ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:52:34 -03:00
static inline void fmi_unmute ( struct fmi * fmi )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:52:34 -03:00
mutex_lock ( & fmi - > lock ) ;
outb ( 0x08 , fmi - > io ) ;
mutex_unlock ( & fmi - > lock ) ;
2005-04-16 15:20:36 -07:00
}
2009-03-06 13:52:34 -03:00
static inline int fmi_getsigstr ( struct fmi * fmi )
2005-04-16 15:20:36 -07:00
{
int val ;
int res ;
2006-04-08 16:06:16 -03:00
2009-03-06 13:52:34 -03:00
mutex_lock ( & fmi - > lock ) ;
2009-12-10 17:06:44 -03:00
val = fmi - > mute ? 0x00 : 0x08 ; /* mute/unmute */
2009-03-06 13:52:34 -03:00
outb ( val , fmi - > io ) ;
outb ( val | 0x10 , fmi - > io ) ;
2005-04-16 15:20:36 -07:00
msleep ( 143 ) ; /* was schedule_timeout(HZ/7) */
2009-03-06 13:52:34 -03:00
res = ( int ) inb ( fmi - > io + 1 ) ;
outb ( val , fmi - > io ) ;
2006-04-08 16:06:16 -03:00
2009-03-06 13:52:34 -03:00
mutex_unlock ( & fmi - > lock ) ;
2005-04-16 15:20:36 -07:00
return ( res & 2 ) ? 0 : 0xFFFF ;
}
2013-06-14 17:01:39 -03:00
static void fmi_set_freq ( struct fmi * fmi )
{
fmi - > curfreq = clamp ( fmi - > curfreq , RSF16_MINFREQ , RSF16_MAXFREQ ) ;
/* rounding in steps of 800 to match the freq
that will be used */
lm7000_set_freq ( ( fmi - > curfreq / 800 ) * 800 , fmi , fmi_set_pins ) ;
}
2007-04-23 17:52:12 -03:00
static int vidioc_querycap ( struct file * file , void * priv ,
struct v4l2_capability * v )
2005-04-16 15:20:36 -07:00
{
2007-04-23 17:52:12 -03:00
strlcpy ( v - > driver , " radio-sf16fmi " , sizeof ( v - > driver ) ) ;
2012-05-19 13:18:26 -03:00
strlcpy ( v - > card , " SF16-FMI/FMP/FMD radio " , sizeof ( v - > card ) ) ;
2013-06-14 17:01:38 -03:00
strlcpy ( v - > bus_info , " ISA:radio-sf16fmi " , sizeof ( v - > bus_info ) ) ;
2013-02-03 09:44:14 -03:00
v - > device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO ;
v - > capabilities = v - > device_caps | V4L2_CAP_DEVICE_CAPS ;
2007-04-23 17:52:12 -03:00
return 0 ;
}
static int vidioc_g_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * v )
{
2009-03-06 13:52:34 -03:00
struct fmi * fmi = video_drvdata ( file ) ;
2007-04-23 17:52:12 -03:00
if ( v - > index > 0 )
return - EINVAL ;
2009-03-06 13:52:34 -03:00
strlcpy ( v - > name , " FM " , sizeof ( v - > name ) ) ;
2007-04-23 17:52:12 -03:00
v - > type = V4L2_TUNER_RADIO ;
2009-05-02 12:04:13 -03:00
v - > rangelow = RSF16_MINFREQ ;
v - > rangehigh = RSF16_MAXFREQ ;
2009-05-02 11:52:35 -03:00
v - > rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO ;
2009-05-02 12:06:15 -03:00
v - > capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW ;
2007-04-23 17:52:12 -03:00
v - > audmode = V4L2_TUNER_MODE_STEREO ;
v - > signal = fmi_getsigstr ( fmi ) ;
return 0 ;
}
2006-04-08 16:06:16 -03:00
2007-04-23 17:52:12 -03:00
static int vidioc_s_tuner ( struct file * file , void * priv ,
2013-03-15 06:10:06 -03:00
const struct v4l2_tuner * v )
2007-04-23 17:52:12 -03:00
{
2009-03-06 13:52:34 -03:00
return v - > index ? - EINVAL : 0 ;
2007-04-23 17:52:12 -03:00
}
2006-08-08 09:10:02 -03:00
2007-04-23 17:52:12 -03:00
static int vidioc_s_frequency ( struct file * file , void * priv ,
2013-03-19 04:09:26 -03:00
const struct v4l2_frequency * f )
2007-04-23 17:52:12 -03:00
{
2009-03-06 13:52:34 -03:00
struct fmi * fmi = video_drvdata ( file ) ;
2007-04-23 17:52:12 -03:00
2009-11-27 04:33:25 -03:00
if ( f - > tuner ! = 0 | | f - > type ! = V4L2_TUNER_RADIO )
return - EINVAL ;
2013-06-14 17:01:39 -03:00
fmi - > curfreq = f - > frequency ;
fmi_set_freq ( fmi ) ;
2007-04-23 17:52:12 -03:00
return 0 ;
}
2006-08-08 09:10:02 -03:00
2007-04-23 17:52:12 -03:00
static int vidioc_g_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * f )
{
2009-03-06 13:52:34 -03:00
struct fmi * fmi = video_drvdata ( file ) ;
2006-08-08 09:10:02 -03:00
2009-11-27 04:33:25 -03:00
if ( f - > tuner ! = 0 )
return - EINVAL ;
2007-04-23 17:52:12 -03:00
f - > type = V4L2_TUNER_RADIO ;
f - > frequency = fmi - > curfreq ;
return 0 ;
}
2006-08-08 09:10:02 -03:00
2013-02-03 09:51:06 -03:00
static int fmi_s_ctrl ( struct v4l2_ctrl * ctrl )
2007-04-23 17:52:12 -03:00
{
2013-02-03 09:51:06 -03:00
struct fmi * fmi = container_of ( ctrl - > handler , struct fmi , hdl ) ;
2006-08-08 09:10:02 -03:00
2007-04-23 17:52:12 -03:00
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_MUTE :
2013-02-03 09:51:06 -03:00
if ( ctrl - > val )
2009-03-06 13:52:34 -03:00
fmi_mute ( fmi ) ;
2007-04-23 17:52:12 -03:00
else
2009-03-06 13:52:34 -03:00
fmi_unmute ( fmi ) ;
2013-02-03 09:51:06 -03:00
fmi - > mute = ctrl - > val ;
2007-04-23 17:52:12 -03:00
return 0 ;
}
return - EINVAL ;
}
2013-02-03 09:51:06 -03:00
static const struct v4l2_ctrl_ops fmi_ctrl_ops = {
. s_ctrl = fmi_s_ctrl ,
} ;
2008-12-30 06:58:20 -03:00
static const struct v4l2_file_operations fmi_fops = {
2005-04-16 15:20:36 -07:00
. owner = THIS_MODULE ,
2013-02-03 09:51:59 -03:00
. open = v4l2_fh_open ,
. release = v4l2_fh_release ,
. poll = v4l2_ctrl_poll ,
2010-11-14 09:36:23 -03:00
. unlocked_ioctl = video_ioctl2 ,
2005-04-16 15:20:36 -07:00
} ;
2008-07-21 02:57:38 -03:00
static const struct v4l2_ioctl_ops fmi_ioctl_ops = {
2007-04-23 17:52:12 -03: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 ,
2013-02-03 09:51:59 -03:00
. vidioc_log_status = v4l2_ctrl_log_status ,
. vidioc_subscribe_event = v4l2_ctrl_subscribe_event ,
. vidioc_unsubscribe_event = v4l2_event_unsubscribe ,
2005-04-16 15:20:36 -07:00
} ;
/* ladis: this is my card. does any other types exist? */
2012-12-21 13:17:53 -08:00
static struct isapnp_device_id id_table [ ] = {
2012-05-19 13:18:26 -03:00
/* SF16-FMI */
2005-04-16 15:20:36 -07:00
{ ISAPNP_ANY_ID , ISAPNP_ANY_ID ,
ISAPNP_VENDOR ( ' M ' , ' F ' , ' R ' ) , ISAPNP_FUNCTION ( 0xad10 ) , 0 } ,
2012-05-19 13:18:26 -03:00
/* SF16-FMD */
{ ISAPNP_ANY_ID , ISAPNP_ANY_ID ,
ISAPNP_VENDOR ( ' M ' , ' F ' , ' R ' ) , ISAPNP_FUNCTION ( 0xad12 ) , 0 } ,
2005-04-16 15:20:36 -07:00
{ ISAPNP_CARD_END , } ,
} ;
MODULE_DEVICE_TABLE ( isapnp , id_table ) ;
2008-01-23 02:39:39 -03:00
static int __init isapnp_fmi_probe ( void )
2005-04-16 15:20:36 -07: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 13:52:34 -03:00
printk ( KERN_ERR " radio-sf16fmi: PnP configure failed (out of resources?) \n " ) ;
2005-04-16 15:20:36 -07: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 13:52:34 -03:00
printk ( KERN_INFO " radio-sf16fmi: PnP reports card at %#x \n " , i ) ;
2005-04-16 15:20:36 -07:00
return i ;
}
static int __init fmi_init ( void )
{
2009-03-06 13:52:34 -03:00
struct fmi * fmi = & fmi_card ;
struct v4l2_device * v4l2_dev = & fmi - > v4l2_dev ;
2013-02-03 09:51:06 -03:00
struct v4l2_ctrl_handler * hdl = & fmi - > hdl ;
2009-12-10 17:12:32 -03:00
int res , i ;
int probe_ports [ ] = { 0 , 0x284 , 0x384 } ;
if ( io < 0 ) {
for ( i = 0 ; i < ARRAY_SIZE ( probe_ports ) ; i + + ) {
io = probe_ports [ i ] ;
if ( io = = 0 ) {
io = isapnp_fmi_probe ( ) ;
if ( io < 0 )
continue ;
2014-09-03 15:54:17 -03:00
pnp_attached = true ;
2009-12-10 17:12:32 -03:00
}
if ( ! request_region ( io , 2 , " radio-sf16fmi " ) ) {
if ( pnp_attached )
pnp_device_detach ( dev ) ;
io = - 1 ;
continue ;
}
if ( pnp_attached | |
( ( inb ( io ) & 0xf9 ) = = 0xf9 & & ( inb ( io ) & 0x4 ) = = 0 ) )
break ;
release_region ( io , 2 ) ;
io = - 1 ;
}
} else {
if ( ! request_region ( io , 2 , " radio-sf16fmi " ) ) {
printk ( KERN_ERR " radio-sf16fmi: port %#x already in use \n " , io ) ;
return - EBUSY ;
}
if ( inb ( io ) = = 0xff ) {
printk ( KERN_ERR " radio-sf16fmi: card not present at %#x \n " , io ) ;
release_region ( io , 2 ) ;
return - ENODEV ;
}
}
if ( io < 0 ) {
printk ( KERN_ERR " radio-sf16fmi: no cards found \n " ) ;
return - ENODEV ;
}
2009-03-06 13:52:34 -03:00
strlcpy ( v4l2_dev - > name , " sf16fmi " , sizeof ( v4l2_dev - > name ) ) ;
fmi - > io = io ;
2005-04-16 15:20:36 -07:00
2009-03-06 13:52:34 -03:00
res = v4l2_device_register ( NULL , v4l2_dev ) ;
if ( res < 0 ) {
release_region ( fmi - > io , 2 ) ;
2009-12-10 17:12:32 -03:00
if ( pnp_attached )
pnp_device_detach ( dev ) ;
2009-03-06 13:52:34 -03:00
v4l2_err ( v4l2_dev , " Could not register v4l2_device \n " ) ;
return res ;
}
2013-02-03 09:51:06 -03:00
v4l2_ctrl_handler_init ( hdl , 1 ) ;
v4l2_ctrl_new_std ( hdl , & fmi_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 " ) ;
v4l2_ctrl_handler_free ( hdl ) ;
v4l2_device_unregister ( v4l2_dev ) ;
return res ;
}
2009-03-06 13:52:34 -03:00
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 16:06:16 -03:00
2009-03-06 13:52:34 -03:00
mutex_init ( & fmi - > lock ) ;
2006-04-08 16:06:16 -03:00
2013-06-14 17:01:39 -03:00
/* mute card and set default frequency */
2014-09-03 15:54:17 -03:00
fmi - > mute = true ;
2013-06-14 17:01:39 -03:00
fmi - > curfreq = RSF16_MINFREQ ;
fmi_set_freq ( fmi ) ;
2010-11-14 09:36:23 -03:00
2009-03-06 13:52:34 -03:00
if ( video_register_device ( & fmi - > vdev , VFL_TYPE_RADIO , radio_nr ) < 0 ) {
2013-02-03 09:51:06 -03:00
v4l2_ctrl_handler_free ( hdl ) ;
2009-03-06 13:52:34 -03:00
v4l2_device_unregister ( v4l2_dev ) ;
release_region ( fmi - > io , 2 ) ;
2009-12-10 17:12:32 -03:00
if ( pnp_attached )
pnp_device_detach ( dev ) ;
2005-04-16 15:20:36 -07:00
return - EINVAL ;
}
2006-04-08 16:06:16 -03:00
2009-03-06 13:52:34 -03:00
v4l2_info ( v4l2_dev , " card driver at 0x%x \n " , fmi - > io ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
2009-03-06 13:52:34 -03:00
static void __exit fmi_exit ( void )
2005-04-16 15:20:36 -07:00
{
2009-03-06 13:52:34 -03:00
struct fmi * fmi = & fmi_card ;
2013-02-03 09:51:06 -03:00
v4l2_ctrl_handler_free ( & fmi - > hdl ) ;
2009-03-06 13:52:34 -03:00
video_unregister_device ( & fmi - > vdev ) ;
v4l2_device_unregister ( & fmi - > v4l2_dev ) ;
release_region ( fmi - > io , 2 ) ;
2009-12-10 17:12:32 -03:00
if ( dev & & pnp_attached )
2005-04-16 15:20:36 -07:00
pnp_device_detach ( dev ) ;
}
module_init ( fmi_init ) ;
2009-03-06 13:52:34 -03:00
module_exit ( fmi_exit ) ;