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 133*c123b867SDouglas Landgraf static int vidioc_querycap(struct file *file, void *priv, 134*c123b867SDouglas Landgraf struct v4l2_capability *v) 1351da177e4SLinus Torvalds { 136a2ef73afSMauro Carvalho Chehab strlcpy(v->driver, "radio-sf16fmi", sizeof(v->driver)); 137a2ef73afSMauro Carvalho Chehab strlcpy(v->card, "SF16-FMx radio", sizeof(v->card)); 138a2ef73afSMauro Carvalho Chehab sprintf(v->bus_info, "ISA"); 139a2ef73afSMauro Carvalho Chehab v->version = RADIO_VERSION; 140a2ef73afSMauro Carvalho Chehab v->capabilities = V4L2_CAP_TUNER; 1411da177e4SLinus Torvalds return 0; 1421da177e4SLinus Torvalds } 143*c123b867SDouglas Landgraf 144*c123b867SDouglas Landgraf static int vidioc_g_tuner(struct file *file, void *priv, 145*c123b867SDouglas Landgraf struct v4l2_tuner *v) 1461da177e4SLinus Torvalds { 1471da177e4SLinus Torvalds int mult; 148*c123b867SDouglas Landgraf struct video_device *dev = video_devdata(file); 149*c123b867SDouglas Landgraf struct fmi_device *fmi = dev->priv; 1501da177e4SLinus Torvalds 151a2ef73afSMauro Carvalho Chehab if (v->index > 0) 1521da177e4SLinus Torvalds return -EINVAL; 153a2ef73afSMauro Carvalho Chehab 1541da177e4SLinus Torvalds strcpy(v->name, "FM"); 155a2ef73afSMauro Carvalho Chehab v->type = V4L2_TUNER_RADIO; 156a2ef73afSMauro Carvalho Chehab mult = (fmi->flags & V4L2_TUNER_CAP_LOW) ? 1 : 1000; 1571da177e4SLinus Torvalds v->rangelow = RSF16_MINFREQ/mult; 1581da177e4SLinus Torvalds v->rangehigh = RSF16_MAXFREQ/mult; 159a2ef73afSMauro Carvalho Chehab v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_MODE_STEREO; 160fa38ad65SMauro Carvalho Chehab v->capability = fmi->flags&V4L2_TUNER_CAP_LOW; 161a2ef73afSMauro Carvalho Chehab v->audmode = V4L2_TUNER_MODE_STEREO; 1621da177e4SLinus Torvalds v->signal = fmi_getsigstr(fmi); 1631da177e4SLinus Torvalds return 0; 1641da177e4SLinus Torvalds } 165a2ef73afSMauro Carvalho Chehab 166*c123b867SDouglas Landgraf static int vidioc_s_tuner(struct file *file, void *priv, 167*c123b867SDouglas Landgraf struct v4l2_tuner *v) 168*c123b867SDouglas Landgraf { 169a2ef73afSMauro Carvalho Chehab if (v->index > 0) 1701da177e4SLinus Torvalds return -EINVAL; 1711da177e4SLinus Torvalds return 0; 1721da177e4SLinus Torvalds } 173*c123b867SDouglas Landgraf 174*c123b867SDouglas Landgraf static int vidioc_s_frequency(struct file *file, void *priv, 175*c123b867SDouglas Landgraf struct v4l2_frequency *f) 1761da177e4SLinus Torvalds { 177*c123b867SDouglas Landgraf struct video_device *dev = video_devdata(file); 178*c123b867SDouglas Landgraf struct fmi_device *fmi = dev->priv; 179a2ef73afSMauro Carvalho Chehab 180a2ef73afSMauro Carvalho Chehab if (!(fmi->flags & V4L2_TUNER_CAP_LOW)) 181a2ef73afSMauro Carvalho Chehab f->frequency *= 1000; 182a2ef73afSMauro Carvalho Chehab if (f->frequency < RSF16_MINFREQ || 183a2ef73afSMauro Carvalho Chehab f->frequency > RSF16_MAXFREQ ) 1841da177e4SLinus Torvalds return -EINVAL; 1851da177e4SLinus Torvalds /*rounding in steps of 800 to match th freq 1861da177e4SLinus Torvalds that will be used */ 187a2ef73afSMauro Carvalho Chehab fmi->curfreq = (f->frequency/800)*800; 1881da177e4SLinus Torvalds fmi_setfreq(fmi); 1891da177e4SLinus Torvalds return 0; 1901da177e4SLinus Torvalds } 191*c123b867SDouglas Landgraf 192*c123b867SDouglas Landgraf static int vidioc_g_frequency(struct file *file, void *priv, 193*c123b867SDouglas Landgraf struct v4l2_frequency *f) 1941da177e4SLinus Torvalds { 195*c123b867SDouglas Landgraf struct video_device *dev = video_devdata(file); 196*c123b867SDouglas Landgraf struct fmi_device *fmi = dev->priv; 197a2ef73afSMauro Carvalho Chehab 198a2ef73afSMauro Carvalho Chehab f->type = V4L2_TUNER_RADIO; 199a2ef73afSMauro Carvalho Chehab f->frequency = fmi->curfreq; 200a2ef73afSMauro Carvalho Chehab if (!(fmi->flags & V4L2_TUNER_CAP_LOW)) 201a2ef73afSMauro Carvalho Chehab f->frequency /= 1000; 2021da177e4SLinus Torvalds return 0; 2031da177e4SLinus Torvalds } 204*c123b867SDouglas Landgraf 205*c123b867SDouglas Landgraf static int vidioc_queryctrl(struct file *file, void *priv, 206*c123b867SDouglas Landgraf struct v4l2_queryctrl *qc) 2071da177e4SLinus Torvalds { 208a2ef73afSMauro Carvalho Chehab int i; 209a2ef73afSMauro Carvalho Chehab 210a2ef73afSMauro Carvalho Chehab for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { 211a2ef73afSMauro Carvalho Chehab if (qc->id && qc->id == radio_qctrl[i].id) { 212a2ef73afSMauro Carvalho Chehab memcpy(qc, &(radio_qctrl[i]), 213a2ef73afSMauro Carvalho Chehab sizeof(*qc)); 214*c123b867SDouglas Landgraf return 0; 215a2ef73afSMauro Carvalho Chehab } 216a2ef73afSMauro Carvalho Chehab } 2171da177e4SLinus Torvalds return -EINVAL; 2181da177e4SLinus Torvalds } 219*c123b867SDouglas Landgraf 220*c123b867SDouglas Landgraf static int vidioc_g_ctrl(struct file *file, void *priv, 221*c123b867SDouglas Landgraf struct v4l2_control *ctrl) 2221da177e4SLinus Torvalds { 223*c123b867SDouglas Landgraf struct video_device *dev = video_devdata(file); 224*c123b867SDouglas Landgraf struct fmi_device *fmi = dev->priv; 225a2ef73afSMauro Carvalho Chehab 226a2ef73afSMauro Carvalho Chehab switch (ctrl->id) { 227a2ef73afSMauro Carvalho Chehab case V4L2_CID_AUDIO_MUTE: 228a2ef73afSMauro Carvalho Chehab ctrl->value = fmi->curvol; 229*c123b867SDouglas Landgraf return 0; 230a2ef73afSMauro Carvalho Chehab } 231a2ef73afSMauro Carvalho Chehab return -EINVAL; 232a2ef73afSMauro Carvalho Chehab } 233*c123b867SDouglas Landgraf 234*c123b867SDouglas Landgraf static int vidioc_s_ctrl(struct file *file, void *priv, 235*c123b867SDouglas Landgraf struct v4l2_control *ctrl) 236a2ef73afSMauro Carvalho Chehab { 237*c123b867SDouglas Landgraf struct video_device *dev = video_devdata(file); 238*c123b867SDouglas Landgraf struct fmi_device *fmi = dev->priv; 239a2ef73afSMauro Carvalho Chehab 240a2ef73afSMauro Carvalho Chehab switch (ctrl->id) { 241a2ef73afSMauro Carvalho Chehab case V4L2_CID_AUDIO_MUTE: 242a2ef73afSMauro Carvalho Chehab if (ctrl->value) 243a2ef73afSMauro Carvalho Chehab fmi_mute(fmi->port); 244a2ef73afSMauro Carvalho Chehab else 245a2ef73afSMauro Carvalho Chehab fmi_unmute(fmi->port); 246a2ef73afSMauro Carvalho Chehab fmi->curvol = ctrl->value; 247*c123b867SDouglas Landgraf return 0; 248a2ef73afSMauro Carvalho Chehab } 249a2ef73afSMauro Carvalho Chehab return -EINVAL; 2501da177e4SLinus Torvalds } 251*c123b867SDouglas Landgraf 252*c123b867SDouglas Landgraf static int vidioc_g_audio(struct file *file, void *priv, 253*c123b867SDouglas Landgraf struct v4l2_audio *a) 254*c123b867SDouglas Landgraf { 255*c123b867SDouglas Landgraf if (a->index > 1) 256*c123b867SDouglas Landgraf return -EINVAL; 257*c123b867SDouglas Landgraf 258*c123b867SDouglas Landgraf strcpy(a->name, "Radio"); 259*c123b867SDouglas Landgraf a->capability = V4L2_AUDCAP_STEREO; 260*c123b867SDouglas Landgraf return 0; 2611da177e4SLinus Torvalds } 2621da177e4SLinus Torvalds 263*c123b867SDouglas Landgraf static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) 2641da177e4SLinus Torvalds { 265*c123b867SDouglas Landgraf *i = 0; 266*c123b867SDouglas Landgraf return 0; 267*c123b867SDouglas Landgraf } 268*c123b867SDouglas Landgraf 269*c123b867SDouglas Landgraf static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) 270*c123b867SDouglas Landgraf { 271*c123b867SDouglas Landgraf if (i != 0) 272*c123b867SDouglas Landgraf return -EINVAL; 273*c123b867SDouglas Landgraf return 0; 274*c123b867SDouglas Landgraf } 275*c123b867SDouglas Landgraf 276*c123b867SDouglas Landgraf static int vidioc_s_audio(struct file *file, void *priv, 277*c123b867SDouglas Landgraf struct v4l2_audio *a) 278*c123b867SDouglas Landgraf { 279*c123b867SDouglas Landgraf if (a->index != 0) 280*c123b867SDouglas Landgraf return -EINVAL; 281*c123b867SDouglas Landgraf return 0; 2821da177e4SLinus Torvalds } 2831da177e4SLinus Torvalds 2841da177e4SLinus Torvalds static struct fmi_device fmi_unit; 2851da177e4SLinus Torvalds 286fa027c2aSArjan van de Ven static const struct file_operations fmi_fops = { 2871da177e4SLinus Torvalds .owner = THIS_MODULE, 2881da177e4SLinus Torvalds .open = video_exclusive_open, 2891da177e4SLinus Torvalds .release = video_exclusive_release, 290*c123b867SDouglas Landgraf .ioctl = video_ioctl2, 2910d0fbf81SArnd Bergmann .compat_ioctl = v4l_compat_ioctl32, 2921da177e4SLinus Torvalds .llseek = no_llseek, 2931da177e4SLinus Torvalds }; 2941da177e4SLinus Torvalds 2951da177e4SLinus Torvalds static struct video_device fmi_radio= 2961da177e4SLinus Torvalds { 2971da177e4SLinus Torvalds .owner = THIS_MODULE, 2981da177e4SLinus Torvalds .name = "SF16FMx radio", 2991da177e4SLinus Torvalds .type = VID_TYPE_TUNER, 300a2ef73afSMauro Carvalho Chehab .hardware = 0, 3011da177e4SLinus Torvalds .fops = &fmi_fops, 302*c123b867SDouglas Landgraf .vidioc_querycap = vidioc_querycap, 303*c123b867SDouglas Landgraf .vidioc_g_tuner = vidioc_g_tuner, 304*c123b867SDouglas Landgraf .vidioc_s_tuner = vidioc_s_tuner, 305*c123b867SDouglas Landgraf .vidioc_g_audio = vidioc_g_audio, 306*c123b867SDouglas Landgraf .vidioc_s_audio = vidioc_s_audio, 307*c123b867SDouglas Landgraf .vidioc_g_input = vidioc_g_input, 308*c123b867SDouglas Landgraf .vidioc_s_input = vidioc_s_input, 309*c123b867SDouglas Landgraf .vidioc_g_frequency = vidioc_g_frequency, 310*c123b867SDouglas Landgraf .vidioc_s_frequency = vidioc_s_frequency, 311*c123b867SDouglas Landgraf .vidioc_queryctrl = vidioc_queryctrl, 312*c123b867SDouglas Landgraf .vidioc_g_ctrl = vidioc_g_ctrl, 313*c123b867SDouglas Landgraf .vidioc_s_ctrl = vidioc_s_ctrl, 3141da177e4SLinus Torvalds }; 3151da177e4SLinus Torvalds 3161da177e4SLinus Torvalds /* ladis: this is my card. does any other types exist? */ 3171da177e4SLinus Torvalds static struct isapnp_device_id id_table[] __devinitdata = { 3181da177e4SLinus Torvalds { ISAPNP_ANY_ID, ISAPNP_ANY_ID, 3191da177e4SLinus Torvalds ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0}, 3201da177e4SLinus Torvalds { ISAPNP_CARD_END, }, 3211da177e4SLinus Torvalds }; 3221da177e4SLinus Torvalds 3231da177e4SLinus Torvalds MODULE_DEVICE_TABLE(isapnp, id_table); 3241da177e4SLinus Torvalds 3251da177e4SLinus Torvalds static int isapnp_fmi_probe(void) 3261da177e4SLinus Torvalds { 3271da177e4SLinus Torvalds int i = 0; 3281da177e4SLinus Torvalds 3291da177e4SLinus Torvalds while (id_table[i].card_vendor != 0 && dev == NULL) { 3301da177e4SLinus Torvalds dev = pnp_find_dev(NULL, id_table[i].vendor, 3311da177e4SLinus Torvalds id_table[i].function, NULL); 3321da177e4SLinus Torvalds i++; 3331da177e4SLinus Torvalds } 3341da177e4SLinus Torvalds 3351da177e4SLinus Torvalds if (!dev) 3361da177e4SLinus Torvalds return -ENODEV; 3371da177e4SLinus Torvalds if (pnp_device_attach(dev) < 0) 3381da177e4SLinus Torvalds return -EAGAIN; 3391da177e4SLinus Torvalds if (pnp_activate_dev(dev) < 0) { 3401da177e4SLinus Torvalds printk ("radio-sf16fmi: PnP configure failed (out of resources?)\n"); 3411da177e4SLinus Torvalds pnp_device_detach(dev); 3421da177e4SLinus Torvalds return -ENOMEM; 3431da177e4SLinus Torvalds } 3441da177e4SLinus Torvalds if (!pnp_port_valid(dev, 0)) { 3451da177e4SLinus Torvalds pnp_device_detach(dev); 3461da177e4SLinus Torvalds return -ENODEV; 3471da177e4SLinus Torvalds } 3481da177e4SLinus Torvalds 3491da177e4SLinus Torvalds i = pnp_port_start(dev, 0); 3501da177e4SLinus Torvalds printk ("radio-sf16fmi: PnP reports card at %#x\n", i); 3511da177e4SLinus Torvalds 3521da177e4SLinus Torvalds return i; 3531da177e4SLinus Torvalds } 3541da177e4SLinus Torvalds 3551da177e4SLinus Torvalds static int __init fmi_init(void) 3561da177e4SLinus Torvalds { 3571da177e4SLinus Torvalds if (io < 0) 3581da177e4SLinus Torvalds io = isapnp_fmi_probe(); 3591da177e4SLinus Torvalds if (io < 0) { 3601da177e4SLinus Torvalds printk(KERN_ERR "radio-sf16fmi: No PnP card found.\n"); 3611da177e4SLinus Torvalds return io; 3621da177e4SLinus Torvalds } 3631da177e4SLinus Torvalds if (!request_region(io, 2, "radio-sf16fmi")) { 3641da177e4SLinus Torvalds printk(KERN_ERR "radio-sf16fmi: port 0x%x already in use\n", io); 3651da177e4SLinus Torvalds return -EBUSY; 3661da177e4SLinus Torvalds } 3671da177e4SLinus Torvalds 3681da177e4SLinus Torvalds fmi_unit.port = io; 3691da177e4SLinus Torvalds fmi_unit.curvol = 0; 3701da177e4SLinus Torvalds fmi_unit.curfreq = 0; 371a2ef73afSMauro Carvalho Chehab fmi_unit.flags = V4L2_TUNER_CAP_LOW; 3721da177e4SLinus Torvalds fmi_radio.priv = &fmi_unit; 3731da177e4SLinus Torvalds 3743593cab5SIngo Molnar mutex_init(&lock); 3751da177e4SLinus Torvalds 3761da177e4SLinus Torvalds if (video_register_device(&fmi_radio, VFL_TYPE_RADIO, radio_nr) == -1) { 3771da177e4SLinus Torvalds release_region(io, 2); 3781da177e4SLinus Torvalds return -EINVAL; 3791da177e4SLinus Torvalds } 3801da177e4SLinus Torvalds 3811da177e4SLinus Torvalds printk(KERN_INFO "SF16FMx radio card driver at 0x%x\n", io); 3821da177e4SLinus Torvalds /* mute card - prevents noisy bootups */ 3831da177e4SLinus Torvalds fmi_mute(io); 3841da177e4SLinus Torvalds return 0; 3851da177e4SLinus Torvalds } 3861da177e4SLinus Torvalds 3871da177e4SLinus Torvalds MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood"); 3881da177e4SLinus Torvalds MODULE_DESCRIPTION("A driver for the SF16MI radio."); 3891da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 3901da177e4SLinus Torvalds 3911da177e4SLinus Torvalds module_param(io, int, 0); 3921da177e4SLinus Torvalds MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)"); 3931da177e4SLinus Torvalds module_param(radio_nr, int, 0); 3941da177e4SLinus Torvalds 3951da177e4SLinus Torvalds static void __exit fmi_cleanup_module(void) 3961da177e4SLinus Torvalds { 3971da177e4SLinus Torvalds video_unregister_device(&fmi_radio); 3981da177e4SLinus Torvalds release_region(io, 2); 3991da177e4SLinus Torvalds if (dev) 4001da177e4SLinus Torvalds pnp_device_detach(dev); 4011da177e4SLinus Torvalds } 4021da177e4SLinus Torvalds 4031da177e4SLinus Torvalds module_init(fmi_init); 4041da177e4SLinus Torvalds module_exit(fmi_cleanup_module); 405