1 /* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card
3 * by Fred Gleason <fredg@wava.com>
6 * (Loosely) based on code for the Aztech radio card by
8 * Russell Kroll (rkroll@exploits.org)
11 * Jason Lewis (jlewis@twilight.vtc.vsc.edu)
12 * Scott McGrath (smcgrath@twilight.vtc.vsc.edu)
13 * William McGrath (wmcgrath@twilight.vtc.vsc.edu)
16 * 2000-04-29 Russell Kroll <rkroll@exploits.org>
17 * Added ISAPnP detection for Linux 2.3/2.4
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)
23 * 2002-01-17 Adam Belay <ambx1@neo.rr.com>
24 * Updated to latest pnp code
26 * 2003-01-31 Alan Cox <alan@redhat.com>
27 * Cleaned up locking, delay code, general odds and ends
29 * 2006-07-30 Hans J. Koch <koch@hjk-az.de>
33 #include <linux/module.h> /* Modules */
34 #include <linux/init.h> /* Initdata */
35 #include <linux/ioport.h> /* request_region */
36 #include <linux/delay.h> /* udelay */
37 #include <asm/io.h> /* outb, outb_p */
38 #include <asm/uaccess.h> /* copy to/from user */
39 #include <linux/videodev2.h> /* V4L2 API defs */
40 #include <media/v4l2-common.h>
41 #include <linux/param.h>
42 #include <linux/pnp.h>
44 #define RDS_BUFFER 256
48 #define CADET_VERSION KERNEL_VERSION(0,3,3)
50 static int io=-1; /* default to isapnp activation */
51 static int radio_nr = -1;
53 static int curtuner=0;
54 static int tunestat=0;
55 static int sigstrength=0;
56 static wait_queue_head_t read_queue;
57 static struct timer_list readtimer;
58 static __u8 rdsin=0,rdsout=0,rdsstat=0;
59 static unsigned char rdsbuf[RDS_BUFFER];
60 static spinlock_t cadet_io_lock;
62 static int cadet_probe(void);
65 * Signal Strength Threshold Values
66 * The V4L API spec does not define any particular unit for the signal
67 * strength value. These values are in microvolts of RF at the tuner's input.
69 static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}};
75 int ret = V4L2_TUNER_SUB_MONO;
76 if(curtuner != 0) /* Only FM has stereo capability! */
77 return V4L2_TUNER_SUB_MONO;
79 spin_lock(&cadet_io_lock);
80 outb(7,io); /* Select tuner control */
81 if( (inb(io+1) & 0x40) == 0)
82 ret = V4L2_TUNER_SUB_STEREO;
83 spin_unlock(&cadet_io_lock);
97 spin_lock(&cadet_io_lock);
99 outb(7,io); /* Select tuner control */
100 curvol=inb(io+1); /* Save current volume/mute setting */
101 outb(0x00,io+1); /* Ensure WRITE-ENABLE is LOW */
105 * Read the shift register
108 fifo=(fifo<<1)|((inb(io+1)>>7)&0x01);
117 * Restore volume/mute setting
120 spin_unlock(&cadet_io_lock);
129 unsigned freq=0,test,fifo=0;
132 * Read current tuning
134 fifo=cadet_gettune();
137 * Convert to actual frequency
139 if(curtuner==0) { /* FM */
148 freq-=10700000; /* IF frequency is 10.7 MHz */
149 freq=(freq*16)/1000000; /* Make it 1/16 MHz */
151 if(curtuner==1) { /* AM */
152 freq=((fifo&0x7fff)-2010)*16;
159 cadet_settune(unsigned fifo)
164 spin_lock(&cadet_io_lock);
166 outb(7,io); /* Select tuner control */
168 * Write the shift register
171 test=(fifo>>23)&0x02; /* Align data for SDO */
172 test|=0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */
173 outb(7,io); /* Select tuner control */
174 outb(test,io+1); /* Initialize for write */
176 test|=0x01; /* Toggle SCK High */
178 test&=0xfe; /* Toggle SCK Low */
180 fifo=fifo<<1; /* Prepare the next bit */
181 test=0x1c|((fifo>>23)&0x02);
184 spin_unlock(&cadet_io_lock);
188 cadet_setfreq(unsigned freq)
195 * Formulate a fifo command
198 if(curtuner==0) { /* FM */
200 freq=(freq*1000)/16; /* Make it kHz */
201 freq+=10700; /* IF is 10700 kHz */
211 if(curtuner==1) { /* AM */
212 fifo=(freq/16)+2010; /* Make it kHz */
213 fifo|=0x100000; /* Select AM Band */
217 * Save current volume/mute setting
220 spin_lock(&cadet_io_lock);
221 outb(7,io); /* Select tuner control */
223 spin_unlock(&cadet_io_lock);
229 cadet_settune(fifo|(j<<16));
231 spin_lock(&cadet_io_lock);
232 outb(7,io); /* Select tuner control */
234 spin_unlock(&cadet_io_lock);
239 if((tunestat & 0x40) == 0) { /* Tuned */
240 sigstrength=sigtable[curtuner][j];
253 spin_lock(&cadet_io_lock);
255 outb(7,io); /* Select tuner control */
256 if((inb(io + 1) & 0x20) != 0)
259 spin_unlock(&cadet_io_lock);
265 cadet_setvol(int vol)
267 spin_lock(&cadet_io_lock);
268 outb(7,io); /* Select tuner control */
273 spin_unlock(&cadet_io_lock);
277 cadet_handler(unsigned long data)
280 * Service the RDS fifo
283 if(spin_trylock(&cadet_io_lock))
285 outb(0x3,io); /* Select RDS Decoder Control */
286 if((inb(io+1)&0x20)!=0) {
287 printk(KERN_CRIT "cadet: RDS fifo overflow\n");
289 outb(0x80,io); /* Select RDS fifo */
290 while((inb(io)&0x80)!=0) {
291 rdsbuf[rdsin]=inb(io+1);
293 printk(KERN_WARNING "cadet: RDS buffer overflow\n");
297 spin_unlock(&cadet_io_lock);
301 * Service pending read
304 wake_up_interruptible(&read_queue);
309 init_timer(&readtimer);
310 readtimer.function=cadet_handler;
311 readtimer.data=(unsigned long)0;
312 readtimer.expires=jiffies+(HZ/20);
313 add_timer(&readtimer);
319 cadet_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
322 unsigned char readbuf[RDS_BUFFER];
325 spin_lock(&cadet_io_lock);
327 outb(0x80,io); /* Select RDS fifo */
328 spin_unlock(&cadet_io_lock);
329 init_timer(&readtimer);
330 readtimer.function=cadet_handler;
331 readtimer.data=(unsigned long)0;
332 readtimer.expires=jiffies+(HZ/20);
333 add_timer(&readtimer);
336 if (file->f_flags & O_NONBLOCK)
338 interruptible_sleep_on(&read_queue);
340 while( i<count && rdsin!=rdsout)
341 readbuf[i++]=rdsbuf[rdsout++];
343 if (copy_to_user(data,readbuf,i))
350 static int cadet_do_ioctl(struct inode *inode, struct file *file,
351 unsigned int cmd, void *arg)
355 case VIDIOC_QUERYCAP:
357 struct v4l2_capability *cap = arg;
358 memset(cap,0,sizeof(*cap));
362 cap->version = CADET_VERSION;
363 strcpy(cap->driver, "ADS Cadet");
364 strcpy(cap->card, "ADS Cadet");
369 struct v4l2_tuner *t = arg;
370 memset(t,0,sizeof(*t));
371 t->type = V4L2_TUNER_RADIO;
374 case 0: strcpy(t->name, "FM");
375 t->capability = V4L2_TUNER_CAP_STEREO;
376 t->rangelow = 1400; /* 87.5 MHz */
377 t->rangehigh = 1728; /* 108.0 MHz */
378 t->rxsubchans=cadet_getstereo();
379 switch (t->rxsubchans){
380 case V4L2_TUNER_SUB_MONO:
381 t->audmode = V4L2_TUNER_MODE_MONO;
383 case V4L2_TUNER_SUB_STEREO:
384 t->audmode = V4L2_TUNER_MODE_STEREO;
389 case 1: strcpy(t->name, "AM");
390 t->capability = V4L2_TUNER_CAP_LOW;
391 t->rangelow = 8320; /* 520 kHz */
392 t->rangehigh = 26400; /* 1650 kHz */
393 t->rxsubchans = V4L2_TUNER_SUB_MONO;
394 t->audmode = V4L2_TUNER_MODE_MONO;
400 t->signal = sigstrength; /* We might need to modify scaling of this */
405 struct v4l2_tuner *t = arg;
406 if((t->index != 0)&&(t->index != 1))
412 case VIDIOC_G_FREQUENCY:
414 struct v4l2_frequency *f = arg;
415 memset(f,0,sizeof(*f));
417 f->type = V4L2_TUNER_RADIO;
418 f->frequency = cadet_getfreq();
421 case VIDIOC_S_FREQUENCY:
423 struct v4l2_frequency *f = arg;
424 if (f->type != V4L2_TUNER_RADIO){
427 if((curtuner==0)&&((f->frequency<1400)||(f->frequency>1728))) {
430 if((curtuner==1)&&((f->frequency<8320)||(f->frequency>26400))) {
433 cadet_setfreq(f->frequency);
438 struct v4l2_control *c = arg;
440 case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */
441 c->value = (cadet_getvol() == 0);
443 case V4L2_CID_AUDIO_VOLUME:
444 c->value = cadet_getvol();
453 struct v4l2_control *c = arg;
455 case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */
456 if (c->value) cadet_setvol(0);
457 else cadet_setvol(0xffff);
459 case V4L2_CID_AUDIO_VOLUME:
460 cadet_setvol(c->value);
474 cadet_ioctl(struct inode *inode, struct file *file,
475 unsigned int cmd, unsigned long arg)
477 return video_usercopy(inode, file, cmd, arg, cadet_do_ioctl);
481 cadet_open(struct inode *inode, struct file *file)
484 if (1 == users) init_waitqueue_head(&read_queue);
489 cadet_release(struct inode *inode, struct file *file)
493 del_timer_sync(&readtimer);
500 cadet_poll(struct file *file, struct poll_table_struct *wait)
502 poll_wait(file,&read_queue,wait);
504 return POLLIN | POLLRDNORM;
509 static struct file_operations cadet_fops = {
510 .owner = THIS_MODULE,
512 .release = cadet_release,
514 .ioctl = cadet_ioctl,
516 .compat_ioctl = v4l_compat_ioctl32,
520 static struct video_device cadet_radio=
522 .owner = THIS_MODULE,
523 .name = "Cadet radio",
524 .type = VID_TYPE_TUNER,
528 static struct pnp_device_id cadet_pnp_devices[] = {
529 /* ADS Cadet AM/FM Radio Card */
530 {.id = "MSM0c24", .driver_data = 0},
534 MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices);
536 static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id)
540 /* only support one device */
544 if (!pnp_port_valid(dev, 0)) {
548 io = pnp_port_start(dev, 0);
550 printk ("radio-cadet: PnP reports device at %#x\n", io);
555 static struct pnp_driver cadet_pnp_driver = {
556 .name = "radio-cadet",
557 .id_table = cadet_pnp_devices,
558 .probe = cadet_pnp_probe,
562 static int cadet_probe(void)
564 static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
569 if (request_region(io, 2, "cadet-probe")) {
571 if(cadet_getfreq()==1410) {
572 release_region(io, 2);
575 release_region(io, 2);
582 * io should only be set if the user has used something like
583 * isapnp (the userspace program) to initialize this card for us
586 static int __init cadet_init(void)
588 spin_lock_init(&cadet_io_lock);
591 * If a probe was requested then probe ISAPnP first (safest)
594 pnp_register_driver(&cadet_pnp_driver);
596 * If that fails then probe unsafely if probe is requested
607 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
611 if (!request_region(io,2,"cadet"))
613 if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) {
614 release_region(io,2);
617 printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io);
620 pnp_unregister_driver(&cadet_pnp_driver);
626 MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
627 MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");
628 MODULE_LICENSE("GPL");
630 module_param(io, int, 0);
631 MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
632 module_param(radio_nr, int, 0);
634 static void __exit cadet_cleanup_module(void)
636 video_unregister_device(&cadet_radio);
637 release_region(io,2);
638 pnp_unregister_driver(&cadet_pnp_driver);
641 module_init(cadet_init);
642 module_exit(cadet_cleanup_module);