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 <media/v4l2-common.h> 38 #include <linux/param.h> 39 #include <linux/pnp.h> 40 41 #define RDS_BUFFER 256 42 43 static int io=-1; /* default to isapnp activation */ 44 static int radio_nr = -1; 45 static int users=0; 46 static int curtuner=0; 47 static int tunestat=0; 48 static int sigstrength=0; 49 static wait_queue_head_t read_queue; 50 static struct timer_list readtimer; 51 static __u8 rdsin=0,rdsout=0,rdsstat=0; 52 static unsigned char rdsbuf[RDS_BUFFER]; 53 static spinlock_t cadet_io_lock; 54 55 static int cadet_probe(void); 56 57 /* 58 * Signal Strength Threshold Values 59 * The V4L API spec does not define any particular unit for the signal 60 * strength value. These values are in microvolts of RF at the tuner's input. 61 */ 62 static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}}; 63 64 static int cadet_getrds(void) 65 { 66 int rdsstat=0; 67 68 spin_lock(&cadet_io_lock); 69 outb(3,io); /* Select Decoder Control/Status */ 70 outb(inb(io+1)&0x7f,io+1); /* Reset RDS detection */ 71 spin_unlock(&cadet_io_lock); 72 73 msleep(100); 74 75 spin_lock(&cadet_io_lock); 76 outb(3,io); /* Select Decoder Control/Status */ 77 if((inb(io+1)&0x80)!=0) { 78 rdsstat|=VIDEO_TUNER_RDS_ON; 79 } 80 if((inb(io+1)&0x10)!=0) { 81 rdsstat|=VIDEO_TUNER_MBS_ON; 82 } 83 spin_unlock(&cadet_io_lock); 84 return rdsstat; 85 } 86 87 static int cadet_getstereo(void) 88 { 89 int ret = 0; 90 if(curtuner != 0) /* Only FM has stereo capability! */ 91 return 0; 92 93 spin_lock(&cadet_io_lock); 94 outb(7,io); /* Select tuner control */ 95 if( (inb(io+1) & 0x40) == 0) 96 ret = 1; 97 spin_unlock(&cadet_io_lock); 98 return ret; 99 } 100 101 static unsigned cadet_gettune(void) 102 { 103 int curvol,i; 104 unsigned fifo=0; 105 106 /* 107 * Prepare for read 108 */ 109 110 spin_lock(&cadet_io_lock); 111 112 outb(7,io); /* Select tuner control */ 113 curvol=inb(io+1); /* Save current volume/mute setting */ 114 outb(0x00,io+1); /* Ensure WRITE-ENABLE is LOW */ 115 tunestat=0xffff; 116 117 /* 118 * Read the shift register 119 */ 120 for(i=0;i<25;i++) { 121 fifo=(fifo<<1)|((inb(io+1)>>7)&0x01); 122 if(i<24) { 123 outb(0x01,io+1); 124 tunestat&=inb(io+1); 125 outb(0x00,io+1); 126 } 127 } 128 129 /* 130 * Restore volume/mute setting 131 */ 132 outb(curvol,io+1); 133 spin_unlock(&cadet_io_lock); 134 135 return fifo; 136 } 137 138 static unsigned cadet_getfreq(void) 139 { 140 int i; 141 unsigned freq=0,test,fifo=0; 142 143 /* 144 * Read current tuning 145 */ 146 fifo=cadet_gettune(); 147 148 /* 149 * Convert to actual frequency 150 */ 151 if(curtuner==0) { /* FM */ 152 test=12500; 153 for(i=0;i<14;i++) { 154 if((fifo&0x01)!=0) { 155 freq+=test; 156 } 157 test=test<<1; 158 fifo=fifo>>1; 159 } 160 freq-=10700000; /* IF frequency is 10.7 MHz */ 161 freq=(freq*16)/1000000; /* Make it 1/16 MHz */ 162 } 163 if(curtuner==1) { /* AM */ 164 freq=((fifo&0x7fff)-2010)*16; 165 } 166 167 return freq; 168 } 169 170 static void cadet_settune(unsigned fifo) 171 { 172 int i; 173 unsigned test; 174 175 spin_lock(&cadet_io_lock); 176 177 outb(7,io); /* Select tuner control */ 178 /* 179 * Write the shift register 180 */ 181 test=0; 182 test=(fifo>>23)&0x02; /* Align data for SDO */ 183 test|=0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */ 184 outb(7,io); /* Select tuner control */ 185 outb(test,io+1); /* Initialize for write */ 186 for(i=0;i<25;i++) { 187 test|=0x01; /* Toggle SCK High */ 188 outb(test,io+1); 189 test&=0xfe; /* Toggle SCK Low */ 190 outb(test,io+1); 191 fifo=fifo<<1; /* Prepare the next bit */ 192 test=0x1c|((fifo>>23)&0x02); 193 outb(test,io+1); 194 } 195 spin_unlock(&cadet_io_lock); 196 } 197 198 static void cadet_setfreq(unsigned freq) 199 { 200 unsigned fifo; 201 int i,j,test; 202 int curvol; 203 204 /* 205 * Formulate a fifo command 206 */ 207 fifo=0; 208 if(curtuner==0) { /* FM */ 209 test=102400; 210 freq=(freq*1000)/16; /* Make it kHz */ 211 freq+=10700; /* IF is 10700 kHz */ 212 for(i=0;i<14;i++) { 213 fifo=fifo<<1; 214 if(freq>=test) { 215 fifo|=0x01; 216 freq-=test; 217 } 218 test=test>>1; 219 } 220 } 221 if(curtuner==1) { /* AM */ 222 fifo=(freq/16)+2010; /* Make it kHz */ 223 fifo|=0x100000; /* Select AM Band */ 224 } 225 226 /* 227 * Save current volume/mute setting 228 */ 229 230 spin_lock(&cadet_io_lock); 231 outb(7,io); /* Select tuner control */ 232 curvol=inb(io+1); 233 spin_unlock(&cadet_io_lock); 234 235 /* 236 * Tune the card 237 */ 238 for(j=3;j>-1;j--) { 239 cadet_settune(fifo|(j<<16)); 240 241 spin_lock(&cadet_io_lock); 242 outb(7,io); /* Select tuner control */ 243 outb(curvol,io+1); 244 spin_unlock(&cadet_io_lock); 245 246 msleep(100); 247 248 cadet_gettune(); 249 if((tunestat & 0x40) == 0) { /* Tuned */ 250 sigstrength=sigtable[curtuner][j]; 251 return; 252 } 253 } 254 sigstrength=0; 255 } 256 257 258 static int cadet_getvol(void) 259 { 260 int ret = 0; 261 262 spin_lock(&cadet_io_lock); 263 264 outb(7,io); /* Select tuner control */ 265 if((inb(io + 1) & 0x20) != 0) 266 ret = 0xffff; 267 268 spin_unlock(&cadet_io_lock); 269 return ret; 270 } 271 272 273 static void cadet_setvol(int vol) 274 { 275 spin_lock(&cadet_io_lock); 276 outb(7,io); /* Select tuner control */ 277 if(vol>0) 278 outb(0x20,io+1); 279 else 280 outb(0x00,io+1); 281 spin_unlock(&cadet_io_lock); 282 } 283 284 static void cadet_handler(unsigned long data) 285 { 286 /* 287 * Service the RDS fifo 288 */ 289 290 if(spin_trylock(&cadet_io_lock)) 291 { 292 outb(0x3,io); /* Select RDS Decoder Control */ 293 if((inb(io+1)&0x20)!=0) { 294 printk(KERN_CRIT "cadet: RDS fifo overflow\n"); 295 } 296 outb(0x80,io); /* Select RDS fifo */ 297 while((inb(io)&0x80)!=0) { 298 rdsbuf[rdsin]=inb(io+1); 299 if(rdsin==rdsout) 300 printk(KERN_WARNING "cadet: RDS buffer overflow\n"); 301 else 302 rdsin++; 303 } 304 spin_unlock(&cadet_io_lock); 305 } 306 307 /* 308 * Service pending read 309 */ 310 if( rdsin!=rdsout) 311 wake_up_interruptible(&read_queue); 312 313 /* 314 * Clean up and exit 315 */ 316 init_timer(&readtimer); 317 readtimer.function=cadet_handler; 318 readtimer.data=(unsigned long)0; 319 readtimer.expires=jiffies+(HZ/20); 320 add_timer(&readtimer); 321 } 322 323 324 325 static ssize_t cadet_read(struct file *file, char __user *data, 326 size_t count, loff_t *ppos) 327 { 328 int i=0; 329 unsigned char readbuf[RDS_BUFFER]; 330 331 if(rdsstat==0) { 332 spin_lock(&cadet_io_lock); 333 rdsstat=1; 334 outb(0x80,io); /* Select RDS fifo */ 335 spin_unlock(&cadet_io_lock); 336 init_timer(&readtimer); 337 readtimer.function=cadet_handler; 338 readtimer.data=(unsigned long)0; 339 readtimer.expires=jiffies+(HZ/20); 340 add_timer(&readtimer); 341 } 342 if(rdsin==rdsout) { 343 if (file->f_flags & O_NONBLOCK) 344 return -EWOULDBLOCK; 345 interruptible_sleep_on(&read_queue); 346 } 347 while( i<count && rdsin!=rdsout) 348 readbuf[i++]=rdsbuf[rdsout++]; 349 350 if (copy_to_user(data,readbuf,i)) 351 return -EFAULT; 352 return i; 353 } 354 355 356 357 static int cadet_do_ioctl(struct inode *inode, struct file *file, 358 unsigned int cmd, void *arg) 359 { 360 switch(cmd) 361 { 362 case VIDIOCGCAP: 363 { 364 struct video_capability *v = arg; 365 memset(v,0,sizeof(*v)); 366 v->type=VID_TYPE_TUNER; 367 v->channels=2; 368 v->audios=1; 369 strcpy(v->name, "ADS Cadet"); 370 return 0; 371 } 372 case VIDIOCGTUNER: 373 { 374 struct video_tuner *v = arg; 375 if((v->tuner<0)||(v->tuner>1)) { 376 return -EINVAL; 377 } 378 switch(v->tuner) { 379 case 0: 380 strcpy(v->name,"FM"); 381 v->rangelow=1400; /* 87.5 MHz */ 382 v->rangehigh=1728; /* 108.0 MHz */ 383 v->flags=0; 384 v->mode=0; 385 v->mode|=VIDEO_MODE_AUTO; 386 v->signal=sigstrength; 387 if(cadet_getstereo()==1) { 388 v->flags|=VIDEO_TUNER_STEREO_ON; 389 } 390 v->flags|=cadet_getrds(); 391 break; 392 case 1: 393 strcpy(v->name,"AM"); 394 v->rangelow=8320; /* 520 kHz */ 395 v->rangehigh=26400; /* 1650 kHz */ 396 v->flags=0; 397 v->flags|=VIDEO_TUNER_LOW; 398 v->mode=0; 399 v->mode|=VIDEO_MODE_AUTO; 400 v->signal=sigstrength; 401 break; 402 } 403 return 0; 404 } 405 case VIDIOCSTUNER: 406 { 407 struct video_tuner *v = arg; 408 if((v->tuner<0)||(v->tuner>1)) { 409 return -EINVAL; 410 } 411 curtuner=v->tuner; 412 return 0; 413 } 414 case VIDIOCGFREQ: 415 { 416 unsigned long *freq = arg; 417 *freq = cadet_getfreq(); 418 return 0; 419 } 420 case VIDIOCSFREQ: 421 { 422 unsigned long *freq = arg; 423 if((curtuner==0)&&((*freq<1400)||(*freq>1728))) { 424 return -EINVAL; 425 } 426 if((curtuner==1)&&((*freq<8320)||(*freq>26400))) { 427 return -EINVAL; 428 } 429 cadet_setfreq(*freq); 430 return 0; 431 } 432 case VIDIOCGAUDIO: 433 { 434 struct video_audio *v = arg; 435 memset(v,0, sizeof(*v)); 436 v->flags=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; 437 if(cadet_getstereo()==0) { 438 v->mode=VIDEO_SOUND_MONO; 439 } else { 440 v->mode=VIDEO_SOUND_STEREO; 441 } 442 v->volume=cadet_getvol(); 443 v->step=0xffff; 444 strcpy(v->name, "Radio"); 445 return 0; 446 } 447 case VIDIOCSAUDIO: 448 { 449 struct video_audio *v = arg; 450 if(v->audio) 451 return -EINVAL; 452 cadet_setvol(v->volume); 453 if(v->flags&VIDEO_AUDIO_MUTE) 454 cadet_setvol(0); 455 else 456 cadet_setvol(0xffff); 457 return 0; 458 } 459 default: 460 return -ENOIOCTLCMD; 461 } 462 } 463 464 static int cadet_ioctl(struct inode *inode, struct file *file, 465 unsigned int cmd, unsigned long arg) 466 { 467 return video_usercopy(inode, file, cmd, arg, cadet_do_ioctl); 468 } 469 470 static int cadet_open(struct inode *inode, struct file *file) 471 { 472 if(users) 473 return -EBUSY; 474 users++; 475 init_waitqueue_head(&read_queue); 476 return 0; 477 } 478 479 static int cadet_release(struct inode *inode, struct file *file) 480 { 481 del_timer_sync(&readtimer); 482 rdsstat=0; 483 users--; 484 return 0; 485 } 486 487 488 static struct file_operations cadet_fops = { 489 .owner = THIS_MODULE, 490 .open = cadet_open, 491 .release = cadet_release, 492 .read = cadet_read, 493 .ioctl = cadet_ioctl, 494 .compat_ioctl = v4l_compat_ioctl32, 495 .llseek = no_llseek, 496 }; 497 498 static struct video_device cadet_radio= 499 { 500 .owner = THIS_MODULE, 501 .name = "Cadet radio", 502 .type = VID_TYPE_TUNER, 503 .hardware = VID_HARDWARE_CADET, 504 .fops = &cadet_fops, 505 }; 506 507 static struct pnp_device_id cadet_pnp_devices[] = { 508 /* ADS Cadet AM/FM Radio Card */ 509 {.id = "MSM0c24", .driver_data = 0}, 510 {.id = ""} 511 }; 512 513 MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices); 514 515 static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id) 516 { 517 if (!dev) 518 return -ENODEV; 519 /* only support one device */ 520 if (io > 0) 521 return -EBUSY; 522 523 if (!pnp_port_valid(dev, 0)) { 524 return -ENODEV; 525 } 526 527 io = pnp_port_start(dev, 0); 528 529 printk ("radio-cadet: PnP reports device at %#x\n", io); 530 531 return io; 532 } 533 534 static struct pnp_driver cadet_pnp_driver = { 535 .name = "radio-cadet", 536 .id_table = cadet_pnp_devices, 537 .probe = cadet_pnp_probe, 538 .remove = NULL, 539 }; 540 541 static int cadet_probe(void) 542 { 543 static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e}; 544 int i; 545 546 for(i=0;i<8;i++) { 547 io=iovals[i]; 548 if (request_region(io, 2, "cadet-probe")) { 549 cadet_setfreq(1410); 550 if(cadet_getfreq()==1410) { 551 release_region(io, 2); 552 return io; 553 } 554 release_region(io, 2); 555 } 556 } 557 return -1; 558 } 559 560 /* 561 * io should only be set if the user has used something like 562 * isapnp (the userspace program) to initialize this card for us 563 */ 564 565 static int __init cadet_init(void) 566 { 567 spin_lock_init(&cadet_io_lock); 568 569 /* 570 * If a probe was requested then probe ISAPnP first (safest) 571 */ 572 if (io < 0) 573 pnp_register_driver(&cadet_pnp_driver); 574 /* 575 * If that fails then probe unsafely if probe is requested 576 */ 577 if(io < 0) 578 io = cadet_probe (); 579 580 /* 581 * Else we bail out 582 */ 583 584 if(io < 0) { 585 #ifdef MODULE 586 printk(KERN_ERR "You must set an I/O address with io=0x???\n"); 587 #endif 588 goto fail; 589 } 590 if (!request_region(io,2,"cadet")) 591 goto fail; 592 if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) { 593 release_region(io,2); 594 goto fail; 595 } 596 printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io); 597 return 0; 598 fail: 599 pnp_unregister_driver(&cadet_pnp_driver); 600 return -1; 601 } 602 603 604 605 MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); 606 MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card."); 607 MODULE_LICENSE("GPL"); 608 609 module_param(io, int, 0); 610 MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)"); 611 module_param(radio_nr, int, 0); 612 613 static void __exit cadet_cleanup_module(void) 614 { 615 video_unregister_device(&cadet_radio); 616 release_region(io,2); 617 pnp_unregister_driver(&cadet_pnp_driver); 618 } 619 620 module_init(cadet_init); 621 module_exit(cadet_cleanup_module); 622 623