11da177e4SLinus Torvalds /* RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff
21da177e4SLinus Torvalds  *
31da177e4SLinus Torvalds  * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood
4d9b01449SAlan Cox  * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk>
51da177e4SLinus Torvalds  * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
61da177e4SLinus Torvalds  *
71da177e4SLinus Torvalds  * TODO: Allow for more than one of these foolish entities :-)
81da177e4SLinus Torvalds  *
9f8c559f8SMauro Carvalho Chehab  * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
101da177e4SLinus Torvalds  */
111da177e4SLinus Torvalds 
121da177e4SLinus Torvalds #include <linux/module.h>	/* Modules 			*/
131da177e4SLinus Torvalds #include <linux/init.h>		/* Initdata			*/
14fb911ee8SPeter Osterlund #include <linux/ioport.h>	/* request_region		*/
151da177e4SLinus Torvalds #include <linux/delay.h>	/* udelay			*/
16f8c559f8SMauro Carvalho Chehab #include <linux/videodev2.h>	/* kernel radio structs		*/
17922c78e9SHans Verkuil #include <linux/mutex.h>
18f8c559f8SMauro Carvalho Chehab #include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
19922c78e9SHans Verkuil #include <linux/io.h>		/* outb, outb_p			*/
20922c78e9SHans Verkuil #include <linux/uaccess.h>	/* copy to/from user		*/
21922c78e9SHans Verkuil #include <media/v4l2-device.h>
22922c78e9SHans Verkuil #include <media/v4l2-ioctl.h>
23f8c559f8SMauro Carvalho Chehab 
24922c78e9SHans Verkuil MODULE_AUTHOR("Ben Pfaff");
25922c78e9SHans Verkuil MODULE_DESCRIPTION("A driver for the RadioTrack II radio card.");
26922c78e9SHans Verkuil MODULE_LICENSE("GPL");
27f8c559f8SMauro Carvalho Chehab 
281da177e4SLinus Torvalds #ifndef CONFIG_RADIO_RTRACK2_PORT
291da177e4SLinus Torvalds #define CONFIG_RADIO_RTRACK2_PORT -1
301da177e4SLinus Torvalds #endif
311da177e4SLinus Torvalds 
321da177e4SLinus Torvalds static int io = CONFIG_RADIO_RTRACK2_PORT;
331da177e4SLinus Torvalds static int radio_nr = -1;
341da177e4SLinus Torvalds 
35922c78e9SHans Verkuil module_param(io, int, 0);
36922c78e9SHans Verkuil MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20c or 0x30c)");
37922c78e9SHans Verkuil module_param(radio_nr, int, 0);
38922c78e9SHans Verkuil 
39922c78e9SHans Verkuil #define RADIO_VERSION KERNEL_VERSION(0, 0, 2)
40922c78e9SHans Verkuil 
41922c78e9SHans Verkuil struct rtrack2
421da177e4SLinus Torvalds {
43922c78e9SHans Verkuil 	struct v4l2_device v4l2_dev;
44922c78e9SHans Verkuil 	struct video_device vdev;
45922c78e9SHans Verkuil 	int io;
461da177e4SLinus Torvalds 	unsigned long curfreq;
471da177e4SLinus Torvalds 	int muted;
48922c78e9SHans Verkuil 	struct mutex lock;
491da177e4SLinus Torvalds };
501da177e4SLinus Torvalds 
51922c78e9SHans Verkuil static struct rtrack2 rtrack2_card;
52922c78e9SHans Verkuil 
531da177e4SLinus Torvalds 
541da177e4SLinus Torvalds /* local things */
551da177e4SLinus Torvalds 
56922c78e9SHans Verkuil static void rt_mute(struct rtrack2 *dev)
571da177e4SLinus Torvalds {
581da177e4SLinus Torvalds 	if (dev->muted)
591da177e4SLinus Torvalds 		return;
60922c78e9SHans Verkuil 	mutex_lock(&dev->lock);
61922c78e9SHans Verkuil 	outb(1, dev->io);
62922c78e9SHans Verkuil 	mutex_unlock(&dev->lock);
63922c78e9SHans Verkuil 	mutex_unlock(&dev->lock);
641da177e4SLinus Torvalds 	dev->muted = 1;
651da177e4SLinus Torvalds }
661da177e4SLinus Torvalds 
67922c78e9SHans Verkuil static void rt_unmute(struct rtrack2 *dev)
681da177e4SLinus Torvalds {
691da177e4SLinus Torvalds 	if(dev->muted == 0)
701da177e4SLinus Torvalds 		return;
71922c78e9SHans Verkuil 	mutex_lock(&dev->lock);
72922c78e9SHans Verkuil 	outb(0, dev->io);
73922c78e9SHans Verkuil 	mutex_unlock(&dev->lock);
741da177e4SLinus Torvalds 	dev->muted = 0;
751da177e4SLinus Torvalds }
761da177e4SLinus Torvalds 
77922c78e9SHans Verkuil static void zero(struct rtrack2 *dev)
781da177e4SLinus Torvalds {
79922c78e9SHans Verkuil 	outb_p(1, dev->io);
80922c78e9SHans Verkuil 	outb_p(3, dev->io);
81922c78e9SHans Verkuil 	outb_p(1, dev->io);
821da177e4SLinus Torvalds }
831da177e4SLinus Torvalds 
84922c78e9SHans Verkuil static void one(struct rtrack2 *dev)
851da177e4SLinus Torvalds {
86922c78e9SHans Verkuil 	outb_p(5, dev->io);
87922c78e9SHans Verkuil 	outb_p(7, dev->io);
88922c78e9SHans Verkuil 	outb_p(5, dev->io);
891da177e4SLinus Torvalds }
901da177e4SLinus Torvalds 
91922c78e9SHans Verkuil static int rt_setfreq(struct rtrack2 *dev, unsigned long freq)
921da177e4SLinus Torvalds {
931da177e4SLinus Torvalds 	int i;
941da177e4SLinus Torvalds 
95922c78e9SHans Verkuil 	mutex_lock(&dev->lock);
96922c78e9SHans Verkuil 	dev->curfreq = freq;
971da177e4SLinus Torvalds 	freq = freq / 200 + 856;
981da177e4SLinus Torvalds 
99922c78e9SHans Verkuil 	outb_p(0xc8, dev->io);
100922c78e9SHans Verkuil 	outb_p(0xc9, dev->io);
101922c78e9SHans Verkuil 	outb_p(0xc9, dev->io);
1021da177e4SLinus Torvalds 
1031da177e4SLinus Torvalds 	for (i = 0; i < 10; i++)
104922c78e9SHans Verkuil 		zero(dev);
1051da177e4SLinus Torvalds 
1061da177e4SLinus Torvalds 	for (i = 14; i >= 0; i--)
1071da177e4SLinus Torvalds 		if (freq & (1 << i))
108922c78e9SHans Verkuil 			one(dev);
1091da177e4SLinus Torvalds 		else
110922c78e9SHans Verkuil 			zero(dev);
1111da177e4SLinus Torvalds 
112922c78e9SHans Verkuil 	outb_p(0xc8, dev->io);
1131da177e4SLinus Torvalds 	if (!dev->muted)
114922c78e9SHans Verkuil 		outb_p(0, dev->io);
1151da177e4SLinus Torvalds 
116922c78e9SHans Verkuil 	mutex_unlock(&dev->lock);
1171da177e4SLinus Torvalds 	return 0;
1181da177e4SLinus Torvalds }
1191da177e4SLinus Torvalds 
12025f30389SDouglas Landgraf static int vidioc_querycap(struct file *file, void *priv,
12125f30389SDouglas Landgraf 				struct v4l2_capability *v)
12225f30389SDouglas Landgraf {
12325f30389SDouglas Landgraf 	strlcpy(v->driver, "radio-rtrack2", sizeof(v->driver));
12425f30389SDouglas Landgraf 	strlcpy(v->card, "RadioTrack II", sizeof(v->card));
125922c78e9SHans Verkuil 	strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
12625f30389SDouglas Landgraf 	v->version = RADIO_VERSION;
127922c78e9SHans Verkuil 	v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
12825f30389SDouglas Landgraf 	return 0;
12925f30389SDouglas Landgraf }
13025f30389SDouglas Landgraf 
13125f30389SDouglas Landgraf static int vidioc_s_tuner(struct file *file, void *priv,
13225f30389SDouglas Landgraf 				struct v4l2_tuner *v)
13325f30389SDouglas Landgraf {
134922c78e9SHans Verkuil 	return v->index ? -EINVAL : 0;
13525f30389SDouglas Landgraf }
13625f30389SDouglas Landgraf 
137922c78e9SHans Verkuil static int rt_getsigstr(struct rtrack2 *dev)
1381da177e4SLinus Torvalds {
139922c78e9SHans Verkuil 	int sig = 1;
140922c78e9SHans Verkuil 
141922c78e9SHans Verkuil 	mutex_lock(&dev->lock);
142922c78e9SHans Verkuil 	if (inb(dev->io) & 2)	/* bit set = no signal present	*/
143922c78e9SHans Verkuil 		sig = 0;
144922c78e9SHans Verkuil 	mutex_unlock(&dev->lock);
145922c78e9SHans Verkuil 	return sig;
1461da177e4SLinus Torvalds }
1471da177e4SLinus Torvalds 
14825f30389SDouglas Landgraf static int vidioc_g_tuner(struct file *file, void *priv,
14925f30389SDouglas Landgraf 				struct v4l2_tuner *v)
1501da177e4SLinus Torvalds {
151922c78e9SHans Verkuil 	struct rtrack2 *rt = video_drvdata(file);
1521da177e4SLinus Torvalds 
153f8c559f8SMauro Carvalho Chehab 	if (v->index > 0)
1541da177e4SLinus Torvalds 		return -EINVAL;
155f8c559f8SMauro Carvalho Chehab 
156922c78e9SHans Verkuil 	strlcpy(v->name, "FM", sizeof(v->name));
157f8c559f8SMauro Carvalho Chehab 	v->type = V4L2_TUNER_RADIO;
158922c78e9SHans Verkuil 	v->rangelow = 88 * 16000;
159922c78e9SHans Verkuil 	v->rangehigh = 108 * 16000;
160f8c559f8SMauro Carvalho Chehab 	v->rxsubchans = V4L2_TUNER_SUB_MONO;
161f8c559f8SMauro Carvalho Chehab 	v->capability = V4L2_TUNER_CAP_LOW;
162f8c559f8SMauro Carvalho Chehab 	v->audmode = V4L2_TUNER_MODE_MONO;
163f8c559f8SMauro Carvalho Chehab 	v->signal = 0xFFFF * rt_getsigstr(rt);
1641da177e4SLinus Torvalds 	return 0;
1651da177e4SLinus Torvalds }
166f8c559f8SMauro Carvalho Chehab 
16725f30389SDouglas Landgraf static int vidioc_s_frequency(struct file *file, void *priv,
16825f30389SDouglas Landgraf 				struct v4l2_frequency *f)
1691da177e4SLinus Torvalds {
170922c78e9SHans Verkuil 	struct rtrack2 *rt = video_drvdata(file);
171f8c559f8SMauro Carvalho Chehab 
172922c78e9SHans Verkuil 	rt_setfreq(rt, f->frequency);
1731da177e4SLinus Torvalds 	return 0;
1741da177e4SLinus Torvalds }
17525f30389SDouglas Landgraf 
17625f30389SDouglas Landgraf static int vidioc_g_frequency(struct file *file, void *priv,
17725f30389SDouglas Landgraf 				struct v4l2_frequency *f)
1781da177e4SLinus Torvalds {
179922c78e9SHans Verkuil 	struct rtrack2 *rt = video_drvdata(file);
180f8c559f8SMauro Carvalho Chehab 
181f8c559f8SMauro Carvalho Chehab 	f->type = V4L2_TUNER_RADIO;
182f8c559f8SMauro Carvalho Chehab 	f->frequency = rt->curfreq;
1831da177e4SLinus Torvalds 	return 0;
1841da177e4SLinus Torvalds }
18525f30389SDouglas Landgraf 
18625f30389SDouglas Landgraf static int vidioc_queryctrl(struct file *file, void *priv,
18725f30389SDouglas Landgraf 				struct v4l2_queryctrl *qc)
1881da177e4SLinus Torvalds {
189922c78e9SHans Verkuil 	switch (qc->id) {
190922c78e9SHans Verkuil 	case V4L2_CID_AUDIO_MUTE:
191922c78e9SHans Verkuil 		return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
192922c78e9SHans Verkuil 	case V4L2_CID_AUDIO_VOLUME:
193922c78e9SHans Verkuil 		return v4l2_ctrl_query_fill(qc, 0, 65535, 65535, 65535);
194f8c559f8SMauro Carvalho Chehab 	}
1951da177e4SLinus Torvalds 	return -EINVAL;
196f8c559f8SMauro Carvalho Chehab }
19725f30389SDouglas Landgraf 
19825f30389SDouglas Landgraf static int vidioc_g_ctrl(struct file *file, void *priv,
19925f30389SDouglas Landgraf 				struct v4l2_control *ctrl)
200f8c559f8SMauro Carvalho Chehab {
201922c78e9SHans Verkuil 	struct rtrack2 *rt = video_drvdata(file);
2021da177e4SLinus Torvalds 
203f8c559f8SMauro Carvalho Chehab 	switch (ctrl->id) {
204f8c559f8SMauro Carvalho Chehab 	case V4L2_CID_AUDIO_MUTE:
205f8c559f8SMauro Carvalho Chehab 		ctrl->value = rt->muted;
20625f30389SDouglas Landgraf 		return 0;
207f8c559f8SMauro Carvalho Chehab 	case V4L2_CID_AUDIO_VOLUME:
208f8c559f8SMauro Carvalho Chehab 		if (rt->muted)
209f8c559f8SMauro Carvalho Chehab 			ctrl->value = 0;
2101da177e4SLinus Torvalds 		else
211f8c559f8SMauro Carvalho Chehab 			ctrl->value = 65535;
21225f30389SDouglas Landgraf 		return 0;
213f8c559f8SMauro Carvalho Chehab 	}
214f8c559f8SMauro Carvalho Chehab 	return -EINVAL;
215f8c559f8SMauro Carvalho Chehab }
21625f30389SDouglas Landgraf 
21725f30389SDouglas Landgraf static int vidioc_s_ctrl(struct file *file, void *priv,
21825f30389SDouglas Landgraf 				struct v4l2_control *ctrl)
219f8c559f8SMauro Carvalho Chehab {
220922c78e9SHans Verkuil 	struct rtrack2 *rt = video_drvdata(file);
2211da177e4SLinus Torvalds 
222f8c559f8SMauro Carvalho Chehab 	switch (ctrl->id) {
223f8c559f8SMauro Carvalho Chehab 	case V4L2_CID_AUDIO_MUTE:
22425f30389SDouglas Landgraf 		if (ctrl->value)
225f8c559f8SMauro Carvalho Chehab 			rt_mute(rt);
22625f30389SDouglas Landgraf 		else
227f8c559f8SMauro Carvalho Chehab 			rt_unmute(rt);
22825f30389SDouglas Landgraf 		return 0;
229f8c559f8SMauro Carvalho Chehab 	case V4L2_CID_AUDIO_VOLUME:
23025f30389SDouglas Landgraf 		if (ctrl->value)
231f8c559f8SMauro Carvalho Chehab 			rt_unmute(rt);
23225f30389SDouglas Landgraf 		else
233f8c559f8SMauro Carvalho Chehab 			rt_mute(rt);
23425f30389SDouglas Landgraf 		return 0;
235f8c559f8SMauro Carvalho Chehab 	}
236f8c559f8SMauro Carvalho Chehab 	return -EINVAL;
2371da177e4SLinus Torvalds }
2381da177e4SLinus Torvalds 
2398b811cf0SDouglas Landgraf static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
2408b811cf0SDouglas Landgraf {
2418b811cf0SDouglas Landgraf 	*i = 0;
2428b811cf0SDouglas Landgraf 	return 0;
2438b811cf0SDouglas Landgraf }
2448b811cf0SDouglas Landgraf 
2458b811cf0SDouglas Landgraf static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
2468b811cf0SDouglas Landgraf {
247922c78e9SHans Verkuil 	return i ? -EINVAL : 0;
248922c78e9SHans Verkuil }
249922c78e9SHans Verkuil 
250922c78e9SHans Verkuil static int vidioc_g_audio(struct file *file, void *priv,
251922c78e9SHans Verkuil 				struct v4l2_audio *a)
252922c78e9SHans Verkuil {
253922c78e9SHans Verkuil 	a->index = 0;
254922c78e9SHans Verkuil 	strlcpy(a->name, "Radio", sizeof(a->name));
255922c78e9SHans Verkuil 	a->capability = V4L2_AUDCAP_STEREO;
2568b811cf0SDouglas Landgraf 	return 0;
2578b811cf0SDouglas Landgraf }
2588b811cf0SDouglas Landgraf 
2598b811cf0SDouglas Landgraf static int vidioc_s_audio(struct file *file, void *priv,
2608b811cf0SDouglas Landgraf 				struct v4l2_audio *a)
2618b811cf0SDouglas Landgraf {
262922c78e9SHans Verkuil 	return a->index ? -EINVAL : 0;
263922c78e9SHans Verkuil }
264922c78e9SHans Verkuil 
265922c78e9SHans Verkuil static int rtrack2_open(struct file *file)
266922c78e9SHans Verkuil {
2678b811cf0SDouglas Landgraf 	return 0;
2688b811cf0SDouglas Landgraf }
2698b811cf0SDouglas Landgraf 
270922c78e9SHans Verkuil static int rtrack2_release(struct file *file)
2713ca685aaSHans Verkuil {
2723ca685aaSHans Verkuil 	return 0;
2733ca685aaSHans Verkuil }
2743ca685aaSHans Verkuil 
275bec43661SHans Verkuil static const struct v4l2_file_operations rtrack2_fops = {
2761da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
277922c78e9SHans Verkuil 	.open           = rtrack2_open,
278922c78e9SHans Verkuil 	.release        = rtrack2_release,
27925f30389SDouglas Landgraf 	.ioctl		= video_ioctl2,
2801da177e4SLinus Torvalds };
2811da177e4SLinus Torvalds 
282a399810cSHans Verkuil static const struct v4l2_ioctl_ops rtrack2_ioctl_ops = {
28325f30389SDouglas Landgraf 	.vidioc_querycap    = vidioc_querycap,
28425f30389SDouglas Landgraf 	.vidioc_g_tuner     = vidioc_g_tuner,
28525f30389SDouglas Landgraf 	.vidioc_s_tuner     = vidioc_s_tuner,
28625f30389SDouglas Landgraf 	.vidioc_g_frequency = vidioc_g_frequency,
28725f30389SDouglas Landgraf 	.vidioc_s_frequency = vidioc_s_frequency,
28825f30389SDouglas Landgraf 	.vidioc_queryctrl   = vidioc_queryctrl,
28925f30389SDouglas Landgraf 	.vidioc_g_ctrl      = vidioc_g_ctrl,
29025f30389SDouglas Landgraf 	.vidioc_s_ctrl      = vidioc_s_ctrl,
2918b811cf0SDouglas Landgraf 	.vidioc_g_audio     = vidioc_g_audio,
2928b811cf0SDouglas Landgraf 	.vidioc_s_audio     = vidioc_s_audio,
2938b811cf0SDouglas Landgraf 	.vidioc_g_input     = vidioc_g_input,
2948b811cf0SDouglas Landgraf 	.vidioc_s_input     = vidioc_s_input,
2951da177e4SLinus Torvalds };
2961da177e4SLinus Torvalds 
2971da177e4SLinus Torvalds static int __init rtrack2_init(void)
2981da177e4SLinus Torvalds {
299922c78e9SHans Verkuil 	struct rtrack2 *dev = &rtrack2_card;
300922c78e9SHans Verkuil 	struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
301922c78e9SHans Verkuil 	int res;
302922c78e9SHans Verkuil 
303922c78e9SHans Verkuil 	strlcpy(v4l2_dev->name, "rtrack2", sizeof(v4l2_dev->name));
304922c78e9SHans Verkuil 	dev->io = io;
305922c78e9SHans Verkuil 	if (dev->io == -1) {
306922c78e9SHans Verkuil 		v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or io=0x30c\n");
3071da177e4SLinus Torvalds 		return -EINVAL;
3081da177e4SLinus Torvalds 	}
309922c78e9SHans Verkuil 	if (!request_region(dev->io, 4, "rtrack2")) {
310922c78e9SHans Verkuil 		v4l2_err(v4l2_dev, "port 0x%x already in use\n", dev->io);
3111da177e4SLinus Torvalds 		return -EBUSY;
3121da177e4SLinus Torvalds 	}
3131da177e4SLinus Torvalds 
314922c78e9SHans Verkuil 	res = v4l2_device_register(NULL, v4l2_dev);
315922c78e9SHans Verkuil 	if (res < 0) {
316922c78e9SHans Verkuil 		release_region(dev->io, 4);
317922c78e9SHans Verkuil 		v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
318922c78e9SHans Verkuil 		return res;
319922c78e9SHans Verkuil 	}
3201da177e4SLinus Torvalds 
321922c78e9SHans Verkuil 	strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
322922c78e9SHans Verkuil 	dev->vdev.v4l2_dev = v4l2_dev;
323922c78e9SHans Verkuil 	dev->vdev.fops = &rtrack2_fops;
324922c78e9SHans Verkuil 	dev->vdev.ioctl_ops = &rtrack2_ioctl_ops;
325922c78e9SHans Verkuil 	dev->vdev.release = video_device_release_empty;
326922c78e9SHans Verkuil 	video_set_drvdata(&dev->vdev, dev);
327922c78e9SHans Verkuil 
328922c78e9SHans Verkuil 	mutex_init(&dev->lock);
329922c78e9SHans Verkuil 	if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
330922c78e9SHans Verkuil 		v4l2_device_unregister(v4l2_dev);
331922c78e9SHans Verkuil 		release_region(dev->io, 4);
3321da177e4SLinus Torvalds 		return -EINVAL;
3331da177e4SLinus Torvalds 	}
3341da177e4SLinus Torvalds 
335922c78e9SHans Verkuil 	v4l2_info(v4l2_dev, "AIMSlab Radiotrack II card driver.\n");
3361da177e4SLinus Torvalds 
3371da177e4SLinus Torvalds 	/* mute card - prevents noisy bootups */
338922c78e9SHans Verkuil 	outb(1, dev->io);
339922c78e9SHans Verkuil 	dev->muted = 1;
3401da177e4SLinus Torvalds 
3411da177e4SLinus Torvalds 	return 0;
3421da177e4SLinus Torvalds }
3431da177e4SLinus Torvalds 
344922c78e9SHans Verkuil static void __exit rtrack2_exit(void)
3451da177e4SLinus Torvalds {
346922c78e9SHans Verkuil 	struct rtrack2 *dev = &rtrack2_card;
347922c78e9SHans Verkuil 
348922c78e9SHans Verkuil 	video_unregister_device(&dev->vdev);
349922c78e9SHans Verkuil 	v4l2_device_unregister(&dev->v4l2_dev);
350922c78e9SHans Verkuil 	release_region(dev->io, 4);
3511da177e4SLinus Torvalds }
3521da177e4SLinus Torvalds 
3531da177e4SLinus Torvalds module_init(rtrack2_init);
354922c78e9SHans Verkuil module_exit(rtrack2_exit);
355