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 inconsistancy 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 
29 #include <linux/module.h>	/* Modules                        */
30 #include <linux/init.h>		/* Initdata                       */
31 #include <linux/ioport.h>	/* check_region, request_region   */
32 #include <linux/delay.h>	/* udelay                 */
33 #include <asm/io.h>		/* outb, outb_p                   */
34 #include <asm/uaccess.h>	/* copy to/from user              */
35 #include <linux/videodev.h>	/* kernel radio structs           */
36 #include <linux/config.h>	/* CONFIG_RADIO_ZOLTRIX_PORT      */
37 
38 #ifndef CONFIG_RADIO_ZOLTRIX_PORT
39 #define CONFIG_RADIO_ZOLTRIX_PORT -1
40 #endif
41 
42 static int io = CONFIG_RADIO_ZOLTRIX_PORT;
43 static int radio_nr = -1;
44 static int users = 0;
45 
46 struct zol_device {
47 	int port;
48 	int curvol;
49 	unsigned long curfreq;
50 	int muted;
51 	unsigned int stereo;
52 	struct semaphore lock;
53 };
54 
55 
56 /* local things */
57 
sleep_delay(void)58 static void sleep_delay(void)
59 {
60 	/* Sleep nicely for +/- 10 mS */
61 	schedule();
62 }
63 
zol_setvol(struct zol_device * dev,int vol)64 static int zol_setvol(struct zol_device *dev, int vol)
65 {
66 	dev->curvol = vol;
67 	if (dev->muted)
68 		return 0;
69 
70 	down(&dev->lock);
71 	if (vol == 0) {
72 		outb(0, io);
73 		outb(0, io);
74 		inb(io + 3);    /* Zoltrix needs to be read to confirm */
75 		up(&dev->lock);
76 		return 0;
77 	}
78 
79 	outb(dev->curvol-1, io);
80 	sleep_delay();
81 	inb(io + 2);
82 	up(&dev->lock);
83 	return 0;
84 }
85 
zol_mute(struct zol_device * dev)86 static void zol_mute(struct zol_device *dev)
87 {
88 	dev->muted = 1;
89 	down(&dev->lock);
90 	outb(0, io);
91 	outb(0, io);
92 	inb(io + 3);            /* Zoltrix needs to be read to confirm */
93 	up(&dev->lock);
94 }
95 
zol_unmute(struct zol_device * dev)96 static void zol_unmute(struct zol_device *dev)
97 {
98 	dev->muted = 0;
99 	zol_setvol(dev, dev->curvol);
100 }
101 
zol_setfreq(struct zol_device * dev,unsigned long freq)102 static int zol_setfreq(struct zol_device *dev, unsigned long freq)
103 {
104 	/* tunes the radio to the desired frequency */
105 	unsigned long long bitmask, f, m;
106 	unsigned int stereo = dev->stereo;
107 	int i;
108 
109 	if (freq == 0)
110 		return 1;
111 	m = (freq / 160 - 8800) * 2;
112 	f = (unsigned long long) m + 0x4d1c;
113 
114 	bitmask = 0xc480402c10080000ull;
115 	i = 45;
116 
117 	down(&dev->lock);
118 
119 	outb(0, io);
120 	outb(0, io);
121 	inb(io + 3);            /* Zoltrix needs to be read to confirm */
122 
123 	outb(0x40, io);
124 	outb(0xc0, io);
125 
126 	bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31));
127 	while (i--) {
128 		if ((bitmask & 0x8000000000000000ull) != 0) {
129 			outb(0x80, io);
130 			udelay(50);
131 			outb(0x00, io);
132 			udelay(50);
133 			outb(0x80, io);
134 			udelay(50);
135 		} else {
136 			outb(0xc0, io);
137 			udelay(50);
138 			outb(0x40, io);
139 			udelay(50);
140 			outb(0xc0, io);
141 			udelay(50);
142 		}
143 		bitmask *= 2;
144 	}
145 	/* termination sequence */
146 	outb(0x80, io);
147 	outb(0xc0, io);
148 	outb(0x40, io);
149 	udelay(1000);
150 	inb(io+2);
151 
152         udelay(1000);
153 
154 	if (dev->muted)
155 	{
156 		outb(0, io);
157 		outb(0, io);
158 		inb(io + 3);
159 		udelay(1000);
160 	}
161 
162 	up(&dev->lock);
163 
164 	if(!dev->muted)
165 	{
166 	        zol_setvol(dev, dev->curvol);
167 	}
168 	return 0;
169 }
170 
171 /* Get signal strength */
172 
zol_getsigstr(struct zol_device * dev)173 int zol_getsigstr(struct zol_device *dev)
174 {
175 	int a, b;
176 
177 	down(&dev->lock);
178 	outb(0x00, io);         /* This stuff I found to do nothing */
179 	outb(dev->curvol, io);
180 	sleep_delay();
181 	sleep_delay();
182 
183 	a = inb(io);
184 	sleep_delay();
185 	b = inb(io);
186 
187 	up(&dev->lock);
188 
189 	if (a != b)
190 		return (0);
191 
192         if ((a == 0xcf) || (a == 0xdf)  /* I found this out by playing */
193 		|| (a == 0xef))       /* with a binary scanner on the card io */
194 		return (1);
195  	return (0);
196 }
197 
zol_is_stereo(struct zol_device * dev)198 int zol_is_stereo (struct zol_device *dev)
199 {
200 	int x1, x2;
201 
202 	down(&dev->lock);
203 
204 	outb(0x00, io);
205 	outb(dev->curvol, io);
206 	sleep_delay();
207 	sleep_delay();
208 
209 	x1 = inb(io);
210 	sleep_delay();
211 	x2 = inb(io);
212 
213 	up(&dev->lock);
214 
215 	if ((x1 == x2) && (x1 == 0xcf))
216 		return 1;
217 	return 0;
218 }
219 
zol_ioctl(struct video_device * dev,unsigned int cmd,void * arg)220 static int zol_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
221 {
222 	struct zol_device *zol = dev->priv;
223 
224 	switch (cmd) {
225 	case VIDIOCGCAP:
226 		{
227 			struct video_capability v;
228 			v.type = VID_TYPE_TUNER;
229 			v.channels = 1 + zol->stereo;
230 			v.audios = 1;
231 			/* No we don't do pictures */
232 			v.maxwidth = 0;
233 			v.maxheight = 0;
234 			v.minwidth = 0;
235 			v.minheight = 0;
236 			strcpy(v.name, "Zoltrix Radio");
237 			if (copy_to_user(arg, &v, sizeof(v)))
238 				return -EFAULT;
239 			return 0;
240 		}
241 	case VIDIOCGTUNER:
242 		{
243 			struct video_tuner v;
244 			if (copy_from_user(&v, arg, sizeof(v)))
245 				return -EFAULT;
246 			if (v.tuner)
247 				return -EINVAL;
248 			strcpy(v.name, "FM");
249 			v.rangelow = (int) (88.0 * 16000);
250 			v.rangehigh = (int) (108.0 * 16000);
251 			v.flags = zol_is_stereo(zol)
252 					? VIDEO_TUNER_STEREO_ON : 0;
253 			v.flags |= VIDEO_TUNER_LOW;
254 			v.mode = VIDEO_MODE_AUTO;
255 			v.signal = 0xFFFF * zol_getsigstr(zol);
256 			if (copy_to_user(arg, &v, sizeof(v)))
257 				return -EFAULT;
258 			return 0;
259 		}
260 	case VIDIOCSTUNER:
261 		{
262 			struct video_tuner v;
263 			if (copy_from_user(&v, arg, sizeof(v)))
264 				return -EFAULT;
265 			if (v.tuner != 0)
266 				return -EINVAL;
267 			/* Only 1 tuner so no setting needed ! */
268 			return 0;
269 		}
270 	case VIDIOCGFREQ:
271 		if (copy_to_user(arg, &zol->curfreq, sizeof(zol->curfreq)))
272 			return -EFAULT;
273 		return 0;
274 	case VIDIOCSFREQ:
275 		if (copy_from_user(&zol->curfreq, arg, sizeof(zol->curfreq)))
276 			return -EFAULT;
277 		zol_setfreq(zol, zol->curfreq);
278 		return 0;
279 	case VIDIOCGAUDIO:
280 		{
281 			struct video_audio v;
282 			memset(&v, 0, sizeof(v));
283 			v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME;
284 			v.mode |= zol_is_stereo(zol)
285 				? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO;
286 			v.volume = zol->curvol * 4096;
287 			v.step = 4096;
288 			strcpy(v.name, "Zoltrix Radio");
289 			if (copy_to_user(arg, &v, sizeof(v)))
290 				return -EFAULT;
291 			return 0;
292 		}
293 	case VIDIOCSAUDIO:
294 		{
295 			struct video_audio v;
296 			if (copy_from_user(&v, arg, sizeof(v)))
297 				return -EFAULT;
298 			if (v.audio)
299 				return -EINVAL;
300 
301 			if (v.flags & VIDEO_AUDIO_MUTE)
302 				zol_mute(zol);
303 			else
304 			{
305 				zol_unmute(zol);
306 				zol_setvol(zol, v.volume / 4096);
307 			}
308 
309 			if (v.mode & VIDEO_SOUND_STEREO)
310 			{
311 				zol->stereo = 1;
312 				zol_setfreq(zol, zol->curfreq);
313 			}
314 			if (v.mode & VIDEO_SOUND_MONO)
315 			{
316 				zol->stereo = 0;
317 				zol_setfreq(zol, zol->curfreq);
318 			}
319 
320 			return 0;
321 		}
322 	default:
323 		return -ENOIOCTLCMD;
324 	}
325 }
326 
zol_open(struct video_device * dev,int flags)327 static int zol_open(struct video_device *dev, int flags)
328 {
329 	if (users)
330 		return -EBUSY;
331 	users++;
332 	return 0;
333 }
334 
zol_close(struct video_device * dev)335 static void zol_close(struct video_device *dev)
336 {
337 	users--;
338 }
339 
340 static struct zol_device zoltrix_unit;
341 
342 static struct video_device zoltrix_radio =
343 {
344 	owner:		THIS_MODULE,
345 	name:		"Zoltrix Radio Plus",
346 	type:		VID_TYPE_TUNER,
347 	hardware:	VID_HARDWARE_ZOLTRIX,
348 	open:		zol_open,
349 	close:		zol_close,
350 	ioctl:		zol_ioctl,
351 };
352 
zoltrix_init(void)353 static int __init zoltrix_init(void)
354 {
355 	if (io == -1) {
356 		printk(KERN_ERR "You must set an I/O address with io=0x???\n");
357 		return -EINVAL;
358 	}
359 	if ((io != 0x20c) && (io != 0x30c)) {
360 		printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n");
361 		return -ENXIO;
362 	}
363 
364 	zoltrix_radio.priv = &zoltrix_unit;
365 	if (!request_region(io, 2, "zoltrix")) {
366 		printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io);
367 		return -EBUSY;
368 	}
369 
370 	if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) == -1)
371 	{
372 		release_region(io, 2);
373 		return -EINVAL;
374 	}
375 	printk(KERN_INFO "Zoltrix Radio Plus card driver.\n");
376 
377 	init_MUTEX(&zoltrix_unit.lock);
378 
379 	/* mute card - prevents noisy bootups */
380 
381 	/* this ensures that the volume is all the way down  */
382 
383 	outb(0, io);
384 	outb(0, io);
385 	sleep_delay();
386 	sleep_delay();
387 	inb(io + 3);
388 
389 	zoltrix_unit.curvol = 0;
390 	zoltrix_unit.stereo = 1;
391 
392 	return 0;
393 }
394 
395 MODULE_AUTHOR("C.van Schaik");
396 MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
397 MODULE_LICENSE("GPL");
398 
399 MODULE_PARM(io, "i");
400 MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
401 MODULE_PARM(radio_nr, "i");
402 
403 EXPORT_NO_SYMBOLS;
404 
zoltrix_cleanup_module(void)405 static void __exit zoltrix_cleanup_module(void)
406 {
407 	video_unregister_device(&zoltrix_radio);
408 	release_region(io, 2);
409 }
410 
411 module_init(zoltrix_init);
412 module_exit(zoltrix_cleanup_module);
413 
414