xref: /linux/drivers/media/radio/radio-cadet.c (revision 5e8d780d745c1619aba81fe7166c5a4b5cad2b84)
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 
623