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