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 * 16*a2ef73afSMauro Carvalho Chehab * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> 171da177e4SLinus Torvalds */ 181da177e4SLinus Torvalds 191da177e4SLinus Torvalds #include <linux/kernel.h> /* __setup */ 201da177e4SLinus Torvalds #include <linux/module.h> /* Modules */ 211da177e4SLinus Torvalds #include <linux/init.h> /* Initdata */ 22fb911ee8SPeter Osterlund #include <linux/ioport.h> /* request_region */ 231da177e4SLinus Torvalds #include <linux/delay.h> /* udelay */ 24*a2ef73afSMauro Carvalho Chehab #include <linux/videodev2.h> /* kernel radio structs */ 255e87efa3SMauro Carvalho Chehab #include <media/v4l2-common.h> 261da177e4SLinus Torvalds #include <linux/isapnp.h> 271da177e4SLinus Torvalds #include <asm/io.h> /* outb, outb_p */ 281da177e4SLinus Torvalds #include <asm/uaccess.h> /* copy to/from user */ 293593cab5SIngo Molnar #include <linux/mutex.h> 301da177e4SLinus Torvalds 31*a2ef73afSMauro Carvalho Chehab #define RADIO_VERSION KERNEL_VERSION(0,0,2) 32*a2ef73afSMauro Carvalho Chehab 33*a2ef73afSMauro Carvalho Chehab static struct v4l2_queryctrl radio_qctrl[] = { 34*a2ef73afSMauro Carvalho Chehab { 35*a2ef73afSMauro Carvalho Chehab .id = V4L2_CID_AUDIO_MUTE, 36*a2ef73afSMauro Carvalho Chehab .name = "Mute", 37*a2ef73afSMauro Carvalho Chehab .minimum = 0, 38*a2ef73afSMauro Carvalho Chehab .maximum = 1, 39*a2ef73afSMauro Carvalho Chehab .default_value = 1, 40*a2ef73afSMauro Carvalho Chehab .type = V4L2_CTRL_TYPE_BOOLEAN, 41*a2ef73afSMauro Carvalho Chehab } 42*a2ef73afSMauro Carvalho Chehab }; 43*a2ef73afSMauro Carvalho Chehab 441da177e4SLinus Torvalds struct fmi_device 451da177e4SLinus Torvalds { 461da177e4SLinus Torvalds int port; 471da177e4SLinus Torvalds int curvol; /* 1 or 0 */ 481da177e4SLinus Torvalds unsigned long curfreq; /* freq in kHz */ 491da177e4SLinus Torvalds __u32 flags; 501da177e4SLinus Torvalds }; 511da177e4SLinus Torvalds 521da177e4SLinus Torvalds static int io = -1; 531da177e4SLinus Torvalds static int radio_nr = -1; 541da177e4SLinus Torvalds static struct pnp_dev *dev = NULL; 553593cab5SIngo Molnar static struct mutex lock; 561da177e4SLinus Torvalds 571da177e4SLinus Torvalds /* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */ 581da177e4SLinus Torvalds /* It is only useful to give freq in intervall of 800 (=0.05Mhz), 591da177e4SLinus Torvalds * other bits will be truncated, e.g 92.7400016 -> 92.7, but 601da177e4SLinus Torvalds * 92.7400017 -> 92.75 611da177e4SLinus Torvalds */ 621da177e4SLinus Torvalds #define RSF16_ENCODE(x) ((x)/800+214) 631da177e4SLinus Torvalds #define RSF16_MINFREQ 87*16000 641da177e4SLinus Torvalds #define RSF16_MAXFREQ 108*16000 651da177e4SLinus Torvalds 661da177e4SLinus Torvalds static void outbits(int bits, unsigned int data, int port) 671da177e4SLinus Torvalds { 681da177e4SLinus Torvalds while(bits--) { 691da177e4SLinus Torvalds if(data & 1) { 701da177e4SLinus Torvalds outb(5, port); 711da177e4SLinus Torvalds udelay(6); 721da177e4SLinus Torvalds outb(7, port); 731da177e4SLinus Torvalds udelay(6); 741da177e4SLinus Torvalds } else { 751da177e4SLinus Torvalds outb(1, port); 761da177e4SLinus Torvalds udelay(6); 771da177e4SLinus Torvalds outb(3, port); 781da177e4SLinus Torvalds udelay(6); 791da177e4SLinus Torvalds } 801da177e4SLinus Torvalds data>>=1; 811da177e4SLinus Torvalds } 821da177e4SLinus Torvalds } 831da177e4SLinus Torvalds 841da177e4SLinus Torvalds static inline void fmi_mute(int port) 851da177e4SLinus Torvalds { 863593cab5SIngo Molnar mutex_lock(&lock); 871da177e4SLinus Torvalds outb(0x00, port); 883593cab5SIngo Molnar mutex_unlock(&lock); 891da177e4SLinus Torvalds } 901da177e4SLinus Torvalds 911da177e4SLinus Torvalds static inline void fmi_unmute(int port) 921da177e4SLinus Torvalds { 933593cab5SIngo Molnar mutex_lock(&lock); 941da177e4SLinus Torvalds outb(0x08, port); 953593cab5SIngo Molnar mutex_unlock(&lock); 961da177e4SLinus Torvalds } 971da177e4SLinus Torvalds 981da177e4SLinus Torvalds static inline int fmi_setfreq(struct fmi_device *dev) 991da177e4SLinus Torvalds { 1001da177e4SLinus Torvalds int myport = dev->port; 1011da177e4SLinus Torvalds unsigned long freq = dev->curfreq; 1021da177e4SLinus Torvalds 1033593cab5SIngo Molnar mutex_lock(&lock); 1041da177e4SLinus Torvalds 1051da177e4SLinus Torvalds outbits(16, RSF16_ENCODE(freq), myport); 1061da177e4SLinus Torvalds outbits(8, 0xC0, myport); 1071da177e4SLinus Torvalds msleep(143); /* was schedule_timeout(HZ/7) */ 1083593cab5SIngo Molnar mutex_unlock(&lock); 1091da177e4SLinus Torvalds if (dev->curvol) fmi_unmute(myport); 1101da177e4SLinus Torvalds return 0; 1111da177e4SLinus Torvalds } 1121da177e4SLinus Torvalds 1131da177e4SLinus Torvalds static inline int fmi_getsigstr(struct fmi_device *dev) 1141da177e4SLinus Torvalds { 1151da177e4SLinus Torvalds int val; 1161da177e4SLinus Torvalds int res; 1171da177e4SLinus Torvalds int myport = dev->port; 1181da177e4SLinus Torvalds 1191da177e4SLinus Torvalds 1203593cab5SIngo Molnar mutex_lock(&lock); 1211da177e4SLinus Torvalds val = dev->curvol ? 0x08 : 0x00; /* unmute/mute */ 1221da177e4SLinus Torvalds outb(val, myport); 1231da177e4SLinus Torvalds outb(val | 0x10, myport); 1241da177e4SLinus Torvalds msleep(143); /* was schedule_timeout(HZ/7) */ 1251da177e4SLinus Torvalds res = (int)inb(myport+1); 1261da177e4SLinus Torvalds outb(val, myport); 1271da177e4SLinus Torvalds 1283593cab5SIngo Molnar mutex_unlock(&lock); 1291da177e4SLinus Torvalds return (res & 2) ? 0 : 0xFFFF; 1301da177e4SLinus Torvalds } 1311da177e4SLinus Torvalds 1321da177e4SLinus Torvalds static int fmi_do_ioctl(struct inode *inode, struct file *file, 1331da177e4SLinus Torvalds unsigned int cmd, void *arg) 1341da177e4SLinus Torvalds { 1351da177e4SLinus Torvalds struct video_device *dev = video_devdata(file); 1361da177e4SLinus Torvalds struct fmi_device *fmi=dev->priv; 1371da177e4SLinus Torvalds 1381da177e4SLinus Torvalds switch(cmd) 1391da177e4SLinus Torvalds { 140*a2ef73afSMauro Carvalho Chehab case VIDIOC_QUERYCAP: 1411da177e4SLinus Torvalds { 142*a2ef73afSMauro Carvalho Chehab struct v4l2_capability *v = arg; 1431da177e4SLinus Torvalds memset(v,0,sizeof(*v)); 144*a2ef73afSMauro Carvalho Chehab strlcpy(v->driver, "radio-sf16fmi", sizeof (v->driver)); 145*a2ef73afSMauro Carvalho Chehab strlcpy(v->card, "SF16-FMx radio", sizeof (v->card)); 146*a2ef73afSMauro Carvalho Chehab sprintf(v->bus_info,"ISA"); 147*a2ef73afSMauro Carvalho Chehab v->version = RADIO_VERSION; 148*a2ef73afSMauro Carvalho Chehab v->capabilities = V4L2_CAP_TUNER; 149*a2ef73afSMauro Carvalho Chehab 1501da177e4SLinus Torvalds return 0; 1511da177e4SLinus Torvalds } 152*a2ef73afSMauro Carvalho Chehab case VIDIOC_G_TUNER: 1531da177e4SLinus Torvalds { 154*a2ef73afSMauro Carvalho Chehab struct v4l2_tuner *v = arg; 1551da177e4SLinus Torvalds int mult; 1561da177e4SLinus Torvalds 157*a2ef73afSMauro Carvalho Chehab if (v->index > 0) 1581da177e4SLinus Torvalds return -EINVAL; 159*a2ef73afSMauro Carvalho Chehab 160*a2ef73afSMauro Carvalho Chehab memset(v,0,sizeof(*v)); 1611da177e4SLinus Torvalds strcpy(v->name, "FM"); 162*a2ef73afSMauro Carvalho Chehab v->type = V4L2_TUNER_RADIO; 163*a2ef73afSMauro Carvalho Chehab 164*a2ef73afSMauro Carvalho Chehab mult = (fmi->flags & V4L2_TUNER_CAP_LOW) ? 1 : 1000; 1651da177e4SLinus Torvalds v->rangelow = RSF16_MINFREQ/mult; 1661da177e4SLinus Torvalds v->rangehigh = RSF16_MAXFREQ/mult; 167*a2ef73afSMauro Carvalho Chehab v->rxsubchans =V4L2_TUNER_SUB_MONO | V4L2_TUNER_MODE_STEREO; 168*a2ef73afSMauro Carvalho Chehab v->capability=fmi->flags; 169*a2ef73afSMauro Carvalho Chehab v->audmode = V4L2_TUNER_MODE_STEREO; 1701da177e4SLinus Torvalds v->signal = fmi_getsigstr(fmi); 171*a2ef73afSMauro Carvalho Chehab 1721da177e4SLinus Torvalds return 0; 1731da177e4SLinus Torvalds } 174*a2ef73afSMauro Carvalho Chehab case VIDIOC_S_TUNER: 1751da177e4SLinus Torvalds { 176*a2ef73afSMauro Carvalho Chehab struct v4l2_tuner *v = arg; 177*a2ef73afSMauro Carvalho Chehab 178*a2ef73afSMauro Carvalho Chehab if (v->index > 0) 1791da177e4SLinus Torvalds return -EINVAL; 180*a2ef73afSMauro Carvalho Chehab 1811da177e4SLinus Torvalds return 0; 1821da177e4SLinus Torvalds } 183*a2ef73afSMauro Carvalho Chehab case VIDIOC_S_FREQUENCY: 1841da177e4SLinus Torvalds { 185*a2ef73afSMauro Carvalho Chehab struct v4l2_frequency *f = arg; 186*a2ef73afSMauro Carvalho Chehab 187*a2ef73afSMauro Carvalho Chehab if (!(fmi->flags & V4L2_TUNER_CAP_LOW)) 188*a2ef73afSMauro Carvalho Chehab f->frequency *= 1000; 189*a2ef73afSMauro Carvalho Chehab if (f->frequency < RSF16_MINFREQ || 190*a2ef73afSMauro Carvalho Chehab f->frequency > RSF16_MAXFREQ ) 1911da177e4SLinus Torvalds return -EINVAL; 1921da177e4SLinus Torvalds /*rounding in steps of 800 to match th freq 1931da177e4SLinus Torvalds that will be used */ 194*a2ef73afSMauro Carvalho Chehab fmi->curfreq = (f->frequency/800)*800; 1951da177e4SLinus Torvalds fmi_setfreq(fmi); 196*a2ef73afSMauro Carvalho Chehab 1971da177e4SLinus Torvalds return 0; 1981da177e4SLinus Torvalds } 199*a2ef73afSMauro Carvalho Chehab case VIDIOC_G_FREQUENCY: 2001da177e4SLinus Torvalds { 201*a2ef73afSMauro Carvalho Chehab struct v4l2_frequency *f = arg; 202*a2ef73afSMauro Carvalho Chehab 203*a2ef73afSMauro Carvalho Chehab f->type = V4L2_TUNER_RADIO; 204*a2ef73afSMauro Carvalho Chehab f->frequency = fmi->curfreq; 205*a2ef73afSMauro Carvalho Chehab if (!(fmi->flags & V4L2_TUNER_CAP_LOW)) 206*a2ef73afSMauro Carvalho Chehab f->frequency /= 1000; 207*a2ef73afSMauro Carvalho Chehab 2081da177e4SLinus Torvalds return 0; 2091da177e4SLinus Torvalds } 210*a2ef73afSMauro Carvalho Chehab case VIDIOC_QUERYCTRL: 2111da177e4SLinus Torvalds { 212*a2ef73afSMauro Carvalho Chehab struct v4l2_queryctrl *qc = arg; 213*a2ef73afSMauro Carvalho Chehab int i; 214*a2ef73afSMauro Carvalho Chehab 215*a2ef73afSMauro Carvalho Chehab for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { 216*a2ef73afSMauro Carvalho Chehab if (qc->id && qc->id == radio_qctrl[i].id) { 217*a2ef73afSMauro Carvalho Chehab memcpy(qc, &(radio_qctrl[i]), 218*a2ef73afSMauro Carvalho Chehab sizeof(*qc)); 219*a2ef73afSMauro Carvalho Chehab return (0); 220*a2ef73afSMauro Carvalho Chehab } 221*a2ef73afSMauro Carvalho Chehab } 2221da177e4SLinus Torvalds return -EINVAL; 2231da177e4SLinus Torvalds } 224*a2ef73afSMauro Carvalho Chehab case VIDIOC_G_CTRL: 2251da177e4SLinus Torvalds { 226*a2ef73afSMauro Carvalho Chehab struct v4l2_control *ctrl= arg; 227*a2ef73afSMauro Carvalho Chehab 228*a2ef73afSMauro Carvalho Chehab switch (ctrl->id) { 229*a2ef73afSMauro Carvalho Chehab case V4L2_CID_AUDIO_MUTE: 230*a2ef73afSMauro Carvalho Chehab ctrl->value=fmi->curvol; 231*a2ef73afSMauro Carvalho Chehab return (0); 232*a2ef73afSMauro Carvalho Chehab } 233*a2ef73afSMauro Carvalho Chehab return -EINVAL; 234*a2ef73afSMauro Carvalho Chehab } 235*a2ef73afSMauro Carvalho Chehab case VIDIOC_S_CTRL: 236*a2ef73afSMauro Carvalho Chehab { 237*a2ef73afSMauro Carvalho Chehab struct v4l2_control *ctrl= arg; 238*a2ef73afSMauro Carvalho Chehab 239*a2ef73afSMauro Carvalho Chehab switch (ctrl->id) { 240*a2ef73afSMauro Carvalho Chehab case V4L2_CID_AUDIO_MUTE: 241*a2ef73afSMauro Carvalho Chehab { 242*a2ef73afSMauro Carvalho Chehab if (ctrl->value) 243*a2ef73afSMauro Carvalho Chehab fmi_mute(fmi->port); 244*a2ef73afSMauro Carvalho Chehab else 245*a2ef73afSMauro Carvalho Chehab fmi_unmute(fmi->port); 246*a2ef73afSMauro Carvalho Chehab 247*a2ef73afSMauro Carvalho Chehab fmi->curvol=ctrl->value; 248*a2ef73afSMauro Carvalho Chehab return (0); 249*a2ef73afSMauro Carvalho Chehab } 250*a2ef73afSMauro Carvalho Chehab } 251*a2ef73afSMauro Carvalho Chehab return -EINVAL; 2521da177e4SLinus Torvalds } 2531da177e4SLinus Torvalds default: 254*a2ef73afSMauro Carvalho Chehab return v4l_compat_translate_ioctl(inode,file,cmd,arg, 255*a2ef73afSMauro Carvalho Chehab fmi_do_ioctl); 2561da177e4SLinus Torvalds } 2571da177e4SLinus Torvalds } 2581da177e4SLinus Torvalds 2591da177e4SLinus Torvalds static int fmi_ioctl(struct inode *inode, struct file *file, 2601da177e4SLinus Torvalds unsigned int cmd, unsigned long arg) 2611da177e4SLinus Torvalds { 2621da177e4SLinus Torvalds return video_usercopy(inode, file, cmd, arg, fmi_do_ioctl); 2631da177e4SLinus Torvalds } 2641da177e4SLinus Torvalds 2651da177e4SLinus Torvalds static struct fmi_device fmi_unit; 2661da177e4SLinus Torvalds 2671da177e4SLinus Torvalds static struct file_operations fmi_fops = { 2681da177e4SLinus Torvalds .owner = THIS_MODULE, 2691da177e4SLinus Torvalds .open = video_exclusive_open, 2701da177e4SLinus Torvalds .release = video_exclusive_release, 2711da177e4SLinus Torvalds .ioctl = fmi_ioctl, 2720d0fbf81SArnd Bergmann .compat_ioctl = v4l_compat_ioctl32, 2731da177e4SLinus Torvalds .llseek = no_llseek, 2741da177e4SLinus Torvalds }; 2751da177e4SLinus Torvalds 2761da177e4SLinus Torvalds static struct video_device fmi_radio= 2771da177e4SLinus Torvalds { 2781da177e4SLinus Torvalds .owner = THIS_MODULE, 2791da177e4SLinus Torvalds .name = "SF16FMx radio", 2801da177e4SLinus Torvalds .type = VID_TYPE_TUNER, 281*a2ef73afSMauro Carvalho Chehab .hardware = 0, 2821da177e4SLinus Torvalds .fops = &fmi_fops, 2831da177e4SLinus Torvalds }; 2841da177e4SLinus Torvalds 2851da177e4SLinus Torvalds /* ladis: this is my card. does any other types exist? */ 2861da177e4SLinus Torvalds static struct isapnp_device_id id_table[] __devinitdata = { 2871da177e4SLinus Torvalds { ISAPNP_ANY_ID, ISAPNP_ANY_ID, 2881da177e4SLinus Torvalds ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0}, 2891da177e4SLinus Torvalds { ISAPNP_CARD_END, }, 2901da177e4SLinus Torvalds }; 2911da177e4SLinus Torvalds 2921da177e4SLinus Torvalds MODULE_DEVICE_TABLE(isapnp, id_table); 2931da177e4SLinus Torvalds 2941da177e4SLinus Torvalds static int isapnp_fmi_probe(void) 2951da177e4SLinus Torvalds { 2961da177e4SLinus Torvalds int i = 0; 2971da177e4SLinus Torvalds 2981da177e4SLinus Torvalds while (id_table[i].card_vendor != 0 && dev == NULL) { 2991da177e4SLinus Torvalds dev = pnp_find_dev(NULL, id_table[i].vendor, 3001da177e4SLinus Torvalds id_table[i].function, NULL); 3011da177e4SLinus Torvalds i++; 3021da177e4SLinus Torvalds } 3031da177e4SLinus Torvalds 3041da177e4SLinus Torvalds if (!dev) 3051da177e4SLinus Torvalds return -ENODEV; 3061da177e4SLinus Torvalds if (pnp_device_attach(dev) < 0) 3071da177e4SLinus Torvalds return -EAGAIN; 3081da177e4SLinus Torvalds if (pnp_activate_dev(dev) < 0) { 3091da177e4SLinus Torvalds printk ("radio-sf16fmi: PnP configure failed (out of resources?)\n"); 3101da177e4SLinus Torvalds pnp_device_detach(dev); 3111da177e4SLinus Torvalds return -ENOMEM; 3121da177e4SLinus Torvalds } 3131da177e4SLinus Torvalds if (!pnp_port_valid(dev, 0)) { 3141da177e4SLinus Torvalds pnp_device_detach(dev); 3151da177e4SLinus Torvalds return -ENODEV; 3161da177e4SLinus Torvalds } 3171da177e4SLinus Torvalds 3181da177e4SLinus Torvalds i = pnp_port_start(dev, 0); 3191da177e4SLinus Torvalds printk ("radio-sf16fmi: PnP reports card at %#x\n", i); 3201da177e4SLinus Torvalds 3211da177e4SLinus Torvalds return i; 3221da177e4SLinus Torvalds } 3231da177e4SLinus Torvalds 3241da177e4SLinus Torvalds static int __init fmi_init(void) 3251da177e4SLinus Torvalds { 3261da177e4SLinus Torvalds if (io < 0) 3271da177e4SLinus Torvalds io = isapnp_fmi_probe(); 3281da177e4SLinus Torvalds if (io < 0) { 3291da177e4SLinus Torvalds printk(KERN_ERR "radio-sf16fmi: No PnP card found.\n"); 3301da177e4SLinus Torvalds return io; 3311da177e4SLinus Torvalds } 3321da177e4SLinus Torvalds if (!request_region(io, 2, "radio-sf16fmi")) { 3331da177e4SLinus Torvalds printk(KERN_ERR "radio-sf16fmi: port 0x%x already in use\n", io); 3341da177e4SLinus Torvalds return -EBUSY; 3351da177e4SLinus Torvalds } 3361da177e4SLinus Torvalds 3371da177e4SLinus Torvalds fmi_unit.port = io; 3381da177e4SLinus Torvalds fmi_unit.curvol = 0; 3391da177e4SLinus Torvalds fmi_unit.curfreq = 0; 340*a2ef73afSMauro Carvalho Chehab fmi_unit.flags = V4L2_TUNER_CAP_LOW; 3411da177e4SLinus Torvalds fmi_radio.priv = &fmi_unit; 3421da177e4SLinus Torvalds 3433593cab5SIngo Molnar mutex_init(&lock); 3441da177e4SLinus Torvalds 3451da177e4SLinus Torvalds if (video_register_device(&fmi_radio, VFL_TYPE_RADIO, radio_nr) == -1) { 3461da177e4SLinus Torvalds release_region(io, 2); 3471da177e4SLinus Torvalds return -EINVAL; 3481da177e4SLinus Torvalds } 3491da177e4SLinus Torvalds 3501da177e4SLinus Torvalds printk(KERN_INFO "SF16FMx radio card driver at 0x%x\n", io); 3511da177e4SLinus Torvalds /* mute card - prevents noisy bootups */ 3521da177e4SLinus Torvalds fmi_mute(io); 3531da177e4SLinus Torvalds return 0; 3541da177e4SLinus Torvalds } 3551da177e4SLinus Torvalds 3561da177e4SLinus Torvalds MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood"); 3571da177e4SLinus Torvalds MODULE_DESCRIPTION("A driver for the SF16MI radio."); 3581da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 3591da177e4SLinus Torvalds 3601da177e4SLinus Torvalds module_param(io, int, 0); 3611da177e4SLinus Torvalds MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)"); 3621da177e4SLinus Torvalds module_param(radio_nr, int, 0); 3631da177e4SLinus Torvalds 3641da177e4SLinus Torvalds static void __exit fmi_cleanup_module(void) 3651da177e4SLinus Torvalds { 3661da177e4SLinus Torvalds video_unregister_device(&fmi_radio); 3671da177e4SLinus Torvalds release_region(io, 2); 3681da177e4SLinus Torvalds if (dev) 3691da177e4SLinus Torvalds pnp_device_detach(dev); 3701da177e4SLinus Torvalds } 3711da177e4SLinus Torvalds 3721da177e4SLinus Torvalds module_init(fmi_init); 3731da177e4SLinus Torvalds module_exit(fmi_cleanup_module); 374