11da177e4SLinus Torvalds /* RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff 21da177e4SLinus Torvalds * 31da177e4SLinus Torvalds * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood 4d9b01449SAlan Cox * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> 51da177e4SLinus Torvalds * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> 61da177e4SLinus Torvalds * 71da177e4SLinus Torvalds * TODO: Allow for more than one of these foolish entities :-) 81da177e4SLinus Torvalds * 9f8c559f8SMauro Carvalho Chehab * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> 101da177e4SLinus Torvalds */ 111da177e4SLinus Torvalds 121da177e4SLinus Torvalds #include <linux/module.h> /* Modules */ 131da177e4SLinus Torvalds #include <linux/init.h> /* Initdata */ 14fb911ee8SPeter Osterlund #include <linux/ioport.h> /* request_region */ 151da177e4SLinus Torvalds #include <linux/delay.h> /* udelay */ 16f8c559f8SMauro Carvalho Chehab #include <linux/videodev2.h> /* kernel radio structs */ 17922c78e9SHans Verkuil #include <linux/mutex.h> 18f8c559f8SMauro Carvalho Chehab #include <linux/version.h> /* for KERNEL_VERSION MACRO */ 19922c78e9SHans Verkuil #include <linux/io.h> /* outb, outb_p */ 20922c78e9SHans Verkuil #include <linux/uaccess.h> /* copy to/from user */ 21922c78e9SHans Verkuil #include <media/v4l2-device.h> 22922c78e9SHans Verkuil #include <media/v4l2-ioctl.h> 23f8c559f8SMauro Carvalho Chehab 24922c78e9SHans Verkuil MODULE_AUTHOR("Ben Pfaff"); 25922c78e9SHans Verkuil MODULE_DESCRIPTION("A driver for the RadioTrack II radio card."); 26922c78e9SHans Verkuil MODULE_LICENSE("GPL"); 27f8c559f8SMauro Carvalho Chehab 281da177e4SLinus Torvalds #ifndef CONFIG_RADIO_RTRACK2_PORT 291da177e4SLinus Torvalds #define CONFIG_RADIO_RTRACK2_PORT -1 301da177e4SLinus Torvalds #endif 311da177e4SLinus Torvalds 321da177e4SLinus Torvalds static int io = CONFIG_RADIO_RTRACK2_PORT; 331da177e4SLinus Torvalds static int radio_nr = -1; 341da177e4SLinus Torvalds 35922c78e9SHans Verkuil module_param(io, int, 0); 36922c78e9SHans Verkuil MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20c or 0x30c)"); 37922c78e9SHans Verkuil module_param(radio_nr, int, 0); 38922c78e9SHans Verkuil 39922c78e9SHans Verkuil #define RADIO_VERSION KERNEL_VERSION(0, 0, 2) 40922c78e9SHans Verkuil 41922c78e9SHans Verkuil struct rtrack2 421da177e4SLinus Torvalds { 43922c78e9SHans Verkuil struct v4l2_device v4l2_dev; 44922c78e9SHans Verkuil struct video_device vdev; 45922c78e9SHans Verkuil int io; 461da177e4SLinus Torvalds unsigned long curfreq; 471da177e4SLinus Torvalds int muted; 48922c78e9SHans Verkuil struct mutex lock; 491da177e4SLinus Torvalds }; 501da177e4SLinus Torvalds 51922c78e9SHans Verkuil static struct rtrack2 rtrack2_card; 52922c78e9SHans Verkuil 531da177e4SLinus Torvalds 541da177e4SLinus Torvalds /* local things */ 551da177e4SLinus Torvalds 56922c78e9SHans Verkuil static void rt_mute(struct rtrack2 *dev) 571da177e4SLinus Torvalds { 581da177e4SLinus Torvalds if (dev->muted) 591da177e4SLinus Torvalds return; 60922c78e9SHans Verkuil mutex_lock(&dev->lock); 61922c78e9SHans Verkuil outb(1, dev->io); 62922c78e9SHans Verkuil mutex_unlock(&dev->lock); 63922c78e9SHans Verkuil mutex_unlock(&dev->lock); 641da177e4SLinus Torvalds dev->muted = 1; 651da177e4SLinus Torvalds } 661da177e4SLinus Torvalds 67922c78e9SHans Verkuil static void rt_unmute(struct rtrack2 *dev) 681da177e4SLinus Torvalds { 691da177e4SLinus Torvalds if(dev->muted == 0) 701da177e4SLinus Torvalds return; 71922c78e9SHans Verkuil mutex_lock(&dev->lock); 72922c78e9SHans Verkuil outb(0, dev->io); 73922c78e9SHans Verkuil mutex_unlock(&dev->lock); 741da177e4SLinus Torvalds dev->muted = 0; 751da177e4SLinus Torvalds } 761da177e4SLinus Torvalds 77922c78e9SHans Verkuil static void zero(struct rtrack2 *dev) 781da177e4SLinus Torvalds { 79922c78e9SHans Verkuil outb_p(1, dev->io); 80922c78e9SHans Verkuil outb_p(3, dev->io); 81922c78e9SHans Verkuil outb_p(1, dev->io); 821da177e4SLinus Torvalds } 831da177e4SLinus Torvalds 84922c78e9SHans Verkuil static void one(struct rtrack2 *dev) 851da177e4SLinus Torvalds { 86922c78e9SHans Verkuil outb_p(5, dev->io); 87922c78e9SHans Verkuil outb_p(7, dev->io); 88922c78e9SHans Verkuil outb_p(5, dev->io); 891da177e4SLinus Torvalds } 901da177e4SLinus Torvalds 91922c78e9SHans Verkuil static int rt_setfreq(struct rtrack2 *dev, unsigned long freq) 921da177e4SLinus Torvalds { 931da177e4SLinus Torvalds int i; 941da177e4SLinus Torvalds 95922c78e9SHans Verkuil mutex_lock(&dev->lock); 96922c78e9SHans Verkuil dev->curfreq = freq; 971da177e4SLinus Torvalds freq = freq / 200 + 856; 981da177e4SLinus Torvalds 99922c78e9SHans Verkuil outb_p(0xc8, dev->io); 100922c78e9SHans Verkuil outb_p(0xc9, dev->io); 101922c78e9SHans Verkuil outb_p(0xc9, dev->io); 1021da177e4SLinus Torvalds 1031da177e4SLinus Torvalds for (i = 0; i < 10; i++) 104922c78e9SHans Verkuil zero(dev); 1051da177e4SLinus Torvalds 1061da177e4SLinus Torvalds for (i = 14; i >= 0; i--) 1071da177e4SLinus Torvalds if (freq & (1 << i)) 108922c78e9SHans Verkuil one(dev); 1091da177e4SLinus Torvalds else 110922c78e9SHans Verkuil zero(dev); 1111da177e4SLinus Torvalds 112922c78e9SHans Verkuil outb_p(0xc8, dev->io); 1131da177e4SLinus Torvalds if (!dev->muted) 114922c78e9SHans Verkuil outb_p(0, dev->io); 1151da177e4SLinus Torvalds 116922c78e9SHans Verkuil mutex_unlock(&dev->lock); 1171da177e4SLinus Torvalds return 0; 1181da177e4SLinus Torvalds } 1191da177e4SLinus Torvalds 12025f30389SDouglas Landgraf static int vidioc_querycap(struct file *file, void *priv, 12125f30389SDouglas Landgraf struct v4l2_capability *v) 12225f30389SDouglas Landgraf { 12325f30389SDouglas Landgraf strlcpy(v->driver, "radio-rtrack2", sizeof(v->driver)); 12425f30389SDouglas Landgraf strlcpy(v->card, "RadioTrack II", sizeof(v->card)); 125922c78e9SHans Verkuil strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); 12625f30389SDouglas Landgraf v->version = RADIO_VERSION; 127922c78e9SHans Verkuil v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 12825f30389SDouglas Landgraf return 0; 12925f30389SDouglas Landgraf } 13025f30389SDouglas Landgraf 13125f30389SDouglas Landgraf static int vidioc_s_tuner(struct file *file, void *priv, 13225f30389SDouglas Landgraf struct v4l2_tuner *v) 13325f30389SDouglas Landgraf { 134922c78e9SHans Verkuil return v->index ? -EINVAL : 0; 13525f30389SDouglas Landgraf } 13625f30389SDouglas Landgraf 137922c78e9SHans Verkuil static int rt_getsigstr(struct rtrack2 *dev) 1381da177e4SLinus Torvalds { 139922c78e9SHans Verkuil int sig = 1; 140922c78e9SHans Verkuil 141922c78e9SHans Verkuil mutex_lock(&dev->lock); 142922c78e9SHans Verkuil if (inb(dev->io) & 2) /* bit set = no signal present */ 143922c78e9SHans Verkuil sig = 0; 144922c78e9SHans Verkuil mutex_unlock(&dev->lock); 145922c78e9SHans Verkuil return sig; 1461da177e4SLinus Torvalds } 1471da177e4SLinus Torvalds 14825f30389SDouglas Landgraf static int vidioc_g_tuner(struct file *file, void *priv, 14925f30389SDouglas Landgraf struct v4l2_tuner *v) 1501da177e4SLinus Torvalds { 151922c78e9SHans Verkuil struct rtrack2 *rt = video_drvdata(file); 1521da177e4SLinus Torvalds 153f8c559f8SMauro Carvalho Chehab if (v->index > 0) 1541da177e4SLinus Torvalds return -EINVAL; 155f8c559f8SMauro Carvalho Chehab 156922c78e9SHans Verkuil strlcpy(v->name, "FM", sizeof(v->name)); 157f8c559f8SMauro Carvalho Chehab v->type = V4L2_TUNER_RADIO; 158922c78e9SHans Verkuil v->rangelow = 88 * 16000; 159922c78e9SHans Verkuil v->rangehigh = 108 * 16000; 160f8c559f8SMauro Carvalho Chehab v->rxsubchans = V4L2_TUNER_SUB_MONO; 161f8c559f8SMauro Carvalho Chehab v->capability = V4L2_TUNER_CAP_LOW; 162f8c559f8SMauro Carvalho Chehab v->audmode = V4L2_TUNER_MODE_MONO; 163f8c559f8SMauro Carvalho Chehab v->signal = 0xFFFF * rt_getsigstr(rt); 1641da177e4SLinus Torvalds return 0; 1651da177e4SLinus Torvalds } 166f8c559f8SMauro Carvalho Chehab 16725f30389SDouglas Landgraf static int vidioc_s_frequency(struct file *file, void *priv, 16825f30389SDouglas Landgraf struct v4l2_frequency *f) 1691da177e4SLinus Torvalds { 170922c78e9SHans Verkuil struct rtrack2 *rt = video_drvdata(file); 171f8c559f8SMauro Carvalho Chehab 172922c78e9SHans Verkuil rt_setfreq(rt, f->frequency); 1731da177e4SLinus Torvalds return 0; 1741da177e4SLinus Torvalds } 17525f30389SDouglas Landgraf 17625f30389SDouglas Landgraf static int vidioc_g_frequency(struct file *file, void *priv, 17725f30389SDouglas Landgraf struct v4l2_frequency *f) 1781da177e4SLinus Torvalds { 179922c78e9SHans Verkuil struct rtrack2 *rt = video_drvdata(file); 180f8c559f8SMauro Carvalho Chehab 181f8c559f8SMauro Carvalho Chehab f->type = V4L2_TUNER_RADIO; 182f8c559f8SMauro Carvalho Chehab f->frequency = rt->curfreq; 1831da177e4SLinus Torvalds return 0; 1841da177e4SLinus Torvalds } 18525f30389SDouglas Landgraf 18625f30389SDouglas Landgraf static int vidioc_queryctrl(struct file *file, void *priv, 18725f30389SDouglas Landgraf struct v4l2_queryctrl *qc) 1881da177e4SLinus Torvalds { 189922c78e9SHans Verkuil switch (qc->id) { 190922c78e9SHans Verkuil case V4L2_CID_AUDIO_MUTE: 191922c78e9SHans Verkuil return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); 192922c78e9SHans Verkuil case V4L2_CID_AUDIO_VOLUME: 193922c78e9SHans Verkuil return v4l2_ctrl_query_fill(qc, 0, 65535, 65535, 65535); 194f8c559f8SMauro Carvalho Chehab } 1951da177e4SLinus Torvalds return -EINVAL; 196f8c559f8SMauro Carvalho Chehab } 19725f30389SDouglas Landgraf 19825f30389SDouglas Landgraf static int vidioc_g_ctrl(struct file *file, void *priv, 19925f30389SDouglas Landgraf struct v4l2_control *ctrl) 200f8c559f8SMauro Carvalho Chehab { 201922c78e9SHans Verkuil struct rtrack2 *rt = video_drvdata(file); 2021da177e4SLinus Torvalds 203f8c559f8SMauro Carvalho Chehab switch (ctrl->id) { 204f8c559f8SMauro Carvalho Chehab case V4L2_CID_AUDIO_MUTE: 205f8c559f8SMauro Carvalho Chehab ctrl->value = rt->muted; 20625f30389SDouglas Landgraf return 0; 207f8c559f8SMauro Carvalho Chehab case V4L2_CID_AUDIO_VOLUME: 208f8c559f8SMauro Carvalho Chehab if (rt->muted) 209f8c559f8SMauro Carvalho Chehab ctrl->value = 0; 2101da177e4SLinus Torvalds else 211f8c559f8SMauro Carvalho Chehab ctrl->value = 65535; 21225f30389SDouglas Landgraf return 0; 213f8c559f8SMauro Carvalho Chehab } 214f8c559f8SMauro Carvalho Chehab return -EINVAL; 215f8c559f8SMauro Carvalho Chehab } 21625f30389SDouglas Landgraf 21725f30389SDouglas Landgraf static int vidioc_s_ctrl(struct file *file, void *priv, 21825f30389SDouglas Landgraf struct v4l2_control *ctrl) 219f8c559f8SMauro Carvalho Chehab { 220922c78e9SHans Verkuil struct rtrack2 *rt = video_drvdata(file); 2211da177e4SLinus Torvalds 222f8c559f8SMauro Carvalho Chehab switch (ctrl->id) { 223f8c559f8SMauro Carvalho Chehab case V4L2_CID_AUDIO_MUTE: 22425f30389SDouglas Landgraf if (ctrl->value) 225f8c559f8SMauro Carvalho Chehab rt_mute(rt); 22625f30389SDouglas Landgraf else 227f8c559f8SMauro Carvalho Chehab rt_unmute(rt); 22825f30389SDouglas Landgraf return 0; 229f8c559f8SMauro Carvalho Chehab case V4L2_CID_AUDIO_VOLUME: 23025f30389SDouglas Landgraf if (ctrl->value) 231f8c559f8SMauro Carvalho Chehab rt_unmute(rt); 23225f30389SDouglas Landgraf else 233f8c559f8SMauro Carvalho Chehab rt_mute(rt); 23425f30389SDouglas Landgraf return 0; 235f8c559f8SMauro Carvalho Chehab } 236f8c559f8SMauro Carvalho Chehab return -EINVAL; 2371da177e4SLinus Torvalds } 2381da177e4SLinus Torvalds 2398b811cf0SDouglas Landgraf static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) 2408b811cf0SDouglas Landgraf { 2418b811cf0SDouglas Landgraf *i = 0; 2428b811cf0SDouglas Landgraf return 0; 2438b811cf0SDouglas Landgraf } 2448b811cf0SDouglas Landgraf 2458b811cf0SDouglas Landgraf static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) 2468b811cf0SDouglas Landgraf { 247922c78e9SHans Verkuil return i ? -EINVAL : 0; 248922c78e9SHans Verkuil } 249922c78e9SHans Verkuil 250922c78e9SHans Verkuil static int vidioc_g_audio(struct file *file, void *priv, 251922c78e9SHans Verkuil struct v4l2_audio *a) 252922c78e9SHans Verkuil { 253922c78e9SHans Verkuil a->index = 0; 254922c78e9SHans Verkuil strlcpy(a->name, "Radio", sizeof(a->name)); 255922c78e9SHans Verkuil a->capability = V4L2_AUDCAP_STEREO; 2568b811cf0SDouglas Landgraf return 0; 2578b811cf0SDouglas Landgraf } 2588b811cf0SDouglas Landgraf 2598b811cf0SDouglas Landgraf static int vidioc_s_audio(struct file *file, void *priv, 2608b811cf0SDouglas Landgraf struct v4l2_audio *a) 2618b811cf0SDouglas Landgraf { 262922c78e9SHans Verkuil return a->index ? -EINVAL : 0; 263922c78e9SHans Verkuil } 264922c78e9SHans Verkuil 265922c78e9SHans Verkuil static int rtrack2_open(struct file *file) 266922c78e9SHans Verkuil { 2678b811cf0SDouglas Landgraf return 0; 2688b811cf0SDouglas Landgraf } 2698b811cf0SDouglas Landgraf 270922c78e9SHans Verkuil static int rtrack2_release(struct file *file) 2713ca685aaSHans Verkuil { 2723ca685aaSHans Verkuil return 0; 2733ca685aaSHans Verkuil } 2743ca685aaSHans Verkuil 275bec43661SHans Verkuil static const struct v4l2_file_operations rtrack2_fops = { 2761da177e4SLinus Torvalds .owner = THIS_MODULE, 277922c78e9SHans Verkuil .open = rtrack2_open, 278922c78e9SHans Verkuil .release = rtrack2_release, 27925f30389SDouglas Landgraf .ioctl = video_ioctl2, 2801da177e4SLinus Torvalds }; 2811da177e4SLinus Torvalds 282a399810cSHans Verkuil static const struct v4l2_ioctl_ops rtrack2_ioctl_ops = { 28325f30389SDouglas Landgraf .vidioc_querycap = vidioc_querycap, 28425f30389SDouglas Landgraf .vidioc_g_tuner = vidioc_g_tuner, 28525f30389SDouglas Landgraf .vidioc_s_tuner = vidioc_s_tuner, 28625f30389SDouglas Landgraf .vidioc_g_frequency = vidioc_g_frequency, 28725f30389SDouglas Landgraf .vidioc_s_frequency = vidioc_s_frequency, 28825f30389SDouglas Landgraf .vidioc_queryctrl = vidioc_queryctrl, 28925f30389SDouglas Landgraf .vidioc_g_ctrl = vidioc_g_ctrl, 29025f30389SDouglas Landgraf .vidioc_s_ctrl = vidioc_s_ctrl, 2918b811cf0SDouglas Landgraf .vidioc_g_audio = vidioc_g_audio, 2928b811cf0SDouglas Landgraf .vidioc_s_audio = vidioc_s_audio, 2938b811cf0SDouglas Landgraf .vidioc_g_input = vidioc_g_input, 2948b811cf0SDouglas Landgraf .vidioc_s_input = vidioc_s_input, 2951da177e4SLinus Torvalds }; 2961da177e4SLinus Torvalds 2971da177e4SLinus Torvalds static int __init rtrack2_init(void) 2981da177e4SLinus Torvalds { 299922c78e9SHans Verkuil struct rtrack2 *dev = &rtrack2_card; 300922c78e9SHans Verkuil struct v4l2_device *v4l2_dev = &dev->v4l2_dev; 301922c78e9SHans Verkuil int res; 302922c78e9SHans Verkuil 303922c78e9SHans Verkuil strlcpy(v4l2_dev->name, "rtrack2", sizeof(v4l2_dev->name)); 304922c78e9SHans Verkuil dev->io = io; 305922c78e9SHans Verkuil if (dev->io == -1) { 306922c78e9SHans Verkuil v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or io=0x30c\n"); 3071da177e4SLinus Torvalds return -EINVAL; 3081da177e4SLinus Torvalds } 309922c78e9SHans Verkuil if (!request_region(dev->io, 4, "rtrack2")) { 310922c78e9SHans Verkuil v4l2_err(v4l2_dev, "port 0x%x already in use\n", dev->io); 3111da177e4SLinus Torvalds return -EBUSY; 3121da177e4SLinus Torvalds } 3131da177e4SLinus Torvalds 314922c78e9SHans Verkuil res = v4l2_device_register(NULL, v4l2_dev); 315922c78e9SHans Verkuil if (res < 0) { 316922c78e9SHans Verkuil release_region(dev->io, 4); 317922c78e9SHans Verkuil v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); 318922c78e9SHans Verkuil return res; 319922c78e9SHans Verkuil } 3201da177e4SLinus Torvalds 321922c78e9SHans Verkuil strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); 322922c78e9SHans Verkuil dev->vdev.v4l2_dev = v4l2_dev; 323922c78e9SHans Verkuil dev->vdev.fops = &rtrack2_fops; 324922c78e9SHans Verkuil dev->vdev.ioctl_ops = &rtrack2_ioctl_ops; 325922c78e9SHans Verkuil dev->vdev.release = video_device_release_empty; 326922c78e9SHans Verkuil video_set_drvdata(&dev->vdev, dev); 327922c78e9SHans Verkuil 328922c78e9SHans Verkuil mutex_init(&dev->lock); 329922c78e9SHans Verkuil if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { 330922c78e9SHans Verkuil v4l2_device_unregister(v4l2_dev); 331922c78e9SHans Verkuil release_region(dev->io, 4); 3321da177e4SLinus Torvalds return -EINVAL; 3331da177e4SLinus Torvalds } 3341da177e4SLinus Torvalds 335922c78e9SHans Verkuil v4l2_info(v4l2_dev, "AIMSlab Radiotrack II card driver.\n"); 3361da177e4SLinus Torvalds 3371da177e4SLinus Torvalds /* mute card - prevents noisy bootups */ 338922c78e9SHans Verkuil outb(1, dev->io); 339922c78e9SHans Verkuil dev->muted = 1; 3401da177e4SLinus Torvalds 3411da177e4SLinus Torvalds return 0; 3421da177e4SLinus Torvalds } 3431da177e4SLinus Torvalds 344922c78e9SHans Verkuil static void __exit rtrack2_exit(void) 3451da177e4SLinus Torvalds { 346922c78e9SHans Verkuil struct rtrack2 *dev = &rtrack2_card; 347922c78e9SHans Verkuil 348922c78e9SHans Verkuil video_unregister_device(&dev->vdev); 349922c78e9SHans Verkuil v4l2_device_unregister(&dev->v4l2_dev); 350922c78e9SHans Verkuil release_region(dev->io, 4); 3511da177e4SLinus Torvalds } 3521da177e4SLinus Torvalds 3531da177e4SLinus Torvalds module_init(rtrack2_init); 354922c78e9SHans Verkuil module_exit(rtrack2_exit); 355