1 /* zoltrix radio plus driver for Linux radio support 2 * (c) 1998 C. van Schaik <carl@leg.uct.ac.za> 3 * 4 * BUGS 5 * Due to the inconsistency in reading from the signal flags 6 * it is difficult to get an accurate tuned signal. 7 * 8 * It seems that the card is not linear to 0 volume. It cuts off 9 * at a low volume, and it is not possible (at least I have not found) 10 * to get fine volume control over the low volume range. 11 * 12 * Some code derived from code by Romolo Manfredini 13 * romolo@bicnet.it 14 * 15 * 1999-05-06 - (C. van Schaik) 16 * - Make signal strength and stereo scans 17 * kinder to cpu while in delay 18 * 1999-01-05 - (C. van Schaik) 19 * - Changed tuning to 1/160Mhz accuracy 20 * - Added stereo support 21 * (card defaults to stereo) 22 * (can explicitly force mono on the card) 23 * (can detect if station is in stereo) 24 * - Added unmute function 25 * - Reworked ioctl functions 26 * 2002-07-15 - Fix Stereo typo 27 */ 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, msleep */ 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 <media/v4l2-common.h> 37 #include <linux/config.h> /* CONFIG_RADIO_ZOLTRIX_PORT */ 38 39 #ifndef CONFIG_RADIO_ZOLTRIX_PORT 40 #define CONFIG_RADIO_ZOLTRIX_PORT -1 41 #endif 42 43 static int io = CONFIG_RADIO_ZOLTRIX_PORT; 44 static int radio_nr = -1; 45 46 struct zol_device { 47 int port; 48 int curvol; 49 unsigned long curfreq; 50 int muted; 51 unsigned int stereo; 52 struct mutex lock; 53 }; 54 55 static int zol_setvol(struct zol_device *dev, int vol) 56 { 57 dev->curvol = vol; 58 if (dev->muted) 59 return 0; 60 61 mutex_lock(&dev->lock); 62 if (vol == 0) { 63 outb(0, io); 64 outb(0, io); 65 inb(io + 3); /* Zoltrix needs to be read to confirm */ 66 mutex_unlock(&dev->lock); 67 return 0; 68 } 69 70 outb(dev->curvol-1, io); 71 msleep(10); 72 inb(io + 2); 73 mutex_unlock(&dev->lock); 74 return 0; 75 } 76 77 static void zol_mute(struct zol_device *dev) 78 { 79 dev->muted = 1; 80 mutex_lock(&dev->lock); 81 outb(0, io); 82 outb(0, io); 83 inb(io + 3); /* Zoltrix needs to be read to confirm */ 84 mutex_unlock(&dev->lock); 85 } 86 87 static void zol_unmute(struct zol_device *dev) 88 { 89 dev->muted = 0; 90 zol_setvol(dev, dev->curvol); 91 } 92 93 static int zol_setfreq(struct zol_device *dev, unsigned long freq) 94 { 95 /* tunes the radio to the desired frequency */ 96 unsigned long long bitmask, f, m; 97 unsigned int stereo = dev->stereo; 98 int i; 99 100 if (freq == 0) 101 return 1; 102 m = (freq / 160 - 8800) * 2; 103 f = (unsigned long long) m + 0x4d1c; 104 105 bitmask = 0xc480402c10080000ull; 106 i = 45; 107 108 mutex_lock(&dev->lock); 109 110 outb(0, io); 111 outb(0, io); 112 inb(io + 3); /* Zoltrix needs to be read to confirm */ 113 114 outb(0x40, io); 115 outb(0xc0, io); 116 117 bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31)); 118 while (i--) { 119 if ((bitmask & 0x8000000000000000ull) != 0) { 120 outb(0x80, io); 121 udelay(50); 122 outb(0x00, io); 123 udelay(50); 124 outb(0x80, io); 125 udelay(50); 126 } else { 127 outb(0xc0, io); 128 udelay(50); 129 outb(0x40, io); 130 udelay(50); 131 outb(0xc0, io); 132 udelay(50); 133 } 134 bitmask *= 2; 135 } 136 /* termination sequence */ 137 outb(0x80, io); 138 outb(0xc0, io); 139 outb(0x40, io); 140 udelay(1000); 141 inb(io+2); 142 143 udelay(1000); 144 145 if (dev->muted) 146 { 147 outb(0, io); 148 outb(0, io); 149 inb(io + 3); 150 udelay(1000); 151 } 152 153 mutex_unlock(&dev->lock); 154 155 if(!dev->muted) 156 { 157 zol_setvol(dev, dev->curvol); 158 } 159 return 0; 160 } 161 162 /* Get signal strength */ 163 164 static int zol_getsigstr(struct zol_device *dev) 165 { 166 int a, b; 167 168 mutex_lock(&dev->lock); 169 outb(0x00, io); /* This stuff I found to do nothing */ 170 outb(dev->curvol, io); 171 msleep(20); 172 173 a = inb(io); 174 msleep(10); 175 b = inb(io); 176 177 mutex_unlock(&dev->lock); 178 179 if (a != b) 180 return (0); 181 182 if ((a == 0xcf) || (a == 0xdf) /* I found this out by playing */ 183 || (a == 0xef)) /* with a binary scanner on the card io */ 184 return (1); 185 return (0); 186 } 187 188 static int zol_is_stereo (struct zol_device *dev) 189 { 190 int x1, x2; 191 192 mutex_lock(&dev->lock); 193 194 outb(0x00, io); 195 outb(dev->curvol, io); 196 msleep(20); 197 198 x1 = inb(io); 199 msleep(10); 200 x2 = inb(io); 201 202 mutex_unlock(&dev->lock); 203 204 if ((x1 == x2) && (x1 == 0xcf)) 205 return 1; 206 return 0; 207 } 208 209 static int zol_do_ioctl(struct inode *inode, struct file *file, 210 unsigned int cmd, void *arg) 211 { 212 struct video_device *dev = video_devdata(file); 213 struct zol_device *zol = dev->priv; 214 215 switch (cmd) { 216 case VIDIOCGCAP: 217 { 218 struct video_capability *v = arg; 219 220 memset(v,0,sizeof(*v)); 221 v->type = VID_TYPE_TUNER; 222 v->channels = 1 + zol->stereo; 223 v->audios = 1; 224 strcpy(v->name, "Zoltrix Radio"); 225 return 0; 226 } 227 case VIDIOCGTUNER: 228 { 229 struct video_tuner *v = arg; 230 if (v->tuner) 231 return -EINVAL; 232 strcpy(v->name, "FM"); 233 v->rangelow = (int) (88.0 * 16000); 234 v->rangehigh = (int) (108.0 * 16000); 235 v->flags = zol_is_stereo(zol) 236 ? VIDEO_TUNER_STEREO_ON : 0; 237 v->flags |= VIDEO_TUNER_LOW; 238 v->mode = VIDEO_MODE_AUTO; 239 v->signal = 0xFFFF * zol_getsigstr(zol); 240 return 0; 241 } 242 case VIDIOCSTUNER: 243 { 244 struct video_tuner *v = arg; 245 if (v->tuner != 0) 246 return -EINVAL; 247 /* Only 1 tuner so no setting needed ! */ 248 return 0; 249 } 250 case VIDIOCGFREQ: 251 { 252 unsigned long *freq = arg; 253 *freq = zol->curfreq; 254 return 0; 255 } 256 case VIDIOCSFREQ: 257 { 258 unsigned long *freq = arg; 259 zol->curfreq = *freq; 260 zol_setfreq(zol, zol->curfreq); 261 return 0; 262 } 263 case VIDIOCGAUDIO: 264 { 265 struct video_audio *v = arg; 266 memset(v, 0, sizeof(*v)); 267 v->flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME; 268 v->mode |= zol_is_stereo(zol) 269 ? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO; 270 v->volume = zol->curvol * 4096; 271 v->step = 4096; 272 strcpy(v->name, "Zoltrix Radio"); 273 return 0; 274 } 275 case VIDIOCSAUDIO: 276 { 277 struct video_audio *v = arg; 278 if (v->audio) 279 return -EINVAL; 280 281 if (v->flags & VIDEO_AUDIO_MUTE) 282 zol_mute(zol); 283 else { 284 zol_unmute(zol); 285 zol_setvol(zol, v->volume / 4096); 286 } 287 288 if (v->mode & VIDEO_SOUND_STEREO) { 289 zol->stereo = 1; 290 zol_setfreq(zol, zol->curfreq); 291 } 292 if (v->mode & VIDEO_SOUND_MONO) { 293 zol->stereo = 0; 294 zol_setfreq(zol, zol->curfreq); 295 } 296 return 0; 297 } 298 default: 299 return -ENOIOCTLCMD; 300 } 301 } 302 303 static int zol_ioctl(struct inode *inode, struct file *file, 304 unsigned int cmd, unsigned long arg) 305 { 306 return video_usercopy(inode, file, cmd, arg, zol_do_ioctl); 307 } 308 309 static struct zol_device zoltrix_unit; 310 311 static struct file_operations zoltrix_fops = 312 { 313 .owner = THIS_MODULE, 314 .open = video_exclusive_open, 315 .release = video_exclusive_release, 316 .ioctl = zol_ioctl, 317 .compat_ioctl = v4l_compat_ioctl32, 318 .llseek = no_llseek, 319 }; 320 321 static struct video_device zoltrix_radio = 322 { 323 .owner = THIS_MODULE, 324 .name = "Zoltrix Radio Plus", 325 .type = VID_TYPE_TUNER, 326 .hardware = VID_HARDWARE_ZOLTRIX, 327 .fops = &zoltrix_fops, 328 }; 329 330 static int __init zoltrix_init(void) 331 { 332 if (io == -1) { 333 printk(KERN_ERR "You must set an I/O address with io=0x???\n"); 334 return -EINVAL; 335 } 336 if ((io != 0x20c) && (io != 0x30c)) { 337 printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n"); 338 return -ENXIO; 339 } 340 341 zoltrix_radio.priv = &zoltrix_unit; 342 if (!request_region(io, 2, "zoltrix")) { 343 printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io); 344 return -EBUSY; 345 } 346 347 if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) == -1) 348 { 349 release_region(io, 2); 350 return -EINVAL; 351 } 352 printk(KERN_INFO "Zoltrix Radio Plus card driver.\n"); 353 354 mutex_init(&zoltrix_unit.lock); 355 356 /* mute card - prevents noisy bootups */ 357 358 /* this ensures that the volume is all the way down */ 359 360 outb(0, io); 361 outb(0, io); 362 msleep(20); 363 inb(io + 3); 364 365 zoltrix_unit.curvol = 0; 366 zoltrix_unit.stereo = 1; 367 368 return 0; 369 } 370 371 MODULE_AUTHOR("C.van Schaik"); 372 MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus."); 373 MODULE_LICENSE("GPL"); 374 375 module_param(io, int, 0); 376 MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)"); 377 module_param(radio_nr, int, 0); 378 379 static void __exit zoltrix_cleanup_module(void) 380 { 381 video_unregister_device(&zoltrix_radio); 382 release_region(io, 2); 383 } 384 385 module_init(zoltrix_init); 386 module_exit(zoltrix_cleanup_module); 387 388