radio-cadet.c
上传用户:lgb322
上传日期:2013-02-24
资源大小:30529k
文件大小:15k
源码类别:

嵌入式Linux

开发平台:

Unix_Linux

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