1 /* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card
 
   3  * by Fred Gleason <fredg@wava.com>
 
   6  * (Loosely) based on code for the Aztech radio card by
 
   8  * Russell Kroll    (rkroll@exploits.org)
 
  11  * Jason Lewis      (jlewis@twilight.vtc.vsc.edu) 
 
  12  * Scott McGrath    (smcgrath@twilight.vtc.vsc.edu)
 
  13  * William McGrath  (wmcgrath@twilight.vtc.vsc.edu)
 
  16  * 2000-04-29   Russell Kroll <rkroll@exploits.org>
 
  17  *              Added ISAPnP detection for Linux 2.3/2.4
 
  19  * 2001-01-10   Russell Kroll <rkroll@exploits.org>
 
  20  *              Removed dead CONFIG_RADIO_CADET_PORT code
 
  21  *              PnP detection on load is now default (no args necessary)
 
  23  * 2002-01-17   Adam Belay <ambx1@neo.rr.com>
 
  24  *              Updated to latest pnp code
 
  26  * 2003-01-31   Alan Cox <alan@redhat.com>
 
  27  *              Cleaned up locking, delay code, general odds and ends
 
  30 #include <linux/module.h>       /* Modules                      */
 
  31 #include <linux/init.h>         /* Initdata                     */
 
  32 #include <linux/ioport.h>       /* request_region               */
 
  33 #include <linux/delay.h>        /* udelay                       */
 
  34 #include <asm/io.h>             /* outb, outb_p                 */
 
  35 #include <asm/uaccess.h>        /* copy to/from user            */
 
  36 #include <linux/videodev.h>     /* kernel radio structs         */
 
  37 #include <linux/param.h>
 
  38 #include <linux/pnp.h>
 
  40 #define RDS_BUFFER 256
 
  42 static int io=-1;               /* default to isapnp activation */
 
  43 static int radio_nr = -1;
 
  45 static int curtuner=0;
 
  46 static int tunestat=0;
 
  47 static int sigstrength=0;
 
  48 static wait_queue_head_t read_queue;
 
  49 static struct timer_list readtimer;
 
  50 static __u8 rdsin=0,rdsout=0,rdsstat=0;
 
  51 static unsigned char rdsbuf[RDS_BUFFER];
 
  52 static spinlock_t cadet_io_lock;
 
  54 static int cadet_probe(void);
 
  57  * Signal Strength Threshold Values
 
  58  * The V4L API spec does not define any particular unit for the signal 
 
  59  * strength value.  These values are in microvolts of RF at the tuner's input.
 
  61 static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}};
 
  63 static int cadet_getrds(void)
 
  67         spin_lock(&cadet_io_lock);
 
  68         outb(3,io);                 /* Select Decoder Control/Status */
 
  69         outb(inb(io+1)&0x7f,io+1);  /* Reset RDS detection */
 
  70         spin_unlock(&cadet_io_lock);
 
  74         spin_lock(&cadet_io_lock);      
 
  75         outb(3,io);                 /* Select Decoder Control/Status */
 
  76         if((inb(io+1)&0x80)!=0) {
 
  77                 rdsstat|=VIDEO_TUNER_RDS_ON;
 
  79         if((inb(io+1)&0x10)!=0) {
 
  80                 rdsstat|=VIDEO_TUNER_MBS_ON;
 
  82         spin_unlock(&cadet_io_lock);
 
  86 static int cadet_getstereo(void)
 
  89         if(curtuner != 0)       /* Only FM has stereo capability! */
 
  92         spin_lock(&cadet_io_lock);
 
  93         outb(7,io);          /* Select tuner control */
 
  94         if( (inb(io+1) & 0x40) == 0)
 
  96         spin_unlock(&cadet_io_lock);
 
 100 static unsigned cadet_gettune(void)
 
 109         spin_lock(&cadet_io_lock);
 
 111         outb(7,io);       /* Select tuner control */
 
 112         curvol=inb(io+1); /* Save current volume/mute setting */
 
 113         outb(0x00,io+1);  /* Ensure WRITE-ENABLE is LOW */
 
 117          * Read the shift register
 
 120                 fifo=(fifo<<1)|((inb(io+1)>>7)&0x01);
 
 129          * Restore volume/mute setting
 
 132         spin_unlock(&cadet_io_lock);
 
 137 static unsigned cadet_getfreq(void)
 
 140         unsigned freq=0,test,fifo=0;
 
 143          * Read current tuning
 
 145         fifo=cadet_gettune();
 
 148          * Convert to actual frequency
 
 150         if(curtuner==0) {    /* FM */
 
 159                 freq-=10700000;           /* IF frequency is 10.7 MHz */
 
 160                 freq=(freq*16)/1000000;   /* Make it 1/16 MHz */
 
 162         if(curtuner==1) {    /* AM */
 
 163                 freq=((fifo&0x7fff)-2010)*16;
 
 169 static void cadet_settune(unsigned fifo)
 
 174         spin_lock(&cadet_io_lock);
 
 176         outb(7,io);                /* Select tuner control */
 
 178          * Write the shift register
 
 181         test=(fifo>>23)&0x02;      /* Align data for SDO */
 
 182         test|=0x1c;                /* SDM=1, SWE=1, SEN=1, SCK=0 */
 
 183         outb(7,io);                /* Select tuner control */
 
 184         outb(test,io+1);           /* Initialize for write */
 
 186                 test|=0x01;              /* Toggle SCK High */
 
 188                 test&=0xfe;              /* Toggle SCK Low */
 
 190                 fifo=fifo<<1;            /* Prepare the next bit */
 
 191                 test=0x1c|((fifo>>23)&0x02);
 
 194         spin_unlock(&cadet_io_lock);
 
 197 static void cadet_setfreq(unsigned freq)
 
 204          * Formulate a fifo command
 
 207         if(curtuner==0) {    /* FM */
 
 209                 freq=(freq*1000)/16;       /* Make it kHz */
 
 210                 freq+=10700;               /* IF is 10700 kHz */
 
 220         if(curtuner==1) {    /* AM */
 
 221                 fifo=(freq/16)+2010;            /* Make it kHz */
 
 222                 fifo|=0x100000;            /* Select AM Band */
 
 226          * Save current volume/mute setting
 
 229         spin_lock(&cadet_io_lock);
 
 230         outb(7,io);                /* Select tuner control */
 
 232         spin_unlock(&cadet_io_lock);
 
 238                 cadet_settune(fifo|(j<<16));
 
 240                 spin_lock(&cadet_io_lock);
 
 241                 outb(7,io);         /* Select tuner control */
 
 243                 spin_unlock(&cadet_io_lock);
 
 248                 if((tunestat & 0x40) == 0) {   /* Tuned */
 
 249                         sigstrength=sigtable[curtuner][j];
 
 257 static int cadet_getvol(void)
 
 261         spin_lock(&cadet_io_lock);
 
 263         outb(7,io);                /* Select tuner control */
 
 264         if((inb(io + 1) & 0x20) != 0)
 
 267         spin_unlock(&cadet_io_lock);
 
 272 static void cadet_setvol(int vol)
 
 274         spin_lock(&cadet_io_lock);
 
 275         outb(7,io);                /* Select tuner control */
 
 280         spin_unlock(&cadet_io_lock);
 
 283 static void cadet_handler(unsigned long data)
 
 286          * Service the RDS fifo
 
 289         if(spin_trylock(&cadet_io_lock))
 
 291                 outb(0x3,io);       /* Select RDS Decoder Control */
 
 292                 if((inb(io+1)&0x20)!=0) {
 
 293                         printk(KERN_CRIT "cadet: RDS fifo overflow\n");
 
 295                 outb(0x80,io);      /* Select RDS fifo */
 
 296                 while((inb(io)&0x80)!=0) {
 
 297                         rdsbuf[rdsin]=inb(io+1);
 
 299                                 printk(KERN_WARNING "cadet: RDS buffer overflow\n");
 
 303                 spin_unlock(&cadet_io_lock);
 
 307          * Service pending read
 
 310                 wake_up_interruptible(&read_queue);
 
 315         init_timer(&readtimer);
 
 316         readtimer.function=cadet_handler;
 
 317         readtimer.data=(unsigned long)0;
 
 318         readtimer.expires=jiffies+(HZ/20);
 
 319         add_timer(&readtimer);
 
 324 static ssize_t cadet_read(struct file *file, char __user *data,
 
 325                           size_t count, loff_t *ppos)
 
 328         unsigned char readbuf[RDS_BUFFER];
 
 331                 spin_lock(&cadet_io_lock);
 
 333                 outb(0x80,io);        /* Select RDS fifo */
 
 334                 spin_unlock(&cadet_io_lock);
 
 335                 init_timer(&readtimer);
 
 336                 readtimer.function=cadet_handler;
 
 337                 readtimer.data=(unsigned long)0;
 
 338                 readtimer.expires=jiffies+(HZ/20);
 
 339                 add_timer(&readtimer);
 
 342                 if (file->f_flags & O_NONBLOCK)
 
 344                 interruptible_sleep_on(&read_queue);
 
 346         while( i<count && rdsin!=rdsout)
 
 347                 readbuf[i++]=rdsbuf[rdsout++];
 
 349         if (copy_to_user(data,readbuf,i))
 
 356 static int cadet_do_ioctl(struct inode *inode, struct file *file,
 
 357                           unsigned int cmd, void *arg)
 
 363                         struct video_capability *v = arg;
 
 364                         memset(v,0,sizeof(*v));
 
 365                         v->type=VID_TYPE_TUNER;
 
 368                         strcpy(v->name, "ADS Cadet");
 
 373                         struct video_tuner *v = arg;
 
 374                         if((v->tuner<0)||(v->tuner>1)) {
 
 379                                 strcpy(v->name,"FM");
 
 380                                 v->rangelow=1400;     /* 87.5 MHz */
 
 381                                 v->rangehigh=1728;    /* 108.0 MHz */
 
 384                                 v->mode|=VIDEO_MODE_AUTO;
 
 385                                 v->signal=sigstrength;
 
 386                                 if(cadet_getstereo()==1) {
 
 387                                         v->flags|=VIDEO_TUNER_STEREO_ON;
 
 389                                 v->flags|=cadet_getrds();
 
 392                                 strcpy(v->name,"AM");
 
 393                                 v->rangelow=8320;      /* 520 kHz */
 
 394                                 v->rangehigh=26400;    /* 1650 kHz */
 
 396                                 v->flags|=VIDEO_TUNER_LOW;
 
 398                                 v->mode|=VIDEO_MODE_AUTO;
 
 399                                 v->signal=sigstrength;
 
 406                         struct video_tuner *v = arg;
 
 407                         if((v->tuner<0)||(v->tuner>1)) {
 
 415                         unsigned long *freq = arg;
 
 416                         *freq = cadet_getfreq();
 
 421                         unsigned long *freq = arg;
 
 422                         if((curtuner==0)&&((*freq<1400)||(*freq>1728))) {
 
 425                         if((curtuner==1)&&((*freq<8320)||(*freq>26400))) {
 
 428                         cadet_setfreq(*freq);
 
 433                         struct video_audio *v = arg;
 
 434                         memset(v,0, sizeof(*v));
 
 435                         v->flags=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
 
 436                         if(cadet_getstereo()==0) {
 
 437                                 v->mode=VIDEO_SOUND_MONO;
 
 439                                 v->mode=VIDEO_SOUND_STEREO;
 
 441                         v->volume=cadet_getvol();
 
 443                         strcpy(v->name, "Radio");
 
 448                         struct video_audio *v = arg;
 
 451                         cadet_setvol(v->volume);
 
 452                         if(v->flags&VIDEO_AUDIO_MUTE) 
 
 455                                 cadet_setvol(0xffff);
 
 463 static int cadet_ioctl(struct inode *inode, struct file *file,
 
 464                        unsigned int cmd, unsigned long arg)
 
 466         return video_usercopy(inode, file, cmd, arg, cadet_do_ioctl);
 
 469 static int cadet_open(struct inode *inode, struct file *file)
 
 474         init_waitqueue_head(&read_queue);
 
 478 static int cadet_release(struct inode *inode, struct file *file)
 
 480         del_timer_sync(&readtimer);
 
 487 static struct file_operations cadet_fops = {
 
 488         .owner          = THIS_MODULE,
 
 490         .release        = cadet_release,
 
 492         .ioctl          = cadet_ioctl,
 
 493         .compat_ioctl   = v4l_compat_ioctl32,
 
 497 static struct video_device cadet_radio=
 
 499         .owner          = THIS_MODULE,
 
 500         .name           = "Cadet radio",
 
 501         .type           = VID_TYPE_TUNER,
 
 502         .hardware       = VID_HARDWARE_CADET,
 
 506 static struct pnp_device_id cadet_pnp_devices[] = {
 
 507         /* ADS Cadet AM/FM Radio Card */
 
 508         {.id = "MSM0c24", .driver_data = 0},
 
 512 MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices);
 
 514 static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id)
 
 518         /* only support one device */
 
 522         if (!pnp_port_valid(dev, 0)) {
 
 526         io = pnp_port_start(dev, 0);
 
 528         printk ("radio-cadet: PnP reports device at %#x\n", io);
 
 533 static struct pnp_driver cadet_pnp_driver = {
 
 534         .name           = "radio-cadet",
 
 535         .id_table       = cadet_pnp_devices,
 
 536         .probe          = cadet_pnp_probe,
 
 540 static int cadet_probe(void)
 
 542         static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
 
 547                 if (request_region(io, 2, "cadet-probe")) {
 
 549                         if(cadet_getfreq()==1410) {
 
 550                                 release_region(io, 2);
 
 553                         release_region(io, 2);
 
 560  * io should only be set if the user has used something like
 
 561  * isapnp (the userspace program) to initialize this card for us
 
 564 static int __init cadet_init(void)
 
 566         spin_lock_init(&cadet_io_lock);
 
 569          *      If a probe was requested then probe ISAPnP first (safest)
 
 572                 pnp_register_driver(&cadet_pnp_driver);
 
 574          *      If that fails then probe unsafely if probe is requested
 
 585                 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
 
 589         if (!request_region(io,2,"cadet"))
 
 591         if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) {
 
 592                 release_region(io,2);
 
 595         printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io);
 
 598         pnp_unregister_driver(&cadet_pnp_driver);
 
 604 MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
 
 605 MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");
 
 606 MODULE_LICENSE("GPL");
 
 608 module_param(io, int, 0);
 
 609 MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
 
 610 module_param(radio_nr, int, 0);
 
 612 static void __exit cadet_cleanup_module(void)
 
 614         video_unregister_device(&cadet_radio);
 
 615         release_region(io,2);
 
 616         pnp_unregister_driver(&cadet_pnp_driver);
 
 619 module_init(cadet_init);
 
 620 module_exit(cadet_cleanup_module);