Linux 2.4.0-test7-pre7
[davej-history.git] / drivers / media / radio / radio-cadet.c
blobed2056deaff625f1e5f1650209774e145645320e
1 /* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card
3 * by Fred Gleason <fredg@wava.com>
4 * Version 0.3.3
6 * (Loosely) based on code for the Aztech radio card by
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)
17 #include <linux/module.h> /* Modules */
18 #include <linux/init.h> /* Initdata */
19 #include <linux/ioport.h> /* check_region, request_region */
20 #include <linux/delay.h> /* udelay */
21 #include <asm/io.h> /* outb, outb_p */
22 #include <asm/uaccess.h> /* copy to/from user */
23 #include <linux/videodev.h> /* kernel radio structs */
24 #include <linux/config.h> /* CONFIG_RADIO_CADET_PORT */
25 #include <linux/param.h>
27 #ifndef CONFIG_RADIO_CADET_PORT
28 #define CONFIG_RADIO_CADET_PORT 0x330
29 #endif
30 #define RDS_BUFFER 256
32 static int io=CONFIG_RADIO_CADET_PORT;
33 static int users=0;
34 static int curtuner=0;
35 static int tunestat=0;
36 static int sigstrength=0;
37 static wait_queue_head_t tunerq,rdsq,readq;
38 struct timer_list tunertimer,rdstimer,readtimer;
39 static __u8 rdsin=0,rdsout=0,rdsstat=0;
40 static unsigned char rdsbuf[RDS_BUFFER];
41 static int cadet_lock=0;
44 * Signal Strength Threshold Values
45 * The V4L API spec does not define any particular unit for the signal
46 * strength value. These values are in microvolts of RF at the tuner's input.
48 static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}};
52 void cadet_wake(unsigned long qnum)
54 switch(qnum) {
55 case 0: /* cadet_setfreq */
56 wake_up(&tunerq);
57 break;
58 case 1: /* cadet_getrds */
59 wake_up(&rdsq);
60 break;
66 static int cadet_getrds(void)
68 int rdsstat=0;
70 cadet_lock++;
71 outb(3,io); /* Select Decoder Control/Status */
72 outb(inb(io+1)&0x7f,io+1); /* Reset RDS detection */
73 cadet_lock--;
74 init_timer(&rdstimer);
75 rdstimer.function=cadet_wake;
76 rdstimer.data=(unsigned long)1;
77 rdstimer.expires=jiffies+(HZ/10);
78 init_waitqueue_head(&rdsq);
79 add_timer(&rdstimer);
80 sleep_on(&rdsq);
82 cadet_lock++;
83 outb(3,io); /* Select Decoder Control/Status */
84 if((inb(io+1)&0x80)!=0) {
85 rdsstat|=VIDEO_TUNER_RDS_ON;
87 if((inb(io+1)&0x10)!=0) {
88 rdsstat|=VIDEO_TUNER_MBS_ON;
90 cadet_lock--;
91 return rdsstat;
97 static int cadet_getstereo(void)
99 if(curtuner!=0) { /* Only FM has stereo capability! */
100 return 0;
102 cadet_lock++;
103 outb(7,io); /* Select tuner control */
104 if((inb(io+1)&0x40)==0) {
105 cadet_lock--;
106 return 1; /* Stereo pilot detected */
108 else {
109 cadet_lock--;
110 return 0; /* Mono */
116 static unsigned cadet_gettune(void)
118 int curvol,i;
119 unsigned fifo=0;
122 * Prepare for read
124 cadet_lock++;
125 outb(7,io); /* Select tuner control */
126 curvol=inb(io+1); /* Save current volume/mute setting */
127 outb(0x00,io+1); /* Ensure WRITE-ENABLE is LOW */
128 tunestat=0xffff;
131 * Read the shift register
133 for(i=0;i<25;i++) {
134 fifo=(fifo<<1)|((inb(io+1)>>7)&0x01);
135 if(i<24) {
136 outb(0x01,io+1);
137 tunestat&=inb(io+1);
138 outb(0x00,io+1);
143 * Restore volume/mute setting
145 outb(curvol,io+1);
146 cadet_lock--;
148 return fifo;
153 static unsigned cadet_getfreq(void)
155 int i;
156 unsigned freq=0,test,fifo=0;
159 * Read current tuning
161 fifo=cadet_gettune();
164 * Convert to actual frequency
166 if(curtuner==0) { /* FM */
167 test=12500;
168 for(i=0;i<14;i++) {
169 if((fifo&0x01)!=0) {
170 freq+=test;
172 test=test<<1;
173 fifo=fifo>>1;
175 freq-=10700000; /* IF frequency is 10.7 MHz */
176 freq=(freq*16)/1000000; /* Make it 1/16 MHz */
178 if(curtuner==1) { /* AM */
179 freq=((fifo&0x7fff)-2010)*16;
182 return freq;
187 static void cadet_settune(unsigned fifo)
189 int i;
190 unsigned test;
192 cadet_lock++;
193 outb(7,io); /* Select tuner control */
195 * Write the shift register
197 test=0;
198 test=(fifo>>23)&0x02; /* Align data for SDO */
199 test|=0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */
200 outb(7,io); /* Select tuner control */
201 outb(test,io+1); /* Initialize for write */
202 for(i=0;i<25;i++) {
203 test|=0x01; /* Toggle SCK High */
204 outb(test,io+1);
205 test&=0xfe; /* Toggle SCK Low */
206 outb(test,io+1);
207 fifo=fifo<<1; /* Prepare the next bit */
208 test=0x1c|((fifo>>23)&0x02);
209 outb(test,io+1);
211 cadet_lock--;
216 static void cadet_setfreq(unsigned freq)
218 unsigned fifo;
219 int i,j,test;
220 int curvol;
223 * Formulate a fifo command
225 fifo=0;
226 if(curtuner==0) { /* FM */
227 test=102400;
228 freq=(freq*1000)/16; /* Make it kHz */
229 freq+=10700; /* IF is 10700 kHz */
230 for(i=0;i<14;i++) {
231 fifo=fifo<<1;
232 if(freq>=test) {
233 fifo|=0x01;
234 freq-=test;
236 test=test>>1;
239 if(curtuner==1) { /* AM */
240 fifo=(freq/16)+2010; /* Make it kHz */
241 fifo|=0x100000; /* Select AM Band */
245 * Save current volume/mute setting
247 cadet_lock++;
248 outb(7,io); /* Select tuner control */
249 curvol=inb(io+1);
252 * Tune the card
254 for(j=3;j>-1;j--) {
255 cadet_settune(fifo|(j<<16));
256 outb(7,io); /* Select tuner control */
257 outb(curvol,io+1);
258 cadet_lock--;
259 init_timer(&tunertimer);
260 tunertimer.function=cadet_wake;
261 tunertimer.data=(unsigned long)0;
262 tunertimer.expires=jiffies+(HZ/10);
263 init_waitqueue_head(&tunerq);
264 add_timer(&tunertimer);
265 sleep_on(&tunerq);
266 cadet_gettune();
267 if((tunestat&0x40)==0) { /* Tuned */
268 sigstrength=sigtable[curtuner][j];
269 return;
271 cadet_lock++;
273 cadet_lock--;
274 sigstrength=0;
278 static int cadet_getvol(void)
280 cadet_lock++;
281 outb(7,io); /* Select tuner control */
282 if((inb(io+1)&0x20)!=0) {
283 cadet_lock--;
284 return 0xffff;
286 else {
287 cadet_lock--;
288 return 0;
293 static void cadet_setvol(int vol)
295 cadet_lock++;
296 outb(7,io); /* Select tuner control */
297 if(vol>0) {
298 outb(0x20,io+1);
300 else {
301 outb(0x00,io+1);
303 cadet_lock--;
308 void cadet_handler(unsigned long data)
311 * Service the RDS fifo
313 if(cadet_lock==0) {
314 outb(0x3,io); /* Select RDS Decoder Control */
315 if((inb(io+1)&0x20)!=0) {
316 printk(KERN_CRIT "cadet: RDS fifo overflow\n");
318 outb(0x80,io); /* Select RDS fifo */
319 while((inb(io)&0x80)!=0) {
320 rdsbuf[rdsin++]=inb(io+1);
321 if(rdsin==rdsout) {
322 printk(KERN_CRIT "cadet: RDS buffer overflow\n");
328 * Service pending read
330 if( rdsin!=rdsout) {
331 wake_up_interruptible(&readq);
335 * Clean up and exit
337 init_timer(&readtimer);
338 readtimer.function=cadet_handler;
339 readtimer.data=(unsigned long)0;
340 readtimer.expires=jiffies+(HZ/20);
341 add_timer(&readtimer);
346 static long cadet_read(struct video_device *v,char *buf,unsigned long count,
347 int nonblock)
349 int i=0;
350 unsigned char readbuf[RDS_BUFFER];
352 if(rdsstat==0) {
353 cadet_lock++;
354 rdsstat=1;
355 outb(0x80,io); /* Select RDS fifo */
356 cadet_lock--;
357 init_timer(&readtimer);
358 readtimer.function=cadet_handler;
359 readtimer.data=(unsigned long)0;
360 readtimer.expires=jiffies+(HZ/20);
361 add_timer(&readtimer);
363 if(rdsin==rdsout) {
364 if(nonblock) {
365 return -EWOULDBLOCK;
367 interruptible_sleep_on(&readq);
369 while((i<count)&&(rdsin!=rdsout)) {
370 readbuf[i++]=rdsbuf[rdsout++];
372 if(copy_to_user(buf,readbuf,i)) {
373 return -EFAULT;
375 return i;
380 static int cadet_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
382 unsigned freq;
383 switch(cmd)
385 case VIDIOCGCAP:
387 struct video_capability v;
388 v.type=VID_TYPE_TUNER;
389 v.channels=2;
390 v.audios=1;
391 /* No we don't do pictures */
392 v.maxwidth=0;
393 v.maxheight=0;
394 v.minwidth=0;
395 v.minheight=0;
396 strcpy(v.name, "ADS Cadet");
397 if(copy_to_user(arg,&v,sizeof(v)))
398 return -EFAULT;
399 return 0;
401 case VIDIOCGTUNER:
403 struct video_tuner v;
404 if(copy_from_user(&v, arg,sizeof(v))!=0) {
405 return -EFAULT;
407 if((v.tuner<0)||(v.tuner>1)) {
408 return -EINVAL;
410 switch(v.tuner) {
411 case 0:
412 strcpy(v.name,"FM");
413 v.rangelow=1400; /* 87.5 MHz */
414 v.rangehigh=1728; /* 108.0 MHz */
415 v.flags=0;
416 v.mode=0;
417 v.mode|=VIDEO_MODE_AUTO;
418 v.signal=sigstrength;
419 if(cadet_getstereo()==1) {
420 v.flags|=VIDEO_TUNER_STEREO_ON;
422 v.flags|=cadet_getrds();
423 if(copy_to_user(arg,&v, sizeof(v))) {
424 return -EFAULT;
426 break;
427 case 1:
428 strcpy(v.name,"AM");
429 v.rangelow=8320; /* 520 kHz */
430 v.rangehigh=26400; /* 1650 kHz */
431 v.flags=0;
432 v.flags|=VIDEO_TUNER_LOW;
433 v.mode=0;
434 v.mode|=VIDEO_MODE_AUTO;
435 v.signal=sigstrength;
436 if(copy_to_user(arg,&v, sizeof(v))) {
437 return -EFAULT;
439 break;
441 return 0;
443 case VIDIOCSTUNER:
445 struct video_tuner v;
446 if(copy_from_user(&v, arg, sizeof(v))) {
447 return -EFAULT;
449 if((v.tuner<0)||(v.tuner>1)) {
450 return -EINVAL;
452 curtuner=v.tuner;
453 return 0;
455 case VIDIOCGFREQ:
456 freq=cadet_getfreq();
457 if(copy_to_user(arg, &freq, sizeof(freq)))
458 return -EFAULT;
459 return 0;
460 case VIDIOCSFREQ:
461 if(copy_from_user(&freq, arg,sizeof(freq)))
462 return -EFAULT;
463 if((curtuner==0)&&((freq<1400)||(freq>1728))) {
464 return -EINVAL;
466 if((curtuner==1)&&((freq<8320)||(freq>26400))) {
467 return -EINVAL;
469 cadet_setfreq(freq);
470 return 0;
471 case VIDIOCGAUDIO:
473 struct video_audio v;
474 memset(&v,0, sizeof(v));
475 v.flags=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
476 if(cadet_getstereo()==0) {
477 v.mode=VIDEO_SOUND_MONO;
479 else {
480 v.mode=VIDEO_SOUND_STEREO;
482 v.volume=cadet_getvol();
483 v.step=0xffff;
484 strcpy(v.name, "Radio");
485 if(copy_to_user(arg,&v, sizeof(v)))
486 return -EFAULT;
487 return 0;
489 case VIDIOCSAUDIO:
491 struct video_audio v;
492 if(copy_from_user(&v, arg, sizeof(v)))
493 return -EFAULT;
494 if(v.audio)
495 return -EINVAL;
496 cadet_setvol(v.volume);
497 if(v.flags&VIDEO_AUDIO_MUTE)
498 cadet_setvol(0);
499 else
500 cadet_setvol(0xffff);
501 return 0;
503 default:
504 return -ENOIOCTLCMD;
509 static int cadet_open(struct video_device *dev, int flags)
511 if(users)
512 return -EBUSY;
513 users++;
514 MOD_INC_USE_COUNT;
515 init_waitqueue_head(&readq);
516 return 0;
519 static void cadet_close(struct video_device *dev)
521 if(rdsstat==1) {
522 del_timer(&readtimer);
523 rdsstat=0;
525 users--;
526 MOD_DEC_USE_COUNT;
530 static struct video_device cadet_radio=
532 "Cadet radio",
533 VID_TYPE_TUNER,
534 VID_HARDWARE_CADET,
535 cadet_open,
536 cadet_close,
537 cadet_read,
538 NULL, /* Can't write */
539 NULL, /* No poll */
540 cadet_ioctl,
541 NULL,
542 NULL
545 #ifndef MODULE
546 static int cadet_probe(void)
548 static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
549 int i;
551 for(i=0;i<8;i++) {
552 io=iovals[i];
553 if(check_region(io,2)>=0) {
554 cadet_setfreq(1410);
555 if(cadet_getfreq()==1410) {
556 return io;
560 return -1;
562 #endif
564 static int __init cadet_init(void)
566 #ifndef MODULE
567 io = cadet_probe ();
568 #endif
570 if(io < 0) {
571 #ifdef MODULE
572 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
573 #endif
574 return -EINVAL;
576 if (!request_region(io,2,"cadet"))
577 return -EBUSY;
578 if(video_register_device(&cadet_radio,VFL_TYPE_RADIO)==-1) {
579 release_region(io,2);
580 return -EINVAL;
582 printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io);
583 return 0;
588 MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
589 MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");
590 MODULE_PARM(io, "i");
591 MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
593 EXPORT_NO_SYMBOLS;
595 static void __exit cadet_cleanup_module(void)
597 video_unregister_device(&cadet_radio);
598 release_region(io,2);
601 module_init(cadet_init);
602 module_exit(cadet_cleanup_module);