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 * 61da177e4SLinus Torvalds * Fitted to new interface by Alan Cox <alan.cox@linux.org> 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 */ 25a2ef73afSMauro Carvalho Chehab #include <linux/videodev2.h> /* kernel radio structs */ 265e87efa3SMauro Carvalho Chehab #include <media/v4l2-common.h> 271da177e4SLinus Torvalds #include <linux/isapnp.h> 281da177e4SLinus Torvalds #include <asm/io.h> /* outb, outb_p */ 291da177e4SLinus Torvalds #include <asm/uaccess.h> /* copy to/from user */ 303593cab5SIngo Molnar #include <linux/mutex.h> 311da177e4SLinus Torvalds 32a2ef73afSMauro Carvalho Chehab #define RADIO_VERSION KERNEL_VERSION(0,0,2) 33a2ef73afSMauro Carvalho Chehab 34a2ef73afSMauro Carvalho Chehab static struct v4l2_queryctrl radio_qctrl[] = { 35a2ef73afSMauro Carvalho Chehab { 36a2ef73afSMauro Carvalho Chehab .id = V4L2_CID_AUDIO_MUTE, 37a2ef73afSMauro Carvalho Chehab .name = "Mute", 38a2ef73afSMauro Carvalho Chehab .minimum = 0, 39a2ef73afSMauro Carvalho Chehab .maximum = 1, 40a2ef73afSMauro Carvalho Chehab .default_value = 1, 41a2ef73afSMauro Carvalho Chehab .type = V4L2_CTRL_TYPE_BOOLEAN, 42a2ef73afSMauro Carvalho Chehab } 43a2ef73afSMauro Carvalho Chehab }; 44a2ef73afSMauro Carvalho Chehab 451da177e4SLinus Torvalds struct fmi_device 461da177e4SLinus Torvalds { 471da177e4SLinus Torvalds int port; 481da177e4SLinus Torvalds int curvol; /* 1 or 0 */ 491da177e4SLinus Torvalds unsigned long curfreq; /* freq in kHz */ 501da177e4SLinus Torvalds __u32 flags; 511da177e4SLinus Torvalds }; 521da177e4SLinus Torvalds 531da177e4SLinus Torvalds static int io = -1; 541da177e4SLinus Torvalds static int radio_nr = -1; 551da177e4SLinus Torvalds static struct pnp_dev *dev = NULL; 563593cab5SIngo Molnar static struct mutex lock; 571da177e4SLinus Torvalds 581da177e4SLinus Torvalds /* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */ 591da177e4SLinus Torvalds /* It is only useful to give freq in intervall of 800 (=0.05Mhz), 601da177e4SLinus Torvalds * other bits will be truncated, e.g 92.7400016 -> 92.7, but 611da177e4SLinus Torvalds * 92.7400017 -> 92.75 621da177e4SLinus Torvalds */ 631da177e4SLinus Torvalds #define RSF16_ENCODE(x) ((x)/800+214) 641da177e4SLinus Torvalds #define RSF16_MINFREQ 87*16000 651da177e4SLinus Torvalds #define RSF16_MAXFREQ 108*16000 661da177e4SLinus Torvalds 671da177e4SLinus Torvalds static void outbits(int bits, unsigned int data, int port) 681da177e4SLinus Torvalds { 691da177e4SLinus Torvalds while(bits--) { 701da177e4SLinus Torvalds if(data & 1) { 711da177e4SLinus Torvalds outb(5, port); 721da177e4SLinus Torvalds udelay(6); 731da177e4SLinus Torvalds outb(7, port); 741da177e4SLinus Torvalds udelay(6); 751da177e4SLinus Torvalds } else { 761da177e4SLinus Torvalds outb(1, port); 771da177e4SLinus Torvalds udelay(6); 781da177e4SLinus Torvalds outb(3, port); 791da177e4SLinus Torvalds udelay(6); 801da177e4SLinus Torvalds } 811da177e4SLinus Torvalds data>>=1; 821da177e4SLinus Torvalds } 831da177e4SLinus Torvalds } 841da177e4SLinus Torvalds 851da177e4SLinus Torvalds static inline void fmi_mute(int port) 861da177e4SLinus Torvalds { 873593cab5SIngo Molnar mutex_lock(&lock); 881da177e4SLinus Torvalds outb(0x00, port); 893593cab5SIngo Molnar mutex_unlock(&lock); 901da177e4SLinus Torvalds } 911da177e4SLinus Torvalds 921da177e4SLinus Torvalds static inline void fmi_unmute(int port) 931da177e4SLinus Torvalds { 943593cab5SIngo Molnar mutex_lock(&lock); 951da177e4SLinus Torvalds outb(0x08, port); 963593cab5SIngo Molnar mutex_unlock(&lock); 971da177e4SLinus Torvalds } 981da177e4SLinus Torvalds 991da177e4SLinus Torvalds static inline int fmi_setfreq(struct fmi_device *dev) 1001da177e4SLinus Torvalds { 1011da177e4SLinus Torvalds int myport = dev->port; 1021da177e4SLinus Torvalds unsigned long freq = dev->curfreq; 1031da177e4SLinus Torvalds 1043593cab5SIngo Molnar mutex_lock(&lock); 1051da177e4SLinus Torvalds 1061da177e4SLinus Torvalds outbits(16, RSF16_ENCODE(freq), myport); 1071da177e4SLinus Torvalds outbits(8, 0xC0, myport); 1081da177e4SLinus Torvalds msleep(143); /* was schedule_timeout(HZ/7) */ 1093593cab5SIngo Molnar mutex_unlock(&lock); 1101da177e4SLinus Torvalds if (dev->curvol) fmi_unmute(myport); 1111da177e4SLinus Torvalds return 0; 1121da177e4SLinus Torvalds } 1131da177e4SLinus Torvalds 1141da177e4SLinus Torvalds static inline int fmi_getsigstr(struct fmi_device *dev) 1151da177e4SLinus Torvalds { 1161da177e4SLinus Torvalds int val; 1171da177e4SLinus Torvalds int res; 1181da177e4SLinus Torvalds int myport = dev->port; 1191da177e4SLinus Torvalds 1201da177e4SLinus Torvalds 1213593cab5SIngo Molnar mutex_lock(&lock); 1221da177e4SLinus Torvalds val = dev->curvol ? 0x08 : 0x00; /* unmute/mute */ 1231da177e4SLinus Torvalds outb(val, myport); 1241da177e4SLinus Torvalds outb(val | 0x10, myport); 1251da177e4SLinus Torvalds msleep(143); /* was schedule_timeout(HZ/7) */ 1261da177e4SLinus Torvalds res = (int)inb(myport+1); 1271da177e4SLinus Torvalds outb(val, myport); 1281da177e4SLinus Torvalds 1293593cab5SIngo Molnar mutex_unlock(&lock); 1301da177e4SLinus Torvalds return (res & 2) ? 0 : 0xFFFF; 1311da177e4SLinus Torvalds } 1321da177e4SLinus Torvalds 1331da177e4SLinus Torvalds static int fmi_do_ioctl(struct inode *inode, struct file *file, 1341da177e4SLinus Torvalds unsigned int cmd, void *arg) 1351da177e4SLinus Torvalds { 1361da177e4SLinus Torvalds struct video_device *dev = video_devdata(file); 1371da177e4SLinus Torvalds struct fmi_device *fmi=dev->priv; 1381da177e4SLinus Torvalds 1391da177e4SLinus Torvalds switch(cmd) 1401da177e4SLinus Torvalds { 141a2ef73afSMauro Carvalho Chehab case VIDIOC_QUERYCAP: 1421da177e4SLinus Torvalds { 143a2ef73afSMauro Carvalho Chehab struct v4l2_capability *v = arg; 1441da177e4SLinus Torvalds memset(v,0,sizeof(*v)); 145a2ef73afSMauro Carvalho Chehab strlcpy(v->driver, "radio-sf16fmi", sizeof (v->driver)); 146a2ef73afSMauro Carvalho Chehab strlcpy(v->card, "SF16-FMx radio", sizeof (v->card)); 147a2ef73afSMauro Carvalho Chehab sprintf(v->bus_info,"ISA"); 148a2ef73afSMauro Carvalho Chehab v->version = RADIO_VERSION; 149a2ef73afSMauro Carvalho Chehab v->capabilities = V4L2_CAP_TUNER; 150a2ef73afSMauro Carvalho Chehab 1511da177e4SLinus Torvalds return 0; 1521da177e4SLinus Torvalds } 153a2ef73afSMauro Carvalho Chehab case VIDIOC_G_TUNER: 1541da177e4SLinus Torvalds { 155a2ef73afSMauro Carvalho Chehab struct v4l2_tuner *v = arg; 1561da177e4SLinus Torvalds int mult; 1571da177e4SLinus Torvalds 158a2ef73afSMauro Carvalho Chehab if (v->index > 0) 1591da177e4SLinus Torvalds return -EINVAL; 160a2ef73afSMauro Carvalho Chehab 161a2ef73afSMauro Carvalho Chehab memset(v,0,sizeof(*v)); 1621da177e4SLinus Torvalds strcpy(v->name, "FM"); 163a2ef73afSMauro Carvalho Chehab v->type = V4L2_TUNER_RADIO; 164a2ef73afSMauro Carvalho Chehab 165a2ef73afSMauro Carvalho Chehab mult = (fmi->flags & V4L2_TUNER_CAP_LOW) ? 1 : 1000; 1661da177e4SLinus Torvalds v->rangelow = RSF16_MINFREQ/mult; 1671da177e4SLinus Torvalds v->rangehigh = RSF16_MAXFREQ/mult; 168a2ef73afSMauro Carvalho Chehab v->rxsubchans =V4L2_TUNER_SUB_MONO | V4L2_TUNER_MODE_STEREO; 169fa38ad65SMauro Carvalho Chehab v->capability=fmi->flags&V4L2_TUNER_CAP_LOW; 170a2ef73afSMauro Carvalho Chehab v->audmode = V4L2_TUNER_MODE_STEREO; 1711da177e4SLinus Torvalds v->signal = fmi_getsigstr(fmi); 172a2ef73afSMauro Carvalho Chehab 1731da177e4SLinus Torvalds return 0; 1741da177e4SLinus Torvalds } 175a2ef73afSMauro Carvalho Chehab case VIDIOC_S_TUNER: 1761da177e4SLinus Torvalds { 177a2ef73afSMauro Carvalho Chehab struct v4l2_tuner *v = arg; 178a2ef73afSMauro Carvalho Chehab 179a2ef73afSMauro Carvalho Chehab if (v->index > 0) 1801da177e4SLinus Torvalds return -EINVAL; 181a2ef73afSMauro Carvalho Chehab 1821da177e4SLinus Torvalds return 0; 1831da177e4SLinus Torvalds } 184a2ef73afSMauro Carvalho Chehab case VIDIOC_S_FREQUENCY: 1851da177e4SLinus Torvalds { 186a2ef73afSMauro Carvalho Chehab struct v4l2_frequency *f = arg; 187a2ef73afSMauro Carvalho Chehab 188a2ef73afSMauro Carvalho Chehab if (!(fmi->flags & V4L2_TUNER_CAP_LOW)) 189a2ef73afSMauro Carvalho Chehab f->frequency *= 1000; 190a2ef73afSMauro Carvalho Chehab if (f->frequency < RSF16_MINFREQ || 191a2ef73afSMauro Carvalho Chehab f->frequency > RSF16_MAXFREQ ) 1921da177e4SLinus Torvalds return -EINVAL; 1931da177e4SLinus Torvalds /*rounding in steps of 800 to match th freq 1941da177e4SLinus Torvalds that will be used */ 195a2ef73afSMauro Carvalho Chehab fmi->curfreq = (f->frequency/800)*800; 1961da177e4SLinus Torvalds fmi_setfreq(fmi); 197a2ef73afSMauro Carvalho Chehab 1981da177e4SLinus Torvalds return 0; 1991da177e4SLinus Torvalds } 200a2ef73afSMauro Carvalho Chehab case VIDIOC_G_FREQUENCY: 2011da177e4SLinus Torvalds { 202a2ef73afSMauro Carvalho Chehab struct v4l2_frequency *f = arg; 203a2ef73afSMauro Carvalho Chehab 204a2ef73afSMauro Carvalho Chehab f->type = V4L2_TUNER_RADIO; 205a2ef73afSMauro Carvalho Chehab f->frequency = fmi->curfreq; 206a2ef73afSMauro Carvalho Chehab if (!(fmi->flags & V4L2_TUNER_CAP_LOW)) 207a2ef73afSMauro Carvalho Chehab f->frequency /= 1000; 208a2ef73afSMauro Carvalho Chehab 2091da177e4SLinus Torvalds return 0; 2101da177e4SLinus Torvalds } 211a2ef73afSMauro Carvalho Chehab case VIDIOC_QUERYCTRL: 2121da177e4SLinus Torvalds { 213a2ef73afSMauro Carvalho Chehab struct v4l2_queryctrl *qc = arg; 214a2ef73afSMauro Carvalho Chehab int i; 215a2ef73afSMauro Carvalho Chehab 216a2ef73afSMauro Carvalho Chehab for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { 217a2ef73afSMauro Carvalho Chehab if (qc->id && qc->id == radio_qctrl[i].id) { 218a2ef73afSMauro Carvalho Chehab memcpy(qc, &(radio_qctrl[i]), 219a2ef73afSMauro Carvalho Chehab sizeof(*qc)); 220a2ef73afSMauro Carvalho Chehab return (0); 221a2ef73afSMauro Carvalho Chehab } 222a2ef73afSMauro Carvalho Chehab } 2231da177e4SLinus Torvalds return -EINVAL; 2241da177e4SLinus Torvalds } 225a2ef73afSMauro Carvalho Chehab case VIDIOC_G_CTRL: 2261da177e4SLinus Torvalds { 227a2ef73afSMauro Carvalho Chehab struct v4l2_control *ctrl= arg; 228a2ef73afSMauro Carvalho Chehab 229a2ef73afSMauro Carvalho Chehab switch (ctrl->id) { 230a2ef73afSMauro Carvalho Chehab case V4L2_CID_AUDIO_MUTE: 231a2ef73afSMauro Carvalho Chehab ctrl->value=fmi->curvol; 232a2ef73afSMauro Carvalho Chehab return (0); 233a2ef73afSMauro Carvalho Chehab } 234a2ef73afSMauro Carvalho Chehab return -EINVAL; 235a2ef73afSMauro Carvalho Chehab } 236a2ef73afSMauro Carvalho Chehab case VIDIOC_S_CTRL: 237a2ef73afSMauro Carvalho Chehab { 238a2ef73afSMauro Carvalho Chehab struct v4l2_control *ctrl= arg; 239a2ef73afSMauro Carvalho Chehab 240a2ef73afSMauro Carvalho Chehab switch (ctrl->id) { 241a2ef73afSMauro Carvalho Chehab case V4L2_CID_AUDIO_MUTE: 242a2ef73afSMauro Carvalho Chehab { 243a2ef73afSMauro Carvalho Chehab if (ctrl->value) 244a2ef73afSMauro Carvalho Chehab fmi_mute(fmi->port); 245a2ef73afSMauro Carvalho Chehab else 246a2ef73afSMauro Carvalho Chehab fmi_unmute(fmi->port); 247a2ef73afSMauro Carvalho Chehab 248a2ef73afSMauro Carvalho Chehab fmi->curvol=ctrl->value; 249a2ef73afSMauro Carvalho Chehab return (0); 250a2ef73afSMauro Carvalho Chehab } 251a2ef73afSMauro Carvalho Chehab } 252a2ef73afSMauro Carvalho Chehab return -EINVAL; 2531da177e4SLinus Torvalds } 2541da177e4SLinus Torvalds default: 255a2ef73afSMauro Carvalho Chehab return v4l_compat_translate_ioctl(inode,file,cmd,arg, 256a2ef73afSMauro Carvalho Chehab fmi_do_ioctl); 2571da177e4SLinus Torvalds } 2581da177e4SLinus Torvalds } 2591da177e4SLinus Torvalds 2601da177e4SLinus Torvalds static int fmi_ioctl(struct inode *inode, struct file *file, 2611da177e4SLinus Torvalds unsigned int cmd, unsigned long arg) 2621da177e4SLinus Torvalds { 2631da177e4SLinus Torvalds return video_usercopy(inode, file, cmd, arg, fmi_do_ioctl); 2641da177e4SLinus Torvalds } 2651da177e4SLinus Torvalds 2661da177e4SLinus Torvalds static struct fmi_device fmi_unit; 2671da177e4SLinus Torvalds 2681da177e4SLinus Torvalds static struct file_operations fmi_fops = { 2691da177e4SLinus Torvalds .owner = THIS_MODULE, 2701da177e4SLinus Torvalds .open = video_exclusive_open, 2711da177e4SLinus Torvalds .release = video_exclusive_release, 2721da177e4SLinus Torvalds .ioctl = fmi_ioctl, 2730d0fbf81SArnd Bergmann .compat_ioctl = v4l_compat_ioctl32, 2741da177e4SLinus Torvalds .llseek = no_llseek, 2751da177e4SLinus Torvalds }; 2761da177e4SLinus Torvalds 2771da177e4SLinus Torvalds static struct video_device fmi_radio= 2781da177e4SLinus Torvalds { 2791da177e4SLinus Torvalds .owner = THIS_MODULE, 2801da177e4SLinus Torvalds .name = "SF16FMx radio", 2811da177e4SLinus Torvalds .type = VID_TYPE_TUNER, 282a2ef73afSMauro Carvalho Chehab .hardware = 0, 2831da177e4SLinus Torvalds .fops = &fmi_fops, 2841da177e4SLinus Torvalds }; 2851da177e4SLinus Torvalds 2861da177e4SLinus Torvalds /* ladis: this is my card. does any other types exist? */ 2871da177e4SLinus Torvalds static struct isapnp_device_id id_table[] __devinitdata = { 2881da177e4SLinus Torvalds { ISAPNP_ANY_ID, ISAPNP_ANY_ID, 2891da177e4SLinus Torvalds ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0}, 2901da177e4SLinus Torvalds { ISAPNP_CARD_END, }, 2911da177e4SLinus Torvalds }; 2921da177e4SLinus Torvalds 2931da177e4SLinus Torvalds MODULE_DEVICE_TABLE(isapnp, id_table); 2941da177e4SLinus Torvalds 2951da177e4SLinus Torvalds static int isapnp_fmi_probe(void) 2961da177e4SLinus Torvalds { 2971da177e4SLinus Torvalds int i = 0; 2981da177e4SLinus Torvalds 2991da177e4SLinus Torvalds while (id_table[i].card_vendor != 0 && dev == NULL) { 3001da177e4SLinus Torvalds dev = pnp_find_dev(NULL, id_table[i].vendor, 3011da177e4SLinus Torvalds id_table[i].function, NULL); 3021da177e4SLinus Torvalds i++; 3031da177e4SLinus Torvalds } 3041da177e4SLinus Torvalds 3051da177e4SLinus Torvalds if (!dev) 3061da177e4SLinus Torvalds return -ENODEV; 3071da177e4SLinus Torvalds if (pnp_device_attach(dev) < 0) 3081da177e4SLinus Torvalds return -EAGAIN; 3091da177e4SLinus Torvalds if (pnp_activate_dev(dev) < 0) { 3101da177e4SLinus Torvalds printk ("radio-sf16fmi: PnP configure failed (out of resources?)\n"); 3111da177e4SLinus Torvalds pnp_device_detach(dev); 3121da177e4SLinus Torvalds return -ENOMEM; 3131da177e4SLinus Torvalds } 3141da177e4SLinus Torvalds if (!pnp_port_valid(dev, 0)) { 3151da177e4SLinus Torvalds pnp_device_detach(dev); 3161da177e4SLinus Torvalds return -ENODEV; 3171da177e4SLinus Torvalds } 3181da177e4SLinus Torvalds 3191da177e4SLinus Torvalds i = pnp_port_start(dev, 0); 3201da177e4SLinus Torvalds printk ("radio-sf16fmi: PnP reports card at %#x\n", i); 3211da177e4SLinus Torvalds 3221da177e4SLinus Torvalds return i; 3231da177e4SLinus Torvalds } 3241da177e4SLinus Torvalds 3251da177e4SLinus Torvalds static int __init fmi_init(void) 3261da177e4SLinus Torvalds { 3271da177e4SLinus Torvalds if (io < 0) 3281da177e4SLinus Torvalds io = isapnp_fmi_probe(); 3291da177e4SLinus Torvalds if (io < 0) { 3301da177e4SLinus Torvalds printk(KERN_ERR "radio-sf16fmi: No PnP card found.\n"); 3311da177e4SLinus Torvalds return io; 3321da177e4SLinus Torvalds } 3331da177e4SLinus Torvalds if (!request_region(io, 2, "radio-sf16fmi")) { 3341da177e4SLinus Torvalds printk(KERN_ERR "radio-sf16fmi: port 0x%x already in use\n", io); 3351da177e4SLinus Torvalds return -EBUSY; 3361da177e4SLinus Torvalds } 3371da177e4SLinus Torvalds 3381da177e4SLinus Torvalds fmi_unit.port = io; 3391da177e4SLinus Torvalds fmi_unit.curvol = 0; 3401da177e4SLinus Torvalds fmi_unit.curfreq = 0; 341a2ef73afSMauro Carvalho Chehab fmi_unit.flags = V4L2_TUNER_CAP_LOW; 3421da177e4SLinus Torvalds fmi_radio.priv = &fmi_unit; 3431da177e4SLinus Torvalds 3443593cab5SIngo Molnar mutex_init(&lock); 3451da177e4SLinus Torvalds 3461da177e4SLinus Torvalds if (video_register_device(&fmi_radio, VFL_TYPE_RADIO, radio_nr) == -1) { 3471da177e4SLinus Torvalds release_region(io, 2); 3481da177e4SLinus Torvalds return -EINVAL; 3491da177e4SLinus Torvalds } 3501da177e4SLinus Torvalds 3511da177e4SLinus Torvalds printk(KERN_INFO "SF16FMx radio card driver at 0x%x\n", io); 3521da177e4SLinus Torvalds /* mute card - prevents noisy bootups */ 3531da177e4SLinus Torvalds fmi_mute(io); 3541da177e4SLinus Torvalds return 0; 3551da177e4SLinus Torvalds } 3561da177e4SLinus Torvalds 3571da177e4SLinus Torvalds MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood"); 3581da177e4SLinus Torvalds MODULE_DESCRIPTION("A driver for the SF16MI radio."); 3591da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 3601da177e4SLinus Torvalds 3611da177e4SLinus Torvalds module_param(io, int, 0); 3621da177e4SLinus Torvalds MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)"); 3631da177e4SLinus Torvalds module_param(radio_nr, int, 0); 3641da177e4SLinus Torvalds 3651da177e4SLinus Torvalds static void __exit fmi_cleanup_module(void) 3661da177e4SLinus Torvalds { 3671da177e4SLinus Torvalds video_unregister_device(&fmi_radio); 3681da177e4SLinus Torvalds release_region(io, 2); 3691da177e4SLinus Torvalds if (dev) 3701da177e4SLinus Torvalds pnp_device_detach(dev); 3711da177e4SLinus Torvalds } 3721da177e4SLinus Torvalds 3731da177e4SLinus Torvalds module_init(fmi_init); 3741da177e4SLinus Torvalds module_exit(fmi_cleanup_module); 375