2009-11-27 11:24:13 +01:00
/* Miro PCM20 radio driver for Linux radio support
* ( c ) 1998 Ruurd Reitsma < R . A . Reitsma @ wbmt . tudelft . nl >
* Thanks to Norberto Pellici for the ACI device interface specification
* The API part is based on the radiotrack driver by M . Kirkwood
* This driver relies on the aci mixer provided by the snd - miro
* ALSA driver .
* Look there for further info . . .
*/
/* What ever you think about the ACI, version 0x07 is not very well!
* I can ' t get frequency , ' tuner status ' , ' tuner flags ' or mute / mono
* conditions . . . Robert
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/videodev2.h>
# include <media/v4l2-device.h>
# include <media/v4l2-ioctl.h>
2013-01-30 05:50:28 -03:00
# include <media/v4l2-ctrls.h>
2013-01-30 05:54:33 -03:00
# include <media/v4l2-fh.h>
# include <media/v4l2-event.h>
2009-11-27 11:24:13 +01:00
# include <sound/aci.h>
static int radio_nr = - 1 ;
module_param ( radio_nr , int , 0 ) ;
MODULE_PARM_DESC ( radio_nr , " Set radio device number (/dev/radioX). Default: -1 (autodetect) " ) ;
struct pcm20 {
struct v4l2_device v4l2_dev ;
struct video_device vdev ;
2013-01-30 05:50:28 -03:00
struct v4l2_ctrl_handler ctrl_handler ;
2009-11-27 11:24:13 +01:00
unsigned long freq ;
2013-01-30 05:55:50 -03:00
u32 audmode ;
2009-11-27 11:24:13 +01:00
struct snd_miro_aci * aci ;
2010-11-14 09:36:23 -03:00
struct mutex lock ;
2009-11-27 11:24:13 +01:00
} ;
static struct pcm20 pcm20_card = {
2013-01-30 05:55:50 -03:00
. freq = 87 * 16000 ,
. audmode = V4L2_TUNER_MODE_STEREO ,
2009-11-27 11:24:13 +01:00
} ;
static int pcm20_setfreq ( struct pcm20 * dev , unsigned long freq )
{
unsigned char freql ;
unsigned char freqh ;
struct snd_miro_aci * aci = dev - > aci ;
freq / = 160 ;
if ( ! ( aci - > aci_version = = 0x07 | | aci - > aci_version > = 0xb0 ) )
freq / = 10 ; /* I don't know exactly which version
* needs this hack */
freql = freq & 0xff ;
freqh = freq > > 8 ;
return snd_aci_cmd ( aci , ACI_WRITE_TUNE , freql , freqh ) ;
}
static const struct v4l2_file_operations pcm20_fops = {
. owner = THIS_MODULE ,
2013-01-30 05:54:33 -03:00
. open = v4l2_fh_open ,
. poll = v4l2_ctrl_poll ,
. release = v4l2_fh_release ,
2010-11-14 09:36:23 -03:00
. unlocked_ioctl = video_ioctl2 ,
2009-11-27 11:24:13 +01:00
} ;
static int vidioc_querycap ( struct file * file , void * priv ,
struct v4l2_capability * v )
{
2013-01-30 11:26:36 -03:00
struct pcm20 * dev = video_drvdata ( file ) ;
2009-11-27 11:24:13 +01:00
strlcpy ( v - > driver , " Miro PCM20 " , sizeof ( v - > driver ) ) ;
strlcpy ( v - > card , " Miro PCM20 " , sizeof ( v - > card ) ) ;
2013-01-30 11:26:36 -03:00
snprintf ( v - > bus_info , sizeof ( v - > bus_info ) , " ISA:%s " , dev - > v4l2_dev . name ) ;
v - > device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO ;
v - > capabilities = v - > device_caps | V4L2_CAP_DEVICE_CAPS ;
2009-11-27 11:24:13 +01:00
return 0 ;
}
static int vidioc_g_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * v )
{
2013-01-30 05:55:50 -03:00
struct pcm20 * dev = video_drvdata ( file ) ;
2012-05-28 11:10:01 -03:00
int res ;
2013-01-30 05:55:50 -03:00
if ( v - > index )
2009-11-27 11:24:13 +01:00
return - EINVAL ;
strlcpy ( v - > name , " FM " , sizeof ( v - > name ) ) ;
v - > type = V4L2_TUNER_RADIO ;
v - > rangelow = 87 * 16000 ;
v - > rangehigh = 108 * 16000 ;
2012-05-28 11:10:01 -03:00
res = snd_aci_cmd ( dev - > aci , ACI_READ_TUNERSTATION , - 1 , - 1 ) ;
v - > signal = ( res & 0x80 ) ? 0 : 0xffff ;
/* Note: stereo detection does not work if the audio is muted,
it will default to mono in that case . */
res = snd_aci_cmd ( dev - > aci , ACI_READ_TUNERSTEREO , - 1 , - 1 ) ;
v - > rxsubchans = ( res & 0x40 ) ? V4L2_TUNER_SUB_MONO :
V4L2_TUNER_SUB_STEREO ;
2013-01-30 05:55:50 -03:00
v - > capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO ;
v - > audmode = dev - > audmode ;
2009-11-27 11:24:13 +01:00
return 0 ;
}
static int vidioc_s_tuner ( struct file * file , void * priv ,
2013-03-15 06:10:06 -03:00
const struct v4l2_tuner * v )
2009-11-27 11:24:13 +01:00
{
2013-01-30 05:55:50 -03:00
struct pcm20 * dev = video_drvdata ( file ) ;
if ( v - > index )
return - EINVAL ;
if ( v - > audmode > V4L2_TUNER_MODE_STEREO )
2013-03-15 06:10:06 -03:00
dev - > audmode = V4L2_TUNER_MODE_STEREO ;
else
dev - > audmode = v - > audmode ;
2013-01-30 05:55:50 -03:00
snd_aci_cmd ( dev - > aci , ACI_SET_TUNERMONO ,
2013-03-15 06:10:06 -03:00
dev - > audmode = = V4L2_TUNER_MODE_MONO , - 1 ) ;
2013-01-30 05:55:50 -03:00
return 0 ;
2009-11-27 11:24:13 +01:00
}
static int vidioc_g_frequency ( struct file * file , void * priv ,
struct v4l2_frequency * f )
{
struct pcm20 * dev = video_drvdata ( file ) ;
if ( f - > tuner ! = 0 )
return - EINVAL ;
f - > type = V4L2_TUNER_RADIO ;
f - > frequency = dev - > freq ;
return 0 ;
}
static int vidioc_s_frequency ( struct file * file , void * priv ,
2013-03-19 04:09:26 -03:00
const struct v4l2_frequency * f )
2009-11-27 11:24:13 +01:00
{
struct pcm20 * dev = video_drvdata ( file ) ;
if ( f - > tuner ! = 0 | | f - > type ! = V4L2_TUNER_RADIO )
return - EINVAL ;
2013-03-19 04:09:26 -03:00
dev - > freq = clamp_t ( u32 , f - > frequency , 87 * 16000U , 108 * 16000U ) ;
2013-01-30 05:55:50 -03:00
pcm20_setfreq ( dev , dev - > freq ) ;
2009-11-27 11:24:13 +01:00
return 0 ;
}
2013-01-30 05:50:28 -03:00
static int pcm20_s_ctrl ( struct v4l2_ctrl * ctrl )
2009-11-27 11:24:13 +01:00
{
2013-01-30 05:50:28 -03:00
struct pcm20 * dev = container_of ( ctrl - > handler , struct pcm20 , ctrl_handler ) ;
2009-11-27 11:24:13 +01:00
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_MUTE :
2013-01-30 05:55:50 -03:00
snd_aci_cmd ( dev - > aci , ACI_SET_TUNERMUTE , ctrl - > val , - 1 ) ;
2013-01-30 05:50:28 -03:00
return 0 ;
2009-11-27 11:24:13 +01:00
}
2013-01-30 05:50:28 -03:00
return - EINVAL ;
2009-11-27 11:24:13 +01:00
}
static const struct v4l2_ioctl_ops pcm20_ioctl_ops = {
. 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-01-30 05:54:33 -03:00
. vidioc_log_status = v4l2_ctrl_log_status ,
. vidioc_subscribe_event = v4l2_ctrl_subscribe_event ,
. vidioc_unsubscribe_event = v4l2_event_unsubscribe ,
2013-01-30 05:50:28 -03:00
} ;
static const struct v4l2_ctrl_ops pcm20_ctrl_ops = {
. s_ctrl = pcm20_s_ctrl ,
2009-11-27 11:24:13 +01:00
} ;
static int __init pcm20_init ( void )
{
struct pcm20 * dev = & pcm20_card ;
struct v4l2_device * v4l2_dev = & dev - > v4l2_dev ;
2013-01-30 05:50:28 -03:00
struct v4l2_ctrl_handler * hdl ;
2009-11-27 11:24:13 +01:00
int res ;
dev - > aci = snd_aci_get_aci ( ) ;
if ( dev - > aci = = NULL ) {
v4l2_err ( v4l2_dev ,
" you must load the snd-miro driver first! \n " ) ;
return - ENODEV ;
}
2013-01-30 11:26:36 -03:00
strlcpy ( v4l2_dev - > name , " radio-miropcm20 " , sizeof ( v4l2_dev - > name ) ) ;
2010-11-14 09:36:23 -03:00
mutex_init ( & dev - > lock ) ;
2009-11-27 11:24:13 +01:00
res = v4l2_device_register ( NULL , v4l2_dev ) ;
if ( res < 0 ) {
v4l2_err ( v4l2_dev , " could not register v4l2_device \n " ) ;
return - EINVAL ;
}
2013-01-30 05:50:28 -03:00
hdl = & dev - > ctrl_handler ;
v4l2_ctrl_handler_init ( hdl , 1 ) ;
v4l2_ctrl_new_std ( hdl , & pcm20_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 control \n " ) ;
goto err_hdl ;
}
2009-11-27 11:24:13 +01:00
strlcpy ( dev - > vdev . name , v4l2_dev - > name , sizeof ( dev - > vdev . name ) ) ;
dev - > vdev . v4l2_dev = v4l2_dev ;
dev - > vdev . fops = & pcm20_fops ;
dev - > vdev . ioctl_ops = & pcm20_ioctl_ops ;
dev - > vdev . release = video_device_release_empty ;
2010-11-14 09:36:23 -03:00
dev - > vdev . lock = & dev - > lock ;
2013-01-30 05:54:33 -03:00
set_bit ( V4L2_FL_USE_FH_PRIO , & dev - > vdev . flags ) ;
2009-11-27 11:24:13 +01:00
video_set_drvdata ( & dev - > vdev , dev ) ;
2013-01-30 05:55:50 -03:00
snd_aci_cmd ( dev - > aci , ACI_SET_TUNERMONO ,
dev - > audmode = = V4L2_TUNER_MODE_MONO , - 1 ) ;
pcm20_setfreq ( dev , dev - > freq ) ;
2009-11-27 11:24:13 +01:00
if ( video_register_device ( & dev - > vdev , VFL_TYPE_RADIO , radio_nr ) < 0 )
2013-01-30 05:50:28 -03:00
goto err_hdl ;
2009-11-27 11:24:13 +01:00
v4l2_info ( v4l2_dev , " Mirosound PCM20 Radio tuner \n " ) ;
return 0 ;
2013-01-30 05:50:28 -03:00
err_hdl :
v4l2_ctrl_handler_free ( hdl ) ;
2009-11-27 11:24:13 +01:00
v4l2_device_unregister ( v4l2_dev ) ;
return - EINVAL ;
}
MODULE_AUTHOR ( " Ruurd Reitsma, Krzysztof Helt " ) ;
MODULE_DESCRIPTION ( " A driver for the Miro PCM20 radio card. " ) ;
MODULE_LICENSE ( " GPL " ) ;
static void __exit pcm20_cleanup ( void )
{
struct pcm20 * dev = & pcm20_card ;
video_unregister_device ( & dev - > vdev ) ;
2013-01-30 05:55:50 -03:00
snd_aci_cmd ( dev - > aci , ACI_SET_TUNERMUTE , 1 , - 1 ) ;
2013-01-30 05:50:28 -03:00
v4l2_ctrl_handler_free ( & dev - > ctrl_handler ) ;
2009-11-27 11:24:13 +01:00
v4l2_device_unregister ( & dev - > v4l2_dev ) ;
}
module_init ( pcm20_init ) ;
module_exit ( pcm20_cleanup ) ;