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

嵌入式Linux

开发平台:

Unix_Linux

  1. /* Typhoon Radio Card driver for radio support
  2.  * (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de>
  3.  *
  4.  * Card manufacturer:
  5.  * http://194.18.155.92/idc/prod2.idc?nr=50753&lang=e
  6.  *
  7.  * Notes on the hardware
  8.  *
  9.  * This card has two output sockets, one for speakers and one for line.
  10.  * The speaker output has volume control, but only in four discrete
  11.  * steps. The line output has neither volume control nor mute.
  12.  *
  13.  * The card has auto-stereo according to its manual, although it all
  14.  * sounds mono to me (even with the Win/DOS drivers). Maybe it's my
  15.  * antenna - I really don't know for sure.
  16.  *
  17.  * Frequency control is done digitally.
  18.  *
  19.  * Volume control is done digitally, but there are only four different
  20.  * possible values. So you should better always turn the volume up and
  21.  * use line control. I got the best results by connecting line output
  22.  * to the sound card microphone input. For such a configuration the
  23.  * volume control has no effect, since volume control only influences
  24.  * the speaker output.
  25.  *
  26.  * There is no explicit mute/unmute. So I set the radio frequency to a
  27.  * value where I do expect just noise and turn the speaker volume down.
  28.  * The frequency change is necessary since the card never seems to be
  29.  * completely silent.
  30.  */
  31. #include <linux/module.h> /* Modules                        */
  32. #include <linux/init.h> /* Initdata                       */
  33. #include <linux/ioport.h> /* check_region, request_region   */
  34. #include <linux/proc_fs.h> /* radio card status report   */
  35. #include <asm/io.h> /* outb, outb_p                   */
  36. #include <asm/uaccess.h> /* copy to/from user              */
  37. #include <linux/videodev.h> /* kernel radio structs           */
  38. #include <linux/config.h> /* CONFIG_RADIO_TYPHOON_*         */
  39. #define BANNER "Typhoon Radio Card driver v0.1n"
  40. #ifndef CONFIG_RADIO_TYPHOON_PORT
  41. #define CONFIG_RADIO_TYPHOON_PORT -1
  42. #endif
  43. #ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ
  44. #define CONFIG_RADIO_TYPHOON_MUTEFREQ 0
  45. #endif
  46. #ifndef CONFIG_PROC_FS
  47. #undef CONFIG_RADIO_TYPHOON_PROC_FS
  48. #endif
  49. struct typhoon_device {
  50. int users;
  51. int iobase;
  52. int curvol;
  53. int muted;
  54. unsigned long curfreq;
  55. unsigned long mutefreq;
  56. };
  57. static void typhoon_setvol_generic(struct typhoon_device *dev, int vol);
  58. static int typhoon_setfreq_generic(struct typhoon_device *dev,
  59.    unsigned long frequency);
  60. static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency);
  61. static void typhoon_mute(struct typhoon_device *dev);
  62. static void typhoon_unmute(struct typhoon_device *dev);
  63. static int typhoon_setvol(struct typhoon_device *dev, int vol);
  64. static int typhoon_ioctl(struct video_device *dev, unsigned int cmd, void *arg);
  65. static int typhoon_open(struct video_device *dev, int flags);
  66. static void typhoon_close(struct video_device *dev);
  67. #ifdef CONFIG_RADIO_TYPHOON_PROC_FS
  68. static int typhoon_get_info(char *buf, char **start, off_t offset, int len);
  69. #endif
  70. static void typhoon_setvol_generic(struct typhoon_device *dev, int vol)
  71. {
  72. vol >>= 14; /* Map 16 bit to 2 bit */
  73. vol &= 3;
  74. outb_p(vol / 2, dev->iobase); /* Set the volume, high bit. */
  75. outb_p(vol % 2, dev->iobase + 2); /* Set the volume, low bit. */
  76. }
  77. static int typhoon_setfreq_generic(struct typhoon_device *dev,
  78.    unsigned long frequency)
  79. {
  80. unsigned long outval;
  81. unsigned long x;
  82. /*
  83.  * The frequency transfer curve is not linear. The best fit I could
  84.  * get is
  85.  *
  86.  * outval = -155 + exp((f + 15.55) * 0.057))
  87.  *
  88.  * where frequency f is in MHz. Since we don't have exp in the kernel,
  89.  * I approximate this function by a third order polynomial.
  90.  *
  91.  */
  92. x = frequency / 160;
  93. outval = (x * x + 2500) / 5000;
  94. outval = (outval * x + 5000) / 10000;
  95. outval -= (10 * x * x + 10433) / 20866;
  96. outval += 4 * x - 11505;
  97. outb_p((outval >> 8) & 0x01, dev->iobase + 4);
  98. outb_p(outval >> 9, dev->iobase + 6);
  99. outb_p(outval & 0xff, dev->iobase + 8);
  100. return 0;
  101. }
  102. static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency)
  103. {
  104. typhoon_setfreq_generic(dev, frequency);
  105. dev->curfreq = frequency;
  106. return 0;
  107. }
  108. static void typhoon_mute(struct typhoon_device *dev)
  109. {
  110. if (dev->muted == 1)
  111. return;
  112. typhoon_setvol_generic(dev, 0);
  113. typhoon_setfreq_generic(dev, dev->mutefreq);
  114. dev->muted = 1;
  115. }
  116. static void typhoon_unmute(struct typhoon_device *dev)
  117. {
  118. if (dev->muted == 0)
  119. return;
  120. typhoon_setfreq_generic(dev, dev->curfreq);
  121. typhoon_setvol_generic(dev, dev->curvol);
  122. dev->muted = 0;
  123. }
  124. static int typhoon_setvol(struct typhoon_device *dev, int vol)
  125. {
  126. if (dev->muted && vol != 0) { /* user is unmuting the card */
  127. dev->curvol = vol;
  128. typhoon_unmute(dev);
  129. return 0;
  130. }
  131. if (vol == dev->curvol) /* requested volume == current */
  132. return 0;
  133. if (vol == 0) { /* volume == 0 means mute the card */
  134. typhoon_mute(dev);
  135. dev->curvol = vol;
  136. return 0;
  137. }
  138. typhoon_setvol_generic(dev, vol);
  139. dev->curvol = vol;
  140. return 0;
  141. }
  142. static int typhoon_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
  143. {
  144. struct typhoon_device *typhoon = dev->priv;
  145. switch (cmd) {
  146. case VIDIOCGCAP:
  147. {
  148. struct video_capability v;
  149. v.type = VID_TYPE_TUNER;
  150. v.channels = 1;
  151. v.audios = 1;
  152. /* No we don't do pictures */
  153. v.maxwidth = 0;
  154. v.maxheight = 0;
  155. v.minwidth = 0;
  156. v.minheight = 0;
  157. strcpy(v.name, "Typhoon Radio");
  158. if (copy_to_user(arg, &v, sizeof(v)))
  159. return -EFAULT;
  160. return 0;
  161. }
  162. case VIDIOCGTUNER:
  163. {
  164. struct video_tuner v;
  165. if (copy_from_user(&v, arg, sizeof(v)) != 0)
  166. return -EFAULT;
  167. if (v.tuner) /* Only 1 tuner */
  168. return -EINVAL;
  169. v.rangelow = 875 * 1600;
  170. v.rangehigh = 1080 * 1600;
  171. v.flags = VIDEO_TUNER_LOW;
  172. v.mode = VIDEO_MODE_AUTO;
  173. v.signal = 0xFFFF; /* We can't get the signal strength */
  174. strcpy(v.name, "FM");
  175. if (copy_to_user(arg, &v, sizeof(v)))
  176. return -EFAULT;
  177. return 0;
  178. }
  179. case VIDIOCSTUNER:
  180. {
  181. struct video_tuner v;
  182. if (copy_from_user(&v, arg, sizeof(v)))
  183. return -EFAULT;
  184. if (v.tuner != 0)
  185. return -EINVAL;
  186. /* Only 1 tuner so no setting needed ! */
  187. return 0;
  188. }
  189. case VIDIOCGFREQ:
  190. if (copy_to_user(arg, &typhoon->curfreq,
  191.  sizeof(typhoon->curfreq)))
  192. return -EFAULT;
  193. return 0;
  194. case VIDIOCSFREQ:
  195. if (copy_from_user(&typhoon->curfreq, arg,
  196.    sizeof(typhoon->curfreq)))
  197. return -EFAULT;
  198. typhoon_setfreq(typhoon, typhoon->curfreq);
  199. return 0;
  200. case VIDIOCGAUDIO:
  201. {
  202. struct video_audio v;
  203. memset(&v, 0, sizeof(v));
  204. v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME;
  205. v.mode |= VIDEO_SOUND_MONO;
  206. v.volume = typhoon->curvol;
  207. v.step = 1 << 14;
  208. strcpy(v.name, "Typhoon Radio");
  209. if (copy_to_user(arg, &v, sizeof(v)))
  210. return -EFAULT;
  211. return 0;
  212. }
  213. case VIDIOCSAUDIO:
  214. {
  215. struct video_audio v;
  216. if (copy_from_user(&v, arg, sizeof(v)))
  217. return -EFAULT;
  218. if (v.audio)
  219. return -EINVAL;
  220. if (v.flags & VIDEO_AUDIO_MUTE)
  221. typhoon_mute(typhoon);
  222. else
  223. typhoon_unmute(typhoon);
  224. if (v.flags & VIDEO_AUDIO_VOLUME)
  225. typhoon_setvol(typhoon, v.volume);
  226. return 0;
  227. }
  228. default:
  229. return -ENOIOCTLCMD;
  230. }
  231. }
  232. static int typhoon_open(struct video_device *dev, int flags)
  233. {
  234. struct typhoon_device *typhoon = dev->priv;
  235. if (typhoon->users)
  236. return -EBUSY;
  237. typhoon->users++;
  238. return 0;
  239. }
  240. static void typhoon_close(struct video_device *dev)
  241. {
  242. struct typhoon_device *typhoon = dev->priv;
  243. typhoon->users--;
  244. }
  245. static struct typhoon_device typhoon_unit =
  246. {
  247. iobase: CONFIG_RADIO_TYPHOON_PORT,
  248. curfreq: CONFIG_RADIO_TYPHOON_MUTEFREQ,
  249. mutefreq: CONFIG_RADIO_TYPHOON_MUTEFREQ,
  250. };
  251. static struct video_device typhoon_radio =
  252. {
  253. owner: THIS_MODULE,
  254. name: "Typhoon Radio",
  255. type: VID_TYPE_TUNER,
  256. hardware: VID_HARDWARE_TYPHOON,
  257. open: typhoon_open,
  258. close: typhoon_close,
  259. ioctl: typhoon_ioctl,
  260. };
  261. #ifdef CONFIG_RADIO_TYPHOON_PROC_FS
  262. static int typhoon_get_info(char *buf, char **start, off_t offset, int len)
  263. {
  264. char *out = buf;
  265. #ifdef MODULE
  266.     #define MODULEPROCSTRING "Driver loaded as a module"
  267. #else
  268.     #define MODULEPROCSTRING "Driver compiled into kernel"
  269. #endif
  270. /* output must be kept under PAGE_SIZE */
  271. out += sprintf(out, BANNER);
  272. out += sprintf(out, "Load type: " MODULEPROCSTRING "nn");
  273. out += sprintf(out, "frequency = %lu kHzn",
  274. typhoon_unit.curfreq >> 4);
  275. out += sprintf(out, "volume = %dn", typhoon_unit.curvol);
  276. out += sprintf(out, "mute = %sn", typhoon_unit.muted ?
  277. "on" : "off");
  278. out += sprintf(out, "iobase = 0x%xn", typhoon_unit.iobase);
  279. out += sprintf(out, "mute frequency = %lu kHzn",
  280. typhoon_unit.mutefreq >> 4);
  281. return out - buf;
  282. }
  283. #endif /* CONFIG_RADIO_TYPHOON_PROC_FS */
  284. MODULE_AUTHOR("Dr. Henrik Seidel");
  285. MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio).");
  286. MODULE_LICENSE("GPL");
  287. MODULE_PARM(io, "i");
  288. MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)");
  289. MODULE_PARM(mutefreq, "i");
  290. MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)");
  291. MODULE_PARM(radio_nr, "i");
  292. EXPORT_NO_SYMBOLS;
  293. static int io = -1;
  294. static int radio_nr = -1;
  295. #ifdef MODULE
  296. static unsigned long mutefreq = 0;
  297. #endif
  298. static int __init typhoon_init(void)
  299. {
  300. #ifdef MODULE
  301. if (io == -1) {
  302. printk(KERN_ERR "radio-typhoon: You must set an I/O address with io=0x316 or io=0x336n");
  303. return -EINVAL;
  304. }
  305. typhoon_unit.iobase = io;
  306. if (mutefreq < 87000 || mutefreq > 108500) {
  307. printk(KERN_ERR "radio-typhoon: You must set a frequency (in kHz) used when muting the card,n");
  308. printk(KERN_ERR "radio-typhoon: e.g. with "mutefreq=87500" (87000 <= mutefreq <= 108500)n");
  309. return -EINVAL;
  310. }
  311. typhoon_unit.mutefreq = mutefreq;
  312. #endif /* MODULE */
  313. printk(KERN_INFO BANNER);
  314. io = typhoon_unit.iobase;
  315. if (!request_region(io, 8, "typhoon")) {
  316. printk(KERN_ERR "radio-typhoon: port 0x%x already in usen",
  317.        typhoon_unit.iobase);
  318. return -EBUSY;
  319. }
  320. typhoon_radio.priv = &typhoon_unit;
  321. if (video_register_device(&typhoon_radio, VFL_TYPE_RADIO, radio_nr) == -1)
  322. {
  323. release_region(io, 8);
  324. return -EINVAL;
  325. }
  326. printk(KERN_INFO "radio-typhoon: port 0x%x.n", typhoon_unit.iobase);
  327. printk(KERN_INFO "radio-typhoon: mute frequency is %lu kHz.n",
  328.        typhoon_unit.mutefreq);
  329. typhoon_unit.mutefreq <<= 4;
  330. /* mute card - prevents noisy bootups */
  331. typhoon_mute(&typhoon_unit);
  332. #ifdef CONFIG_RADIO_TYPHOON_PROC_FS
  333. if (!create_proc_info_entry("driver/radio-typhoon", 0, NULL,
  334.     typhoon_get_info)) 
  335.      printk(KERN_ERR "radio-typhoon: registering /proc/driver/radio-typhoon failedn");
  336. #endif
  337. return 0;
  338. }
  339. static void __exit typhoon_cleanup_module(void)
  340. {
  341. #ifdef CONFIG_RADIO_TYPHOON_PROC_FS
  342. remove_proc_entry("driver/radio-typhoon", NULL);
  343. #endif
  344. video_unregister_device(&typhoon_radio);
  345. release_region(io, 8);
  346. }
  347. module_init(typhoon_init);
  348. module_exit(typhoon_cleanup_module);