0d0fbf8152
This moves the 32 bit ioctl compatibility handlers for Video4Linux into a new file and adds explicit calls to them to each v4l device driver. Unfortunately, there does not seem to be any code handling the v4l2 ioctls, so quite often the code goes through two separate conversions, first from 32 bit v4l to 64 bit v4l, and from there to 64 bit v4l2. My patch does not change that, so there is still much room for improvement. Also, some drivers have additional ioctl numbers, for which the conversion should be handled internally to that driver. Signed-off-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Mauro Carvalho Chehab <mchehab@brturbo.com.br>
621 lines
14 KiB
C
621 lines
14 KiB
C
/* 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
|
|
* Jason Lewis (jlewis@twilight.vtc.vsc.edu)
|
|
* 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
|
|
*
|
|
* 2003-01-31 Alan Cox <alan@redhat.com>
|
|
* Cleaned up locking, delay code, general odds and ends
|
|
*/
|
|
|
|
#include <linux/module.h> /* Modules */
|
|
#include <linux/init.h> /* Initdata */
|
|
#include <linux/ioport.h> /* request_region */
|
|
#include <linux/delay.h> /* udelay */
|
|
#include <asm/io.h> /* outb, outb_p */
|
|
#include <asm/uaccess.h> /* copy to/from user */
|
|
#include <linux/videodev.h> /* kernel radio structs */
|
|
#include <linux/param.h>
|
|
#include <linux/pnp.h>
|
|
|
|
#define RDS_BUFFER 256
|
|
|
|
static int io=-1; /* default to isapnp activation */
|
|
static int radio_nr = -1;
|
|
static int users=0;
|
|
static int curtuner=0;
|
|
static int tunestat=0;
|
|
static int sigstrength=0;
|
|
static wait_queue_head_t read_queue;
|
|
static struct timer_list readtimer;
|
|
static __u8 rdsin=0,rdsout=0,rdsstat=0;
|
|
static unsigned char rdsbuf[RDS_BUFFER];
|
|
static spinlock_t cadet_io_lock;
|
|
|
|
static int cadet_probe(void);
|
|
|
|
/*
|
|
* Signal Strength Threshold Values
|
|
* The V4L API spec does not define any particular unit for the signal
|
|
* strength value. These values are in microvolts of RF at the tuner's input.
|
|
*/
|
|
static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}};
|
|
|
|
static int cadet_getrds(void)
|
|
{
|
|
int rdsstat=0;
|
|
|
|
spin_lock(&cadet_io_lock);
|
|
outb(3,io); /* Select Decoder Control/Status */
|
|
outb(inb(io+1)&0x7f,io+1); /* Reset RDS detection */
|
|
spin_unlock(&cadet_io_lock);
|
|
|
|
msleep(100);
|
|
|
|
spin_lock(&cadet_io_lock);
|
|
outb(3,io); /* Select Decoder Control/Status */
|
|
if((inb(io+1)&0x80)!=0) {
|
|
rdsstat|=VIDEO_TUNER_RDS_ON;
|
|
}
|
|
if((inb(io+1)&0x10)!=0) {
|
|
rdsstat|=VIDEO_TUNER_MBS_ON;
|
|
}
|
|
spin_unlock(&cadet_io_lock);
|
|
return rdsstat;
|
|
}
|
|
|
|
static int cadet_getstereo(void)
|
|
{
|
|
int ret = 0;
|
|
if(curtuner != 0) /* Only FM has stereo capability! */
|
|
return 0;
|
|
|
|
spin_lock(&cadet_io_lock);
|
|
outb(7,io); /* Select tuner control */
|
|
if( (inb(io+1) & 0x40) == 0)
|
|
ret = 1;
|
|
spin_unlock(&cadet_io_lock);
|
|
return ret;
|
|
}
|
|
|
|
static unsigned cadet_gettune(void)
|
|
{
|
|
int curvol,i;
|
|
unsigned fifo=0;
|
|
|
|
/*
|
|
* Prepare for read
|
|
*/
|
|
|
|
spin_lock(&cadet_io_lock);
|
|
|
|
outb(7,io); /* Select tuner control */
|
|
curvol=inb(io+1); /* Save current volume/mute setting */
|
|
outb(0x00,io+1); /* Ensure WRITE-ENABLE is LOW */
|
|
tunestat=0xffff;
|
|
|
|
/*
|
|
* Read the shift register
|
|
*/
|
|
for(i=0;i<25;i++) {
|
|
fifo=(fifo<<1)|((inb(io+1)>>7)&0x01);
|
|
if(i<24) {
|
|
outb(0x01,io+1);
|
|
tunestat&=inb(io+1);
|
|
outb(0x00,io+1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Restore volume/mute setting
|
|
*/
|
|
outb(curvol,io+1);
|
|
spin_unlock(&cadet_io_lock);
|
|
|
|
return fifo;
|
|
}
|
|
|
|
static unsigned cadet_getfreq(void)
|
|
{
|
|
int i;
|
|
unsigned freq=0,test,fifo=0;
|
|
|
|
/*
|
|
* Read current tuning
|
|
*/
|
|
fifo=cadet_gettune();
|
|
|
|
/*
|
|
* Convert to actual frequency
|
|
*/
|
|
if(curtuner==0) { /* FM */
|
|
test=12500;
|
|
for(i=0;i<14;i++) {
|
|
if((fifo&0x01)!=0) {
|
|
freq+=test;
|
|
}
|
|
test=test<<1;
|
|
fifo=fifo>>1;
|
|
}
|
|
freq-=10700000; /* IF frequency is 10.7 MHz */
|
|
freq=(freq*16)/1000000; /* Make it 1/16 MHz */
|
|
}
|
|
if(curtuner==1) { /* AM */
|
|
freq=((fifo&0x7fff)-2010)*16;
|
|
}
|
|
|
|
return freq;
|
|
}
|
|
|
|
static void cadet_settune(unsigned fifo)
|
|
{
|
|
int i;
|
|
unsigned test;
|
|
|
|
spin_lock(&cadet_io_lock);
|
|
|
|
outb(7,io); /* Select tuner control */
|
|
/*
|
|
* Write the shift register
|
|
*/
|
|
test=0;
|
|
test=(fifo>>23)&0x02; /* Align data for SDO */
|
|
test|=0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */
|
|
outb(7,io); /* Select tuner control */
|
|
outb(test,io+1); /* Initialize for write */
|
|
for(i=0;i<25;i++) {
|
|
test|=0x01; /* Toggle SCK High */
|
|
outb(test,io+1);
|
|
test&=0xfe; /* Toggle SCK Low */
|
|
outb(test,io+1);
|
|
fifo=fifo<<1; /* Prepare the next bit */
|
|
test=0x1c|((fifo>>23)&0x02);
|
|
outb(test,io+1);
|
|
}
|
|
spin_unlock(&cadet_io_lock);
|
|
}
|
|
|
|
static void cadet_setfreq(unsigned freq)
|
|
{
|
|
unsigned fifo;
|
|
int i,j,test;
|
|
int curvol;
|
|
|
|
/*
|
|
* Formulate a fifo command
|
|
*/
|
|
fifo=0;
|
|
if(curtuner==0) { /* FM */
|
|
test=102400;
|
|
freq=(freq*1000)/16; /* Make it kHz */
|
|
freq+=10700; /* IF is 10700 kHz */
|
|
for(i=0;i<14;i++) {
|
|
fifo=fifo<<1;
|
|
if(freq>=test) {
|
|
fifo|=0x01;
|
|
freq-=test;
|
|
}
|
|
test=test>>1;
|
|
}
|
|
}
|
|
if(curtuner==1) { /* AM */
|
|
fifo=(freq/16)+2010; /* Make it kHz */
|
|
fifo|=0x100000; /* Select AM Band */
|
|
}
|
|
|
|
/*
|
|
* Save current volume/mute setting
|
|
*/
|
|
|
|
spin_lock(&cadet_io_lock);
|
|
outb(7,io); /* Select tuner control */
|
|
curvol=inb(io+1);
|
|
spin_unlock(&cadet_io_lock);
|
|
|
|
/*
|
|
* Tune the card
|
|
*/
|
|
for(j=3;j>-1;j--) {
|
|
cadet_settune(fifo|(j<<16));
|
|
|
|
spin_lock(&cadet_io_lock);
|
|
outb(7,io); /* Select tuner control */
|
|
outb(curvol,io+1);
|
|
spin_unlock(&cadet_io_lock);
|
|
|
|
msleep(100);
|
|
|
|
cadet_gettune();
|
|
if((tunestat & 0x40) == 0) { /* Tuned */
|
|
sigstrength=sigtable[curtuner][j];
|
|
return;
|
|
}
|
|
}
|
|
sigstrength=0;
|
|
}
|
|
|
|
|
|
static int cadet_getvol(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
spin_lock(&cadet_io_lock);
|
|
|
|
outb(7,io); /* Select tuner control */
|
|
if((inb(io + 1) & 0x20) != 0)
|
|
ret = 0xffff;
|
|
|
|
spin_unlock(&cadet_io_lock);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void cadet_setvol(int vol)
|
|
{
|
|
spin_lock(&cadet_io_lock);
|
|
outb(7,io); /* Select tuner control */
|
|
if(vol>0)
|
|
outb(0x20,io+1);
|
|
else
|
|
outb(0x00,io+1);
|
|
spin_unlock(&cadet_io_lock);
|
|
}
|
|
|
|
static void cadet_handler(unsigned long data)
|
|
{
|
|
/*
|
|
* Service the RDS fifo
|
|
*/
|
|
|
|
if(spin_trylock(&cadet_io_lock))
|
|
{
|
|
outb(0x3,io); /* Select RDS Decoder Control */
|
|
if((inb(io+1)&0x20)!=0) {
|
|
printk(KERN_CRIT "cadet: RDS fifo overflow\n");
|
|
}
|
|
outb(0x80,io); /* Select RDS fifo */
|
|
while((inb(io)&0x80)!=0) {
|
|
rdsbuf[rdsin]=inb(io+1);
|
|
if(rdsin==rdsout)
|
|
printk(KERN_WARNING "cadet: RDS buffer overflow\n");
|
|
else
|
|
rdsin++;
|
|
}
|
|
spin_unlock(&cadet_io_lock);
|
|
}
|
|
|
|
/*
|
|
* Service pending read
|
|
*/
|
|
if( rdsin!=rdsout)
|
|
wake_up_interruptible(&read_queue);
|
|
|
|
/*
|
|
* Clean up and exit
|
|
*/
|
|
init_timer(&readtimer);
|
|
readtimer.function=cadet_handler;
|
|
readtimer.data=(unsigned long)0;
|
|
readtimer.expires=jiffies+(HZ/20);
|
|
add_timer(&readtimer);
|
|
}
|
|
|
|
|
|
|
|
static ssize_t cadet_read(struct file *file, char __user *data,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int i=0;
|
|
unsigned char readbuf[RDS_BUFFER];
|
|
|
|
if(rdsstat==0) {
|
|
spin_lock(&cadet_io_lock);
|
|
rdsstat=1;
|
|
outb(0x80,io); /* Select RDS fifo */
|
|
spin_unlock(&cadet_io_lock);
|
|
init_timer(&readtimer);
|
|
readtimer.function=cadet_handler;
|
|
readtimer.data=(unsigned long)0;
|
|
readtimer.expires=jiffies+(HZ/20);
|
|
add_timer(&readtimer);
|
|
}
|
|
if(rdsin==rdsout) {
|
|
if (file->f_flags & O_NONBLOCK)
|
|
return -EWOULDBLOCK;
|
|
interruptible_sleep_on(&read_queue);
|
|
}
|
|
while( i<count && rdsin!=rdsout)
|
|
readbuf[i++]=rdsbuf[rdsout++];
|
|
|
|
if (copy_to_user(data,readbuf,i))
|
|
return -EFAULT;
|
|
return i;
|
|
}
|
|
|
|
|
|
|
|
static int cadet_do_ioctl(struct inode *inode, struct file *file,
|
|
unsigned int cmd, void *arg)
|
|
{
|
|
switch(cmd)
|
|
{
|
|
case VIDIOCGCAP:
|
|
{
|
|
struct video_capability *v = arg;
|
|
memset(v,0,sizeof(*v));
|
|
v->type=VID_TYPE_TUNER;
|
|
v->channels=2;
|
|
v->audios=1;
|
|
strcpy(v->name, "ADS Cadet");
|
|
return 0;
|
|
}
|
|
case VIDIOCGTUNER:
|
|
{
|
|
struct video_tuner *v = arg;
|
|
if((v->tuner<0)||(v->tuner>1)) {
|
|
return -EINVAL;
|
|
}
|
|
switch(v->tuner) {
|
|
case 0:
|
|
strcpy(v->name,"FM");
|
|
v->rangelow=1400; /* 87.5 MHz */
|
|
v->rangehigh=1728; /* 108.0 MHz */
|
|
v->flags=0;
|
|
v->mode=0;
|
|
v->mode|=VIDEO_MODE_AUTO;
|
|
v->signal=sigstrength;
|
|
if(cadet_getstereo()==1) {
|
|
v->flags|=VIDEO_TUNER_STEREO_ON;
|
|
}
|
|
v->flags|=cadet_getrds();
|
|
break;
|
|
case 1:
|
|
strcpy(v->name,"AM");
|
|
v->rangelow=8320; /* 520 kHz */
|
|
v->rangehigh=26400; /* 1650 kHz */
|
|
v->flags=0;
|
|
v->flags|=VIDEO_TUNER_LOW;
|
|
v->mode=0;
|
|
v->mode|=VIDEO_MODE_AUTO;
|
|
v->signal=sigstrength;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
case VIDIOCSTUNER:
|
|
{
|
|
struct video_tuner *v = arg;
|
|
if((v->tuner<0)||(v->tuner>1)) {
|
|
return -EINVAL;
|
|
}
|
|
curtuner=v->tuner;
|
|
return 0;
|
|
}
|
|
case VIDIOCGFREQ:
|
|
{
|
|
unsigned long *freq = arg;
|
|
*freq = cadet_getfreq();
|
|
return 0;
|
|
}
|
|
case VIDIOCSFREQ:
|
|
{
|
|
unsigned long *freq = arg;
|
|
if((curtuner==0)&&((*freq<1400)||(*freq>1728))) {
|
|
return -EINVAL;
|
|
}
|
|
if((curtuner==1)&&((*freq<8320)||(*freq>26400))) {
|
|
return -EINVAL;
|
|
}
|
|
cadet_setfreq(*freq);
|
|
return 0;
|
|
}
|
|
case VIDIOCGAUDIO:
|
|
{
|
|
struct video_audio *v = arg;
|
|
memset(v,0, sizeof(*v));
|
|
v->flags=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
|
|
if(cadet_getstereo()==0) {
|
|
v->mode=VIDEO_SOUND_MONO;
|
|
} else {
|
|
v->mode=VIDEO_SOUND_STEREO;
|
|
}
|
|
v->volume=cadet_getvol();
|
|
v->step=0xffff;
|
|
strcpy(v->name, "Radio");
|
|
return 0;
|
|
}
|
|
case VIDIOCSAUDIO:
|
|
{
|
|
struct video_audio *v = arg;
|
|
if(v->audio)
|
|
return -EINVAL;
|
|
cadet_setvol(v->volume);
|
|
if(v->flags&VIDEO_AUDIO_MUTE)
|
|
cadet_setvol(0);
|
|
else
|
|
cadet_setvol(0xffff);
|
|
return 0;
|
|
}
|
|
default:
|
|
return -ENOIOCTLCMD;
|
|
}
|
|
}
|
|
|
|
static int cadet_ioctl(struct inode *inode, struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
return video_usercopy(inode, file, cmd, arg, cadet_do_ioctl);
|
|
}
|
|
|
|
static int cadet_open(struct inode *inode, struct file *file)
|
|
{
|
|
if(users)
|
|
return -EBUSY;
|
|
users++;
|
|
init_waitqueue_head(&read_queue);
|
|
return 0;
|
|
}
|
|
|
|
static int cadet_release(struct inode *inode, struct file *file)
|
|
{
|
|
del_timer_sync(&readtimer);
|
|
rdsstat=0;
|
|
users--;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct file_operations cadet_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = cadet_open,
|
|
.release = cadet_release,
|
|
.read = cadet_read,
|
|
.ioctl = cadet_ioctl,
|
|
.compat_ioctl = v4l_compat_ioctl32,
|
|
.llseek = no_llseek,
|
|
};
|
|
|
|
static struct video_device cadet_radio=
|
|
{
|
|
.owner = THIS_MODULE,
|
|
.name = "Cadet radio",
|
|
.type = VID_TYPE_TUNER,
|
|
.hardware = VID_HARDWARE_CADET,
|
|
.fops = &cadet_fops,
|
|
};
|
|
|
|
static struct pnp_device_id cadet_pnp_devices[] = {
|
|
/* ADS Cadet AM/FM Radio Card */
|
|
{.id = "MSM0c24", .driver_data = 0},
|
|
{.id = ""}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices);
|
|
|
|
static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id)
|
|
{
|
|
if (!dev)
|
|
return -ENODEV;
|
|
/* only support one device */
|
|
if (io > 0)
|
|
return -EBUSY;
|
|
|
|
if (!pnp_port_valid(dev, 0)) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
io = pnp_port_start(dev, 0);
|
|
|
|
printk ("radio-cadet: PnP reports device at %#x\n", io);
|
|
|
|
return io;
|
|
}
|
|
|
|
static struct pnp_driver cadet_pnp_driver = {
|
|
.name = "radio-cadet",
|
|
.id_table = cadet_pnp_devices,
|
|
.probe = cadet_pnp_probe,
|
|
.remove = NULL,
|
|
};
|
|
|
|
static int cadet_probe(void)
|
|
{
|
|
static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
|
|
int i;
|
|
|
|
for(i=0;i<8;i++) {
|
|
io=iovals[i];
|
|
if (request_region(io, 2, "cadet-probe")) {
|
|
cadet_setfreq(1410);
|
|
if(cadet_getfreq()==1410) {
|
|
release_region(io, 2);
|
|
return io;
|
|
}
|
|
release_region(io, 2);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
spin_lock_init(&cadet_io_lock);
|
|
|
|
/*
|
|
* If a probe was requested then probe ISAPnP first (safest)
|
|
*/
|
|
if (io < 0)
|
|
pnp_register_driver(&cadet_pnp_driver);
|
|
/*
|
|
* If that fails then probe unsafely if probe is requested
|
|
*/
|
|
if(io < 0)
|
|
io = cadet_probe ();
|
|
|
|
/*
|
|
* Else we bail out
|
|
*/
|
|
|
|
if(io < 0) {
|
|
#ifdef MODULE
|
|
printk(KERN_ERR "You must set an I/O address with io=0x???\n");
|
|
#endif
|
|
goto fail;
|
|
}
|
|
if (!request_region(io,2,"cadet"))
|
|
goto fail;
|
|
if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) {
|
|
release_region(io,2);
|
|
goto fail;
|
|
}
|
|
printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io);
|
|
return 0;
|
|
fail:
|
|
pnp_unregister_driver(&cadet_pnp_driver);
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
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");
|
|
|
|
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);
|
|
|
|
static void __exit cadet_cleanup_module(void)
|
|
{
|
|
video_unregister_device(&cadet_radio);
|
|
release_region(io,2);
|
|
pnp_unregister_driver(&cadet_pnp_driver);
|
|
}
|
|
|
|
module_init(cadet_init);
|
|
module_exit(cadet_cleanup_module);
|
|
|