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 <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 .compat_ioctl = v4l_compat_ioctl32, 494 .llseek = no_llseek, 495 }; 496 497 static struct video_device cadet_radio= 498 { 499 .owner = THIS_MODULE, 500 .name = "Cadet radio", 501 .type = VID_TYPE_TUNER, 502 .hardware = VID_HARDWARE_CADET, 503 .fops = &cadet_fops, 504 }; 505 506 static struct pnp_device_id cadet_pnp_devices[] = { 507 /* ADS Cadet AM/FM Radio Card */ 508 {.id = "MSM0c24", .driver_data = 0}, 509 {.id = ""} 510 }; 511 512 MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices); 513 514 static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id) 515 { 516 if (!dev) 517 return -ENODEV; 518 /* only support one device */ 519 if (io > 0) 520 return -EBUSY; 521 522 if (!pnp_port_valid(dev, 0)) { 523 return -ENODEV; 524 } 525 526 io = pnp_port_start(dev, 0); 527 528 printk ("radio-cadet: PnP reports device at %#x\n", io); 529 530 return io; 531 } 532 533 static struct pnp_driver cadet_pnp_driver = { 534 .name = "radio-cadet", 535 .id_table = cadet_pnp_devices, 536 .probe = cadet_pnp_probe, 537 .remove = NULL, 538 }; 539 540 static int cadet_probe(void) 541 { 542 static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e}; 543 int i; 544 545 for(i=0;i<8;i++) { 546 io=iovals[i]; 547 if (request_region(io, 2, "cadet-probe")) { 548 cadet_setfreq(1410); 549 if(cadet_getfreq()==1410) { 550 release_region(io, 2); 551 return io; 552 } 553 release_region(io, 2); 554 } 555 } 556 return -1; 557 } 558 559 /* 560 * io should only be set if the user has used something like 561 * isapnp (the userspace program) to initialize this card for us 562 */ 563 564 static int __init cadet_init(void) 565 { 566 spin_lock_init(&cadet_io_lock); 567 568 /* 569 * If a probe was requested then probe ISAPnP first (safest) 570 */ 571 if (io < 0) 572 pnp_register_driver(&cadet_pnp_driver); 573 /* 574 * If that fails then probe unsafely if probe is requested 575 */ 576 if(io < 0) 577 io = cadet_probe (); 578 579 /* 580 * Else we bail out 581 */ 582 583 if(io < 0) { 584 #ifdef MODULE 585 printk(KERN_ERR "You must set an I/O address with io=0x???\n"); 586 #endif 587 goto fail; 588 } 589 if (!request_region(io,2,"cadet")) 590 goto fail; 591 if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) { 592 release_region(io,2); 593 goto fail; 594 } 595 printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io); 596 return 0; 597 fail: 598 pnp_unregister_driver(&cadet_pnp_driver); 599 return -1; 600 } 601 602 603 604 MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); 605 MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card."); 606 MODULE_LICENSE("GPL"); 607 608 module_param(io, int, 0); 609 MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)"); 610 module_param(radio_nr, int, 0); 611 612 static void __exit cadet_cleanup_module(void) 613 { 614 video_unregister_device(&cadet_radio); 615 release_region(io,2); 616 pnp_unregister_driver(&cadet_pnp_driver); 617 } 618 619 module_init(cadet_init); 620 module_exit(cadet_cleanup_module); 621 622