radio-cadet.c 14 KB

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