Merge ../linus
[linux-2.6] / 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>       /* 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 <media/v4l2-common.h>
38 #include <linux/param.h>
39 #include <linux/pnp.h>
40
41 #define RDS_BUFFER 256
42
43 static int io=-1;               /* default to isapnp activation */
44 static int radio_nr = -1;
45 static int users=0;
46 static int curtuner=0;
47 static int tunestat=0;
48 static int sigstrength=0;
49 static wait_queue_head_t read_queue;
50 static struct timer_list readtimer;
51 static __u8 rdsin=0,rdsout=0,rdsstat=0;
52 static unsigned char rdsbuf[RDS_BUFFER];
53 static spinlock_t cadet_io_lock;
54
55 static int cadet_probe(void);
56
57 /*
58  * Signal Strength Threshold Values
59  * The V4L API spec does not define any particular unit for the signal
60  * strength value.  These values are in microvolts of RF at the tuner's input.
61  */
62 static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}};
63
64 static int cadet_getrds(void)
65 {
66         int rdsstat=0;
67
68         spin_lock(&cadet_io_lock);
69         outb(3,io);                 /* Select Decoder Control/Status */
70         outb(inb(io+1)&0x7f,io+1);  /* Reset RDS detection */
71         spin_unlock(&cadet_io_lock);
72
73         msleep(100);
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                 msleep(100);
247
248                 cadet_gettune();
249                 if((tunestat & 0x40) == 0) {   /* Tuned */
250                         sigstrength=sigtable[curtuner][j];
251                         return;
252                 }
253         }
254         sigstrength=0;
255 }
256
257
258 static int cadet_getvol(void)
259 {
260         int ret = 0;
261
262         spin_lock(&cadet_io_lock);
263
264         outb(7,io);                /* Select tuner control */
265         if((inb(io + 1) & 0x20) != 0)
266                 ret = 0xffff;
267
268         spin_unlock(&cadet_io_lock);
269         return ret;
270 }
271
272
273 static void cadet_setvol(int vol)
274 {
275         spin_lock(&cadet_io_lock);
276         outb(7,io);                /* Select tuner control */
277         if(vol>0)
278                 outb(0x20,io+1);
279         else
280                 outb(0x00,io+1);
281         spin_unlock(&cadet_io_lock);
282 }
283
284 static void cadet_handler(unsigned long data)
285 {
286         /*
287          * Service the RDS fifo
288          */
289
290         if(spin_trylock(&cadet_io_lock))
291         {
292                 outb(0x3,io);       /* Select RDS Decoder Control */
293                 if((inb(io+1)&0x20)!=0) {
294                         printk(KERN_CRIT "cadet: RDS fifo overflow\n");
295                 }
296                 outb(0x80,io);      /* Select RDS fifo */
297                 while((inb(io)&0x80)!=0) {
298                         rdsbuf[rdsin]=inb(io+1);
299                         if(rdsin==rdsout)
300                                 printk(KERN_WARNING "cadet: RDS buffer overflow\n");
301                         else
302                                 rdsin++;
303                 }
304                 spin_unlock(&cadet_io_lock);
305         }
306
307         /*
308          * Service pending read
309          */
310         if( rdsin!=rdsout)
311                 wake_up_interruptible(&read_queue);
312
313         /*
314          * Clean up and exit
315          */
316         init_timer(&readtimer);
317         readtimer.function=cadet_handler;
318         readtimer.data=(unsigned long)0;
319         readtimer.expires=jiffies+(HZ/20);
320         add_timer(&readtimer);
321 }
322
323
324
325 static ssize_t cadet_read(struct file *file, char __user *data,
326                           size_t count, loff_t *ppos)
327 {
328         int i=0;
329         unsigned char readbuf[RDS_BUFFER];
330
331         if(rdsstat==0) {
332                 spin_lock(&cadet_io_lock);
333                 rdsstat=1;
334                 outb(0x80,io);        /* Select RDS fifo */
335                 spin_unlock(&cadet_io_lock);
336                 init_timer(&readtimer);
337                 readtimer.function=cadet_handler;
338                 readtimer.data=(unsigned long)0;
339                 readtimer.expires=jiffies+(HZ/20);
340                 add_timer(&readtimer);
341         }
342         if(rdsin==rdsout) {
343                 if (file->f_flags & O_NONBLOCK)
344                         return -EWOULDBLOCK;
345                 interruptible_sleep_on(&read_queue);
346         }
347         while( i<count && rdsin!=rdsout)
348                 readbuf[i++]=rdsbuf[rdsout++];
349
350         if (copy_to_user(data,readbuf,i))
351                 return -EFAULT;
352         return i;
353 }
354
355
356
357 static int cadet_do_ioctl(struct inode *inode, struct file *file,
358                           unsigned int cmd, void *arg)
359 {
360         switch(cmd)
361         {
362                 case VIDIOCGCAP:
363                 {
364                         struct video_capability *v = arg;
365                         memset(v,0,sizeof(*v));
366                         v->type=VID_TYPE_TUNER;
367                         v->channels=2;
368                         v->audios=1;
369                         strcpy(v->name, "ADS Cadet");
370                         return 0;
371                 }
372                 case VIDIOCGTUNER:
373                 {
374                         struct video_tuner *v = arg;
375                         if((v->tuner<0)||(v->tuner>1)) {
376                                 return -EINVAL;
377                         }
378                         switch(v->tuner) {
379                                 case 0:
380                                 strcpy(v->name,"FM");
381                                 v->rangelow=1400;     /* 87.5 MHz */
382                                 v->rangehigh=1728;    /* 108.0 MHz */
383                                 v->flags=0;
384                                 v->mode=0;
385                                 v->mode|=VIDEO_MODE_AUTO;
386                                 v->signal=sigstrength;
387                                 if(cadet_getstereo()==1) {
388                                         v->flags|=VIDEO_TUNER_STEREO_ON;
389                                 }
390                                 v->flags|=cadet_getrds();
391                                 break;
392                                 case 1:
393                                 strcpy(v->name,"AM");
394                                 v->rangelow=8320;      /* 520 kHz */
395                                 v->rangehigh=26400;    /* 1650 kHz */
396                                 v->flags=0;
397                                 v->flags|=VIDEO_TUNER_LOW;
398                                 v->mode=0;
399                                 v->mode|=VIDEO_MODE_AUTO;
400                                 v->signal=sigstrength;
401                                 break;
402                         }
403                         return 0;
404                 }
405                 case VIDIOCSTUNER:
406                 {
407                         struct video_tuner *v = arg;
408                         if((v->tuner<0)||(v->tuner>1)) {
409                                 return -EINVAL;
410                         }
411                         curtuner=v->tuner;
412                         return 0;
413                 }
414                 case VIDIOCGFREQ:
415                 {
416                         unsigned long *freq = arg;
417                         *freq = cadet_getfreq();
418                         return 0;
419                 }
420                 case VIDIOCSFREQ:
421                 {
422                         unsigned long *freq = arg;
423                         if((curtuner==0)&&((*freq<1400)||(*freq>1728))) {
424                                 return -EINVAL;
425                         }
426                         if((curtuner==1)&&((*freq<8320)||(*freq>26400))) {
427                                 return -EINVAL;
428                         }
429                         cadet_setfreq(*freq);
430                         return 0;
431                 }
432                 case VIDIOCGAUDIO:
433                 {
434                         struct video_audio *v = arg;
435                         memset(v,0, sizeof(*v));
436                         v->flags=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
437                         if(cadet_getstereo()==0) {
438                                 v->mode=VIDEO_SOUND_MONO;
439                         } else {
440                                 v->mode=VIDEO_SOUND_STEREO;
441                         }
442                         v->volume=cadet_getvol();
443                         v->step=0xffff;
444                         strcpy(v->name, "Radio");
445                         return 0;
446                 }
447                 case VIDIOCSAUDIO:
448                 {
449                         struct video_audio *v = arg;
450                         if(v->audio)
451                                 return -EINVAL;
452                         cadet_setvol(v->volume);
453                         if(v->flags&VIDEO_AUDIO_MUTE)
454                                 cadet_setvol(0);
455                         else
456                                 cadet_setvol(0xffff);
457                         return 0;
458                 }
459                 default:
460                         return -ENOIOCTLCMD;
461         }
462 }
463
464 static int cadet_ioctl(struct inode *inode, struct file *file,
465                        unsigned int cmd, unsigned long arg)
466 {
467         return video_usercopy(inode, file, cmd, arg, cadet_do_ioctl);
468 }
469
470 static int cadet_open(struct inode *inode, struct file *file)
471 {
472         if(users)
473                 return -EBUSY;
474         users++;
475         init_waitqueue_head(&read_queue);
476         return 0;
477 }
478
479 static int cadet_release(struct inode *inode, struct file *file)
480 {
481         del_timer_sync(&readtimer);
482         rdsstat=0;
483         users--;
484         return 0;
485 }
486
487
488 static struct file_operations cadet_fops = {
489         .owner          = THIS_MODULE,
490         .open           = cadet_open,
491         .release        = cadet_release,
492         .read           = cadet_read,
493         .ioctl          = cadet_ioctl,
494         .compat_ioctl   = v4l_compat_ioctl32,
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")) {
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_param(io, int, 0);
610 MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
611 module_param(radio_nr, int, 0);
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