1 /* radiotrack (radioreveal) driver for Linux radio support 2 * (c) 1997 M. Kirkwood 3 * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> 4 * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> 5 * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> 6 * 7 * History: 8 * 1999-02-24 Russell Kroll <rkroll@exploits.org> 9 * Fine tuning/VIDEO_TUNER_LOW 10 * Frequency range expanded to start at 87 MHz 11 * 12 * TODO: Allow for more than one of these foolish entities :-) 13 * 14 * Notes on the hardware (reverse engineered from other peoples' 15 * reverse engineering of AIMS' code :-) 16 * 17 * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); 18 * 19 * The signal strength query is unsurprisingly inaccurate. And it seems 20 * to indicate that (on my card, at least) the frequency setting isn't 21 * too great. (I have to tune up .025MHz from what the freq should be 22 * to get a report that the thing is tuned.) 23 * 24 * Volume control is (ugh) analogue: 25 * out(port, start_increasing_volume); 26 * wait(a_wee_while); 27 * out(port, stop_changing_the_volume); 28 * 29 */ 30 31 #include <linux/module.h> /* Modules */ 32 #include <linux/init.h> /* Initdata */ 33 #include <linux/ioport.h> /* request_region */ 34 #include <linux/delay.h> /* msleep */ 35 #include <linux/videodev2.h> /* kernel radio structs */ 36 #include <linux/io.h> /* outb, outb_p */ 37 #include <media/v4l2-device.h> 38 #include <media/v4l2-ioctl.h> 39 40 MODULE_AUTHOR("M.Kirkwood"); 41 MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card."); 42 MODULE_LICENSE("GPL"); 43 MODULE_VERSION("0.0.3"); 44 45 #ifndef CONFIG_RADIO_RTRACK_PORT 46 #define CONFIG_RADIO_RTRACK_PORT -1 47 #endif 48 49 static int io = CONFIG_RADIO_RTRACK_PORT; 50 static int radio_nr = -1; 51 52 module_param(io, int, 0); 53 MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)"); 54 module_param(radio_nr, int, 0); 55 56 struct rtrack 57 { 58 struct v4l2_device v4l2_dev; 59 struct video_device vdev; 60 int port; 61 int curvol; 62 unsigned long curfreq; 63 int muted; 64 int io; 65 struct mutex lock; 66 }; 67 68 static struct rtrack rtrack_card; 69 70 /* local things */ 71 72 static void rt_decvol(struct rtrack *rt) 73 { 74 outb(0x58, rt->io); /* volume down + sigstr + on */ 75 msleep(100); 76 outb(0xd8, rt->io); /* volume steady + sigstr + on */ 77 } 78 79 static void rt_incvol(struct rtrack *rt) 80 { 81 outb(0x98, rt->io); /* volume up + sigstr + on */ 82 msleep(100); 83 outb(0xd8, rt->io); /* volume steady + sigstr + on */ 84 } 85 86 static void rt_mute(struct rtrack *rt) 87 { 88 rt->muted = 1; 89 mutex_lock(&rt->lock); 90 outb(0xd0, rt->io); /* volume steady, off */ 91 mutex_unlock(&rt->lock); 92 } 93 94 static int rt_setvol(struct rtrack *rt, int vol) 95 { 96 int i; 97 98 mutex_lock(&rt->lock); 99 100 if (vol == rt->curvol) { /* requested volume = current */ 101 if (rt->muted) { /* user is unmuting the card */ 102 rt->muted = 0; 103 outb(0xd8, rt->io); /* enable card */ 104 } 105 mutex_unlock(&rt->lock); 106 return 0; 107 } 108 109 if (vol == 0) { /* volume = 0 means mute the card */ 110 outb(0x48, rt->io); /* volume down but still "on" */ 111 msleep(2000); /* make sure it's totally down */ 112 outb(0xd0, rt->io); /* volume steady, off */ 113 rt->curvol = 0; /* track the volume state! */ 114 mutex_unlock(&rt->lock); 115 return 0; 116 } 117 118 rt->muted = 0; 119 if (vol > rt->curvol) 120 for (i = rt->curvol; i < vol; i++) 121 rt_incvol(rt); 122 else 123 for (i = rt->curvol; i > vol; i--) 124 rt_decvol(rt); 125 126 rt->curvol = vol; 127 mutex_unlock(&rt->lock); 128 return 0; 129 } 130 131 /* the 128+64 on these outb's is to keep the volume stable while tuning 132 * without them, the volume _will_ creep up with each frequency change 133 * and bit 4 (+16) is to keep the signal strength meter enabled 134 */ 135 136 static void send_0_byte(struct rtrack *rt) 137 { 138 if (rt->curvol == 0 || rt->muted) { 139 outb_p(128+64+16+ 1, rt->io); /* wr-enable + data low */ 140 outb_p(128+64+16+2+1, rt->io); /* clock */ 141 } 142 else { 143 outb_p(128+64+16+8+ 1, rt->io); /* on + wr-enable + data low */ 144 outb_p(128+64+16+8+2+1, rt->io); /* clock */ 145 } 146 msleep(1); 147 } 148 149 static void send_1_byte(struct rtrack *rt) 150 { 151 if (rt->curvol == 0 || rt->muted) { 152 outb_p(128+64+16+4 +1, rt->io); /* wr-enable+data high */ 153 outb_p(128+64+16+4+2+1, rt->io); /* clock */ 154 } 155 else { 156 outb_p(128+64+16+8+4 +1, rt->io); /* on+wr-enable+data high */ 157 outb_p(128+64+16+8+4+2+1, rt->io); /* clock */ 158 } 159 160 msleep(1); 161 } 162 163 static int rt_setfreq(struct rtrack *rt, unsigned long freq) 164 { 165 int i; 166 167 mutex_lock(&rt->lock); /* Stop other ops interfering */ 168 169 rt->curfreq = freq; 170 171 /* now uses VIDEO_TUNER_LOW for fine tuning */ 172 173 freq += 171200; /* Add 10.7 MHz IF */ 174 freq /= 800; /* Convert to 50 kHz units */ 175 176 send_0_byte(rt); /* 0: LSB of frequency */ 177 178 for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ 179 if (freq & (1 << i)) 180 send_1_byte(rt); 181 else 182 send_0_byte(rt); 183 184 send_0_byte(rt); /* 14: test bit - always 0 */ 185 send_0_byte(rt); /* 15: test bit - always 0 */ 186 187 send_0_byte(rt); /* 16: band data 0 - always 0 */ 188 send_0_byte(rt); /* 17: band data 1 - always 0 */ 189 send_0_byte(rt); /* 18: band data 2 - always 0 */ 190 send_0_byte(rt); /* 19: time base - always 0 */ 191 192 send_0_byte(rt); /* 20: spacing (0 = 25 kHz) */ 193 send_1_byte(rt); /* 21: spacing (1 = 25 kHz) */ 194 send_0_byte(rt); /* 22: spacing (0 = 25 kHz) */ 195 send_1_byte(rt); /* 23: AM/FM (FM = 1, always) */ 196 197 if (rt->curvol == 0 || rt->muted) 198 outb(0xd0, rt->io); /* volume steady + sigstr */ 199 else 200 outb(0xd8, rt->io); /* volume steady + sigstr + on */ 201 202 mutex_unlock(&rt->lock); 203 204 return 0; 205 } 206 207 static int rt_getsigstr(struct rtrack *rt) 208 { 209 int sig = 1; 210 211 mutex_lock(&rt->lock); 212 if (inb(rt->io) & 2) /* bit set = no signal present */ 213 sig = 0; 214 mutex_unlock(&rt->lock); 215 return sig; 216 } 217 218 static int vidioc_querycap(struct file *file, void *priv, 219 struct v4l2_capability *v) 220 { 221 strlcpy(v->driver, "radio-aimslab", sizeof(v->driver)); 222 strlcpy(v->card, "RadioTrack", sizeof(v->card)); 223 strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); 224 v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 225 return 0; 226 } 227 228 static int vidioc_g_tuner(struct file *file, void *priv, 229 struct v4l2_tuner *v) 230 { 231 struct rtrack *rt = video_drvdata(file); 232 233 if (v->index > 0) 234 return -EINVAL; 235 236 strlcpy(v->name, "FM", sizeof(v->name)); 237 v->type = V4L2_TUNER_RADIO; 238 v->rangelow = 87 * 16000; 239 v->rangehigh = 108 * 16000; 240 v->rxsubchans = V4L2_TUNER_SUB_MONO; 241 v->capability = V4L2_TUNER_CAP_LOW; 242 v->audmode = V4L2_TUNER_MODE_MONO; 243 v->signal = 0xffff * rt_getsigstr(rt); 244 return 0; 245 } 246 247 static int vidioc_s_tuner(struct file *file, void *priv, 248 struct v4l2_tuner *v) 249 { 250 return v->index ? -EINVAL : 0; 251 } 252 253 static int vidioc_s_frequency(struct file *file, void *priv, 254 struct v4l2_frequency *f) 255 { 256 struct rtrack *rt = video_drvdata(file); 257 258 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 259 return -EINVAL; 260 rt_setfreq(rt, f->frequency); 261 return 0; 262 } 263 264 static int vidioc_g_frequency(struct file *file, void *priv, 265 struct v4l2_frequency *f) 266 { 267 struct rtrack *rt = video_drvdata(file); 268 269 if (f->tuner != 0) 270 return -EINVAL; 271 f->type = V4L2_TUNER_RADIO; 272 f->frequency = rt->curfreq; 273 return 0; 274 } 275 276 static int vidioc_queryctrl(struct file *file, void *priv, 277 struct v4l2_queryctrl *qc) 278 { 279 switch (qc->id) { 280 case V4L2_CID_AUDIO_MUTE: 281 return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); 282 case V4L2_CID_AUDIO_VOLUME: 283 return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff); 284 } 285 return -EINVAL; 286 } 287 288 static int vidioc_g_ctrl(struct file *file, void *priv, 289 struct v4l2_control *ctrl) 290 { 291 struct rtrack *rt = video_drvdata(file); 292 293 switch (ctrl->id) { 294 case V4L2_CID_AUDIO_MUTE: 295 ctrl->value = rt->muted; 296 return 0; 297 case V4L2_CID_AUDIO_VOLUME: 298 ctrl->value = rt->curvol; 299 return 0; 300 } 301 return -EINVAL; 302 } 303 304 static int vidioc_s_ctrl(struct file *file, void *priv, 305 struct v4l2_control *ctrl) 306 { 307 struct rtrack *rt = video_drvdata(file); 308 309 switch (ctrl->id) { 310 case V4L2_CID_AUDIO_MUTE: 311 if (ctrl->value) 312 rt_mute(rt); 313 else 314 rt_setvol(rt, rt->curvol); 315 return 0; 316 case V4L2_CID_AUDIO_VOLUME: 317 rt_setvol(rt, ctrl->value); 318 return 0; 319 } 320 return -EINVAL; 321 } 322 323 static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) 324 { 325 *i = 0; 326 return 0; 327 } 328 329 static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) 330 { 331 return i ? -EINVAL : 0; 332 } 333 334 static int vidioc_g_audio(struct file *file, void *priv, 335 struct v4l2_audio *a) 336 { 337 a->index = 0; 338 strlcpy(a->name, "Radio", sizeof(a->name)); 339 a->capability = V4L2_AUDCAP_STEREO; 340 return 0; 341 } 342 343 static int vidioc_s_audio(struct file *file, void *priv, 344 struct v4l2_audio *a) 345 { 346 return a->index ? -EINVAL : 0; 347 } 348 349 static const struct v4l2_file_operations rtrack_fops = { 350 .owner = THIS_MODULE, 351 .unlocked_ioctl = video_ioctl2, 352 }; 353 354 static const struct v4l2_ioctl_ops rtrack_ioctl_ops = { 355 .vidioc_querycap = vidioc_querycap, 356 .vidioc_g_tuner = vidioc_g_tuner, 357 .vidioc_s_tuner = vidioc_s_tuner, 358 .vidioc_g_audio = vidioc_g_audio, 359 .vidioc_s_audio = vidioc_s_audio, 360 .vidioc_g_input = vidioc_g_input, 361 .vidioc_s_input = vidioc_s_input, 362 .vidioc_g_frequency = vidioc_g_frequency, 363 .vidioc_s_frequency = vidioc_s_frequency, 364 .vidioc_queryctrl = vidioc_queryctrl, 365 .vidioc_g_ctrl = vidioc_g_ctrl, 366 .vidioc_s_ctrl = vidioc_s_ctrl, 367 }; 368 369 static int __init rtrack_init(void) 370 { 371 struct rtrack *rt = &rtrack_card; 372 struct v4l2_device *v4l2_dev = &rt->v4l2_dev; 373 int res; 374 375 strlcpy(v4l2_dev->name, "rtrack", sizeof(v4l2_dev->name)); 376 rt->io = io; 377 378 if (rt->io == -1) { 379 v4l2_err(v4l2_dev, "you must set an I/O address with io=0x20f or 0x30f\n"); 380 return -EINVAL; 381 } 382 383 if (!request_region(rt->io, 2, "rtrack")) { 384 v4l2_err(v4l2_dev, "port 0x%x already in use\n", rt->io); 385 return -EBUSY; 386 } 387 388 res = v4l2_device_register(NULL, v4l2_dev); 389 if (res < 0) { 390 release_region(rt->io, 2); 391 v4l2_err(v4l2_dev, "could not register v4l2_device\n"); 392 return res; 393 } 394 395 strlcpy(rt->vdev.name, v4l2_dev->name, sizeof(rt->vdev.name)); 396 rt->vdev.v4l2_dev = v4l2_dev; 397 rt->vdev.fops = &rtrack_fops; 398 rt->vdev.ioctl_ops = &rtrack_ioctl_ops; 399 rt->vdev.release = video_device_release_empty; 400 video_set_drvdata(&rt->vdev, rt); 401 402 /* Set up the I/O locking */ 403 404 mutex_init(&rt->lock); 405 406 /* mute card - prevents noisy bootups */ 407 408 /* this ensures that the volume is all the way down */ 409 outb(0x48, rt->io); /* volume down but still "on" */ 410 msleep(2000); /* make sure it's totally down */ 411 outb(0xc0, rt->io); /* steady volume, mute card */ 412 413 if (video_register_device(&rt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { 414 v4l2_device_unregister(&rt->v4l2_dev); 415 release_region(rt->io, 2); 416 return -EINVAL; 417 } 418 v4l2_info(v4l2_dev, "AIMSlab RadioTrack/RadioReveal card driver.\n"); 419 420 return 0; 421 } 422 423 static void __exit rtrack_exit(void) 424 { 425 struct rtrack *rt = &rtrack_card; 426 427 video_unregister_device(&rt->vdev); 428 v4l2_device_unregister(&rt->v4l2_dev); 429 release_region(rt->io, 2); 430 } 431 432 module_init(rtrack_init); 433 module_exit(rtrack_exit); 434 435