11da177e4SLinus Torvalds /* SF16FMI radio driver for Linux radio support 21da177e4SLinus Torvalds * heavily based on rtrack driver... 31da177e4SLinus Torvalds * (c) 1997 M. Kirkwood 41da177e4SLinus Torvalds * (c) 1998 Petr Vandrovec, vandrove@vc.cvut.cz 51da177e4SLinus Torvalds * 6d9b01449SAlan Cox * Fitted to new interface by Alan Cox <alan@lxorguk.ukuu.org.uk> 71da177e4SLinus Torvalds * Made working and cleaned up functions <mikael.hedin@irf.se> 81da177e4SLinus Torvalds * Support for ISAPnP by Ladislav Michl <ladis@psi.cz> 91da177e4SLinus Torvalds * 101da177e4SLinus Torvalds * Notes on the hardware 111da177e4SLinus Torvalds * 121da177e4SLinus Torvalds * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); 131da177e4SLinus Torvalds * No volume control - only mute/unmute - you have to use line volume 141da177e4SLinus Torvalds * control on SB-part of SF16FMI 151da177e4SLinus Torvalds * 16a2ef73afSMauro Carvalho Chehab * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> 171da177e4SLinus Torvalds */ 181da177e4SLinus Torvalds 192cd885aaSAndrew Morton #include <linux/version.h> 201da177e4SLinus Torvalds #include <linux/kernel.h> /* __setup */ 211da177e4SLinus Torvalds #include <linux/module.h> /* Modules */ 221da177e4SLinus Torvalds #include <linux/init.h> /* Initdata */ 23fb911ee8SPeter Osterlund #include <linux/ioport.h> /* request_region */ 241da177e4SLinus Torvalds #include <linux/delay.h> /* udelay */ 251da177e4SLinus Torvalds #include <linux/isapnp.h> 263593cab5SIngo Molnar #include <linux/mutex.h> 27*c41269fdSHans Verkuil #include <linux/videodev2.h> /* kernel radio structs */ 28*c41269fdSHans Verkuil #include <linux/io.h> /* outb, outb_p */ 29*c41269fdSHans Verkuil #include <linux/uaccess.h> /* copy to/from user */ 30*c41269fdSHans Verkuil #include <media/v4l2-device.h> 31*c41269fdSHans Verkuil #include <media/v4l2-ioctl.h> 321da177e4SLinus Torvalds 33*c41269fdSHans Verkuil MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood"); 34*c41269fdSHans Verkuil MODULE_DESCRIPTION("A driver for the SF16MI radio."); 35*c41269fdSHans Verkuil MODULE_LICENSE("GPL"); 361da177e4SLinus Torvalds 371da177e4SLinus Torvalds static int io = -1; 381da177e4SLinus Torvalds static int radio_nr = -1; 39*c41269fdSHans Verkuil 40*c41269fdSHans Verkuil module_param(io, int, 0); 41*c41269fdSHans Verkuil MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)"); 42*c41269fdSHans Verkuil module_param(radio_nr, int, 0); 43*c41269fdSHans Verkuil 44*c41269fdSHans Verkuil #define RADIO_VERSION KERNEL_VERSION(0, 0, 2) 45*c41269fdSHans Verkuil 46*c41269fdSHans Verkuil struct fmi 47*c41269fdSHans Verkuil { 48*c41269fdSHans Verkuil struct v4l2_device v4l2_dev; 49*c41269fdSHans Verkuil struct video_device vdev; 50*c41269fdSHans Verkuil int io; 51*c41269fdSHans Verkuil int curvol; /* 1 or 0 */ 52*c41269fdSHans Verkuil unsigned long curfreq; /* freq in kHz */ 53*c41269fdSHans Verkuil __u32 flags; 54*c41269fdSHans Verkuil struct mutex lock; 55*c41269fdSHans Verkuil }; 56*c41269fdSHans Verkuil 57*c41269fdSHans Verkuil static struct fmi fmi_card; 58*c41269fdSHans Verkuil static struct pnp_dev *dev; 591da177e4SLinus Torvalds 601da177e4SLinus Torvalds /* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */ 611da177e4SLinus Torvalds /* It is only useful to give freq in intervall of 800 (=0.05Mhz), 621da177e4SLinus Torvalds * other bits will be truncated, e.g 92.7400016 -> 92.7, but 631da177e4SLinus Torvalds * 92.7400017 -> 92.75 641da177e4SLinus Torvalds */ 651da177e4SLinus Torvalds #define RSF16_ENCODE(x) ((x) / 800 + 214) 66*c41269fdSHans Verkuil #define RSF16_MINFREQ (87 * 16000) 67*c41269fdSHans Verkuil #define RSF16_MAXFREQ (108 * 16000) 681da177e4SLinus Torvalds 69*c41269fdSHans Verkuil static void outbits(int bits, unsigned int data, int io) 701da177e4SLinus Torvalds { 711da177e4SLinus Torvalds while (bits--) { 721da177e4SLinus Torvalds if (data & 1) { 73*c41269fdSHans Verkuil outb(5, io); 741da177e4SLinus Torvalds udelay(6); 75*c41269fdSHans Verkuil outb(7, io); 761da177e4SLinus Torvalds udelay(6); 771da177e4SLinus Torvalds } else { 78*c41269fdSHans Verkuil outb(1, io); 791da177e4SLinus Torvalds udelay(6); 80*c41269fdSHans Verkuil outb(3, io); 811da177e4SLinus Torvalds udelay(6); 821da177e4SLinus Torvalds } 831da177e4SLinus Torvalds data >>= 1; 841da177e4SLinus Torvalds } 851da177e4SLinus Torvalds } 861da177e4SLinus Torvalds 87*c41269fdSHans Verkuil static inline void fmi_mute(struct fmi *fmi) 881da177e4SLinus Torvalds { 89*c41269fdSHans Verkuil mutex_lock(&fmi->lock); 90*c41269fdSHans Verkuil outb(0x00, fmi->io); 91*c41269fdSHans Verkuil mutex_unlock(&fmi->lock); 921da177e4SLinus Torvalds } 931da177e4SLinus Torvalds 94*c41269fdSHans Verkuil static inline void fmi_unmute(struct fmi *fmi) 951da177e4SLinus Torvalds { 96*c41269fdSHans Verkuil mutex_lock(&fmi->lock); 97*c41269fdSHans Verkuil outb(0x08, fmi->io); 98*c41269fdSHans Verkuil mutex_unlock(&fmi->lock); 991da177e4SLinus Torvalds } 1001da177e4SLinus Torvalds 101*c41269fdSHans Verkuil static inline int fmi_setfreq(struct fmi *fmi, unsigned long freq) 1021da177e4SLinus Torvalds { 103*c41269fdSHans Verkuil mutex_lock(&fmi->lock); 104*c41269fdSHans Verkuil fmi->curfreq = freq; 1051da177e4SLinus Torvalds 106*c41269fdSHans Verkuil outbits(16, RSF16_ENCODE(freq), fmi->io); 107*c41269fdSHans Verkuil outbits(8, 0xC0, fmi->io); 1081da177e4SLinus Torvalds msleep(143); /* was schedule_timeout(HZ/7) */ 109*c41269fdSHans Verkuil mutex_unlock(&fmi->lock); 110*c41269fdSHans Verkuil if (fmi->curvol) 111*c41269fdSHans Verkuil fmi_unmute(fmi); 1121da177e4SLinus Torvalds return 0; 1131da177e4SLinus Torvalds } 1141da177e4SLinus Torvalds 115*c41269fdSHans Verkuil static inline int fmi_getsigstr(struct fmi *fmi) 1161da177e4SLinus Torvalds { 1171da177e4SLinus Torvalds int val; 1181da177e4SLinus Torvalds int res; 1191da177e4SLinus Torvalds 120*c41269fdSHans Verkuil mutex_lock(&fmi->lock); 121*c41269fdSHans Verkuil val = fmi->curvol ? 0x08 : 0x00; /* unmute/mute */ 122*c41269fdSHans Verkuil outb(val, fmi->io); 123*c41269fdSHans Verkuil outb(val | 0x10, fmi->io); 1241da177e4SLinus Torvalds msleep(143); /* was schedule_timeout(HZ/7) */ 125*c41269fdSHans Verkuil res = (int)inb(fmi->io + 1); 126*c41269fdSHans Verkuil outb(val, fmi->io); 1271da177e4SLinus Torvalds 128*c41269fdSHans Verkuil mutex_unlock(&fmi->lock); 1291da177e4SLinus Torvalds return (res & 2) ? 0 : 0xFFFF; 1301da177e4SLinus Torvalds } 1311da177e4SLinus Torvalds 132c123b867SDouglas Landgraf static int vidioc_querycap(struct file *file, void *priv, 133c123b867SDouglas Landgraf struct v4l2_capability *v) 1341da177e4SLinus Torvalds { 135a2ef73afSMauro Carvalho Chehab strlcpy(v->driver, "radio-sf16fmi", sizeof(v->driver)); 136a2ef73afSMauro Carvalho Chehab strlcpy(v->card, "SF16-FMx radio", sizeof(v->card)); 137*c41269fdSHans Verkuil strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); 138a2ef73afSMauro Carvalho Chehab v->version = RADIO_VERSION; 139*c41269fdSHans Verkuil v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 1401da177e4SLinus Torvalds return 0; 1411da177e4SLinus Torvalds } 142c123b867SDouglas Landgraf 143c123b867SDouglas Landgraf static int vidioc_g_tuner(struct file *file, void *priv, 144c123b867SDouglas Landgraf struct v4l2_tuner *v) 1451da177e4SLinus Torvalds { 1461da177e4SLinus Torvalds int mult; 147*c41269fdSHans Verkuil struct fmi *fmi = video_drvdata(file); 1481da177e4SLinus Torvalds 149a2ef73afSMauro Carvalho Chehab if (v->index > 0) 1501da177e4SLinus Torvalds return -EINVAL; 151a2ef73afSMauro Carvalho Chehab 152*c41269fdSHans Verkuil strlcpy(v->name, "FM", sizeof(v->name)); 153a2ef73afSMauro Carvalho Chehab v->type = V4L2_TUNER_RADIO; 154a2ef73afSMauro Carvalho Chehab mult = (fmi->flags & V4L2_TUNER_CAP_LOW) ? 1 : 1000; 1551da177e4SLinus Torvalds v->rangelow = RSF16_MINFREQ / mult; 1561da177e4SLinus Torvalds v->rangehigh = RSF16_MAXFREQ / mult; 157a2ef73afSMauro Carvalho Chehab v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_MODE_STEREO; 158fa38ad65SMauro Carvalho Chehab v->capability = fmi->flags & V4L2_TUNER_CAP_LOW; 159a2ef73afSMauro Carvalho Chehab v->audmode = V4L2_TUNER_MODE_STEREO; 1601da177e4SLinus Torvalds v->signal = fmi_getsigstr(fmi); 1611da177e4SLinus Torvalds return 0; 1621da177e4SLinus Torvalds } 163a2ef73afSMauro Carvalho Chehab 164c123b867SDouglas Landgraf static int vidioc_s_tuner(struct file *file, void *priv, 165c123b867SDouglas Landgraf struct v4l2_tuner *v) 166c123b867SDouglas Landgraf { 167*c41269fdSHans Verkuil return v->index ? -EINVAL : 0; 1681da177e4SLinus Torvalds } 169c123b867SDouglas Landgraf 170c123b867SDouglas Landgraf static int vidioc_s_frequency(struct file *file, void *priv, 171c123b867SDouglas Landgraf struct v4l2_frequency *f) 1721da177e4SLinus Torvalds { 173*c41269fdSHans Verkuil struct fmi *fmi = video_drvdata(file); 174a2ef73afSMauro Carvalho Chehab 175a2ef73afSMauro Carvalho Chehab if (!(fmi->flags & V4L2_TUNER_CAP_LOW)) 176a2ef73afSMauro Carvalho Chehab f->frequency *= 1000; 177a2ef73afSMauro Carvalho Chehab if (f->frequency < RSF16_MINFREQ || 178a2ef73afSMauro Carvalho Chehab f->frequency > RSF16_MAXFREQ) 1791da177e4SLinus Torvalds return -EINVAL; 180*c41269fdSHans Verkuil /* rounding in steps of 800 to match the freq 1811da177e4SLinus Torvalds that will be used */ 182*c41269fdSHans Verkuil fmi_setfreq(fmi, (f->frequency / 800) * 800); 1831da177e4SLinus Torvalds return 0; 1841da177e4SLinus Torvalds } 185c123b867SDouglas Landgraf 186c123b867SDouglas Landgraf static int vidioc_g_frequency(struct file *file, void *priv, 187c123b867SDouglas Landgraf struct v4l2_frequency *f) 1881da177e4SLinus Torvalds { 189*c41269fdSHans Verkuil struct fmi *fmi = video_drvdata(file); 190a2ef73afSMauro Carvalho Chehab 191a2ef73afSMauro Carvalho Chehab f->type = V4L2_TUNER_RADIO; 192a2ef73afSMauro Carvalho Chehab f->frequency = fmi->curfreq; 193a2ef73afSMauro Carvalho Chehab if (!(fmi->flags & V4L2_TUNER_CAP_LOW)) 194a2ef73afSMauro Carvalho Chehab f->frequency /= 1000; 1951da177e4SLinus Torvalds return 0; 1961da177e4SLinus Torvalds } 197c123b867SDouglas Landgraf 198c123b867SDouglas Landgraf static int vidioc_queryctrl(struct file *file, void *priv, 199c123b867SDouglas Landgraf struct v4l2_queryctrl *qc) 2001da177e4SLinus Torvalds { 201*c41269fdSHans Verkuil switch (qc->id) { 202*c41269fdSHans Verkuil case V4L2_CID_AUDIO_MUTE: 203*c41269fdSHans Verkuil return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); 204a2ef73afSMauro Carvalho Chehab } 2051da177e4SLinus Torvalds return -EINVAL; 2061da177e4SLinus Torvalds } 207c123b867SDouglas Landgraf 208c123b867SDouglas Landgraf static int vidioc_g_ctrl(struct file *file, void *priv, 209c123b867SDouglas Landgraf struct v4l2_control *ctrl) 2101da177e4SLinus Torvalds { 211*c41269fdSHans Verkuil struct fmi *fmi = video_drvdata(file); 212a2ef73afSMauro Carvalho Chehab 213a2ef73afSMauro Carvalho Chehab switch (ctrl->id) { 214a2ef73afSMauro Carvalho Chehab case V4L2_CID_AUDIO_MUTE: 215a2ef73afSMauro Carvalho Chehab ctrl->value = fmi->curvol; 216c123b867SDouglas Landgraf return 0; 217a2ef73afSMauro Carvalho Chehab } 218a2ef73afSMauro Carvalho Chehab return -EINVAL; 219a2ef73afSMauro Carvalho Chehab } 220c123b867SDouglas Landgraf 221c123b867SDouglas Landgraf static int vidioc_s_ctrl(struct file *file, void *priv, 222c123b867SDouglas Landgraf struct v4l2_control *ctrl) 223a2ef73afSMauro Carvalho Chehab { 224*c41269fdSHans Verkuil struct fmi *fmi = video_drvdata(file); 225a2ef73afSMauro Carvalho Chehab 226a2ef73afSMauro Carvalho Chehab switch (ctrl->id) { 227a2ef73afSMauro Carvalho Chehab case V4L2_CID_AUDIO_MUTE: 228a2ef73afSMauro Carvalho Chehab if (ctrl->value) 229*c41269fdSHans Verkuil fmi_mute(fmi); 230a2ef73afSMauro Carvalho Chehab else 231*c41269fdSHans Verkuil fmi_unmute(fmi); 232a2ef73afSMauro Carvalho Chehab fmi->curvol = ctrl->value; 233c123b867SDouglas Landgraf return 0; 234a2ef73afSMauro Carvalho Chehab } 235a2ef73afSMauro Carvalho Chehab return -EINVAL; 2361da177e4SLinus Torvalds } 237c123b867SDouglas Landgraf 238c123b867SDouglas Landgraf static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) 2391da177e4SLinus Torvalds { 240c123b867SDouglas Landgraf *i = 0; 241c123b867SDouglas Landgraf return 0; 242c123b867SDouglas Landgraf } 243c123b867SDouglas Landgraf 244c123b867SDouglas Landgraf static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) 245c123b867SDouglas Landgraf { 246*c41269fdSHans Verkuil return i ? -EINVAL : 0; 247*c41269fdSHans Verkuil } 248*c41269fdSHans Verkuil 249*c41269fdSHans Verkuil static int vidioc_g_audio(struct file *file, void *priv, 250*c41269fdSHans Verkuil struct v4l2_audio *a) 251*c41269fdSHans Verkuil { 252*c41269fdSHans Verkuil a->index = 0; 253*c41269fdSHans Verkuil strlcpy(a->name, "Radio", sizeof(a->name)); 254*c41269fdSHans Verkuil a->capability = V4L2_AUDCAP_STEREO; 255c123b867SDouglas Landgraf return 0; 256c123b867SDouglas Landgraf } 257c123b867SDouglas Landgraf 258c123b867SDouglas Landgraf static int vidioc_s_audio(struct file *file, void *priv, 259c123b867SDouglas Landgraf struct v4l2_audio *a) 260c123b867SDouglas Landgraf { 261*c41269fdSHans Verkuil return a->index ? -EINVAL : 0; 262*c41269fdSHans Verkuil } 263*c41269fdSHans Verkuil 264*c41269fdSHans Verkuil static int fmi_open(struct file *file) 265*c41269fdSHans Verkuil { 266c123b867SDouglas Landgraf return 0; 2671da177e4SLinus Torvalds } 2681da177e4SLinus Torvalds 269*c41269fdSHans Verkuil static int fmi_release(struct file *file) 2703ca685aaSHans Verkuil { 2713ca685aaSHans Verkuil return 0; 2723ca685aaSHans Verkuil } 2733ca685aaSHans Verkuil 274bec43661SHans Verkuil static const struct v4l2_file_operations fmi_fops = { 2751da177e4SLinus Torvalds .owner = THIS_MODULE, 276*c41269fdSHans Verkuil .open = fmi_open, 277*c41269fdSHans Verkuil .release = fmi_release, 278c123b867SDouglas Landgraf .ioctl = video_ioctl2, 2791da177e4SLinus Torvalds }; 2801da177e4SLinus Torvalds 281a399810cSHans Verkuil static const struct v4l2_ioctl_ops fmi_ioctl_ops = { 282c123b867SDouglas Landgraf .vidioc_querycap = vidioc_querycap, 283c123b867SDouglas Landgraf .vidioc_g_tuner = vidioc_g_tuner, 284c123b867SDouglas Landgraf .vidioc_s_tuner = vidioc_s_tuner, 285c123b867SDouglas Landgraf .vidioc_g_audio = vidioc_g_audio, 286c123b867SDouglas Landgraf .vidioc_s_audio = vidioc_s_audio, 287c123b867SDouglas Landgraf .vidioc_g_input = vidioc_g_input, 288c123b867SDouglas Landgraf .vidioc_s_input = vidioc_s_input, 289c123b867SDouglas Landgraf .vidioc_g_frequency = vidioc_g_frequency, 290c123b867SDouglas Landgraf .vidioc_s_frequency = vidioc_s_frequency, 291c123b867SDouglas Landgraf .vidioc_queryctrl = vidioc_queryctrl, 292c123b867SDouglas Landgraf .vidioc_g_ctrl = vidioc_g_ctrl, 293c123b867SDouglas Landgraf .vidioc_s_ctrl = vidioc_s_ctrl, 2941da177e4SLinus Torvalds }; 2951da177e4SLinus Torvalds 2961da177e4SLinus Torvalds /* ladis: this is my card. does any other types exist? */ 2971da177e4SLinus Torvalds static struct isapnp_device_id id_table[] __devinitdata = { 2981da177e4SLinus Torvalds { ISAPNP_ANY_ID, ISAPNP_ANY_ID, 2991da177e4SLinus Torvalds ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0}, 3001da177e4SLinus Torvalds { ISAPNP_CARD_END, }, 3011da177e4SLinus Torvalds }; 3021da177e4SLinus Torvalds 3031da177e4SLinus Torvalds MODULE_DEVICE_TABLE(isapnp, id_table); 3041da177e4SLinus Torvalds 305a999337bSRandy Dunlap static int __init isapnp_fmi_probe(void) 3061da177e4SLinus Torvalds { 3071da177e4SLinus Torvalds int i = 0; 3081da177e4SLinus Torvalds 3091da177e4SLinus Torvalds while (id_table[i].card_vendor != 0 && dev == NULL) { 3101da177e4SLinus Torvalds dev = pnp_find_dev(NULL, id_table[i].vendor, 3111da177e4SLinus Torvalds id_table[i].function, NULL); 3121da177e4SLinus Torvalds i++; 3131da177e4SLinus Torvalds } 3141da177e4SLinus Torvalds 3151da177e4SLinus Torvalds if (!dev) 3161da177e4SLinus Torvalds return -ENODEV; 3171da177e4SLinus Torvalds if (pnp_device_attach(dev) < 0) 3181da177e4SLinus Torvalds return -EAGAIN; 3191da177e4SLinus Torvalds if (pnp_activate_dev(dev) < 0) { 320*c41269fdSHans Verkuil printk(KERN_ERR "radio-sf16fmi: PnP configure failed (out of resources?)\n"); 3211da177e4SLinus Torvalds pnp_device_detach(dev); 3221da177e4SLinus Torvalds return -ENOMEM; 3231da177e4SLinus Torvalds } 3241da177e4SLinus Torvalds if (!pnp_port_valid(dev, 0)) { 3251da177e4SLinus Torvalds pnp_device_detach(dev); 3261da177e4SLinus Torvalds return -ENODEV; 3271da177e4SLinus Torvalds } 3281da177e4SLinus Torvalds 3291da177e4SLinus Torvalds i = pnp_port_start(dev, 0); 330*c41269fdSHans Verkuil printk(KERN_INFO "radio-sf16fmi: PnP reports card at %#x\n", i); 3311da177e4SLinus Torvalds 3321da177e4SLinus Torvalds return i; 3331da177e4SLinus Torvalds } 3341da177e4SLinus Torvalds 3351da177e4SLinus Torvalds static int __init fmi_init(void) 3361da177e4SLinus Torvalds { 337*c41269fdSHans Verkuil struct fmi *fmi = &fmi_card; 338*c41269fdSHans Verkuil struct v4l2_device *v4l2_dev = &fmi->v4l2_dev; 339*c41269fdSHans Verkuil int res; 340*c41269fdSHans Verkuil 3411da177e4SLinus Torvalds if (io < 0) 3421da177e4SLinus Torvalds io = isapnp_fmi_probe(); 343*c41269fdSHans Verkuil strlcpy(v4l2_dev->name, "sf16fmi", sizeof(v4l2_dev->name)); 344*c41269fdSHans Verkuil fmi->io = io; 345*c41269fdSHans Verkuil if (fmi->io < 0) { 346*c41269fdSHans Verkuil v4l2_err(v4l2_dev, "No PnP card found.\n"); 347*c41269fdSHans Verkuil return fmi->io; 3481da177e4SLinus Torvalds } 3491da177e4SLinus Torvalds if (!request_region(io, 2, "radio-sf16fmi")) { 350*c41269fdSHans Verkuil v4l2_err(v4l2_dev, "port 0x%x already in use\n", fmi->io); 351e08a8c9dSMauro Carvalho Chehab pnp_device_detach(dev); 3521da177e4SLinus Torvalds return -EBUSY; 3531da177e4SLinus Torvalds } 3541da177e4SLinus Torvalds 355*c41269fdSHans Verkuil res = v4l2_device_register(NULL, v4l2_dev); 356*c41269fdSHans Verkuil if (res < 0) { 357*c41269fdSHans Verkuil release_region(fmi->io, 2); 358*c41269fdSHans Verkuil pnp_device_detach(dev); 359*c41269fdSHans Verkuil v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); 360*c41269fdSHans Verkuil return res; 361*c41269fdSHans Verkuil } 3621da177e4SLinus Torvalds 363*c41269fdSHans Verkuil fmi->flags = V4L2_TUNER_CAP_LOW; 364*c41269fdSHans Verkuil strlcpy(fmi->vdev.name, v4l2_dev->name, sizeof(fmi->vdev.name)); 365*c41269fdSHans Verkuil fmi->vdev.v4l2_dev = v4l2_dev; 366*c41269fdSHans Verkuil fmi->vdev.fops = &fmi_fops; 367*c41269fdSHans Verkuil fmi->vdev.ioctl_ops = &fmi_ioctl_ops; 368*c41269fdSHans Verkuil fmi->vdev.release = video_device_release_empty; 369*c41269fdSHans Verkuil video_set_drvdata(&fmi->vdev, fmi); 3701da177e4SLinus Torvalds 371*c41269fdSHans Verkuil mutex_init(&fmi->lock); 372*c41269fdSHans Verkuil 373*c41269fdSHans Verkuil if (video_register_device(&fmi->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { 374*c41269fdSHans Verkuil v4l2_device_unregister(v4l2_dev); 375*c41269fdSHans Verkuil release_region(fmi->io, 2); 376*c41269fdSHans Verkuil pnp_device_detach(dev); 3771da177e4SLinus Torvalds return -EINVAL; 3781da177e4SLinus Torvalds } 3791da177e4SLinus Torvalds 380*c41269fdSHans Verkuil v4l2_info(v4l2_dev, "card driver at 0x%x\n", fmi->io); 3811da177e4SLinus Torvalds /* mute card - prevents noisy bootups */ 382*c41269fdSHans Verkuil fmi_mute(fmi); 3831da177e4SLinus Torvalds return 0; 3841da177e4SLinus Torvalds } 3851da177e4SLinus Torvalds 386*c41269fdSHans Verkuil static void __exit fmi_exit(void) 3871da177e4SLinus Torvalds { 388*c41269fdSHans Verkuil struct fmi *fmi = &fmi_card; 389*c41269fdSHans Verkuil 390*c41269fdSHans Verkuil video_unregister_device(&fmi->vdev); 391*c41269fdSHans Verkuil v4l2_device_unregister(&fmi->v4l2_dev); 392*c41269fdSHans Verkuil release_region(fmi->io, 2); 3931da177e4SLinus Torvalds if (dev) 3941da177e4SLinus Torvalds pnp_device_detach(dev); 3951da177e4SLinus Torvalds } 3961da177e4SLinus Torvalds 3971da177e4SLinus Torvalds module_init(fmi_init); 398*c41269fdSHans Verkuil module_exit(fmi_exit); 399