File: /usr/src/linux/drivers/media/radio/radio-zoltrix.c

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