patch-2.1.110 linux/drivers/char/radio-zoltrix.c

Next file: linux/drivers/char/radio.c
Previous file: linux/drivers/char/radio-sf16fmi.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.109/linux/drivers/char/radio-zoltrix.c linux/drivers/char/radio-zoltrix.c
@@ -0,0 +1,337 @@
+/* zoltrix radio plus driver for Linux radio support
+ * (c) 1998 C. van Schaik <carl@leg.uct.ac.za>
+ *
+ * BUGS  
+ *  The signal strength query is unsurprisingly inaccurate.  And it seems
+ *  to indicate that (on my card, at least) the frequency setting isn't
+ *  too great.  It seems to work in a similar way to a car stereo which
+ *  flickers when it is near or on a station...
+ *
+ *  There seems to be a problem with the volume setting that I must still
+ *  figure out. This is a minor problem... It still works but you may
+ *  have to set the card lounder to get the same volume.
+ *
+ *  Some code derived from code by Frans Brinkman
+ */
+
+#include <linux/module.h>	/* Modules                        */
+#include <linux/init.h>		/* Initdata                       */
+#include <linux/ioport.h>	/* check_region, request_region   */
+#include <linux/delay.h>	/* udelay                 */
+#include <asm/io.h>		/* outb, outb_p                   */
+#include <asm/uaccess.h>	/* copy to/from user              */
+#include <linux/videodev.h>	/* kernel radio structs           */
+#include <linux/config.h>	/* CONFIG_RADIO_ZOLTRIX_PORT      */
+
+#ifndef CONFIG_RADIO_ZOLTRIX_PORT
+#define CONFIG_RADIO_ZOLTRIX_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_ZOLTRIX_PORT;
+static int users = 0;
+
+struct zol_device {
+	int port;
+	int curvol;
+	unsigned long curfreq;
+	int muted;
+};
+
+
+/* local things */
+
+static void sleep_delay(long n)
+{
+	/* Sleep nicely for 'n' uS */
+	int d = n / (1000000 / HZ);
+	if (!d)
+		udelay(n);
+	else {
+		/* Yield CPU time */
+		unsigned long x = jiffies;
+		while ((jiffies - x) <= d)
+			schedule();
+	}
+}
+
+static void zol_mute(struct zol_device *dev)
+{
+	dev->muted = 1;
+	outb(0, io);
+	outb(0, io);
+	inb(io + 3);		/* Zoltrix needs to be read to confirm */
+}
+
+static int zol_setvol(struct zol_device *dev, int vol)
+{
+	int l;
+
+	if (vol == dev->curvol) {	/* requested volume = current */
+		if (dev->muted) {	/* user is unmuting the card  */
+			dev->muted = 0;
+			outb(vol, io);
+			sleep_delay(20000);
+			l = inb(io + 2);
+		}
+		return 0;
+	}
+	if (vol == 0) {		/* volume = 0 means mute the card */
+		outb(0, io);
+		outb(0, io);
+		l = inb(io + 3);
+		return 0;
+	}
+	dev->muted = 0;
+	dev->curvol = vol;
+
+	outb(vol, io);
+	sleep_delay(20000);
+	l = inb(io + 2);
+
+	return 0;
+}
+
+static int zol_setfreq(struct zol_device *dev, unsigned long freq)
+{
+	/* tunes the radio to the desired frequency */
+	unsigned long long bitmask, f, m;
+	int i, l;
+
+	m = (freq * 25 / 4 - 8800) * 2;
+	f = (unsigned long long) m + 0x4d1c;
+
+	bitmask = 0xc480402c10080000ull;
+	i = 45;
+
+	outb(0x00, io);
+	outb(0x00, io);
+	sleep_delay(10000);
+	inb(io + 3);
+	outb(0x40, io);
+	outb(0xc0, io);
+
+	bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( /*stereo */ 0 << 31));
+	while (i--) {
+		if ((bitmask & 0x8000000000000000ull) != 0) {
+			outb(0x80, io);
+			sleep_delay(50);
+			outb(0x00, io);
+			sleep_delay(50);
+			outb(0x80, io);
+			sleep_delay(50);
+		} else {
+			outb(0xc0, io);
+			sleep_delay(50);
+			outb(0x40, io);
+			sleep_delay(50);
+			outb(0xc0, io);
+			sleep_delay(50);
+		}
+		bitmask *= 2;
+	}
+	/* termination sequence */
+	outb(0x80, io);
+	outb(0xc0, io);
+	outb(0x40, io);
+	outb(0x00, io);
+	sleep_delay(10000);
+	l = inb(io + 2);
+	sleep_delay(10000);
+	l = inb(io + 1);
+	outb(dev->curvol, io);
+	sleep_delay(10000);
+	l = inb(io + 2);
+	return 0;
+}
+
+/* Get signal strenght */
+int zol_getsigstr(struct zol_device *dev)
+{
+	int a, b;
+	outb(0x00, io);
+	sleep_delay(10000);
+	a = inb(io + 2);
+	sleep_delay(20000);
+	b = inb(io + 1);
+	outb(0x00, io);
+
+	if (((a | b) & 255) != 0x0ff)
+		return (1);
+	else
+		return (0);
+
+	if (inb(io) & 2)	/* bit set = no signal present    */
+		return 0;
+	return 1;		/* signal present               */
+}
+
+static int zol_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+	struct zol_device *rt = dev->priv;
+
+	switch (cmd) {
+	case VIDIOCGCAP:
+		{
+			struct video_capability v;
+			v.type = VID_TYPE_TUNER;
+			v.channels = 1;
+			v.audios = 1;
+			/* No we don't do pictures */
+			v.maxwidth = 0;
+			v.maxheight = 0;
+			v.minwidth = 0;
+			v.minheight = 0;
+			if (copy_to_user(arg, &v, sizeof(v)))
+				return -EFAULT;
+			return 0;
+		}
+	case VIDIOCGTUNER:
+		{
+			struct video_tuner v;
+			if (copy_from_user(&v, arg, sizeof(v)) != 0)
+				return -EFAULT;
+			if (v.tuner)	/* Only 1 tuner */
+				return -EINVAL;
+			v.rangelow = (int) (88.0 * 16);
+			v.rangehigh = (int) (108.0 * 16);
+			v.flags = 0;
+			v.mode = VIDEO_MODE_AUTO;
+			v.signal = 0xFFFF * zol_getsigstr(rt);
+			if (copy_to_user(arg, &v, sizeof(v)))
+				return -EFAULT;
+			return 0;
+		}
+	case VIDIOCSTUNER:
+		{
+			struct video_tuner v;
+			if (copy_from_user(&v, arg, sizeof(v)))
+				return -EFAULT;
+			if (v.tuner != 0)
+				return -EINVAL;
+			/* Only 1 tuner so no setting needed ! */
+			return 0;
+		}
+	case VIDIOCGFREQ:
+		if (copy_to_user(arg, &rt->curfreq, sizeof(rt->curfreq)))
+			return -EFAULT;
+		return 0;
+	case VIDIOCSFREQ:
+		if (copy_from_user(&rt->curfreq, arg, sizeof(rt->curfreq)))
+			return -EFAULT;
+		zol_setfreq(rt, rt->curfreq);
+		return 0;
+	case VIDIOCGAUDIO:
+		{
+			struct video_audio v;
+			memset(&v, 0, sizeof(v));
+			v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME;
+			v.volume = rt->curvol * 6554;
+			strcpy(v.name, "Radio");
+			if (copy_to_user(arg, &v, sizeof(v)))
+				return -EFAULT;
+			return 0;
+		}
+	case VIDIOCSAUDIO:
+		{
+			struct video_audio v;
+			if (copy_from_user(&v, arg, sizeof(v)))
+				return -EFAULT;
+			if (v.audio)
+				return -EINVAL;
+
+			if (v.flags & VIDEO_AUDIO_MUTE)
+				zol_mute(rt);
+			else
+				zol_setvol(rt, v.volume / 6554);
+
+			return 0;
+		}
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+static int zol_open(struct video_device *dev, int flags)
+{
+	if (users)
+		return -EBUSY;
+	users++;
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static void zol_close(struct video_device *dev)
+{
+	users--;
+	MOD_DEC_USE_COUNT;
+}
+
+static struct zol_device zoltrix_unit;
+
+static struct video_device zoltrix_radio =
+{
+	"Zoltrix Radio Plus",
+	VID_TYPE_TUNER,
+	VID_HARDWARE_ZOLTRIX,
+	zol_open,
+	zol_close,
+	NULL,			/* Can't read  (no capture ability) */
+	NULL,			/* Can't write */
+	zol_ioctl,
+	NULL,
+	NULL
+};
+
+__initfunc(int zoltrix_init(struct video_init *v))
+{
+	if (check_region(io, 2)) {
+		printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io);
+		return -EBUSY;
+	}
+	zoltrix_radio.priv = &zoltrix_unit;
+
+	if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO) == -1)
+		return -EINVAL;
+
+	request_region(io, 2, "zoltrix");
+	printk(KERN_INFO "Zoltrix Radio Plus card driver.\n");
+
+	/* mute card - prevents noisy bootups */
+
+	/* this ensures that the volume is all the way down  */
+
+	outb(0, io);
+	outb(0, io);
+	sleep_delay(20000);
+	inb(io + 3);
+
+	zoltrix_unit.curvol = 0;
+
+	return 0;
+}
+
+#ifdef MODULE
+
+MODULE_AUTHOR("C.van Schaik");
+MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
+
+EXPORT_NO_SYMBOLS;
+
+int init_module(void)
+{
+	if (io == -1) {
+		printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+		return -EINVAL;
+	}
+	return zoltrix_init(NULL);
+}
+
+void cleanup_module(void)
+{
+	video_unregister_device(&zoltrix_radio);
+	release_region(io, 2);
+}
+
+#endif

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov