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 32 #include <linux/module.h> /* Modules */ 33 #include <linux/init.h> /* Initdata */ 34 #include <linux/ioport.h> /* request_region */ 35 #include <linux/proc_fs.h> /* radio card status report */ 36 #include <asm/io.h> /* outb, outb_p */ 37 #include <asm/uaccess.h> /* copy to/from user */ 38 #include <linux/videodev.h> /* kernel radio structs */ 39 #include <linux/config.h> /* CONFIG_RADIO_TYPHOON_* */ 40 41 #define BANNER "Typhoon Radio Card driver v0.1\n" 42 43 #ifndef CONFIG_RADIO_TYPHOON_PORT 44 #define CONFIG_RADIO_TYPHOON_PORT -1 45 #endif 46 47 #ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ 48 #define CONFIG_RADIO_TYPHOON_MUTEFREQ 0 49 #endif 50 51 #ifndef CONFIG_PROC_FS 52 #undef CONFIG_RADIO_TYPHOON_PROC_FS 53 #endif 54 55 struct typhoon_device { 56 int users; 57 int iobase; 58 int curvol; 59 int muted; 60 unsigned long curfreq; 61 unsigned long mutefreq; 62 struct semaphore lock; 63 }; 64 65 static void typhoon_setvol_generic(struct typhoon_device *dev, int vol); 66 static int typhoon_setfreq_generic(struct typhoon_device *dev, 67 unsigned long frequency); 68 static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency); 69 static void typhoon_mute(struct typhoon_device *dev); 70 static void typhoon_unmute(struct typhoon_device *dev); 71 static int typhoon_setvol(struct typhoon_device *dev, int vol); 72 static int typhoon_ioctl(struct inode *inode, struct file *file, 73 unsigned int cmd, unsigned long arg); 74 #ifdef CONFIG_RADIO_TYPHOON_PROC_FS 75 static int typhoon_get_info(char *buf, char **start, off_t offset, int len); 76 #endif 77 78 static void typhoon_setvol_generic(struct typhoon_device *dev, int vol) 79 { 80 down(&dev->lock); 81 vol >>= 14; /* Map 16 bit to 2 bit */ 82 vol &= 3; 83 outb_p(vol / 2, dev->iobase); /* Set the volume, high bit. */ 84 outb_p(vol % 2, dev->iobase + 2); /* Set the volume, low bit. */ 85 up(&dev->lock); 86 } 87 88 static int typhoon_setfreq_generic(struct typhoon_device *dev, 89 unsigned long frequency) 90 { 91 unsigned long outval; 92 unsigned long x; 93 94 /* 95 * The frequency transfer curve is not linear. The best fit I could 96 * get is 97 * 98 * outval = -155 + exp((f + 15.55) * 0.057)) 99 * 100 * where frequency f is in MHz. Since we don't have exp in the kernel, 101 * I approximate this function by a third order polynomial. 102 * 103 */ 104 105 down(&dev->lock); 106 x = frequency / 160; 107 outval = (x * x + 2500) / 5000; 108 outval = (outval * x + 5000) / 10000; 109 outval -= (10 * x * x + 10433) / 20866; 110 outval += 4 * x - 11505; 111 112 outb_p((outval >> 8) & 0x01, dev->iobase + 4); 113 outb_p(outval >> 9, dev->iobase + 6); 114 outb_p(outval & 0xff, dev->iobase + 8); 115 up(&dev->lock); 116 117 return 0; 118 } 119 120 static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency) 121 { 122 typhoon_setfreq_generic(dev, frequency); 123 dev->curfreq = frequency; 124 return 0; 125 } 126 127 static void typhoon_mute(struct typhoon_device *dev) 128 { 129 if (dev->muted == 1) 130 return; 131 typhoon_setvol_generic(dev, 0); 132 typhoon_setfreq_generic(dev, dev->mutefreq); 133 dev->muted = 1; 134 } 135 136 static void typhoon_unmute(struct typhoon_device *dev) 137 { 138 if (dev->muted == 0) 139 return; 140 typhoon_setfreq_generic(dev, dev->curfreq); 141 typhoon_setvol_generic(dev, dev->curvol); 142 dev->muted = 0; 143 } 144 145 static int typhoon_setvol(struct typhoon_device *dev, int vol) 146 { 147 if (dev->muted && vol != 0) { /* user is unmuting the card */ 148 dev->curvol = vol; 149 typhoon_unmute(dev); 150 return 0; 151 } 152 if (vol == dev->curvol) /* requested volume == current */ 153 return 0; 154 155 if (vol == 0) { /* volume == 0 means mute the card */ 156 typhoon_mute(dev); 157 dev->curvol = vol; 158 return 0; 159 } 160 typhoon_setvol_generic(dev, vol); 161 dev->curvol = vol; 162 return 0; 163 } 164 165 166 static int typhoon_do_ioctl(struct inode *inode, struct file *file, 167 unsigned int cmd, void *arg) 168 { 169 struct video_device *dev = video_devdata(file); 170 struct typhoon_device *typhoon = dev->priv; 171 172 switch (cmd) { 173 case VIDIOCGCAP: 174 { 175 struct video_capability *v = arg; 176 memset(v,0,sizeof(*v)); 177 v->type = VID_TYPE_TUNER; 178 v->channels = 1; 179 v->audios = 1; 180 strcpy(v->name, "Typhoon Radio"); 181 return 0; 182 } 183 case VIDIOCGTUNER: 184 { 185 struct video_tuner *v = arg; 186 if (v->tuner) /* Only 1 tuner */ 187 return -EINVAL; 188 v->rangelow = 875 * 1600; 189 v->rangehigh = 1080 * 1600; 190 v->flags = VIDEO_TUNER_LOW; 191 v->mode = VIDEO_MODE_AUTO; 192 v->signal = 0xFFFF; /* We can't get the signal strength */ 193 strcpy(v->name, "FM"); 194 return 0; 195 } 196 case VIDIOCSTUNER: 197 { 198 struct video_tuner *v = arg; 199 if (v->tuner != 0) 200 return -EINVAL; 201 /* Only 1 tuner so no setting needed ! */ 202 return 0; 203 } 204 case VIDIOCGFREQ: 205 { 206 unsigned long *freq = arg; 207 *freq = typhoon->curfreq; 208 return 0; 209 } 210 case VIDIOCSFREQ: 211 { 212 unsigned long *freq = arg; 213 typhoon->curfreq = *freq; 214 typhoon_setfreq(typhoon, typhoon->curfreq); 215 return 0; 216 } 217 case VIDIOCGAUDIO: 218 { 219 struct video_audio *v = arg; 220 memset(v, 0, sizeof(*v)); 221 v->flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME; 222 v->mode |= VIDEO_SOUND_MONO; 223 v->volume = typhoon->curvol; 224 v->step = 1 << 14; 225 strcpy(v->name, "Typhoon Radio"); 226 return 0; 227 } 228 case VIDIOCSAUDIO: 229 { 230 struct video_audio *v = arg; 231 if (v->audio) 232 return -EINVAL; 233 if (v->flags & VIDEO_AUDIO_MUTE) 234 typhoon_mute(typhoon); 235 else 236 typhoon_unmute(typhoon); 237 if (v->flags & VIDEO_AUDIO_VOLUME) 238 typhoon_setvol(typhoon, v->volume); 239 return 0; 240 } 241 default: 242 return -ENOIOCTLCMD; 243 } 244 } 245 246 static int typhoon_ioctl(struct inode *inode, struct file *file, 247 unsigned int cmd, unsigned long arg) 248 { 249 return video_usercopy(inode, file, cmd, arg, typhoon_do_ioctl); 250 } 251 252 static struct typhoon_device typhoon_unit = 253 { 254 .iobase = CONFIG_RADIO_TYPHOON_PORT, 255 .curfreq = CONFIG_RADIO_TYPHOON_MUTEFREQ, 256 .mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ, 257 }; 258 259 static struct file_operations typhoon_fops = { 260 .owner = THIS_MODULE, 261 .open = video_exclusive_open, 262 .release = video_exclusive_release, 263 .ioctl = typhoon_ioctl, 264 .compat_ioctl = v4l_compat_ioctl32, 265 .llseek = no_llseek, 266 }; 267 268 static struct video_device typhoon_radio = 269 { 270 .owner = THIS_MODULE, 271 .name = "Typhoon Radio", 272 .type = VID_TYPE_TUNER, 273 .hardware = VID_HARDWARE_TYPHOON, 274 .fops = &typhoon_fops, 275 }; 276 277 #ifdef CONFIG_RADIO_TYPHOON_PROC_FS 278 279 static int typhoon_get_info(char *buf, char **start, off_t offset, int len) 280 { 281 char *out = buf; 282 283 #ifdef MODULE 284 #define MODULEPROCSTRING "Driver loaded as a module" 285 #else 286 #define MODULEPROCSTRING "Driver compiled into kernel" 287 #endif 288 289 /* output must be kept under PAGE_SIZE */ 290 out += sprintf(out, BANNER); 291 out += sprintf(out, "Load type: " MODULEPROCSTRING "\n\n"); 292 out += sprintf(out, "frequency = %lu kHz\n", 293 typhoon_unit.curfreq >> 4); 294 out += sprintf(out, "volume = %d\n", typhoon_unit.curvol); 295 out += sprintf(out, "mute = %s\n", typhoon_unit.muted ? 296 "on" : "off"); 297 out += sprintf(out, "iobase = 0x%x\n", typhoon_unit.iobase); 298 out += sprintf(out, "mute frequency = %lu kHz\n", 299 typhoon_unit.mutefreq >> 4); 300 return out - buf; 301 } 302 303 #endif /* CONFIG_RADIO_TYPHOON_PROC_FS */ 304 305 MODULE_AUTHOR("Dr. Henrik Seidel"); 306 MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio)."); 307 MODULE_LICENSE("GPL"); 308 309 static int io = -1; 310 static int radio_nr = -1; 311 312 module_param(io, int, 0); 313 MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)"); 314 module_param(radio_nr, int, 0); 315 316 #ifdef MODULE 317 static unsigned long mutefreq = 0; 318 module_param(mutefreq, ulong, 0); 319 MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)"); 320 #endif 321 322 static int __init typhoon_init(void) 323 { 324 #ifdef MODULE 325 if (io == -1) { 326 printk(KERN_ERR "radio-typhoon: You must set an I/O address with io=0x316 or io=0x336\n"); 327 return -EINVAL; 328 } 329 typhoon_unit.iobase = io; 330 331 if (mutefreq < 87000 || mutefreq > 108500) { 332 printk(KERN_ERR "radio-typhoon: You must set a frequency (in kHz) used when muting the card,\n"); 333 printk(KERN_ERR "radio-typhoon: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n"); 334 return -EINVAL; 335 } 336 typhoon_unit.mutefreq = mutefreq; 337 #endif /* MODULE */ 338 339 printk(KERN_INFO BANNER); 340 init_MUTEX(&typhoon_unit.lock); 341 io = typhoon_unit.iobase; 342 if (!request_region(io, 8, "typhoon")) { 343 printk(KERN_ERR "radio-typhoon: port 0x%x already in use\n", 344 typhoon_unit.iobase); 345 return -EBUSY; 346 } 347 348 typhoon_radio.priv = &typhoon_unit; 349 if (video_register_device(&typhoon_radio, VFL_TYPE_RADIO, radio_nr) == -1) 350 { 351 release_region(io, 8); 352 return -EINVAL; 353 } 354 printk(KERN_INFO "radio-typhoon: port 0x%x.\n", typhoon_unit.iobase); 355 printk(KERN_INFO "radio-typhoon: mute frequency is %lu kHz.\n", 356 typhoon_unit.mutefreq); 357 typhoon_unit.mutefreq <<= 4; 358 359 /* mute card - prevents noisy bootups */ 360 typhoon_mute(&typhoon_unit); 361 362 #ifdef CONFIG_RADIO_TYPHOON_PROC_FS 363 if (!create_proc_info_entry("driver/radio-typhoon", 0, NULL, 364 typhoon_get_info)) 365 printk(KERN_ERR "radio-typhoon: registering /proc/driver/radio-typhoon failed\n"); 366 #endif 367 368 return 0; 369 } 370 371 static void __exit typhoon_cleanup_module(void) 372 { 373 374 #ifdef CONFIG_RADIO_TYPHOON_PROC_FS 375 remove_proc_entry("driver/radio-typhoon", NULL); 376 #endif 377 378 video_unregister_device(&typhoon_radio); 379 release_region(io, 8); 380 } 381 382 module_init(typhoon_init); 383 module_exit(typhoon_cleanup_module); 384 385