~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

Linux Cross Reference
Linux-2.6.17/drivers/media/radio/radio-cadet.c

Version: ~ [ 2.6.16 ] ~ [ 2.6.17 ] ~
Architecture: ~ [ ia64 ] ~ [ i386 ] ~ [ arm ] ~ [ ppc ] ~ [ sparc64 ] ~

  1 /* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card
  2  *
  3  * by Fred Gleason <fredg@wava.com>
  4  * Version 0.3.3
  5  *
  6  * (Loosely) based on code for the Aztech radio card by
  7  *
  8  * Russell Kroll    (rkroll@exploits.org)
  9  * Quay Ly
 10  * Donald Song
 11  * Jason Lewis      (jlewis@twilight.vtc.vsc.edu) 
 12  * Scott McGrath    (smcgrath@twilight.vtc.vsc.edu)
 13  * William McGrath  (wmcgrath@twilight.vtc.vsc.edu)
 14  *
 15  * History:
 16  * 2000-04-29   Russell Kroll <rkroll@exploits.org>
 17  *              Added ISAPnP detection for Linux 2.3/2.4
 18  *
 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)
 22  *
 23  * 2002-01-17   Adam Belay <ambx1@neo.rr.com>
 24  *              Updated to latest pnp code
 25  *
 26  * 2003-01-31   Alan Cox <alan@redhat.com>
 27  *              Cleaned up locking, delay code, general odds and ends
 28  */
 29 
 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>
 39 
 40 #define RDS_BUFFER 256
 41 
 42 static int io=-1;               /* default to isapnp activation */
 43 static int radio_nr = -1;
 44 static int users=0;
 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;
 53 
 54 static int cadet_probe(void);
 55 
 56 /*
 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.
 60  */
 61 static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}};
 62 
 63 static int cadet_getrds(void)
 64 {
 65         int rdsstat=0;
 66 
 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);
 71         
 72         msleep(100);
 73 
 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;
 78         }
 79         if((inb(io+1)&0x10)!=0) {
 80                 rdsstat|=VIDEO_TUNER_MBS_ON;
 81         }
 82         spin_unlock(&cadet_io_lock);
 83         return rdsstat;
 84 }
 85 
 86 static int cadet_getstereo(void)
 87 {
 88         int ret = 0;
 89         if(curtuner != 0)       /* Only FM has stereo capability! */
 90                 return 0;
 91 
 92         spin_lock(&cadet_io_lock);
 93         outb(7,io);          /* Select tuner control */
 94         if( (inb(io+1) & 0x40) == 0)
 95                 ret = 1;
 96         spin_unlock(&cadet_io_lock);
 97         return ret;
 98 }
 99 
100 static unsigned cadet_gettune(void)
101 {
102         int curvol,i;
103         unsigned fifo=0;
104 
105         /*
106          * Prepare for read
107          */
108 
109         spin_lock(&cadet_io_lock);
110         
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 */
114         tunestat=0xffff;
115 
116         /*
117          * Read the shift register
118          */
119         for(i=0;i<25;i++) {
120                 fifo=(fifo<<1)|((inb(io+1)>>7)&0x01);
121                 if(i<24) {
122                         outb(0x01,io+1);
123                         tunestat&=inb(io+1);
124                         outb(0x00,io+1);
125                 }
126         }
127 
128         /*
129          * Restore volume/mute setting
130          */
131         outb(curvol,io+1);
132         spin_unlock(&cadet_io_lock);
133 
134         return fifo;
135 }
136 
137 static unsigned cadet_getfreq(void)
138 {
139         int i;
140         unsigned freq=0,test,fifo=0;
141 
142         /*
143          * Read current tuning
144          */
145         fifo=cadet_gettune();
146 
147         /*
148          * Convert to actual frequency
149          */
150         if(curtuner==0) {    /* FM */
151                 test=12500;
152                 for(i=0;i<14;i++) {
153                         if((fifo&0x01)!=0) {
154                                 freq+=test;
155                         }
156                         test=test<<1;
157                         fifo=fifo>>1;
158                 }
159                 freq-=10700000;           /* IF frequency is 10.7 MHz */
160                 freq=(freq*16)/1000000;   /* Make it 1/16 MHz */
161         }
162         if(curtuner==1) {    /* AM */
163                 freq=((fifo&0x7fff)-2010)*16;
164         }
165 
166         return freq;
167 }
168 
169 static void cadet_settune(unsigned fifo)
170 {
171         int i;
172         unsigned test;  
173 
174         spin_lock(&cadet_io_lock);
175         
176         outb(7,io);                /* Select tuner control */
177         /*
178          * Write the shift register
179          */
180         test=0;
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 */
185         for(i=0;i<25;i++) {
186                 test|=0x01;              /* Toggle SCK High */
187                 outb(test,io+1);
188                 test&=0xfe;              /* Toggle SCK Low */
189                 outb(test,io+1);
190                 fifo=fifo<<1;            /* Prepare the next bit */
191                 test=0x1c|((fifo>>23)&0x02);
192                 outb(test,io+1);
193         }
194         spin_unlock(&cadet_io_lock);
195 }
196 
197 static void cadet_setfreq(unsigned freq)
198 {
199         unsigned fifo;
200         int i,j,test;
201         int curvol;
202 
203         /* 
204          * Formulate a fifo command
205          */
206         fifo=0;
207         if(curtuner==0) {    /* FM */
208                 test=102400;
209                 freq=(freq*1000)/16;       /* Make it kHz */
210                 freq+=10700;               /* IF is 10700 kHz */
211                 for(i=0;i<14;i++) {
212                         fifo=fifo<<1;
213                         if(freq>=test) {
214                                 fifo|=0x01;
215                                 freq-=test;
216                         }
217                         test=test>>1;
218                 }
219         }
220         if(curtuner==1) {    /* AM */
221                 fifo=(freq/16)+2010;            /* Make it kHz */
222                 fifo|=0x100000;            /* Select AM Band */
223         }
224 
225         /*
226          * Save current volume/mute setting
227          */
228 
229         spin_lock(&cadet_io_lock);
230         outb(7,io);                /* Select tuner control */
231         curvol=inb(io+1); 
232         spin_unlock(&cadet_io_lock);
233 
234         /*
235          * Tune the card
236          */
237         for(j=3;j>-1;j--) {
238                 cadet_settune(fifo|(j<<16));
239                 
240                 spin_lock(&cadet_io_lock);
241                 outb(7,io);         /* Select tuner control */
242                 outb(curvol,io+1);
243                 spin_unlock(&cadet_io_lock);
244                 
245                 msleep(100);
246 
247                 cadet_gettune();
248                 if((tunestat & 0x40) == 0) {   /* Tuned */
249                         sigstrength=sigtable[curtuner][j];
250                         return;
251                 }
252         }
253         sigstrength=0;
254 }
255 
256 
257 static int cadet_getvol(void)
258 {
259         int ret = 0;
260         
261         spin_lock(&cadet_io_lock);
262         
263         outb(7,io);                /* Select tuner control */
264         if((inb(io + 1) & 0x20) != 0)
265                 ret = 0xffff;
266         
267         spin_unlock(&cadet_io_lock);
268         return ret;
269 }
270 
271 
272 static void cadet_setvol(int vol)
273 {
274         spin_lock(&cadet_io_lock);
275         outb(7,io);                /* Select tuner control */
276         if(vol>0)
277                 outb(0x20,io+1);
278         else
279                 outb(0x00,io+1);
280         spin_unlock(&cadet_io_lock);
281 }  
282 
283 static void cadet_handler(unsigned long data)
284 {
285         /*
286          * Service the RDS fifo
287          */
288 
289         if(spin_trylock(&cadet_io_lock))
290         {
291                 outb(0x3,io);       /* Select RDS Decoder Control */
292                 if((inb(io+1)&0x20)!=0) {
293                         printk(KERN_CRIT "cadet: RDS fifo overflow\n");
294                 }
295                 outb(0x80,io);      /* Select RDS fifo */
296                 while((inb(io)&0x80)!=0) {
297                         rdsbuf[rdsin]=inb(io+1);
298                         if(rdsin==rdsout)
299                                 printk(KERN_WARNING "cadet: RDS buffer overflow\n");
300                         else
301                                 rdsin++;
302                 }
303                 spin_unlock(&cadet_io_lock);
304         }
305 
306         /*
307          * Service pending read
308          */
309         if( rdsin!=rdsout)
310                 wake_up_interruptible(&read_queue);
311 
312         /* 
313          * Clean up and exit
314          */
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);
320 }
321 
322 
323 
324 static ssize_t cadet_read(struct file *file, char __user *data,
325                           size_t count, loff_t *ppos)
326 {
327         int i=0;
328         unsigned char readbuf[RDS_BUFFER];
329 
330         if(rdsstat==0) {
331                 spin_lock(&cadet_io_lock);
332                 rdsstat=1;
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);
340         }
341         if(rdsin==rdsout) {
342                 if (file->f_flags & O_NONBLOCK)
343                         return -EWOULDBLOCK;
344                 interruptible_sleep_on(&read_queue);
345         }               
346         while( i<count && rdsin!=rdsout)
347                 readbuf[i++]=rdsbuf[rdsout++];
348 
349         if (copy_to_user(data,readbuf,i))
350                 return -EFAULT;
351         return i;
352 }
353 
354 
355 
356 static int cadet_do_ioctl(struct inode *inode, struct file *file,
357                           unsigned int cmd, void *arg)
358 {
359         switch(cmd)
360         {
361                 case VIDIOCGCAP:
362                 {
363                         struct video_capability *v = arg;
364                         memset(v,0,sizeof(*v));
365                         v->type=VID_TYPE_TUNER;
366                         v->channels=2;
367                         v->audios=1;
368                         strcpy(v->name, "ADS Cadet");
369                         return 0;
370                 }
371                 case VIDIOCGTUNER:
372                 {
373                         struct video_tuner *v = arg;
374                         if((v->tuner<0)||(v->tuner>1)) {
375                                 return -EINVAL;
376                         }
377                         switch(v->tuner) {
378                                 case 0:
379                                 strcpy(v->name,"FM");
380                                 v->rangelow=1400;     /* 87.5 MHz */
381                                 v->rangehigh=1728;    /* 108.0 MHz */
382                                 v->flags=0;
383                                 v->mode=0;
384                                 v->mode|=VIDEO_MODE_AUTO;
385                                 v->signal=sigstrength;
386                                 if(cadet_getstereo()==1) {
387                                         v->flags|=VIDEO_TUNER_STEREO_ON;
388                                 }
389                                 v->flags|=cadet_getrds();
390                                 break;
391                                 case 1:
392                                 strcpy(v->name,"AM");
393                                 v->rangelow=8320;      /* 520 kHz */
394                                 v->rangehigh=26400;    /* 1650 kHz */
395                                 v->flags=0;
396                                 v->flags|=VIDEO_TUNER_LOW;
397                                 v->mode=0;
398                                 v->mode|=VIDEO_MODE_AUTO;
399                                 v->signal=sigstrength;
400                                 break;
401                         }
402                         return 0;
403                 }
404                 case VIDIOCSTUNER:
405                 {
406                         struct video_tuner *v = arg;
407                         if((v->tuner<0)||(v->tuner>1)) {
408                                 return -EINVAL;
409                         }
410                         curtuner=v->tuner;      
411                         return 0;
412                 }
413                 case VIDIOCGFREQ:
414                 {
415                         unsigned long *freq = arg;
416                         *freq = cadet_getfreq();
417                         return 0;
418                 }
419                 case VIDIOCSFREQ:
420                 {
421                         unsigned long *freq = arg;
422                         if((curtuner==0)&&((*freq<1400)||(*freq>1728))) {
423                                 return -EINVAL;
424                         }
425                         if((curtuner==1)&&((*freq<8320)||(*freq>26400))) {
426                                 return -EINVAL;
427                         }
428                         cadet_setfreq(*freq);
429                         return 0;
430                 }
431                 case VIDIOCGAUDIO:
432                 {       
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;
438                         } else {
439                                 v->mode=VIDEO_SOUND_STEREO;
440                         }
441                         v->volume=cadet_getvol();
442                         v->step=0xffff;
443                         strcpy(v->name, "Radio");
444                         return 0;                       
445                 }
446                 case VIDIOCSAUDIO:
447                 {
448                         struct video_audio *v = arg;
449                         if(v->audio) 
450                                 return -EINVAL;
451                         cadet_setvol(v->volume);
452                         if(v->flags&VIDEO_AUDIO_MUTE) 
453                                 cadet_setvol(0);
454                         else
455                                 cadet_setvol(0xffff);
456                         return 0;
457                 }
458                 default:
459                         return -ENOIOCTLCMD;
460         }
461 }
462 
463 static int cadet_ioctl(struct inode *inode, struct file *file,
464                        unsigned int cmd, unsigned long arg)
465 {
466         return video_usercopy(inode, file, cmd, arg, cadet_do_ioctl);
467 }
468 
469 static int cadet_open(struct inode *inode, struct file *file)
470 {
471         if(users)
472                 return -EBUSY;
473         users++;
474         init_waitqueue_head(&read_queue);
475         return 0;
476 }
477 
478 static int cadet_release(struct inode *inode, struct file *file)
479 {
480         del_timer_sync(&readtimer);
481         rdsstat=0;
482         users--;
483         return 0;
484 }
485 
486 
487 static struct file_operations cadet_fops = {
488         .owner          = THIS_MODULE,
489         .open           = cadet_open,
490         .release        = cadet_release,
491         .read           = cadet_read,
492         .ioctl          = cadet_ioctl,
493         .compat_ioctl   = v4l_compat_ioctl32,
494         .llseek         = no_llseek,
495 };
496 
497 static struct video_device cadet_radio=
498 {
499         .owner          = THIS_MODULE,
500         .name           = "Cadet radio",
501         .type           = VID_TYPE_TUNER,
502         .hardware       = VID_HARDWARE_CADET,
503         .fops           = &cadet_fops,
504 };
505 
506 static struct pnp_device_id cadet_pnp_devices[] = {
507         /* ADS Cadet AM/FM Radio Card */
508         {.id = "MSM0c24", .driver_data = 0},
509         {.id = ""}
510 };
511 
512 MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices);
513 
514 static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id)
515 {
516         if (!dev)
517                 return -ENODEV;
518         /* only support one device */
519         if (io > 0)
520                 return -EBUSY;
521 
522         if (!pnp_port_valid(dev, 0)) {
523                 return -ENODEV;
524         }
525 
526         io = pnp_port_start(dev, 0);
527 
528         printk ("radio-cadet: PnP reports device at %#x\n", io);
529 
530         return io;
531 }
532 
533 static struct pnp_driver cadet_pnp_driver = {
534         .name           = "radio-cadet",
535         .id_table       = cadet_pnp_devices,
536         .probe          = cadet_pnp_probe,
537         .remove         = NULL,
538 };
539 
540 static int cadet_probe(void)
541 {
542         static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
543         int i;
544 
545         for(i=0;i<8;i++) {
546                 io=iovals[i];
547                 if (request_region(io, 2, "cadet-probe")) {
548                         cadet_setfreq(1410);
549                         if(cadet_getfreq()==1410) {
550                                 release_region(io, 2);
551                                 return io;
552                         }
553                         release_region(io, 2);
554                 }
555         }
556         return -1;
557 }
558 
559 /* 
560  * io should only be set if the user has used something like
561  * isapnp (the userspace program) to initialize this card for us
562  */
563 
564 static int __init cadet_init(void)
565 {
566         spin_lock_init(&cadet_io_lock);
567         
568         /*
569          *      If a probe was requested then probe ISAPnP first (safest)
570          */
571         if (io < 0)
572                 pnp_register_driver(&cadet_pnp_driver);
573         /*
574          *      If that fails then probe unsafely if probe is requested
575          */
576         if(io < 0)
577                 io = cadet_probe ();
578 
579         /*
580          *      Else we bail out
581          */
582          
583         if(io < 0) {
584 #ifdef MODULE        
585                 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
586 #endif
587                 goto fail;
588         }
589         if (!request_region(io,2,"cadet"))
590                 goto fail;
591         if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) {
592                 release_region(io,2);
593                 goto fail;
594         }
595         printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io);
596         return 0;
597 fail:
598         pnp_unregister_driver(&cadet_pnp_driver);
599         return -1;
600 }
601 
602 
603 
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");
607 
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);
611 
612 static void __exit cadet_cleanup_module(void)
613 {
614         video_unregister_device(&cadet_radio);
615         release_region(io,2);
616         pnp_unregister_driver(&cadet_pnp_driver);
617 }
618 
619 module_init(cadet_init);
620 module_exit(cadet_cleanup_module);
621 
622 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

This page was automatically generated by the LXR engine.
Visit the LXR main site for more information.