File: /usr/src/linux/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     */
24     
25     #include <linux/module.h>	/* Modules 			*/
26     #include <linux/init.h>		/* Initdata			*/
27     #include <linux/ioport.h>	/* check_region, request_region	*/
28     #include <linux/delay.h>	/* udelay			*/
29     #include <asm/io.h>		/* outb, outb_p			*/
30     #include <asm/uaccess.h>	/* copy to/from user		*/
31     #include <linux/videodev.h>	/* kernel radio structs		*/
32     #include <linux/param.h>
33     #include <linux/isapnp.h>
34     
35     #define RDS_BUFFER 256
36     
37     static int io=-1;		/* default to isapnp activation */
38     static int radio_nr = -1;
39     static int users=0;
40     static int curtuner=0;
41     static int tunestat=0;
42     static int sigstrength=0;
43     static wait_queue_head_t tunerq,rdsq,readq;
44     struct timer_list tunertimer,rdstimer,readtimer;
45     static __u8 rdsin=0,rdsout=0,rdsstat=0;
46     static unsigned char rdsbuf[RDS_BUFFER];
47     static int cadet_lock=0;
48     
49     static int cadet_probe(void);
50     static struct pci_dev *dev;
51     static int isapnp_cadet_probe(void);
52     
53     /*
54      * Signal Strength Threshold Values
55      * The V4L API spec does not define any particular unit for the signal 
56      * strength value.  These values are in microvolts of RF at the tuner's input.
57      */
58     static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}};
59     
60     void cadet_wake(unsigned long qnum)
61     {
62             switch(qnum) {
63     	case 0:           /* cadet_setfreq */
64     	        wake_up(&tunerq);
65     		break;
66     	case 1:           /* cadet_getrds */
67     	        wake_up(&rdsq);
68     		break;
69     	}	
70     }
71     
72     
73     
74     static int cadet_getrds(void)
75     {
76             int rdsstat=0;
77     
78     	cadet_lock++;
79             outb(3,io);                 /* Select Decoder Control/Status */
80     	outb(inb(io+1)&0x7f,io+1);  /* Reset RDS detection */
81     	cadet_lock--;
82     	init_timer(&rdstimer);
83     	rdstimer.function=cadet_wake;
84     	rdstimer.data=(unsigned long)1;
85     	rdstimer.expires=jiffies+(HZ/10);
86     	init_waitqueue_head(&rdsq);
87     	add_timer(&rdstimer);
88     	sleep_on(&rdsq);
89     	
90     	cadet_lock++;
91             outb(3,io);                 /* Select Decoder Control/Status */
92     	if((inb(io+1)&0x80)!=0) {
93     	        rdsstat|=VIDEO_TUNER_RDS_ON;
94     	}
95     	if((inb(io+1)&0x10)!=0) {
96     	        rdsstat|=VIDEO_TUNER_MBS_ON;
97     	}
98     	cadet_lock--;
99     	return rdsstat;
100     }
101     
102     
103     
104     
105     static int cadet_getstereo(void)
106     {
107             if(curtuner!=0) {          /* Only FM has stereo capability! */
108     	        return 0;
109     	}
110             cadet_lock++;
111             outb(7,io);          /* Select tuner control */
112             if((inb(io+1)&0x40)==0) {
113     	        cadet_lock--;
114                     return 1;    /* Stereo pilot detected */
115             }
116             else {
117     	        cadet_lock--;
118                     return 0;    /* Mono */
119             }
120     }
121     
122     
123     
124     static unsigned cadet_gettune(void)
125     {
126             int curvol,i;
127     	unsigned fifo=0;
128     
129             /*
130              * Prepare for read
131              */
132     	cadet_lock++;
133             outb(7,io);       /* Select tuner control */
134             curvol=inb(io+1); /* Save current volume/mute setting */
135             outb(0x00,io+1);  /* Ensure WRITE-ENABLE is LOW */
136     	tunestat=0xffff;
137     
138             /*
139              * Read the shift register
140              */
141             for(i=0;i<25;i++) {
142                     fifo=(fifo<<1)|((inb(io+1)>>7)&0x01);
143                     if(i<24) {
144                             outb(0x01,io+1);
145     			tunestat&=inb(io+1);
146                             outb(0x00,io+1);
147                     }
148             }
149     
150             /*
151              * Restore volume/mute setting
152              */
153             outb(curvol,io+1);
154     	cadet_lock--;
155     	
156     	return fifo;
157     }
158     
159     
160     
161     static unsigned cadet_getfreq(void)
162     {
163             int i;
164             unsigned freq=0,test,fifo=0;
165     
166     	/*
167     	 * Read current tuning
168     	 */
169     	fifo=cadet_gettune();
170     
171             /*
172              * Convert to actual frequency
173              */
174     	if(curtuner==0) {    /* FM */
175     	        test=12500;
176                     for(i=0;i<14;i++) {
177                             if((fifo&0x01)!=0) {
178                                     freq+=test;
179                             }
180                             test=test<<1;
181                             fifo=fifo>>1;
182                     }
183                     freq-=10700000;           /* IF frequency is 10.7 MHz */
184                     freq=(freq*16)/1000000;   /* Make it 1/16 MHz */
185     	}
186     	if(curtuner==1) {    /* AM */
187     	        freq=((fifo&0x7fff)-2010)*16;
188     	}
189     
190             return freq;
191     }
192     
193     
194     
195     static void cadet_settune(unsigned fifo)
196     {
197             int i;
198     	unsigned test;  
199     
200     	cadet_lock++;
201     	outb(7,io);                /* Select tuner control */
202     	/*
203     	 * Write the shift register
204     	 */
205     	test=0;
206     	test=(fifo>>23)&0x02;      /* Align data for SDO */
207     	test|=0x1c;                /* SDM=1, SWE=1, SEN=1, SCK=0 */
208     	outb(7,io);                /* Select tuner control */
209     	outb(test,io+1);           /* Initialize for write */
210     	for(i=0;i<25;i++) {
211        	        test|=0x01;              /* Toggle SCK High */
212     		outb(test,io+1);
213     		test&=0xfe;              /* Toggle SCK Low */
214     		outb(test,io+1);
215     		fifo=fifo<<1;            /* Prepare the next bit */
216     		test=0x1c|((fifo>>23)&0x02);
217     		outb(test,io+1);
218     	}
219     	cadet_lock--;
220     }
221     
222     
223     
224     static void cadet_setfreq(unsigned freq)
225     {
226             unsigned fifo;
227             int i,j,test;
228             int curvol;
229     
230             /* 
231              * Formulate a fifo command
232              */
233     	fifo=0;
234     	if(curtuner==0) {    /* FM */
235             	test=102400;
236                     freq=(freq*1000)/16;       /* Make it kHz */
237                     freq+=10700;               /* IF is 10700 kHz */
238                     for(i=0;i<14;i++) {
239                             fifo=fifo<<1;
240                             if(freq>=test) {
241                                     fifo|=0x01;
242                                     freq-=test;
243                             }
244                             test=test>>1;
245                     }
246     	}
247     	if(curtuner==1) {    /* AM */
248                     fifo=(freq/16)+2010;            /* Make it kHz */
249     		fifo|=0x100000;            /* Select AM Band */
250     	}
251     
252             /*
253              * Save current volume/mute setting
254              */
255     	cadet_lock++;
256     	outb(7,io);                /* Select tuner control */
257             curvol=inb(io+1); 
258     
259     	/*
260     	 * Tune the card
261     	 */
262     	for(j=3;j>-1;j--) {
263     	        cadet_settune(fifo|(j<<16));
264     		outb(7,io);         /* Select tuner control */
265     		outb(curvol,io+1);
266     		cadet_lock--;
267     		init_timer(&tunertimer);
268     		tunertimer.function=cadet_wake;
269     		tunertimer.data=(unsigned long)0;
270     		tunertimer.expires=jiffies+(HZ/10);
271     		init_waitqueue_head(&tunerq);
272     		add_timer(&tunertimer);
273     		sleep_on(&tunerq);
274     		cadet_gettune();
275     		if((tunestat&0x40)==0) {   /* Tuned */
276     		        sigstrength=sigtable[curtuner][j];
277     			return;
278     		}
279     		cadet_lock++;
280     	}
281     	cadet_lock--;
282     	sigstrength=0;
283     }
284     
285     
286     static int cadet_getvol(void)
287     {
288             cadet_lock++;
289             outb(7,io);                /* Select tuner control */
290             if((inb(io+1)&0x20)!=0) {
291     	        cadet_lock--;
292                     return 0xffff;
293             }
294             else {
295     	        cadet_lock--;
296                     return 0;
297             }
298     }
299     
300     
301     static void cadet_setvol(int vol)
302     {
303             cadet_lock++;
304             outb(7,io);                /* Select tuner control */
305             if(vol>0) {
306                     outb(0x20,io+1);
307             }
308             else {
309                     outb(0x00,io+1);
310             }
311     	cadet_lock--;
312     }  
313     
314     
315     
316     void cadet_handler(unsigned long data)
317     {
318     	/*
319     	 * Service the RDS fifo
320     	 */
321             if(cadet_lock==0) {
322     	        outb(0x3,io);       /* Select RDS Decoder Control */
323     		if((inb(io+1)&0x20)!=0) {
324     		        printk(KERN_CRIT "cadet: RDS fifo overflow\n");
325     		}
326     		outb(0x80,io);      /* Select RDS fifo */
327     		while((inb(io)&0x80)!=0) {
328     		        rdsbuf[rdsin++]=inb(io+1);
329     			if(rdsin==rdsout) {
330     			        printk(KERN_CRIT "cadet: RDS buffer overflow\n");
331     			}
332     		}
333     	}
334     
335     	/*
336     	 * Service pending read
337     	 */
338     	if( rdsin!=rdsout) {
339     	        wake_up_interruptible(&readq);
340     	}
341     
342     	/* 
343     	 * Clean up and exit
344     	 */
345     	init_timer(&readtimer);
346     	readtimer.function=cadet_handler;
347     	readtimer.data=(unsigned long)0;
348     	readtimer.expires=jiffies+(HZ/20);
349     	add_timer(&readtimer);
350     }
351     
352     
353     
354     static long cadet_read(struct video_device *v,char *buf,unsigned long count,
355     		       int nonblock)
356     {
357             int i=0;
358     	unsigned char readbuf[RDS_BUFFER];
359     
360             if(rdsstat==0) {
361     	        cadet_lock++;
362     	        rdsstat=1;
363     		outb(0x80,io);        /* Select RDS fifo */
364     		cadet_lock--;
365     		init_timer(&readtimer);
366     		readtimer.function=cadet_handler;
367     		readtimer.data=(unsigned long)0;
368     		readtimer.expires=jiffies+(HZ/20);
369     		add_timer(&readtimer);
370     	}
371     	if(rdsin==rdsout) {
372       	        if(nonblock) {
373     		        return -EWOULDBLOCK;
374     		}
375     	        interruptible_sleep_on(&readq);
376     	}		
377     	while((i<count)&&(rdsin!=rdsout)) {
378     	        readbuf[i++]=rdsbuf[rdsout++];
379     	}
380     	if(copy_to_user(buf,readbuf,i)) {
381     	        return -EFAULT;
382     	}
383     	return i;
384     }
385     
386     
387     
388     static int cadet_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
389     {
390             unsigned freq;
391     	switch(cmd)
392     	{
393     		case VIDIOCGCAP:
394     		{
395     			struct video_capability v;
396     			v.type=VID_TYPE_TUNER;
397     			v.channels=2;
398     			v.audios=1;
399     			/* No we don't do pictures */
400     			v.maxwidth=0;
401     			v.maxheight=0;
402     			v.minwidth=0;
403     			v.minheight=0;
404     			strcpy(v.name, "ADS Cadet");
405     			if(copy_to_user(arg,&v,sizeof(v)))
406     				return -EFAULT;
407     			return 0;
408     		}
409     		case VIDIOCGTUNER:
410     		{
411     			struct video_tuner v;
412     			if(copy_from_user(&v, arg,sizeof(v))!=0) { 
413     				return -EFAULT;
414     			}
415     			if((v.tuner<0)||(v.tuner>1)) {
416     				return -EINVAL;
417     			}
418     			switch(v.tuner) {
419     			        case 0:
420     			        strcpy(v.name,"FM");
421     			        v.rangelow=1400;     /* 87.5 MHz */
422     			        v.rangehigh=1728;    /* 108.0 MHz */
423     			        v.flags=0;
424     			        v.mode=0;
425     			        v.mode|=VIDEO_MODE_AUTO;
426     			        v.signal=sigstrength;
427     			        if(cadet_getstereo()==1) {
428     				        v.flags|=VIDEO_TUNER_STEREO_ON;
429     			        }
430     				v.flags|=cadet_getrds();
431     			        if(copy_to_user(arg,&v, sizeof(v))) {
432     				        return -EFAULT;
433     			        }
434     			        break;
435     			        case 1:
436     			        strcpy(v.name,"AM");
437     			        v.rangelow=8320;      /* 520 kHz */
438     			        v.rangehigh=26400;    /* 1650 kHz */
439     			        v.flags=0;
440     			        v.flags|=VIDEO_TUNER_LOW;
441     			        v.mode=0;
442     			        v.mode|=VIDEO_MODE_AUTO;
443     			        v.signal=sigstrength;
444     			        if(copy_to_user(arg,&v, sizeof(v))) {
445     				        return -EFAULT;
446     			        }
447     			        break;
448     			}
449     			return 0;
450     		}
451     		case VIDIOCSTUNER:
452     		{
453     			struct video_tuner v;
454     			if(copy_from_user(&v, arg, sizeof(v))) {
455     				return -EFAULT;
456     			}
457     			if((v.tuner<0)||(v.tuner>1)) {
458     				return -EINVAL;
459     			}
460     			curtuner=v.tuner;	
461     			return 0;
462     		}
463     		case VIDIOCGFREQ:
464     		        freq=cadet_getfreq();
465     			if(copy_to_user(arg, &freq, sizeof(freq)))
466     				return -EFAULT;
467     			return 0;
468     		case VIDIOCSFREQ:
469     			if(copy_from_user(&freq, arg,sizeof(freq)))
470     				return -EFAULT;
471     			if((curtuner==0)&&((freq<1400)||(freq>1728))) {
472     			        return -EINVAL;
473     			}
474     			if((curtuner==1)&&((freq<8320)||(freq>26400))) {
475     			        return -EINVAL;
476     			}
477     			cadet_setfreq(freq);
478     			return 0;
479     		case VIDIOCGAUDIO:
480     		{	
481     			struct video_audio v;
482     			memset(&v,0, sizeof(v));
483     			v.flags=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
484     			if(cadet_getstereo()==0) {
485     			        v.mode=VIDEO_SOUND_MONO;
486     			}
487     			else {
488     			  v.mode=VIDEO_SOUND_STEREO;
489     			}
490     			v.volume=cadet_getvol();
491     			v.step=0xffff;
492     			strcpy(v.name, "Radio");
493     			if(copy_to_user(arg,&v, sizeof(v)))
494     				return -EFAULT;
495     			return 0;			
496     		}
497     		case VIDIOCSAUDIO:
498     		{
499     			struct video_audio v;
500     			if(copy_from_user(&v, arg, sizeof(v))) 
501     				return -EFAULT;	
502     			if(v.audio) 
503     				return -EINVAL;
504     			cadet_setvol(v.volume);
505     			if(v.flags&VIDEO_AUDIO_MUTE) 
506     				cadet_setvol(0);
507     			else
508     				cadet_setvol(0xffff);
509     			return 0;
510     		}
511     		default:
512     			return -ENOIOCTLCMD;
513     	}
514     }
515     
516     
517     static int cadet_open(struct video_device *dev, int flags)
518     {
519     	if(users)
520     		return -EBUSY;
521     	users++;
522     	init_waitqueue_head(&readq);
523     	return 0;
524     }
525     
526     static void cadet_close(struct video_device *dev)
527     {
528             if(rdsstat==1) {
529                     del_timer(&readtimer);
530     		rdsstat=0;
531     	}
532     	users--;
533     }
534     
535     
536     static struct video_device cadet_radio=
537     {
538     	owner:		THIS_MODULE,
539     	name:		"Cadet radio",
540     	type:		VID_TYPE_TUNER,
541     	hardware:	VID_HARDWARE_CADET,
542     	open:		cadet_open,
543     	close:		cadet_close,
544     	read:		cadet_read,
545     	ioctl:		cadet_ioctl,
546     };
547     
548     static int isapnp_cadet_probe(void)
549     {
550     	dev = isapnp_find_dev (NULL, ISAPNP_VENDOR('M','S','M'),
551     	                       ISAPNP_FUNCTION(0x0c24), NULL);
552     
553     	if (!dev)
554     		return -ENODEV;
555     	if (dev->prepare(dev)<0)
556     		return -EAGAIN;
557     	if (!(dev->resource[0].flags & IORESOURCE_IO))
558     		return -ENODEV;
559     	if (dev->activate(dev)<0) {
560     		printk ("radio-cadet: isapnp configure failed (out of resources?)\n");
561     		return -ENOMEM;
562     	}
563     
564     	io = dev->resource[0].start;
565     
566     	printk ("radio-cadet: ISAPnP reports card at %#x\n", io);
567     
568     	return io;
569     }
570     
571     static int cadet_probe(void)
572     {
573             static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
574     	int i;
575     
576     	for(i=0;i<8;i++) {
577     	        io=iovals[i];
578     	        if(request_region(io,2, "cadet-probe")>=0) {
579     		        cadet_setfreq(1410);
580     			if(cadet_getfreq()==1410) {
581     				release_region(io, 2);
582     			        return io;
583     			}
584     			release_region(io, 2);
585     		}
586     	}
587     	return -1;
588     }
589     
590     	/* 
591     	 * io should only be set if the user has used something like
592     	 * isapnp (the userspace program) to initialize this card for us
593     	 */
594     
595     static int __init cadet_init(void)
596     {
597     	/*
598     	 *	If a probe was requested then probe ISAPnP first (safest)
599     	 */
600     	if (io < 0)
601     		io = isapnp_cadet_probe();
602     	/*
603     	 *	If that fails then probe unsafely if probe is requested
604     	 */
605     	if(io < 0)
606     		io = cadet_probe ();
607     
608     	/*
609     	 *	Else we bail out
610     	 */
611     	 
612             if(io < 0) {
613     #ifdef MODULE        
614     		printk(KERN_ERR "You must set an I/O address with io=0x???\n");
615     #endif
616     	        return -EINVAL;
617     	}
618     	if (!request_region(io,2,"cadet"))
619     		return -EBUSY;
620     	if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) {
621     		release_region(io,2);
622     		return -EINVAL;
623     	}
624     	printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io);
625     	return 0;
626     }
627     
628     
629     
630     MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
631     MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");
632     MODULE_PARM(io, "i");
633     MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
634     MODULE_PARM(radio_nr, "i");
635     
636     static struct isapnp_device_id id_table[] __devinitdata = {
637     	{ 	ISAPNP_ANY_ID, ISAPNP_ANY_ID,
638     		ISAPNP_VENDOR('M','S','M'), ISAPNP_FUNCTION(0x0c24), 0 },
639     	{0}
640     };
641     
642     MODULE_DEVICE_TABLE(isapnp, id_table);
643     
644     EXPORT_NO_SYMBOLS;
645     
646     static void __exit cadet_cleanup_module(void)
647     {
648     	video_unregister_device(&cadet_radio);
649     	release_region(io,2);
650     
651     	if (dev)
652     		dev->deactivate(dev);
653     }
654     
655     module_init(cadet_init);
656     module_exit(cadet_cleanup_module);
657     
658