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