xref: /linux/drivers/media/radio/radio-zoltrix.c (revision c537b994505099b7197e7d3125b942ecbcc51eb6)
1 /* zoltrix radio plus driver for Linux radio support
2  * (c) 1998 C. van Schaik <carl@leg.uct.ac.za>
3  *
4  * BUGS
5  *  Due to the inconsistency in reading from the signal flags
6  *  it is difficult to get an accurate tuned signal.
7  *
8  *  It seems that the card is not linear to 0 volume. It cuts off
9  *  at a low volume, and it is not possible (at least I have not found)
10  *  to get fine volume control over the low volume range.
11  *
12  *  Some code derived from code by Romolo Manfredini
13  *				   romolo@bicnet.it
14  *
15  * 1999-05-06 - (C. van Schaik)
16  *	      - Make signal strength and stereo scans
17  *		kinder to cpu while in delay
18  * 1999-01-05 - (C. van Schaik)
19  *	      - Changed tuning to 1/160Mhz accuracy
20  *	      - Added stereo support
21  *		(card defaults to stereo)
22  *		(can explicitly force mono on the card)
23  *		(can detect if station is in stereo)
24  *	      - Added unmute function
25  *	      - Reworked ioctl functions
26  * 2002-07-15 - Fix Stereo typo
27  *
28  * 2006-07-24 - Converted to V4L2 API
29  *		by Mauro Carvalho Chehab <mchehab@infradead.org>
30  */
31 
32 #include <linux/module.h>	/* Modules                        */
33 #include <linux/init.h>		/* Initdata                       */
34 #include <linux/ioport.h>	/* request_region		  */
35 #include <linux/delay.h>	/* udelay, msleep                 */
36 #include <asm/io.h>		/* outb, outb_p                   */
37 #include <asm/uaccess.h>	/* copy to/from user              */
38 #include <linux/videodev2.h>	/* kernel radio structs           */
39 #include <media/v4l2-common.h>
40 
41 #include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
42 #define RADIO_VERSION KERNEL_VERSION(0,0,2)
43 
44 static struct v4l2_queryctrl radio_qctrl[] = {
45 	{
46 		.id            = V4L2_CID_AUDIO_MUTE,
47 		.name          = "Mute",
48 		.minimum       = 0,
49 		.maximum       = 1,
50 		.default_value = 1,
51 		.type          = V4L2_CTRL_TYPE_BOOLEAN,
52 	},{
53 		.id            = V4L2_CID_AUDIO_VOLUME,
54 		.name          = "Volume",
55 		.minimum       = 0,
56 		.maximum       = 65535,
57 		.step          = 4096,
58 		.default_value = 0xff,
59 		.type          = V4L2_CTRL_TYPE_INTEGER,
60 	}
61 };
62 
63 #ifndef CONFIG_RADIO_ZOLTRIX_PORT
64 #define CONFIG_RADIO_ZOLTRIX_PORT -1
65 #endif
66 
67 static int io = CONFIG_RADIO_ZOLTRIX_PORT;
68 static int radio_nr = -1;
69 
70 struct zol_device {
71 	int port;
72 	int curvol;
73 	unsigned long curfreq;
74 	int muted;
75 	unsigned int stereo;
76 	struct mutex lock;
77 };
78 
79 static int zol_setvol(struct zol_device *dev, int vol)
80 {
81 	dev->curvol = vol;
82 	if (dev->muted)
83 		return 0;
84 
85 	mutex_lock(&dev->lock);
86 	if (vol == 0) {
87 		outb(0, io);
88 		outb(0, io);
89 		inb(io + 3);    /* Zoltrix needs to be read to confirm */
90 		mutex_unlock(&dev->lock);
91 		return 0;
92 	}
93 
94 	outb(dev->curvol-1, io);
95 	msleep(10);
96 	inb(io + 2);
97 	mutex_unlock(&dev->lock);
98 	return 0;
99 }
100 
101 static void zol_mute(struct zol_device *dev)
102 {
103 	dev->muted = 1;
104 	mutex_lock(&dev->lock);
105 	outb(0, io);
106 	outb(0, io);
107 	inb(io + 3);            /* Zoltrix needs to be read to confirm */
108 	mutex_unlock(&dev->lock);
109 }
110 
111 static void zol_unmute(struct zol_device *dev)
112 {
113 	dev->muted = 0;
114 	zol_setvol(dev, dev->curvol);
115 }
116 
117 static int zol_setfreq(struct zol_device *dev, unsigned long freq)
118 {
119 	/* tunes the radio to the desired frequency */
120 	unsigned long long bitmask, f, m;
121 	unsigned int stereo = dev->stereo;
122 	int i;
123 
124 	if (freq == 0)
125 		return 1;
126 	m = (freq / 160 - 8800) * 2;
127 	f = (unsigned long long) m + 0x4d1c;
128 
129 	bitmask = 0xc480402c10080000ull;
130 	i = 45;
131 
132 	mutex_lock(&dev->lock);
133 
134 	outb(0, io);
135 	outb(0, io);
136 	inb(io + 3);            /* Zoltrix needs to be read to confirm */
137 
138 	outb(0x40, io);
139 	outb(0xc0, io);
140 
141 	bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31));
142 	while (i--) {
143 		if ((bitmask & 0x8000000000000000ull) != 0) {
144 			outb(0x80, io);
145 			udelay(50);
146 			outb(0x00, io);
147 			udelay(50);
148 			outb(0x80, io);
149 			udelay(50);
150 		} else {
151 			outb(0xc0, io);
152 			udelay(50);
153 			outb(0x40, io);
154 			udelay(50);
155 			outb(0xc0, io);
156 			udelay(50);
157 		}
158 		bitmask *= 2;
159 	}
160 	/* termination sequence */
161 	outb(0x80, io);
162 	outb(0xc0, io);
163 	outb(0x40, io);
164 	udelay(1000);
165 	inb(io+2);
166 
167 	udelay(1000);
168 
169 	if (dev->muted)
170 	{
171 		outb(0, io);
172 		outb(0, io);
173 		inb(io + 3);
174 		udelay(1000);
175 	}
176 
177 	mutex_unlock(&dev->lock);
178 
179 	if(!dev->muted)
180 	{
181 		zol_setvol(dev, dev->curvol);
182 	}
183 	return 0;
184 }
185 
186 /* Get signal strength */
187 
188 static int zol_getsigstr(struct zol_device *dev)
189 {
190 	int a, b;
191 
192 	mutex_lock(&dev->lock);
193 	outb(0x00, io);         /* This stuff I found to do nothing */
194 	outb(dev->curvol, io);
195 	msleep(20);
196 
197 	a = inb(io);
198 	msleep(10);
199 	b = inb(io);
200 
201 	mutex_unlock(&dev->lock);
202 
203 	if (a != b)
204 		return (0);
205 
206 	if ((a == 0xcf) || (a == 0xdf)  /* I found this out by playing */
207 		|| (a == 0xef))       /* with a binary scanner on the card io */
208 		return (1);
209 	return (0);
210 }
211 
212 static int zol_is_stereo (struct zol_device *dev)
213 {
214 	int x1, x2;
215 
216 	mutex_lock(&dev->lock);
217 
218 	outb(0x00, io);
219 	outb(dev->curvol, io);
220 	msleep(20);
221 
222 	x1 = inb(io);
223 	msleep(10);
224 	x2 = inb(io);
225 
226 	mutex_unlock(&dev->lock);
227 
228 	if ((x1 == x2) && (x1 == 0xcf))
229 		return 1;
230 	return 0;
231 }
232 
233 static int zol_do_ioctl(struct inode *inode, struct file *file,
234 			unsigned int cmd, void *arg)
235 {
236 	struct video_device *dev = video_devdata(file);
237 	struct zol_device *zol = dev->priv;
238 
239 	switch (cmd) {
240 		case VIDIOC_QUERYCAP:
241 		{
242 			struct v4l2_capability *v = arg;
243 			memset(v,0,sizeof(*v));
244 			strlcpy(v->driver, "radio-zoltrix", sizeof (v->driver));
245 			strlcpy(v->card, "Zoltrix Radio", sizeof (v->card));
246 			sprintf(v->bus_info,"ISA");
247 			v->version = RADIO_VERSION;
248 			v->capabilities = V4L2_CAP_TUNER;
249 
250 			return 0;
251 		}
252 		case VIDIOC_G_TUNER:
253 		{
254 			struct v4l2_tuner *v = arg;
255 
256 			if (v->index > 0)
257 				return -EINVAL;
258 
259 			memset(v,0,sizeof(*v));
260 			strcpy(v->name, "FM");
261 			v->type = V4L2_TUNER_RADIO;
262 
263 			v->rangelow=(88*16000);
264 			v->rangehigh=(108*16000);
265 			v->rxsubchans =V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO;
266 			v->capability=V4L2_TUNER_CAP_LOW;
267 			if(zol_is_stereo(zol))
268 				v->audmode = V4L2_TUNER_MODE_STEREO;
269 			else
270 				v->audmode = V4L2_TUNER_MODE_MONO;
271 			v->signal=0xFFFF*zol_getsigstr(zol);
272 
273 			return 0;
274 		}
275 		case VIDIOC_S_TUNER:
276 		{
277 			struct v4l2_tuner *v = arg;
278 
279 			if (v->index > 0)
280 				return -EINVAL;
281 
282 			return 0;
283 		}
284 		case VIDIOC_S_FREQUENCY:
285 		{
286 			struct v4l2_frequency *f = arg;
287 
288 			zol->curfreq = f->frequency;
289 			zol_setfreq(zol, zol->curfreq);
290 			return 0;
291 		}
292 		case VIDIOC_G_FREQUENCY:
293 		{
294 			struct v4l2_frequency *f = arg;
295 
296 			f->type = V4L2_TUNER_RADIO;
297 			f->frequency = zol->curfreq;
298 
299 			return 0;
300 		}
301 		case VIDIOC_QUERYCTRL:
302 		{
303 			struct v4l2_queryctrl *qc = arg;
304 			int i;
305 
306 			for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
307 				if (qc->id && qc->id == radio_qctrl[i].id) {
308 					memcpy(qc, &(radio_qctrl[i]),
309 								sizeof(*qc));
310 					return (0);
311 				}
312 			}
313 			return -EINVAL;
314 		}
315 		case VIDIOC_G_CTRL:
316 		{
317 			struct v4l2_control *ctrl= arg;
318 
319 			switch (ctrl->id) {
320 				case V4L2_CID_AUDIO_MUTE:
321 					ctrl->value=zol->muted;
322 					return (0);
323 				case V4L2_CID_AUDIO_VOLUME:
324 					ctrl->value=zol->curvol * 4096;
325 					return (0);
326 			}
327 			return -EINVAL;
328 		}
329 		case VIDIOC_S_CTRL:
330 		{
331 			struct v4l2_control *ctrl= arg;
332 
333 			switch (ctrl->id) {
334 				case V4L2_CID_AUDIO_MUTE:
335 					if (ctrl->value) {
336 						zol_mute(zol);
337 					} else {
338 						zol_unmute(zol);
339 						zol_setvol(zol,zol->curvol);
340 					}
341 					return (0);
342 				case V4L2_CID_AUDIO_VOLUME:
343 					zol_setvol(zol,ctrl->value/4096);
344 					return (0);
345 			}
346 			zol->stereo = 1;
347 			zol_setfreq(zol, zol->curfreq);
348 #if 0
349 /* FIXME: Implement stereo/mono switch on V4L2 */
350 			if (v->mode & VIDEO_SOUND_STEREO) {
351 				zol->stereo = 1;
352 				zol_setfreq(zol, zol->curfreq);
353 			}
354 			if (v->mode & VIDEO_SOUND_MONO) {
355 				zol->stereo = 0;
356 				zol_setfreq(zol, zol->curfreq);
357 			}
358 #endif
359 			return -EINVAL;
360 		}
361 
362 		default:
363 			return v4l_compat_translate_ioctl(inode,file,cmd,arg,
364 							  zol_do_ioctl);
365 	}
366 }
367 
368 static int zol_ioctl(struct inode *inode, struct file *file,
369 		     unsigned int cmd, unsigned long arg)
370 {
371 	return video_usercopy(inode, file, cmd, arg, zol_do_ioctl);
372 }
373 
374 static struct zol_device zoltrix_unit;
375 
376 static const struct file_operations zoltrix_fops =
377 {
378 	.owner		= THIS_MODULE,
379 	.open           = video_exclusive_open,
380 	.release        = video_exclusive_release,
381 	.ioctl		= zol_ioctl,
382 	.compat_ioctl	= v4l_compat_ioctl32,
383 	.llseek         = no_llseek,
384 };
385 
386 static struct video_device zoltrix_radio =
387 {
388 	.owner		= THIS_MODULE,
389 	.name		= "Zoltrix Radio Plus",
390 	.type		= VID_TYPE_TUNER,
391 	.hardware	= 0,
392 	.fops           = &zoltrix_fops,
393 };
394 
395 static int __init zoltrix_init(void)
396 {
397 	if (io == -1) {
398 		printk(KERN_ERR "You must set an I/O address with io=0x???\n");
399 		return -EINVAL;
400 	}
401 	if ((io != 0x20c) && (io != 0x30c)) {
402 		printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n");
403 		return -ENXIO;
404 	}
405 
406 	zoltrix_radio.priv = &zoltrix_unit;
407 	if (!request_region(io, 2, "zoltrix")) {
408 		printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io);
409 		return -EBUSY;
410 	}
411 
412 	if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) == -1)
413 	{
414 		release_region(io, 2);
415 		return -EINVAL;
416 	}
417 	printk(KERN_INFO "Zoltrix Radio Plus card driver.\n");
418 
419 	mutex_init(&zoltrix_unit.lock);
420 
421 	/* mute card - prevents noisy bootups */
422 
423 	/* this ensures that the volume is all the way down  */
424 
425 	outb(0, io);
426 	outb(0, io);
427 	msleep(20);
428 	inb(io + 3);
429 
430 	zoltrix_unit.curvol = 0;
431 	zoltrix_unit.stereo = 1;
432 
433 	return 0;
434 }
435 
436 MODULE_AUTHOR("C.van Schaik");
437 MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
438 MODULE_LICENSE("GPL");
439 
440 module_param(io, int, 0);
441 MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
442 module_param(radio_nr, int, 0);
443 
444 static void __exit zoltrix_cleanup_module(void)
445 {
446 	video_unregister_device(&zoltrix_radio);
447 	release_region(io, 2);
448 }
449 
450 module_init(zoltrix_init);
451 module_exit(zoltrix_cleanup_module);
452 
453