xref: /openbmc/linux/drivers/media/radio/radio-sf16fmi.c (revision c41269fd9275cce88b90af644969c6a5e2067657)
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