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