radio-cadet.c 15 KB

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