diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/media/radio/radio-cadet.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/media/radio/radio-cadet.c')
-rw-r--r-- | drivers/media/radio/radio-cadet.c | 620 |
1 files changed, 620 insertions, 0 deletions
diff --git a/drivers/media/radio/radio-cadet.c b/drivers/media/radio/radio-cadet.c new file mode 100644 index 000000000000..53d399b6652b --- /dev/null +++ b/drivers/media/radio/radio-cadet.c | |||
@@ -0,0 +1,620 @@ | |||
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> /* check_region, 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 | .llseek = no_llseek, | ||
494 | }; | ||
495 | |||
496 | static struct video_device cadet_radio= | ||
497 | { | ||
498 | .owner = THIS_MODULE, | ||
499 | .name = "Cadet radio", | ||
500 | .type = VID_TYPE_TUNER, | ||
501 | .hardware = VID_HARDWARE_CADET, | ||
502 | .fops = &cadet_fops, | ||
503 | }; | ||
504 | |||
505 | static struct pnp_device_id cadet_pnp_devices[] = { | ||
506 | /* ADS Cadet AM/FM Radio Card */ | ||
507 | {.id = "MSM0c24", .driver_data = 0}, | ||
508 | {.id = ""} | ||
509 | }; | ||
510 | |||
511 | MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices); | ||
512 | |||
513 | static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id) | ||
514 | { | ||
515 | if (!dev) | ||
516 | return -ENODEV; | ||
517 | /* only support one device */ | ||
518 | if (io > 0) | ||
519 | return -EBUSY; | ||
520 | |||
521 | if (!pnp_port_valid(dev, 0)) { | ||
522 | return -ENODEV; | ||
523 | } | ||
524 | |||
525 | io = pnp_port_start(dev, 0); | ||
526 | |||
527 | printk ("radio-cadet: PnP reports device at %#x\n", io); | ||
528 | |||
529 | return io; | ||
530 | } | ||
531 | |||
532 | static struct pnp_driver cadet_pnp_driver = { | ||
533 | .name = "radio-cadet", | ||
534 | .id_table = cadet_pnp_devices, | ||
535 | .probe = cadet_pnp_probe, | ||
536 | .remove = NULL, | ||
537 | }; | ||
538 | |||
539 | static int cadet_probe(void) | ||
540 | { | ||
541 | static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e}; | ||
542 | int i; | ||
543 | |||
544 | for(i=0;i<8;i++) { | ||
545 | io=iovals[i]; | ||
546 | if(request_region(io,2, "cadet-probe")>=0) { | ||
547 | cadet_setfreq(1410); | ||
548 | if(cadet_getfreq()==1410) { | ||
549 | release_region(io, 2); | ||
550 | return io; | ||
551 | } | ||
552 | release_region(io, 2); | ||
553 | } | ||
554 | } | ||
555 | return -1; | ||
556 | } | ||
557 | |||
558 | /* | ||
559 | * io should only be set if the user has used something like | ||
560 | * isapnp (the userspace program) to initialize this card for us | ||
561 | */ | ||
562 | |||
563 | static int __init cadet_init(void) | ||
564 | { | ||
565 | spin_lock_init(&cadet_io_lock); | ||
566 | |||
567 | /* | ||
568 | * If a probe was requested then probe ISAPnP first (safest) | ||
569 | */ | ||
570 | if (io < 0) | ||
571 | pnp_register_driver(&cadet_pnp_driver); | ||
572 | /* | ||
573 | * If that fails then probe unsafely if probe is requested | ||
574 | */ | ||
575 | if(io < 0) | ||
576 | io = cadet_probe (); | ||
577 | |||
578 | /* | ||
579 | * Else we bail out | ||
580 | */ | ||
581 | |||
582 | if(io < 0) { | ||
583 | #ifdef MODULE | ||
584 | printk(KERN_ERR "You must set an I/O address with io=0x???\n"); | ||
585 | #endif | ||
586 | goto fail; | ||
587 | } | ||
588 | if (!request_region(io,2,"cadet")) | ||
589 | goto fail; | ||
590 | if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) { | ||
591 | release_region(io,2); | ||
592 | goto fail; | ||
593 | } | ||
594 | printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io); | ||
595 | return 0; | ||
596 | fail: | ||
597 | pnp_unregister_driver(&cadet_pnp_driver); | ||
598 | return -1; | ||
599 | } | ||
600 | |||
601 | |||
602 | |||
603 | MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); | ||
604 | MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card."); | ||
605 | MODULE_LICENSE("GPL"); | ||
606 | |||
607 | module_param(io, int, 0); | ||
608 | MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)"); | ||
609 | module_param(radio_nr, int, 0); | ||
610 | |||
611 | static void __exit cadet_cleanup_module(void) | ||
612 | { | ||
613 | video_unregister_device(&cadet_radio); | ||
614 | release_region(io,2); | ||
615 | pnp_unregister_driver(&cadet_pnp_driver); | ||
616 | } | ||
617 | |||
618 | module_init(cadet_init); | ||
619 | module_exit(cadet_cleanup_module); | ||
620 | |||