Pull sparsemem-v5 into release branch
[sfrench/cifs-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>       /* 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         .llseek         = no_llseek,
494 };
495
496 static struct video_device cadet_radio=
497 {
498         .owner          = THIS_MODULE,
499         .name           = "Cadet radio",
500         .type           = VID_TYPE_TUNER,
501         .hardware       = VID_HARDWARE_CADET,
502         .fops           = &cadet_fops,
503 };
504
505 static struct pnp_device_id cadet_pnp_devices[] = {
506         /* ADS Cadet AM/FM Radio Card */
507         {.id = "MSM0c24", .driver_data = 0},
508         {.id = ""}
509 };
510
511 MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices);
512
513 static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id)
514 {
515         if (!dev)
516                 return -ENODEV;
517         /* only support one device */
518         if (io > 0)
519                 return -EBUSY;
520
521         if (!pnp_port_valid(dev, 0)) {
522                 return -ENODEV;
523         }
524
525         io = pnp_port_start(dev, 0);
526
527         printk ("radio-cadet: PnP reports device at %#x\n", io);
528
529         return io;
530 }
531
532 static struct pnp_driver cadet_pnp_driver = {
533         .name           = "radio-cadet",
534         .id_table       = cadet_pnp_devices,
535         .probe          = cadet_pnp_probe,
536         .remove         = NULL,
537 };
538
539 static int cadet_probe(void)
540 {
541         static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
542         int i;
543
544         for(i=0;i<8;i++) {
545                 io=iovals[i];
546                 if (request_region(io, 2, "cadet-probe")) {
547                         cadet_setfreq(1410);
548                         if(cadet_getfreq()==1410) {
549                                 release_region(io, 2);
550                                 return io;
551                         }
552                         release_region(io, 2);
553                 }
554         }
555         return -1;
556 }
557
558 /* 
559  * io should only be set if the user has used something like
560  * isapnp (the userspace program) to initialize this card for us
561  */
562
563 static int __init cadet_init(void)
564 {
565         spin_lock_init(&cadet_io_lock);
566         
567         /*
568          *      If a probe was requested then probe ISAPnP first (safest)
569          */
570         if (io < 0)
571                 pnp_register_driver(&cadet_pnp_driver);
572         /*
573          *      If that fails then probe unsafely if probe is requested
574          */
575         if(io < 0)
576                 io = cadet_probe ();
577
578         /*
579          *      Else we bail out
580          */
581          
582         if(io < 0) {
583 #ifdef MODULE        
584                 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
585 #endif
586                 goto fail;
587         }
588         if (!request_region(io,2,"cadet"))
589                 goto fail;
590         if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) {
591                 release_region(io,2);
592                 goto fail;
593         }
594         printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io);
595         return 0;
596 fail:
597         pnp_unregister_driver(&cadet_pnp_driver);
598         return -1;
599 }
600
601
602
603 MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
604 MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");
605 MODULE_LICENSE("GPL");
606
607 module_param(io, int, 0);
608 MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
609 module_param(radio_nr, int, 0);
610
611 static void __exit cadet_cleanup_module(void)
612 {
613         video_unregister_device(&cadet_radio);
614         release_region(io,2);
615         pnp_unregister_driver(&cadet_pnp_driver);
616 }
617
618 module_init(cadet_init);
619 module_exit(cadet_cleanup_module);
620