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