2014-07-21 17:45:43 +04:00
/*
* Miro PCM20 radio driver for Linux radio support
2009-11-27 13:24:13 +03:00
* ( 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 . . .
2014-07-21 17:45:43 +04:00
*
* From the original miro RDS sources :
*
* ( c ) 2001 Robert Siemer < Robert . Siemer @ gmx . de >
*
* Many thanks to Fred Seidel < seidel @ metabox . de > , the
* designer of the RDS decoder hardware . With his help
* I was able to code this driver .
* Thanks also to Norberto Pellicci , Dominic Mounteney
* < DMounteney @ pinnaclesys . com > and www . teleauskunft . de
* for good hints on finding Fred . It was somewhat hard
* to locate him here in Germany . . . [ :
*
* This code has been reintroduced and converted to use
* the new V4L2 RDS API by :
*
* Hans Verkuil < hans . verkuil @ cisco . com >
2009-11-27 13:24:13 +03:00
*/
# include <linux/module.h>
# include <linux/init.h>
2014-08-28 21:01:26 +04:00
# include <linux/io.h>
2014-07-21 17:45:43 +04:00
# include <linux/delay.h>
2009-11-27 13:24:13 +03:00
# include <linux/videodev2.h>
2014-07-21 17:45:43 +04:00
# include <linux/kthread.h>
2009-11-27 13:24:13 +03:00
# include <media/v4l2-device.h>
# include <media/v4l2-ioctl.h>
2013-01-30 12:50:28 +04:00
# include <media/v4l2-ctrls.h>
2013-01-30 12:54:33 +04:00
# include <media/v4l2-fh.h>
# include <media/v4l2-event.h>
2009-11-27 13:24:13 +03:00
# include <sound/aci.h>
2014-07-21 17:45:43 +04:00
# define RDS_DATASHIFT 2 /* Bit 2 */
# define RDS_DATAMASK (1 << RDS_DATASHIFT)
# define RDS_BUSYMASK 0x10 /* Bit 4 */
# define RDS_CLOCKMASK 0x08 /* Bit 3 */
# define RDS_DATA(x) (((x) >> RDS_DATASHIFT) & 1)
# define RDS_STATUS 0x01
# define RDS_STATIONNAME 0x02
# define RDS_TEXT 0x03
# define RDS_ALTFREQ 0x04
# define RDS_TIMEDATE 0x05
# define RDS_PI_CODE 0x06
# define RDS_PTYTATP 0x07
# define RDS_RESET 0x08
# define RDS_RXVALUE 0x09
2009-11-27 13:24:13 +03:00
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 12:50:28 +04:00
struct v4l2_ctrl_handler ctrl_handler ;
2014-07-21 17:45:43 +04:00
struct v4l2_ctrl * rds_pty ;
struct v4l2_ctrl * rds_ps_name ;
struct v4l2_ctrl * rds_radio_test ;
struct v4l2_ctrl * rds_ta ;
struct v4l2_ctrl * rds_tp ;
struct v4l2_ctrl * rds_ms ;
/* thread for periodic RDS status checking */
struct task_struct * kthread ;
2009-11-27 13:24:13 +03:00
unsigned long freq ;
2013-01-30 12:55:50 +04:00
u32 audmode ;
2009-11-27 13:24:13 +03:00
struct snd_miro_aci * aci ;
2010-11-14 15:36:23 +03:00
struct mutex lock ;
2009-11-27 13:24:13 +03:00
} ;
static struct pcm20 pcm20_card = {
2013-01-30 12:55:50 +04:00
. freq = 87 * 16000 ,
. audmode = V4L2_TUNER_MODE_STEREO ,
2009-11-27 13:24:13 +03:00
} ;
2014-07-21 17:45:43 +04:00
static int rds_waitread ( struct snd_miro_aci * aci )
{
u8 byte ;
int i = 2000 ;
do {
byte = inb ( aci - > aci_port + ACI_REG_RDS ) ;
i - - ;
} while ( ( byte & RDS_BUSYMASK ) & & i ) ;
/*
* It ' s magic , but without this the data that you read later on
* is unreliable and full of bit errors . With this 1 usec delay
* everything is fine .
*/
udelay ( 1 ) ;
return i ? byte : - 1 ;
}
static int rds_rawwrite ( struct snd_miro_aci * aci , u8 byte )
{
if ( rds_waitread ( aci ) > = 0 ) {
outb ( byte , aci - > aci_port + ACI_REG_RDS ) ;
return 0 ;
}
return - 1 ;
}
static int rds_write ( struct snd_miro_aci * aci , u8 byte )
{
u8 sendbuffer [ 8 ] ;
int i ;
for ( i = 7 ; i > = 0 ; i - - )
sendbuffer [ 7 - i ] = ( byte & ( 1 < < i ) ) ? RDS_DATAMASK : 0 ;
sendbuffer [ 0 ] | = RDS_CLOCKMASK ;
for ( i = 0 ; i < 8 ; i + + )
rds_rawwrite ( aci , sendbuffer [ i ] ) ;
return 0 ;
}
static int rds_readcycle_nowait ( struct snd_miro_aci * aci )
{
outb ( 0 , aci - > aci_port + ACI_REG_RDS ) ;
return rds_waitread ( aci ) ;
}
static int rds_readcycle ( struct snd_miro_aci * aci )
{
if ( rds_rawwrite ( aci , 0 ) < 0 )
return - 1 ;
return rds_waitread ( aci ) ;
}
static int rds_ack ( struct snd_miro_aci * aci )
{
int i = rds_readcycle ( aci ) ;
if ( i < 0 )
return - 1 ;
if ( i & RDS_DATAMASK )
return 0 ; /* ACK */
return 1 ; /* NACK */
}
static int rds_cmd ( struct snd_miro_aci * aci , u8 cmd , u8 databuffer [ ] , u8 datasize )
{
int i , j ;
rds_write ( aci , cmd ) ;
/* RDS_RESET doesn't need further processing */
if ( cmd = = RDS_RESET )
return 0 ;
if ( rds_ack ( aci ) )
return - EIO ;
if ( datasize = = 0 )
return 0 ;
/* to be able to use rds_readcycle_nowait()
I have to waitread ( ) here */
if ( rds_waitread ( aci ) < 0 )
return - 1 ;
memset ( databuffer , 0 , datasize ) ;
for ( i = 0 ; i < 8 * datasize ; i + + ) {
j = rds_readcycle_nowait ( aci ) ;
if ( j < 0 )
return - EIO ;
databuffer [ i / 8 ] | = RDS_DATA ( j ) < < ( 7 - ( i % 8 ) ) ;
}
return 0 ;
}
2009-11-27 13:24:13 +03: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 ;
2014-07-30 03:21:59 +04:00
rds_cmd ( aci , RDS_RESET , NULL , 0 ) ;
2009-11-27 13:24:13 +03:00
return snd_aci_cmd ( aci , ACI_WRITE_TUNE , freql , freqh ) ;
}
static int vidioc_querycap ( struct file * file , void * priv ,
struct v4l2_capability * v )
{
2013-01-30 18:26:36 +04:00
struct pcm20 * dev = video_drvdata ( file ) ;
2009-11-27 13:24:13 +03:00
strlcpy ( v - > driver , " Miro PCM20 " , sizeof ( v - > driver ) ) ;
strlcpy ( v - > card , " Miro PCM20 " , sizeof ( v - > card ) ) ;
2013-01-30 18:26:36 +04:00
snprintf ( v - > bus_info , sizeof ( v - > bus_info ) , " ISA:%s " , dev - > v4l2_dev . name ) ;
2014-07-21 17:45:43 +04:00
v - > device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE ;
2013-01-30 18:26:36 +04:00
v - > capabilities = v - > device_caps | V4L2_CAP_DEVICE_CAPS ;
2009-11-27 13:24:13 +03:00
return 0 ;
}
2014-07-21 17:45:43 +04:00
static bool sanitize ( char * p , int size )
{
int i ;
bool ret = true ;
for ( i = 0 ; i < size ; i + + ) {
2014-07-26 02:33:40 +04:00
if ( p [ i ] < 32 ) {
2014-07-21 17:45:43 +04:00
p [ i ] = ' ' ;
ret = false ;
}
}
return ret ;
}
2009-11-27 13:24:13 +03:00
static int vidioc_g_tuner ( struct file * file , void * priv ,
struct v4l2_tuner * v )
{
2013-01-30 12:55:50 +04:00
struct pcm20 * dev = video_drvdata ( file ) ;
2012-05-28 18:10:01 +04:00
int res ;
2014-07-21 17:45:43 +04:00
u8 buf ;
2013-01-30 12:55:50 +04:00
if ( v - > index )
2009-11-27 13:24:13 +03: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 18:10:01 +04: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 ;
2014-07-21 17:45:43 +04:00
v - > capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_CONTROLS ;
2013-01-30 12:55:50 +04:00
v - > audmode = dev - > audmode ;
2014-07-21 17:45:43 +04:00
res = rds_cmd ( dev - > aci , RDS_RXVALUE , & buf , 1 ) ;
if ( res > = 0 & & buf )
v - > rxsubchans | = V4L2_TUNER_SUB_RDS ;
2009-11-27 13:24:13 +03:00
return 0 ;
}
static int vidioc_s_tuner ( struct file * file , void * priv ,
2013-03-15 13:10:06 +04:00
const struct v4l2_tuner * v )
2009-11-27 13:24:13 +03:00
{
2013-01-30 12:55:50 +04:00
struct pcm20 * dev = video_drvdata ( file ) ;
if ( v - > index )
return - EINVAL ;
if ( v - > audmode > V4L2_TUNER_MODE_STEREO )
2013-03-15 13:10:06 +04:00
dev - > audmode = V4L2_TUNER_MODE_STEREO ;
else
dev - > audmode = v - > audmode ;
2013-01-30 12:55:50 +04:00
snd_aci_cmd ( dev - > aci , ACI_SET_TUNERMONO ,
2013-03-15 13:10:06 +04:00
dev - > audmode = = V4L2_TUNER_MODE_MONO , - 1 ) ;
2013-01-30 12:55:50 +04:00
return 0 ;
2009-11-27 13:24:13 +03: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 11:09:26 +04:00
const struct v4l2_frequency * f )
2009-11-27 13:24:13 +03:00
{
struct pcm20 * dev = video_drvdata ( file ) ;
if ( f - > tuner ! = 0 | | f - > type ! = V4L2_TUNER_RADIO )
return - EINVAL ;
2013-03-19 11:09:26 +04:00
dev - > freq = clamp_t ( u32 , f - > frequency , 87 * 16000U , 108 * 16000U ) ;
2013-01-30 12:55:50 +04:00
pcm20_setfreq ( dev , dev - > freq ) ;
2009-11-27 13:24:13 +03:00
return 0 ;
}
2013-01-30 12:50:28 +04:00
static int pcm20_s_ctrl ( struct v4l2_ctrl * ctrl )
2009-11-27 13:24:13 +03:00
{
2013-01-30 12:50:28 +04:00
struct pcm20 * dev = container_of ( ctrl - > handler , struct pcm20 , ctrl_handler ) ;
2009-11-27 13:24:13 +03:00
switch ( ctrl - > id ) {
case V4L2_CID_AUDIO_MUTE :
2013-01-30 12:55:50 +04:00
snd_aci_cmd ( dev - > aci , ACI_SET_TUNERMUTE , ctrl - > val , - 1 ) ;
2013-01-30 12:50:28 +04:00
return 0 ;
2009-11-27 13:24:13 +03:00
}
2013-01-30 12:50:28 +04:00
return - EINVAL ;
2009-11-27 13:24:13 +03:00
}
2014-07-21 17:45:43 +04:00
static int pcm20_thread ( void * data )
{
struct pcm20 * dev = data ;
const unsigned no_rds_start_counter = 5 ;
const unsigned sleep_msecs = 2000 ;
unsigned no_rds_counter = no_rds_start_counter ;
for ( ; ; ) {
char text_buffer [ 66 ] ;
u8 buf ;
int res ;
msleep_interruptible ( sleep_msecs ) ;
if ( kthread_should_stop ( ) )
break ;
res = rds_cmd ( dev - > aci , RDS_RXVALUE , & buf , 1 ) ;
if ( res )
continue ;
if ( buf = = 0 ) {
if ( no_rds_counter = = 0 )
continue ;
no_rds_counter - - ;
if ( no_rds_counter )
continue ;
/*
* No RDS seen for no_rds_start_counter * sleep_msecs
* milliseconds , clear all RDS controls to their
* default values .
*/
v4l2_ctrl_s_ctrl_string ( dev - > rds_ps_name , " " ) ;
v4l2_ctrl_s_ctrl ( dev - > rds_ms , 1 ) ;
v4l2_ctrl_s_ctrl ( dev - > rds_ta , 0 ) ;
v4l2_ctrl_s_ctrl ( dev - > rds_tp , 0 ) ;
v4l2_ctrl_s_ctrl ( dev - > rds_pty , 0 ) ;
v4l2_ctrl_s_ctrl_string ( dev - > rds_radio_test , " " ) ;
continue ;
}
no_rds_counter = no_rds_start_counter ;
res = rds_cmd ( dev - > aci , RDS_STATUS , & buf , 1 ) ;
if ( res )
continue ;
if ( ( buf > > 3 ) & 1 ) {
res = rds_cmd ( dev - > aci , RDS_STATIONNAME , text_buffer , 8 ) ;
text_buffer [ 8 ] = 0 ;
if ( ! res & & sanitize ( text_buffer , 8 ) )
v4l2_ctrl_s_ctrl_string ( dev - > rds_ps_name , text_buffer ) ;
}
if ( ( buf > > 6 ) & 1 ) {
u8 pty ;
res = rds_cmd ( dev - > aci , RDS_PTYTATP , & pty , 1 ) ;
if ( ! res ) {
v4l2_ctrl_s_ctrl ( dev - > rds_ms , ! ! ( pty & 0x01 ) ) ;
v4l2_ctrl_s_ctrl ( dev - > rds_ta , ! ! ( pty & 0x02 ) ) ;
v4l2_ctrl_s_ctrl ( dev - > rds_tp , ! ! ( pty & 0x80 ) ) ;
v4l2_ctrl_s_ctrl ( dev - > rds_pty , ( pty > > 2 ) & 0x1f ) ;
}
}
if ( ( buf > > 4 ) & 1 ) {
res = rds_cmd ( dev - > aci , RDS_TEXT , text_buffer , 65 ) ;
text_buffer [ 65 ] = 0 ;
if ( ! res & & sanitize ( text_buffer + 1 , 64 ) )
v4l2_ctrl_s_ctrl_string ( dev - > rds_radio_test , text_buffer + 1 ) ;
}
}
return 0 ;
}
static int pcm20_open ( struct file * file )
{
struct pcm20 * dev = video_drvdata ( file ) ;
int res = v4l2_fh_open ( file ) ;
if ( ! res & & v4l2_fh_is_singular_file ( file ) & &
IS_ERR_OR_NULL ( dev - > kthread ) ) {
dev - > kthread = kthread_run ( pcm20_thread , dev , " %s " ,
dev - > v4l2_dev . name ) ;
if ( IS_ERR ( dev - > kthread ) ) {
v4l2_err ( & dev - > v4l2_dev , " kernel_thread() failed \n " ) ;
v4l2_fh_release ( file ) ;
return PTR_ERR ( dev - > kthread ) ;
}
}
return res ;
}
static int pcm20_release ( struct file * file )
{
struct pcm20 * dev = video_drvdata ( file ) ;
if ( v4l2_fh_is_singular_file ( file ) & & ! IS_ERR_OR_NULL ( dev - > kthread ) ) {
kthread_stop ( dev - > kthread ) ;
dev - > kthread = NULL ;
}
return v4l2_fh_release ( file ) ;
}
static const struct v4l2_file_operations pcm20_fops = {
. owner = THIS_MODULE ,
. open = pcm20_open ,
. poll = v4l2_ctrl_poll ,
. release = pcm20_release ,
. unlocked_ioctl = video_ioctl2 ,
} ;
2009-11-27 13:24:13 +03: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 12:54:33 +04:00
. vidioc_log_status = v4l2_ctrl_log_status ,
. vidioc_subscribe_event = v4l2_ctrl_subscribe_event ,
. vidioc_unsubscribe_event = v4l2_event_unsubscribe ,
2013-01-30 12:50:28 +04:00
} ;
static const struct v4l2_ctrl_ops pcm20_ctrl_ops = {
. s_ctrl = pcm20_s_ctrl ,
2009-11-27 13:24:13 +03:00
} ;
static int __init pcm20_init ( void )
{
struct pcm20 * dev = & pcm20_card ;
struct v4l2_device * v4l2_dev = & dev - > v4l2_dev ;
2013-01-30 12:50:28 +04:00
struct v4l2_ctrl_handler * hdl ;
2009-11-27 13:24:13 +03: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 18:26:36 +04:00
strlcpy ( v4l2_dev - > name , " radio-miropcm20 " , sizeof ( v4l2_dev - > name ) ) ;
2010-11-14 15:36:23 +03:00
mutex_init ( & dev - > lock ) ;
2009-11-27 13:24:13 +03: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 12:50:28 +04:00
hdl = & dev - > ctrl_handler ;
2014-07-21 17:45:43 +04:00
v4l2_ctrl_handler_init ( hdl , 7 ) ;
2013-01-30 12:50:28 +04:00
v4l2_ctrl_new_std ( hdl , & pcm20_ctrl_ops ,
V4L2_CID_AUDIO_MUTE , 0 , 1 , 1 , 1 ) ;
2014-07-21 17:45:43 +04:00
dev - > rds_pty = v4l2_ctrl_new_std ( hdl , NULL ,
V4L2_CID_RDS_RX_PTY , 0 , 0x1f , 1 , 0 ) ;
dev - > rds_ps_name = v4l2_ctrl_new_std ( hdl , NULL ,
V4L2_CID_RDS_RX_PS_NAME , 0 , 8 , 8 , 0 ) ;
dev - > rds_radio_test = v4l2_ctrl_new_std ( hdl , NULL ,
V4L2_CID_RDS_RX_RADIO_TEXT , 0 , 64 , 64 , 0 ) ;
dev - > rds_ta = v4l2_ctrl_new_std ( hdl , NULL ,
V4L2_CID_RDS_RX_TRAFFIC_ANNOUNCEMENT , 0 , 1 , 1 , 0 ) ;
dev - > rds_tp = v4l2_ctrl_new_std ( hdl , NULL ,
V4L2_CID_RDS_RX_TRAFFIC_PROGRAM , 0 , 1 , 1 , 0 ) ;
dev - > rds_ms = v4l2_ctrl_new_std ( hdl , NULL ,
V4L2_CID_RDS_RX_MUSIC_SPEECH , 0 , 1 , 1 , 1 ) ;
2013-01-30 12:50:28 +04:00
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 13:24:13 +03: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 15:36:23 +03:00
dev - > vdev . lock = & dev - > lock ;
2009-11-27 13:24:13 +03:00
video_set_drvdata ( & dev - > vdev , dev ) ;
2013-01-30 12:55:50 +04:00
snd_aci_cmd ( dev - > aci , ACI_SET_TUNERMONO ,
dev - > audmode = = V4L2_TUNER_MODE_MONO , - 1 ) ;
pcm20_setfreq ( dev , dev - > freq ) ;
2009-11-27 13:24:13 +03:00
if ( video_register_device ( & dev - > vdev , VFL_TYPE_RADIO , radio_nr ) < 0 )
2013-01-30 12:50:28 +04:00
goto err_hdl ;
2009-11-27 13:24:13 +03:00
v4l2_info ( v4l2_dev , " Mirosound PCM20 Radio tuner \n " ) ;
return 0 ;
2013-01-30 12:50:28 +04:00
err_hdl :
v4l2_ctrl_handler_free ( hdl ) ;
2009-11-27 13:24:13 +03: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 12:55:50 +04:00
snd_aci_cmd ( dev - > aci , ACI_SET_TUNERMUTE , 1 , - 1 ) ;
2013-01-30 12:50:28 +04:00
v4l2_ctrl_handler_free ( & dev - > ctrl_handler ) ;
2009-11-27 13:24:13 +03:00
v4l2_device_unregister ( & dev - > v4l2_dev ) ;
}
module_init ( pcm20_init ) ;
module_exit ( pcm20_cleanup ) ;