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