summaryrefslogtreecommitdiffstats
path: root/drivers/media
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>2000-08-28 22:00:09 +0000
committerRalf Baechle <ralf@linux-mips.org>2000-08-28 22:00:09 +0000
commit1a1d77dd589de5a567fa95e36aa6999c704ceca4 (patch)
tree141e31f89f18b9fe0831f31852e0435ceaccafc5 /drivers/media
parentfb9c690a18b3d66925a65b17441c37fa14d4370b (diff)
Merge with 2.4.0-test7.
Diffstat (limited to 'drivers/media')
-rw-r--r--drivers/media/Config.in13
-rw-r--r--drivers/media/Makefile21
-rw-r--r--drivers/media/happy.c1
-rw-r--r--drivers/media/radio/Config.in52
-rw-r--r--drivers/media/radio/Makefile75
-rw-r--r--drivers/media/radio/radio-aimslab.c390
-rw-r--r--drivers/media/radio/radio-aztech.c330
-rw-r--r--drivers/media/radio/radio-cadet.c603
-rw-r--r--drivers/media/radio/radio-gemtek.c319
-rw-r--r--drivers/media/radio/radio-miropcm20.c239
-rw-r--r--drivers/media/radio/radio-rtrack2.c280
-rw-r--r--drivers/media/radio/radio-sf16fmi.c339
-rw-r--r--drivers/media/radio/radio-terratec.c358
-rw-r--r--drivers/media/radio/radio-trust.c350
-rw-r--r--drivers/media/radio/radio-typhoon.c402
-rw-r--r--drivers/media/radio/radio-zoltrix.c412
-rw-r--r--drivers/media/video/Config.in45
-rw-r--r--drivers/media/video/Makefile96
-rw-r--r--drivers/media/video/audiochip.h60
-rw-r--r--drivers/media/video/bt848.h355
-rw-r--r--drivers/media/video/bttv-cards.c835
-rw-r--r--drivers/media/video/bttv-driver.c3272
-rw-r--r--drivers/media/video/bttv-if.c337
-rw-r--r--drivers/media/video/bttv.h425
-rw-r--r--drivers/media/video/buz.c3479
-rw-r--r--drivers/media/video/buz.h319
-rw-r--r--drivers/media/video/bw-qcam.c1066
-rw-r--r--drivers/media/video/bw-qcam.h68
-rw-r--r--drivers/media/video/c-qcam.c901
-rw-r--r--drivers/media/video/cpia.c3324
-rw-r--r--drivers/media/video/cpia.h421
-rw-r--r--drivers/media/video/cpia_pp.c745
-rw-r--r--drivers/media/video/cpia_usb.c626
-rw-r--r--drivers/media/video/cs8420.h50
-rw-r--r--drivers/media/video/i2c-old.c450
-rw-r--r--drivers/media/video/i2c-parport.c147
-rw-r--r--drivers/media/video/ibmmpeg2.h94
-rw-r--r--drivers/media/video/msp3400.c1454
-rw-r--r--drivers/media/video/planb.c2341
-rw-r--r--drivers/media/video/planb.h232
-rw-r--r--drivers/media/video/pms.c1074
-rw-r--r--drivers/media/video/saa5249.c683
-rw-r--r--drivers/media/video/saa7110.c429
-rw-r--r--drivers/media/video/saa7111.c418
-rw-r--r--drivers/media/video/saa7121.h132
-rw-r--r--drivers/media/video/saa7146.h117
-rw-r--r--drivers/media/video/saa7146reg.h283
-rw-r--r--drivers/media/video/saa7185.c377
-rw-r--r--drivers/media/video/saa7196.h117
-rw-r--r--drivers/media/video/stallion.c5329
-rw-r--r--drivers/media/video/stradis.c2287
-rw-r--r--drivers/media/video/tda7432.c505
-rw-r--r--drivers/media/video/tda8425.c324
-rw-r--r--drivers/media/video/tda985x.c536
-rw-r--r--drivers/media/video/tda9875.c403
-rw-r--r--drivers/media/video/tea6300.c344
-rw-r--r--drivers/media/video/tea6420.c273
-rw-r--r--drivers/media/video/tuner-3036.c227
-rw-r--r--drivers/media/video/tuner.c451
-rw-r--r--drivers/media/video/tuner.h57
-rw-r--r--drivers/media/video/tvmixer.c353
-rw-r--r--drivers/media/video/videodev.c581
-rw-r--r--drivers/media/video/vino.c275
-rw-r--r--drivers/media/video/vino.h118
-rw-r--r--drivers/media/video/zr36057.h168
-rw-r--r--drivers/media/video/zr36060.h35
-rw-r--r--drivers/media/video/zr36120.c2086
-rw-r--r--drivers/media/video/zr36120.h279
-rw-r--r--drivers/media/video/zr36120_i2c.c133
-rw-r--r--drivers/media/video/zr36120_mem.c77
-rw-r--r--drivers/media/video/zr36120_mem.h3
71 files changed, 43800 insertions, 0 deletions
diff --git a/drivers/media/Config.in b/drivers/media/Config.in
new file mode 100644
index 000000000..ad64c65d4
--- /dev/null
+++ b/drivers/media/Config.in
@@ -0,0 +1,13 @@
+#
+# Multimedia device configuration
+#
+mainmenu_option next_comment
+comment 'Multimedia devices'
+
+tristate 'Video For Linux' CONFIG_VIDEO_DEV
+if [ "$CONFIG_VIDEO_DEV" != "n" ]; then
+ source drivers/media/video/Config.in
+ source drivers/media/radio/Config.in
+fi
+
+endmenu
diff --git a/drivers/media/Makefile b/drivers/media/Makefile
new file mode 100644
index 000000000..348687b5f
--- /dev/null
+++ b/drivers/media/Makefile
@@ -0,0 +1,21 @@
+#
+# Makefile for the kernel multimedia device drivers.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definitions are now inherited from the
+# parent makes..
+#
+
+MEDIAS = $(join $(SUB_DIRS),$(SUB_DIRS:%=/%.o))
+
+SUB_DIRS := video radio
+MOD_SUB_DIRS := $(SUB_DIRS)
+ALL_SUB_DIRS := $(SUB_DIRS)
+
+O_TARGET := media.o
+O_OBJS := happy.o $(MEDIAS)
+
+include $(TOPDIR)/Rules.make
diff --git a/drivers/media/happy.c b/drivers/media/happy.c
new file mode 100644
index 000000000..4954935c1
--- /dev/null
+++ b/drivers/media/happy.c
@@ -0,0 +1 @@
+/* This is here to keep the linker happy. */
diff --git a/drivers/media/radio/Config.in b/drivers/media/radio/Config.in
new file mode 100644
index 000000000..34b396acf
--- /dev/null
+++ b/drivers/media/radio/Config.in
@@ -0,0 +1,52 @@
+#
+# Multimedia Video device configuration
+#
+mainmenu_option next_comment
+comment 'Radio Adapters'
+
+dep_tristate ' ADS Cadet AM/FM Tuner' CONFIG_RADIO_CADET $CONFIG_VIDEO_DEV
+dep_tristate ' AIMSlab RadioTrack (aka RadioReveal) support' CONFIG_RADIO_RTRACK $CONFIG_VIDEO_DEV
+if [ "$CONFIG_RADIO_RTRACK" = "y" ]; then
+ hex ' RadioTrack i/o port (0x20f or 0x30f)' CONFIG_RADIO_RTRACK_PORT 20f
+fi
+dep_tristate ' AIMSlab RadioTrack II support' CONFIG_RADIO_RTRACK2 $CONFIG_VIDEO_DEV
+if [ "$CONFIG_RADIO_RTRACK2" = "y" ]; then
+ hex ' RadioTrack II i/o port (0x20c or 0x30c)' CONFIG_RADIO_RTRACK2_PORT 30c
+fi
+dep_tristate ' Aztech/Packard Bell Radio' CONFIG_RADIO_AZTECH $CONFIG_VIDEO_DEV
+if [ "$CONFIG_RADIO_AZTECH" = "y" ]; then
+ hex ' Aztech/Packard Bell I/O port (0x350 or 0x358)' CONFIG_RADIO_AZTECH_PORT 350
+fi
+dep_tristate ' GemTek Radio Card support' CONFIG_RADIO_GEMTEK $CONFIG_VIDEO_DEV
+if [ "$CONFIG_RADIO_GEMTEK" = "y" ]; then
+ hex ' GemTek i/o port (0x20c, 0x30c, 0x24c or 0x34c)' CONFIG_RADIO_GEMTEK_PORT 34c
+fi
+dep_tristate ' Miro PCM20 Radio' CONFIG_RADIO_MIROPCM20 $CONFIG_VIDEO_DEV
+dep_tristate ' SF16FMI Radio' CONFIG_RADIO_SF16FMI $CONFIG_VIDEO_DEV
+if [ "$CONFIG_RADIO_SF16FMI" = "y" ]; then
+ hex ' SF16FMI I/O port (0x284 or 0x384)' CONFIG_RADIO_SF16FMI_PORT 284
+fi
+dep_tristate ' TerraTec ActiveRadio ISA Standalone' CONFIG_RADIO_TERRATEC $CONFIG_VIDEO_DEV
+if [ "$CONFIG_RADIO_TERRATEC" = "y" ]; then
+ hex ' Terratec i/o port (normally 0x590)' CONFIG_RADIO_TERRATEC_PORT 590
+fi
+dep_tristate ' Trust FM radio card' CONFIG_RADIO_TRUST $CONFIG_VIDEO_DEV
+if [ "$CONFIG_RADIO_TRUST" = "y" ]; then
+ hex ' Trust i/o port (usually 0x350 or 0x358)' CONFIG_RADIO_TRUST_PORT 350
+fi
+dep_tristate ' Typhoon Radio (a.k.a. EcoRadio)' CONFIG_RADIO_TYPHOON $CONFIG_VIDEO_DEV
+if [ "$CONFIG_PROC_FS" = "y" ]; then
+ if [ "$CONFIG_RADIO_TYPHOON" != "n" ]; then
+ bool ' Support for /proc/radio-typhoon' CONFIG_RADIO_TYPHOON_PROC_FS
+ fi
+fi
+if [ "$CONFIG_RADIO_TYPHOON" = "y" ]; then
+ hex ' Typhoon I/O port (0x316 or 0x336)' CONFIG_RADIO_TYPHOON_PORT 316
+ int ' Typhoon frequency set when muting the device (kHz)' CONFIG_RADIO_TYPHOON_MUTEFREQ 87500
+fi
+dep_tristate ' Zoltrix Radio' CONFIG_RADIO_ZOLTRIX $CONFIG_VIDEO_DEV
+if [ "$CONFIG_RADIO_ZOLTRIX" = "y" ]; then
+ hex ' ZOLTRIX I/O port (0x20c or 0x30c)' CONFIG_RADIO_ZOLTRIX_PORT 20c
+fi
+
+endmenu
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
new file mode 100644
index 000000000..1b74d6b88
--- /dev/null
+++ b/drivers/media/radio/Makefile
@@ -0,0 +1,75 @@
+#
+# Makefile for the kernel character device drivers.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definitions are now inherited from the
+# parent makes..
+#
+
+O_OBJS :=
+OX_OBJS :=
+M_OBJS :=
+MX_OBJS :=
+
+# Object file lists.
+
+obj-y :=
+obj-m :=
+obj-n :=
+obj- :=
+
+SUB_DIRS :=
+MOD_SUB_DIRS := $(SUB_DIRS)
+ALL_SUB_DIRS := $(SUB_DIRS)
+
+O_TARGET := radio.o
+
+# All of the (potential) objects that export symbols.
+# This list comes from 'grep -l EXPORT_SYMBOL *.[hc]'.
+
+export-objs :=
+
+list-multi :=
+
+obj-$(CONFIG_RADIO_AZTECH) += radio-aztech.o
+obj-$(CONFIG_RADIO_RTRACK2) += radio-rtrack2.o
+obj-$(CONFIG_RADIO_SF16FMI) += radio-sf16fmi.o
+obj-$(CONFIG_RADIO_CADET) += radio-cadet.o
+obj-$(CONFIG_RADIO_TYPHOON) += radio-typhoon.o
+obj-$(CONFIG_RADIO_TERRATEC) += radio-terratec.o
+obj-$(CONFIG_RADIO_RTRACK) += radio-aimslab.o
+obj-$(CONFIG_RADIO_ZOLTRIX) += radio-zoltrix.o
+obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o
+obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o
+obj-$(CONFIG_RADIO_TRUST) += radio-trust.o
+
+# Extract lists of the multi-part drivers.
+# The 'int-*' lists are the intermediate files used to build the multi's.
+
+multi-y := $(filter $(list-multi), $(obj-y))
+multi-m := $(filter $(list-multi), $(obj-m))
+int-y := $(sort $(foreach m, $(multi-y), $($(basename $(m))-objs)))
+int-m := $(sort $(foreach m, $(multi-m), $($(basename $(m))-objs)))
+
+# Files that are both resident and modular: remove from modular.
+
+obj-m := $(filter-out $(obj-y), $(obj-m))
+int-m := $(filter-out $(int-y), $(int-m))
+
+# Take multi-part drivers out of obj-y and put components in.
+
+obj-y := $(filter-out $(list-multi), $(obj-y)) $(int-y)
+
+# Translate to Rules.make lists.
+
+O_OBJS := $(filter-out $(export-objs), $(obj-y))
+OX_OBJS := $(filter $(export-objs), $(obj-y))
+M_OBJS := $(sort $(filter-out $(export-objs), $(obj-m)))
+MX_OBJS := $(sort $(filter $(export-objs), $(obj-m)))
+MI_OBJS := $(sort $(filter-out $(export-objs), $(int-m)))
+MIX_OBJS := $(sort $(filter $(export-objs), $(int-m)))
+
+include $(TOPDIR)/Rules.make
diff --git a/drivers/media/radio/radio-aimslab.c b/drivers/media/radio/radio-aimslab.c
new file mode 100644
index 000000000..d716c54bd
--- /dev/null
+++ b/drivers/media/radio/radio-aimslab.c
@@ -0,0 +1,390 @@
+/* radiotrack (radioreveal) driver for Linux radio support
+ * (c) 1997 M. Kirkwood
+ * Coverted to new API by Alan Cox <Alan.Cox@linux.org>
+ * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
+ *
+ * History:
+ * 1999-02-24 Russell Kroll <rkroll@exploits.org>
+ * Fine tuning/VIDEO_TUNER_LOW
+ * Frequency range expanded to start at 87 MHz
+ *
+ * TODO: Allow for more than one of these foolish entities :-)
+ *
+ * Notes on the hardware (reverse engineered from other peoples'
+ * reverse engineering of AIMS' code :-)
+ *
+ * Frequency control is done digitally -- ie out(port,encodefreq(95.8));
+ *
+ * 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. (I have to tune up .025MHz from what the freq should be
+ * to get a report that the thing is tuned.)
+ *
+ * Volume control is (ugh) analogue:
+ * out(port, start_increasing_volume);
+ * wait(a_wee_while);
+ * out(port, stop_changing_the_volume);
+ *
+ */
+
+#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_RTRACK_PORT */
+#include <asm/semaphore.h> /* Lock for the I/O */
+
+#ifndef CONFIG_RADIO_RTRACK_PORT
+#define CONFIG_RADIO_RTRACK_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_RTRACK_PORT;
+static int users = 0;
+static struct semaphore lock;
+
+struct rt_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 rt_decvol(void)
+{
+ outb(0x58, io); /* volume down + sigstr + on */
+ sleep_delay(100000);
+ outb(0xd8, io); /* volume steady + sigstr + on */
+}
+
+static void rt_incvol(void)
+{
+ outb(0x98, io); /* volume up + sigstr + on */
+ sleep_delay(100000);
+ outb(0xd8, io); /* volume steady + sigstr + on */
+}
+
+static void rt_mute(struct rt_device *dev)
+{
+ dev->muted = 1;
+ down(&lock);
+ outb(0xd0, io); /* volume steady, off */
+ up(&lock);
+}
+
+static int rt_setvol(struct rt_device *dev, int vol)
+{
+ int i;
+
+ down(&lock);
+
+ if(vol == dev->curvol) { /* requested volume = current */
+ if (dev->muted) { /* user is unmuting the card */
+ dev->muted = 0;
+ outb (0xd8, io); /* enable card */
+ }
+ up(&lock);
+ return 0;
+ }
+
+ if(vol == 0) { /* volume = 0 means mute the card */
+ outb(0x48, io); /* volume down but still "on" */
+ sleep_delay(2000000); /* make sure it's totally down */
+ outb(0xd0, io); /* volume steady, off */
+ dev->curvol = 0; /* track the volume state! */
+ up(&lock);
+ return 0;
+ }
+
+ dev->muted = 0;
+ if(vol > dev->curvol)
+ for(i = dev->curvol; i < vol; i++)
+ rt_incvol();
+ else
+ for(i = dev->curvol; i > vol; i--)
+ rt_decvol();
+
+ dev->curvol = vol;
+ up(&lock);
+ return 0;
+}
+
+/* the 128+64 on these outb's is to keep the volume stable while tuning
+ * without them, the volume _will_ creep up with each frequency change
+ * and bit 4 (+16) is to keep the signal strength meter enabled
+ */
+
+void send_0_byte(int port, struct rt_device *dev)
+{
+ if ((dev->curvol == 0) || (dev->muted)) {
+ outb_p(128+64+16+ 1, port); /* wr-enable + data low */
+ outb_p(128+64+16+2+1, port); /* clock */
+ }
+ else {
+ outb_p(128+64+16+8+ 1, port); /* on + wr-enable + data low */
+ outb_p(128+64+16+8+2+1, port); /* clock */
+ }
+ sleep_delay(1000);
+}
+
+void send_1_byte(int port, struct rt_device *dev)
+{
+ if ((dev->curvol == 0) || (dev->muted)) {
+ outb_p(128+64+16+4 +1, port); /* wr-enable+data high */
+ outb_p(128+64+16+4+2+1, port); /* clock */
+ }
+ else {
+ outb_p(128+64+16+8+4 +1, port); /* on+wr-enable+data high */
+ outb_p(128+64+16+8+4+2+1, port); /* clock */
+ }
+
+ sleep_delay(1000);
+}
+
+static int rt_setfreq(struct rt_device *dev, unsigned long freq)
+{
+ int i;
+
+ /* adapted from radio-aztech.c */
+
+ /* now uses VIDEO_TUNER_LOW for fine tuning */
+
+ freq += 171200; /* Add 10.7 MHz IF */
+ freq /= 800; /* Convert to 50 kHz units */
+
+ down(&lock); /* Stop other ops interfering */
+
+ send_0_byte (io, dev); /* 0: LSB of frequency */
+
+ for (i = 0; i < 13; i++) /* : frequency bits (1-13) */
+ if (freq & (1 << i))
+ send_1_byte (io, dev);
+ else
+ send_0_byte (io, dev);
+
+ send_0_byte (io, dev); /* 14: test bit - always 0 */
+ send_0_byte (io, dev); /* 15: test bit - always 0 */
+
+ send_0_byte (io, dev); /* 16: band data 0 - always 0 */
+ send_0_byte (io, dev); /* 17: band data 1 - always 0 */
+ send_0_byte (io, dev); /* 18: band data 2 - always 0 */
+ send_0_byte (io, dev); /* 19: time base - always 0 */
+
+ send_0_byte (io, dev); /* 20: spacing (0 = 25 kHz) */
+ send_1_byte (io, dev); /* 21: spacing (1 = 25 kHz) */
+ send_0_byte (io, dev); /* 22: spacing (0 = 25 kHz) */
+ send_1_byte (io, dev); /* 23: AM/FM (FM = 1, always) */
+
+ if ((dev->curvol == 0) || (dev->muted))
+ outb (0xd0, io); /* volume steady + sigstr */
+ else
+ outb (0xd8, io); /* volume steady + sigstr + on */
+
+ up(&lock);
+
+ return 0;
+}
+
+static int rt_getsigstr(struct rt_device *dev)
+{
+ if (inb(io) & 2) /* bit set = no signal present */
+ return 0;
+ return 1; /* signal present */
+}
+
+static int rt_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct rt_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;
+ strcpy(v.name, "RadioTrack");
+ 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=(87*16000);
+ v.rangehigh=(108*16000);
+ v.flags=VIDEO_TUNER_LOW;
+ v.mode=VIDEO_MODE_AUTO;
+ strcpy(v.name, "FM");
+ v.signal=0xFFFF*rt_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;
+ rt_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;
+ v.step=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)
+ rt_mute(rt);
+ else
+ rt_setvol(rt,v.volume/6554);
+
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int rt_open(struct video_device *dev, int flags)
+{
+ if(users)
+ return -EBUSY;
+ users++;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void rt_close(struct video_device *dev)
+{
+ users--;
+ MOD_DEC_USE_COUNT;
+}
+
+static struct rt_device rtrack_unit;
+
+static struct video_device rtrack_radio=
+{
+ "RadioTrack radio",
+ VID_TYPE_TUNER,
+ VID_HARDWARE_RTRACK,
+ rt_open,
+ rt_close,
+ NULL, /* Can't read (no capture ability) */
+ NULL, /* Can't write */
+ NULL, /* No poll */
+ rt_ioctl,
+ NULL,
+ NULL
+};
+
+static int __init rtrack_init(void)
+{
+ if(io==-1)
+ {
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+ return -EINVAL;
+ }
+
+ if (check_region(io, 2))
+ {
+ printk(KERN_ERR "rtrack: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ rtrack_radio.priv=&rtrack_unit;
+
+ if(video_register_device(&rtrack_radio, VFL_TYPE_RADIO)==-1)
+ return -EINVAL;
+
+ request_region(io, 2, "rtrack");
+ printk(KERN_INFO "AIMSlab RadioTrack/RadioReveal card driver.\n");
+
+ /* Set up the I/O locking */
+
+ init_MUTEX(&lock);
+
+ /* mute card - prevents noisy bootups */
+
+ /* this ensures that the volume is all the way down */
+ outb(0x48, io); /* volume down but still "on" */
+ sleep_delay(2000000); /* make sure it's totally down */
+ outb(0xc0, io); /* steady volume, mute card */
+ rtrack_unit.curvol = 0;
+
+ return 0;
+}
+
+MODULE_AUTHOR("M.Kirkwood");
+MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card.");
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)");
+
+EXPORT_NO_SYMBOLS;
+
+static void __exit cleanup_rtrack_module(void)
+{
+ video_unregister_device(&rtrack_radio);
+ release_region(io,2);
+}
+
+module_init(rtrack_init);
+module_exit(cleanup_rtrack_module);
+
diff --git a/drivers/media/radio/radio-aztech.c b/drivers/media/radio/radio-aztech.c
new file mode 100644
index 000000000..2fb8714ad
--- /dev/null
+++ b/drivers/media/radio/radio-aztech.c
@@ -0,0 +1,330 @@
+/* radio-aztech.c - Aztech radio card driver for Linux 2.2
+ *
+ * Adapted to support the Video for Linux API by
+ * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by:
+ *
+ * Quay Ly
+ * Donald Song
+ * Jason Lewis (jlewis@twilight.vtc.vsc.edu)
+ * Scott McGrath (smcgrath@twilight.vtc.vsc.edu)
+ * William McGrath (wmcgrath@twilight.vtc.vsc.edu)
+ *
+ * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/
+ * along with more information on the card itself.
+ *
+ * History:
+ * 1999-02-24 Russell Kroll <rkroll@exploits.org>
+ * Fine tuning/VIDEO_TUNER_LOW
+ * Range expanded to 87-108 MHz (from 87.9-107.8)
+ *
+ * Notable changes from the original source:
+ * - includes stripped down to the essentials
+ * - for loops used as delays replaced with udelay()
+ * - #defines removed, changed to static values
+ * - tuning structure changed - no more character arrays, other changes
+*/
+
+#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_AZTECH_PORT */
+
+/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */
+
+#ifndef CONFIG_RADIO_AZTECH_PORT
+#define CONFIG_RADIO_AZTECH_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_AZTECH_PORT;
+static int radio_wait_time = 1000;
+static int users = 0;
+static struct semaphore lock;
+
+struct az_device
+{
+ int curvol;
+ unsigned long curfreq;
+ int stereo;
+};
+
+static int volconvert(int level)
+{
+ level>>=14; /* Map 16bits down to 2 bit */
+ level&=3;
+
+ /* convert to card-friendly values */
+ switch (level)
+ {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ return 4;
+ case 3:
+ return 5;
+ }
+ return 0; /* Quieten gcc */
+}
+
+static void send_0_byte (struct az_device *dev)
+{
+ udelay(radio_wait_time);
+ outb_p(2+volconvert(dev->curvol), io);
+ outb_p(64+2+volconvert(dev->curvol), io);
+}
+
+static void send_1_byte (struct az_device *dev)
+{
+ udelay (radio_wait_time);
+ outb_p(128+2+volconvert(dev->curvol), io);
+ outb_p(128+64+2+volconvert(dev->curvol), io);
+}
+
+static int az_setvol(struct az_device *dev, int vol)
+{
+ down(&lock);
+ outb (volconvert(vol), io);
+ up(&lock);
+ return 0;
+}
+
+/* thanks to Michael Dwyer for giving me a dose of clues in
+ * the signal strength department..
+ *
+ * This card has a stereo bit - bit 0 set = mono, not set = stereo
+ * It also has a "signal" bit - bit 1 set = bad signal, not set = good
+ *
+ */
+
+static int az_getsigstr(struct az_device *dev)
+{
+ if (inb(io) & 2) /* bit set = no signal present */
+ return 0;
+ return 1; /* signal present */
+}
+
+static int az_getstereo(struct az_device *dev)
+{
+ if (inb(io) & 1) /* bit set = mono */
+ return 0;
+ return 1; /* stereo */
+}
+
+static int az_setfreq(struct az_device *dev, unsigned long frequency)
+{
+ int i;
+
+ frequency += 171200; /* Add 10.7 MHz IF */
+ frequency /= 800; /* Convert to 50 kHz units */
+
+ down(&lock);
+
+ send_0_byte (dev); /* 0: LSB of frequency */
+
+ for (i = 0; i < 13; i++) /* : frequency bits (1-13) */
+ if (frequency & (1 << i))
+ send_1_byte (dev);
+ else
+ send_0_byte (dev);
+
+ send_0_byte (dev); /* 14: test bit - always 0 */
+ send_0_byte (dev); /* 15: test bit - always 0 */
+ send_0_byte (dev); /* 16: band data 0 - always 0 */
+ if (dev->stereo) /* 17: stereo (1 to enable) */
+ send_1_byte (dev);
+ else
+ send_0_byte (dev);
+
+ send_1_byte (dev); /* 18: band data 1 - unknown */
+ send_0_byte (dev); /* 19: time base - always 0 */
+ send_0_byte (dev); /* 20: spacing (0 = 25 kHz) */
+ send_1_byte (dev); /* 21: spacing (1 = 25 kHz) */
+ send_0_byte (dev); /* 22: spacing (0 = 25 kHz) */
+ send_1_byte (dev); /* 23: AM/FM (FM = 1, always) */
+
+ /* latch frequency */
+
+ udelay (radio_wait_time);
+ outb_p(128+64+volconvert(dev->curvol), io);
+
+ up(&lock);
+
+ return 0;
+}
+
+static int az_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct az_device *az=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;
+ strcpy(v.name, "Aztech Radio");
+ 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=(87*16000);
+ v.rangehigh=(108*16000);
+ v.flags=VIDEO_TUNER_LOW;
+ v.mode=VIDEO_MODE_AUTO;
+ v.signal=0xFFFF*az_getsigstr(az);
+ if(az_getstereo(az))
+ v.flags|=VIDEO_TUNER_STEREO_ON;
+ strcpy(v.name, "FM");
+ 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, &az->curfreq, sizeof(az->curfreq)))
+ return -EFAULT;
+ return 0;
+ case VIDIOCSFREQ:
+ if(copy_from_user(&az->curfreq, arg,sizeof(az->curfreq)))
+ return -EFAULT;
+ az_setfreq(az, az->curfreq);
+ return 0;
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+ memset(&v,0, sizeof(v));
+ v.flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
+ if(az->stereo)
+ v.mode=VIDEO_SOUND_STEREO;
+ else
+ v.mode=VIDEO_SOUND_MONO;
+ v.volume=az->curvol;
+ v.step=16384;
+ 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;
+ az->curvol=v.volume;
+
+ az->stereo=(v.mode&VIDEO_SOUND_STEREO)?1:0;
+ if(v.flags&VIDEO_AUDIO_MUTE)
+ az_setvol(az,0);
+ else
+ az_setvol(az,az->curvol);
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int az_open(struct video_device *dev, int flags)
+{
+ if(users)
+ return -EBUSY;
+ users++;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void az_close(struct video_device *dev)
+{
+ users--;
+ MOD_DEC_USE_COUNT;
+}
+
+static struct az_device aztech_unit;
+
+static struct video_device aztech_radio=
+{
+ "Aztech radio",
+ VID_TYPE_TUNER,
+ VID_HARDWARE_AZTECH,
+ az_open,
+ az_close,
+ NULL, /* Can't read (no capture ability) */
+ NULL, /* Can't write */
+ NULL, /* No poll */
+ az_ioctl,
+ NULL,
+ NULL
+};
+
+static int __init aztech_init(void)
+{
+ if(io==-1)
+ {
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+ return -EINVAL;
+ }
+
+ if (check_region(io, 2))
+ {
+ printk(KERN_ERR "aztech: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ init_MUTEX(&lock);
+ aztech_radio.priv=&aztech_unit;
+
+ if(video_register_device(&aztech_radio, VFL_TYPE_RADIO)==-1)
+ return -EINVAL;
+
+ request_region(io, 2, "aztech");
+ printk(KERN_INFO "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n");
+ /* mute card - prevents noisy bootups */
+ outb (0, io);
+ return 0;
+}
+
+MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
+MODULE_DESCRIPTION("A driver for the Aztech radio card.");
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)");
+
+EXPORT_NO_SYMBOLS;
+
+static void __exit aztech_cleanup(void)
+{
+ video_unregister_device(&aztech_radio);
+ release_region(io,2);
+}
+
+module_init(aztech_init);
+module_exit(aztech_cleanup);
diff --git a/drivers/media/radio/radio-cadet.c b/drivers/media/radio/radio-cadet.c
new file mode 100644
index 000000000..ed2056dea
--- /dev/null
+++ b/drivers/media/radio/radio-cadet.c
@@ -0,0 +1,603 @@
+/* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card
+ *
+ * by Fred Gleason <fredg@wava.com>
+ * Version 0.3.3
+ *
+ * (Loosely) based on code for the Aztech radio card by
+ *
+ * Russell Kroll (rkroll@exploits.org)
+ * Quay Ly
+ * Donald Song
+ * Jason Lewis (jlewis@twilight.vtc.vsc.edu)
+ * Scott McGrath (smcgrath@twilight.vtc.vsc.edu)
+ * William McGrath (wmcgrath@twilight.vtc.vsc.edu)
+ *
+*/
+
+#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_CADET_PORT */
+#include <linux/param.h>
+
+#ifndef CONFIG_RADIO_CADET_PORT
+#define CONFIG_RADIO_CADET_PORT 0x330
+#endif
+#define RDS_BUFFER 256
+
+static int io=CONFIG_RADIO_CADET_PORT;
+static int users=0;
+static int curtuner=0;
+static int tunestat=0;
+static int sigstrength=0;
+static wait_queue_head_t tunerq,rdsq,readq;
+struct timer_list tunertimer,rdstimer,readtimer;
+static __u8 rdsin=0,rdsout=0,rdsstat=0;
+static unsigned char rdsbuf[RDS_BUFFER];
+static int cadet_lock=0;
+
+/*
+ * Signal Strength Threshold Values
+ * The V4L API spec does not define any particular unit for the signal
+ * strength value. These values are in microvolts of RF at the tuner's input.
+ */
+static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}};
+
+
+
+void cadet_wake(unsigned long qnum)
+{
+ switch(qnum) {
+ case 0: /* cadet_setfreq */
+ wake_up(&tunerq);
+ break;
+ case 1: /* cadet_getrds */
+ wake_up(&rdsq);
+ break;
+ }
+}
+
+
+
+static int cadet_getrds(void)
+{
+ int rdsstat=0;
+
+ cadet_lock++;
+ outb(3,io); /* Select Decoder Control/Status */
+ outb(inb(io+1)&0x7f,io+1); /* Reset RDS detection */
+ cadet_lock--;
+ init_timer(&rdstimer);
+ rdstimer.function=cadet_wake;
+ rdstimer.data=(unsigned long)1;
+ rdstimer.expires=jiffies+(HZ/10);
+ init_waitqueue_head(&rdsq);
+ add_timer(&rdstimer);
+ sleep_on(&rdsq);
+
+ cadet_lock++;
+ outb(3,io); /* Select Decoder Control/Status */
+ if((inb(io+1)&0x80)!=0) {
+ rdsstat|=VIDEO_TUNER_RDS_ON;
+ }
+ if((inb(io+1)&0x10)!=0) {
+ rdsstat|=VIDEO_TUNER_MBS_ON;
+ }
+ cadet_lock--;
+ return rdsstat;
+}
+
+
+
+
+static int cadet_getstereo(void)
+{
+ if(curtuner!=0) { /* Only FM has stereo capability! */
+ return 0;
+ }
+ cadet_lock++;
+ outb(7,io); /* Select tuner control */
+ if((inb(io+1)&0x40)==0) {
+ cadet_lock--;
+ return 1; /* Stereo pilot detected */
+ }
+ else {
+ cadet_lock--;
+ return 0; /* Mono */
+ }
+}
+
+
+
+static unsigned cadet_gettune(void)
+{
+ int curvol,i;
+ unsigned fifo=0;
+
+ /*
+ * Prepare for read
+ */
+ cadet_lock++;
+ outb(7,io); /* Select tuner control */
+ curvol=inb(io+1); /* Save current volume/mute setting */
+ outb(0x00,io+1); /* Ensure WRITE-ENABLE is LOW */
+ tunestat=0xffff;
+
+ /*
+ * Read the shift register
+ */
+ for(i=0;i<25;i++) {
+ fifo=(fifo<<1)|((inb(io+1)>>7)&0x01);
+ if(i<24) {
+ outb(0x01,io+1);
+ tunestat&=inb(io+1);
+ outb(0x00,io+1);
+ }
+ }
+
+ /*
+ * Restore volume/mute setting
+ */
+ outb(curvol,io+1);
+ cadet_lock--;
+
+ return fifo;
+}
+
+
+
+static unsigned cadet_getfreq(void)
+{
+ int i;
+ unsigned freq=0,test,fifo=0;
+
+ /*
+ * Read current tuning
+ */
+ fifo=cadet_gettune();
+
+ /*
+ * Convert to actual frequency
+ */
+ if(curtuner==0) { /* FM */
+ test=12500;
+ for(i=0;i<14;i++) {
+ if((fifo&0x01)!=0) {
+ freq+=test;
+ }
+ test=test<<1;
+ fifo=fifo>>1;
+ }
+ freq-=10700000; /* IF frequency is 10.7 MHz */
+ freq=(freq*16)/1000000; /* Make it 1/16 MHz */
+ }
+ if(curtuner==1) { /* AM */
+ freq=((fifo&0x7fff)-2010)*16;
+ }
+
+ return freq;
+}
+
+
+
+static void cadet_settune(unsigned fifo)
+{
+ int i;
+ unsigned test;
+
+ cadet_lock++;
+ outb(7,io); /* Select tuner control */
+ /*
+ * Write the shift register
+ */
+ test=0;
+ test=(fifo>>23)&0x02; /* Align data for SDO */
+ test|=0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */
+ outb(7,io); /* Select tuner control */
+ outb(test,io+1); /* Initialize for write */
+ for(i=0;i<25;i++) {
+ test|=0x01; /* Toggle SCK High */
+ outb(test,io+1);
+ test&=0xfe; /* Toggle SCK Low */
+ outb(test,io+1);
+ fifo=fifo<<1; /* Prepare the next bit */
+ test=0x1c|((fifo>>23)&0x02);
+ outb(test,io+1);
+ }
+ cadet_lock--;
+}
+
+
+
+static void cadet_setfreq(unsigned freq)
+{
+ unsigned fifo;
+ int i,j,test;
+ int curvol;
+
+ /*
+ * Formulate a fifo command
+ */
+ fifo=0;
+ if(curtuner==0) { /* FM */
+ test=102400;
+ freq=(freq*1000)/16; /* Make it kHz */
+ freq+=10700; /* IF is 10700 kHz */
+ for(i=0;i<14;i++) {
+ fifo=fifo<<1;
+ if(freq>=test) {
+ fifo|=0x01;
+ freq-=test;
+ }
+ test=test>>1;
+ }
+ }
+ if(curtuner==1) { /* AM */
+ fifo=(freq/16)+2010; /* Make it kHz */
+ fifo|=0x100000; /* Select AM Band */
+ }
+
+ /*
+ * Save current volume/mute setting
+ */
+ cadet_lock++;
+ outb(7,io); /* Select tuner control */
+ curvol=inb(io+1);
+
+ /*
+ * Tune the card
+ */
+ for(j=3;j>-1;j--) {
+ cadet_settune(fifo|(j<<16));
+ outb(7,io); /* Select tuner control */
+ outb(curvol,io+1);
+ cadet_lock--;
+ init_timer(&tunertimer);
+ tunertimer.function=cadet_wake;
+ tunertimer.data=(unsigned long)0;
+ tunertimer.expires=jiffies+(HZ/10);
+ init_waitqueue_head(&tunerq);
+ add_timer(&tunertimer);
+ sleep_on(&tunerq);
+ cadet_gettune();
+ if((tunestat&0x40)==0) { /* Tuned */
+ sigstrength=sigtable[curtuner][j];
+ return;
+ }
+ cadet_lock++;
+ }
+ cadet_lock--;
+ sigstrength=0;
+}
+
+
+static int cadet_getvol(void)
+{
+ cadet_lock++;
+ outb(7,io); /* Select tuner control */
+ if((inb(io+1)&0x20)!=0) {
+ cadet_lock--;
+ return 0xffff;
+ }
+ else {
+ cadet_lock--;
+ return 0;
+ }
+}
+
+
+static void cadet_setvol(int vol)
+{
+ cadet_lock++;
+ outb(7,io); /* Select tuner control */
+ if(vol>0) {
+ outb(0x20,io+1);
+ }
+ else {
+ outb(0x00,io+1);
+ }
+ cadet_lock--;
+}
+
+
+
+void cadet_handler(unsigned long data)
+{
+ /*
+ * Service the RDS fifo
+ */
+ if(cadet_lock==0) {
+ outb(0x3,io); /* Select RDS Decoder Control */
+ if((inb(io+1)&0x20)!=0) {
+ printk(KERN_CRIT "cadet: RDS fifo overflow\n");
+ }
+ outb(0x80,io); /* Select RDS fifo */
+ while((inb(io)&0x80)!=0) {
+ rdsbuf[rdsin++]=inb(io+1);
+ if(rdsin==rdsout) {
+ printk(KERN_CRIT "cadet: RDS buffer overflow\n");
+ }
+ }
+ }
+
+ /*
+ * Service pending read
+ */
+ if( rdsin!=rdsout) {
+ wake_up_interruptible(&readq);
+ }
+
+ /*
+ * Clean up and exit
+ */
+ init_timer(&readtimer);
+ readtimer.function=cadet_handler;
+ readtimer.data=(unsigned long)0;
+ readtimer.expires=jiffies+(HZ/20);
+ add_timer(&readtimer);
+}
+
+
+
+static long cadet_read(struct video_device *v,char *buf,unsigned long count,
+ int nonblock)
+{
+ int i=0;
+ unsigned char readbuf[RDS_BUFFER];
+
+ if(rdsstat==0) {
+ cadet_lock++;
+ rdsstat=1;
+ outb(0x80,io); /* Select RDS fifo */
+ cadet_lock--;
+ init_timer(&readtimer);
+ readtimer.function=cadet_handler;
+ readtimer.data=(unsigned long)0;
+ readtimer.expires=jiffies+(HZ/20);
+ add_timer(&readtimer);
+ }
+ if(rdsin==rdsout) {
+ if(nonblock) {
+ return -EWOULDBLOCK;
+ }
+ interruptible_sleep_on(&readq);
+ }
+ while((i<count)&&(rdsin!=rdsout)) {
+ readbuf[i++]=rdsbuf[rdsout++];
+ }
+ if(copy_to_user(buf,readbuf,i)) {
+ return -EFAULT;
+ }
+ return i;
+}
+
+
+
+static int cadet_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ unsigned freq;
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability v;
+ v.type=VID_TYPE_TUNER;
+ v.channels=2;
+ v.audios=1;
+ /* No we don't do pictures */
+ v.maxwidth=0;
+ v.maxheight=0;
+ v.minwidth=0;
+ v.minheight=0;
+ strcpy(v.name, "ADS Cadet");
+ 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<0)||(v.tuner>1)) {
+ return -EINVAL;
+ }
+ switch(v.tuner) {
+ case 0:
+ strcpy(v.name,"FM");
+ v.rangelow=1400; /* 87.5 MHz */
+ v.rangehigh=1728; /* 108.0 MHz */
+ v.flags=0;
+ v.mode=0;
+ v.mode|=VIDEO_MODE_AUTO;
+ v.signal=sigstrength;
+ if(cadet_getstereo()==1) {
+ v.flags|=VIDEO_TUNER_STEREO_ON;
+ }
+ v.flags|=cadet_getrds();
+ if(copy_to_user(arg,&v, sizeof(v))) {
+ return -EFAULT;
+ }
+ break;
+ case 1:
+ strcpy(v.name,"AM");
+ v.rangelow=8320; /* 520 kHz */
+ v.rangehigh=26400; /* 1650 kHz */
+ v.flags=0;
+ v.flags|=VIDEO_TUNER_LOW;
+ v.mode=0;
+ v.mode|=VIDEO_MODE_AUTO;
+ v.signal=sigstrength;
+ if(copy_to_user(arg,&v, sizeof(v))) {
+ return -EFAULT;
+ }
+ break;
+ }
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner v;
+ if(copy_from_user(&v, arg, sizeof(v))) {
+ return -EFAULT;
+ }
+ if((v.tuner<0)||(v.tuner>1)) {
+ return -EINVAL;
+ }
+ curtuner=v.tuner;
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ freq=cadet_getfreq();
+ if(copy_to_user(arg, &freq, sizeof(freq)))
+ return -EFAULT;
+ return 0;
+ case VIDIOCSFREQ:
+ if(copy_from_user(&freq, arg,sizeof(freq)))
+ return -EFAULT;
+ if((curtuner==0)&&((freq<1400)||(freq>1728))) {
+ return -EINVAL;
+ }
+ if((curtuner==1)&&((freq<8320)||(freq>26400))) {
+ return -EINVAL;
+ }
+ cadet_setfreq(freq);
+ return 0;
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+ memset(&v,0, sizeof(v));
+ v.flags=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
+ if(cadet_getstereo()==0) {
+ v.mode=VIDEO_SOUND_MONO;
+ }
+ else {
+ v.mode=VIDEO_SOUND_STEREO;
+ }
+ v.volume=cadet_getvol();
+ v.step=0xffff;
+ 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;
+ cadet_setvol(v.volume);
+ if(v.flags&VIDEO_AUDIO_MUTE)
+ cadet_setvol(0);
+ else
+ cadet_setvol(0xffff);
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+
+static int cadet_open(struct video_device *dev, int flags)
+{
+ if(users)
+ return -EBUSY;
+ users++;
+ MOD_INC_USE_COUNT;
+ init_waitqueue_head(&readq);
+ return 0;
+}
+
+static void cadet_close(struct video_device *dev)
+{
+ if(rdsstat==1) {
+ del_timer(&readtimer);
+ rdsstat=0;
+ }
+ users--;
+ MOD_DEC_USE_COUNT;
+}
+
+
+static struct video_device cadet_radio=
+{
+ "Cadet radio",
+ VID_TYPE_TUNER,
+ VID_HARDWARE_CADET,
+ cadet_open,
+ cadet_close,
+ cadet_read,
+ NULL, /* Can't write */
+ NULL, /* No poll */
+ cadet_ioctl,
+ NULL,
+ NULL
+};
+
+#ifndef MODULE
+static int cadet_probe(void)
+{
+ static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
+ int i;
+
+ for(i=0;i<8;i++) {
+ io=iovals[i];
+ if(check_region(io,2)>=0) {
+ cadet_setfreq(1410);
+ if(cadet_getfreq()==1410) {
+ return io;
+ }
+ }
+ }
+ return -1;
+}
+#endif
+
+static int __init cadet_init(void)
+{
+#ifndef MODULE
+ io = cadet_probe ();
+#endif
+
+ if(io < 0) {
+#ifdef MODULE
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+#endif
+ return -EINVAL;
+ }
+ if (!request_region(io,2,"cadet"))
+ return -EBUSY;
+ if(video_register_device(&cadet_radio,VFL_TYPE_RADIO)==-1) {
+ release_region(io,2);
+ return -EINVAL;
+ }
+ printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io);
+ return 0;
+}
+
+
+
+MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
+MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
+
+EXPORT_NO_SYMBOLS;
+
+static void __exit cadet_cleanup_module(void)
+{
+ video_unregister_device(&cadet_radio);
+ release_region(io,2);
+}
+
+module_init(cadet_init);
+module_exit(cadet_cleanup_module);
+
diff --git a/drivers/media/radio/radio-gemtek.c b/drivers/media/radio/radio-gemtek.c
new file mode 100644
index 000000000..8b53dbd0d
--- /dev/null
+++ b/drivers/media/radio/radio-gemtek.c
@@ -0,0 +1,319 @@
+/* GemTek radio card driver for Linux (C) 1998 Jonas Munsin <jmunsin@iki.fi>
+ *
+ * GemTek hasn't released any specs on the card, so the protocol had to
+ * be reverse engineered with dosemu.
+ *
+ * Besides the protocol changes, this is mostly a copy of:
+ *
+ * RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff
+ *
+ * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood
+ * Coverted to new API by Alan Cox <Alan.Cox@linux.org>
+ * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
+ *
+ * TODO: Allow for more than one of these foolish entities :-)
+ *
+ */
+
+#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_GEMTEK_PORT */
+#include <linux/spinlock.h>
+
+#ifndef CONFIG_RADIO_GEMTEK_PORT
+#define CONFIG_RADIO_GEMTEK_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_GEMTEK_PORT;
+static int users = 0;
+static spinlock_t lock;
+
+struct gemtek_device
+{
+ int port;
+ unsigned long curfreq;
+ int muted;
+};
+
+
+/* local things */
+
+/* the correct way to mute the gemtek may be to write the last written
+ * frequency || 0x10, but just writing 0x10 once seems to do it as well
+ */
+static void gemtek_mute(struct gemtek_device *dev)
+{
+ if(dev->muted)
+ return;
+ spin_lock(&lock);
+ outb(0x10, io);
+ spin_unlock(&lock);
+ dev->muted = 1;
+}
+
+static void gemtek_unmute(struct gemtek_device *dev)
+{
+ if(dev->muted == 0)
+ return;
+ spin_lock(&lock);
+ outb(0x20, io);
+ spin_unlock(&lock);
+ dev->muted = 0;
+}
+
+static void zero(void)
+{
+ outb_p(0x04, io);
+ udelay(5);
+ outb_p(0x05, io);
+ udelay(5);
+}
+
+static void one(void)
+{
+ outb_p(0x06, io);
+ udelay(5);
+ outb_p(0x07, io);
+ udelay(5);
+}
+
+static int gemtek_setfreq(struct gemtek_device *dev, unsigned long freq)
+{
+ int i;
+
+/* freq = 78.25*((float)freq/16000.0 + 10.52); */
+
+ freq /= 16;
+ freq += 10520;
+ freq *= 7825;
+ freq /= 100000;
+
+ spin_lock(&lock);
+
+ /* 2 start bits */
+ outb_p(0x03, io);
+ udelay(5);
+ outb_p(0x07, io);
+ udelay(5);
+
+ /* 28 frequency bits (lsb first) */
+ for (i = 0; i < 14; i++)
+ if (freq & (1 << i))
+ one();
+ else
+ zero();
+ /* 36 unknown bits */
+ for (i = 0; i < 11; i++)
+ zero();
+ one();
+ for (i = 0; i < 4; i++)
+ zero();
+ one();
+ zero();
+
+ /* 2 end bits */
+ outb_p(0x03, io);
+ udelay(5);
+ outb_p(0x07, io);
+ udelay(5);
+
+ spin_unlock(&lock);
+
+ return 0;
+}
+
+int gemtek_getsigstr(struct gemtek_device *dev)
+{
+ spin_lock(&lock);
+ inb(io);
+ udelay(5);
+ spin_unlock(&lock);
+ if (inb(io) & 8) /* bit set = no signal present */
+ return 0;
+ return 1; /* signal present */
+}
+
+static int gemtek_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct gemtek_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;
+ strcpy(v.name, "GemTek");
+ 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=87*16000;
+ v.rangehigh=108*16000;
+ v.flags=VIDEO_TUNER_LOW;
+ v.mode=VIDEO_MODE_AUTO;
+ v.signal=0xFFFF*gemtek_getsigstr(rt);
+ strcpy(v.name, "FM");
+ 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;
+ /* needs to be called twice in order for getsigstr to work */
+ gemtek_setfreq(rt, rt->curfreq);
+ gemtek_setfreq(rt, rt->curfreq);
+ return 0;
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+ memset(&v,0, sizeof(v));
+ v.flags|=VIDEO_AUDIO_MUTABLE;
+ v.volume=1;
+ v.step=65535;
+ 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)
+ gemtek_mute(rt);
+ else
+ gemtek_unmute(rt);
+
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int gemtek_open(struct video_device *dev, int flags)
+{
+ if(users)
+ return -EBUSY;
+ users++;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void gemtek_close(struct video_device *dev)
+{
+ users--;
+ MOD_DEC_USE_COUNT;
+}
+
+static struct gemtek_device gemtek_unit;
+
+static struct video_device gemtek_radio=
+{
+ "GemTek radio",
+ VID_TYPE_TUNER,
+ VID_HARDWARE_GEMTEK,
+ gemtek_open,
+ gemtek_close,
+ NULL, /* Can't read (no capture ability) */
+ NULL, /* Can't write */
+ NULL, /* Can't poll */
+ gemtek_ioctl,
+ NULL,
+ NULL
+};
+
+static int __init gemtek_init(void)
+{
+ if(io==-1)
+ {
+ printk(KERN_ERR "You must set an I/O address with io=0x20c, io=0x30c, io=0x24c or io=0x34c (io=0x020c or io=0x248 for the combined sound/radiocard)\n");
+ return -EINVAL;
+ }
+
+ if (check_region(io, 4))
+ {
+ printk(KERN_ERR "gemtek: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ gemtek_radio.priv=&gemtek_unit;
+
+ if(video_register_device(&gemtek_radio, VFL_TYPE_RADIO)==-1)
+ return -EINVAL;
+
+ request_region(io, 4, "gemtek");
+ printk(KERN_INFO "GemTek Radio Card driver.\n");
+
+ spin_lock_init(&lock);
+ /* mute card - prevents noisy bootups */
+ outb(0x10, io);
+ udelay(5);
+ gemtek_unit.muted = 1;
+
+ /* this is _maybe_ unnecessary */
+ outb(0x01, io);
+
+ return 0;
+}
+
+MODULE_AUTHOR("Jonas Munsin");
+MODULE_DESCRIPTION("A driver for the GemTek Radio Card");
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "I/O address of the GemTek card (0x20c, 0x30c, 0x24c or 0x34c (0x20c or 0x248 have been reported to work for the combined sound/radiocard)).");
+
+EXPORT_NO_SYMBOLS;
+
+static void __exit gemtek_cleanup(void)
+{
+ video_unregister_device(&gemtek_radio);
+ release_region(io,4);
+}
+
+module_init(gemtek_init);
+module_exit(gemtek_cleanup);
+
+/*
+ Local variables:
+ compile-command: "gcc -c -DMODVERSIONS -D__KERNEL__ -DMODULE -O6 -Wall -Wstrict-prototypes -I /home/blp/tmp/linux-2.1.111-rtrack/include radio-rtrack2.c"
+ End:
+*/
diff --git a/drivers/media/radio/radio-miropcm20.c b/drivers/media/radio/radio-miropcm20.c
new file mode 100644
index 000000000..1068467c8
--- /dev/null
+++ b/drivers/media/radio/radio-miropcm20.c
@@ -0,0 +1,239 @@
+/* Miro PCM20 radio driver for Linux radio support
+ * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl>
+ * Thanks to Norberto Pellici for the ACI device interface specification
+ * The API part is based on the radiotrack driver by M. Kirkwood
+ * This driver relies on the aci mixer (drivers/sound/lowlevel/aci.c)
+ * Look there for further info...
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev.h> /* kernel radio structs */
+#include "../../sound/miroaci.h" /* ACI Control by acimixer */
+
+static int users = 0;
+
+struct pcm20_device
+{
+ int port;
+ int curvol;
+ unsigned long curfreq;
+ int muted;
+};
+
+
+/* local things */
+
+
+static void pcm20_mute(struct pcm20_device *dev)
+{
+
+ dev->muted = 1;
+ aci_write_cmd(0xa3,0x01);
+
+}
+
+static int pcm20_setvol(struct pcm20_device *dev, int vol)
+{
+
+ if(vol == dev->curvol) { /* requested volume = current */
+ if (dev->muted) { /* user is unmuting the card */
+ dev->muted = 0;
+ aci_write_cmd(0xa3,0x00); /* enable card */
+ }
+
+ return 0;
+ }
+
+ if(vol == 0) { /* volume = 0 means mute the card */
+ aci_write_cmd(0x3d, 0x20);
+ aci_write_cmd(0x35, 0x20);
+ return 0;
+ }
+
+ dev->muted = 0;
+ aci_write_cmd(0x3d, 32-vol); /* Right Channel */
+ aci_write_cmd(0x35, 32-vol); /* Left Channel */
+ dev->curvol = vol;
+
+ return 0;
+}
+
+static int pcm20_setfreq(struct pcm20_device *dev, unsigned long freq)
+{
+ unsigned char freql;
+ unsigned char freqh;
+
+ freq = (freq * 10) / 16;
+ freql = freq & 0xff;
+ freqh = freq >> 8;
+
+
+ aci_write_cmd_d(0xa7, freql, freqh); /* Tune to frequency */
+
+ return 0;
+}
+
+int pcm20_getsigstr(struct pcm20_device *dev)
+{
+ unsigned char buf;
+ aci_indexed_cmd(0xf0, 0x32, &buf);
+ if ((buf & 0x80) == 0x80)
+ return 0;
+ return 1; /* signal present */
+}
+
+static int pcm20_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct pcm20_device *pcm20=dev->priv;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability v;
+ v.type=VID_TYPE_TUNER;
+ strcpy(v.name, "Miro PCM20");
+ 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)(87.5*16);
+ v.rangehigh=(int)(108.0*16);
+ v.flags=0;
+ v.mode=VIDEO_MODE_AUTO;
+ v.signal=0xFFFF*pcm20_getsigstr(pcm20);
+ strcpy(v.name, "FM");
+ 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, &pcm20->curfreq, sizeof(pcm20->curfreq)))
+ return -EFAULT;
+ return 0;
+ case VIDIOCSFREQ:
+ if(copy_from_user(&pcm20->curfreq, arg,sizeof(pcm20->curfreq)))
+ return -EFAULT;
+ pcm20_setfreq(pcm20, pcm20->curfreq);
+ return 0;
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+ memset(&v,0, sizeof(v));
+ v.flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
+ v.volume=pcm20->curvol * 2048;
+ 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)
+ pcm20_mute(pcm20);
+ else
+ pcm20_setvol(pcm20,v.volume/2048);
+
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int pcm20_open(struct video_device *dev, int flags)
+{
+ if(users)
+ return -EBUSY;
+ users++;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void pcm20_close(struct video_device *dev)
+{
+ users--;
+ MOD_DEC_USE_COUNT;
+}
+
+static struct pcm20_device pcm20_unit;
+
+static struct video_device pcm20_radio=
+{
+ "Miro PCM 20 radio",
+ VID_TYPE_TUNER,
+ VID_HARDWARE_RTRACK,
+ pcm20_open,
+ pcm20_close,
+ NULL, /* Can't read (no capture ability) */
+ NULL, /* Can't write */
+ NULL, /* Can't poll */
+ pcm20_ioctl,
+ NULL,
+ NULL
+};
+
+static int __init pcm20_init(void)
+{
+
+ pcm20_radio.priv=&pcm20_unit;
+
+ if(video_register_device(&pcm20_radio, VFL_TYPE_RADIO)==-1)
+ return -EINVAL;
+
+ printk(KERN_INFO "Miro PCM20 radio card driver.\n");
+
+ /* mute card - prevents noisy bootups */
+
+ /* this ensures that the volume is all the way down */
+
+ pcm20_unit.curvol = 0;
+
+ return 0;
+}
+
+MODULE_AUTHOR("Ruurd Reitsma");
+MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card.");
+
+EXPORT_NO_SYMBOLS;
+
+static void __exit pcm20_cleanup(void)
+{
+ video_unregister_device(&pcm20_radio);
+}
+
+module_init(pcm20_init);
+module_exit(pcm20_cleanup);
+
diff --git a/drivers/media/radio/radio-rtrack2.c b/drivers/media/radio/radio-rtrack2.c
new file mode 100644
index 000000000..c060ded4a
--- /dev/null
+++ b/drivers/media/radio/radio-rtrack2.c
@@ -0,0 +1,280 @@
+/* RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff
+ *
+ * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood
+ * Coverted to new API by Alan Cox <Alan.Cox@linux.org>
+ * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
+ *
+ * TODO: Allow for more than one of these foolish entities :-)
+ *
+ */
+
+#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_RTRACK2_PORT */
+#include <linux/spinlock.h>
+
+#ifndef CONFIG_RADIO_RTRACK2_PORT
+#define CONFIG_RADIO_RTRACK2_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_RTRACK2_PORT;
+static int users = 0;
+static spinlock_t lock;
+
+struct rt_device
+{
+ int port;
+ unsigned long curfreq;
+ int muted;
+};
+
+
+/* local things */
+
+static void rt_mute(struct rt_device *dev)
+{
+ if(dev->muted)
+ return;
+ spin_lock(&lock);
+ outb(1, io);
+ spin_unlock(&lock);
+ dev->muted = 1;
+}
+
+static void rt_unmute(struct rt_device *dev)
+{
+ if(dev->muted == 0)
+ return;
+ spin_lock(&lock);
+ outb(0, io);
+ spin_unlock(&lock);
+ dev->muted = 0;
+}
+
+static void zero(void)
+{
+ outb_p(1, io);
+ outb_p(3, io);
+ outb_p(1, io);
+}
+
+static void one(void)
+{
+ outb_p(5, io);
+ outb_p(7, io);
+ outb_p(5, io);
+}
+
+static int rt_setfreq(struct rt_device *dev, unsigned long freq)
+{
+ int i;
+
+ freq = freq / 200 + 856;
+
+ spin_lock(&lock);
+
+ outb_p(0xc8, io);
+ outb_p(0xc9, io);
+ outb_p(0xc9, io);
+
+ for (i = 0; i < 10; i++)
+ zero ();
+
+ for (i = 14; i >= 0; i--)
+ if (freq & (1 << i))
+ one ();
+ else
+ zero ();
+
+ outb_p(0xc8, io);
+ if (!dev->muted)
+ outb_p(0, io);
+
+ spin_unlock(&lock);
+ return 0;
+}
+
+static int rt_getsigstr(struct rt_device *dev)
+{
+ if (inb(io) & 2) /* bit set = no signal present */
+ return 0;
+ return 1; /* signal present */
+}
+
+static int rt_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct rt_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;
+ strcpy(v.name, "RadioTrack II");
+ 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=88*16000;
+ v.rangehigh=108*16000;
+ v.flags=VIDEO_TUNER_LOW;
+ v.mode=VIDEO_MODE_AUTO;
+ v.signal=0xFFFF*rt_getsigstr(rt);
+ strcpy(v.name, "FM");
+ 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;
+ rt_setfreq(rt, rt->curfreq);
+ return 0;
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+ memset(&v,0, sizeof(v));
+ v.flags|=VIDEO_AUDIO_MUTABLE;
+ v.volume=1;
+ v.step=65535;
+ 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)
+ rt_mute(rt);
+ else
+ rt_unmute(rt);
+
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int rt_open(struct video_device *dev, int flags)
+{
+ if(users)
+ return -EBUSY;
+ users++;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void rt_close(struct video_device *dev)
+{
+ users--;
+ MOD_DEC_USE_COUNT;
+}
+
+static struct rt_device rtrack2_unit;
+
+static struct video_device rtrack2_radio=
+{
+ "RadioTrack II radio",
+ VID_TYPE_TUNER,
+ VID_HARDWARE_RTRACK2,
+ rt_open,
+ rt_close,
+ NULL, /* Can't read (no capture ability) */
+ NULL, /* Can't write */
+ NULL, /* Can't poll */
+ rt_ioctl,
+ NULL,
+ NULL
+};
+
+static int __init rtrack2_init(void)
+{
+ if(io==-1)
+ {
+ printk(KERN_ERR "You must set an I/O address with io=0x20c or io=0x30c\n");
+ return -EINVAL;
+ }
+ if (check_region(io, 4))
+ {
+ printk(KERN_ERR "rtrack2: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ rtrack2_radio.priv=&rtrack2_unit;
+
+ spin_lock_init(&lock);
+ if(video_register_device(&rtrack2_radio, VFL_TYPE_RADIO)==-1)
+ return -EINVAL;
+
+ request_region(io, 4, "rtrack2");
+ printk(KERN_INFO "AIMSlab Radiotrack II card driver.\n");
+
+ /* mute card - prevents noisy bootups */
+ outb(1, io);
+ rtrack2_unit.muted = 1;
+
+ return 0;
+}
+
+MODULE_AUTHOR("Ben Pfaff");
+MODULE_DESCRIPTION("A driver for the RadioTrack II radio card.");
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20c or 0x30c)");
+
+EXPORT_NO_SYMBOLS;
+
+static void __exit rtrack2_cleanup_module(void)
+{
+ video_unregister_device(&rtrack2_radio);
+ release_region(io,4);
+}
+
+module_init(rtrack2_init);
+module_exit(rtrack2_cleanup_module);
+
+/*
+ Local variables:
+ compile-command: "gcc -c -DMODVERSIONS -D__KERNEL__ -DMODULE -O6 -Wall -Wstrict-prototypes -I /home/blp/tmp/linux-2.1.111-rtrack/include radio-rtrack2.c"
+ End:
+*/
diff --git a/drivers/media/radio/radio-sf16fmi.c b/drivers/media/radio/radio-sf16fmi.c
new file mode 100644
index 000000000..55328a96f
--- /dev/null
+++ b/drivers/media/radio/radio-sf16fmi.c
@@ -0,0 +1,339 @@
+/* SF16FMI radio driver for Linux radio support
+ * heavily based on rtrack driver...
+ * (c) 1997 M. Kirkwood
+ * (c) 1998 Petr Vandrovec, vandrove@vc.cvut.cz
+ *
+ * Fitted to new interface by Alan Cox <alan.cox@linux.org>
+ * Made working and cleaned up functions <mikael.hedin@irf.se>
+ *
+ * Notes on the hardware
+ *
+ * Frequency control is done digitally -- ie out(port,encodefreq(95.8));
+ * No volume control - only mute/unmute - you have to use line volume
+ * control on SB-part of SF16FMI
+ *
+ */
+
+#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_SF16MI_PORT */
+#include <asm/semaphore.h>
+
+struct fmi_device
+{
+ int port;
+ int curvol; /* 1 or 0 */
+ unsigned long curfreq; /* freq in kHz */
+ __u32 flags;
+};
+
+#ifndef CONFIG_RADIO_SF16FMI_PORT
+#define CONFIG_RADIO_SF16FMI_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_SF16FMI_PORT;
+static int users = 0;
+static struct semaphore lock;
+
+/* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */
+/* It is only usefull to give freq in intervall of 800 (=0.05Mhz),
+ * other bits will be truncated, e.g 92.7400016 -> 92.7, but
+ * 92.7400017 -> 92.75
+ */
+#define RSF16_ENCODE(x) ((x)/800+214)
+#define RSF16_MINFREQ 87*16000
+#define RSF16_MAXFREQ 108*16000
+
+static void outbits(int bits, unsigned int data, int port)
+{
+ while(bits--) {
+ if(data & 1) {
+ outb(5, port);
+ udelay(6);
+ outb(7, port);
+ udelay(6);
+ } else {
+ outb(1, port);
+ udelay(6);
+ outb(3, port);
+ udelay(6);
+ }
+ data>>=1;
+ }
+}
+
+static inline void fmi_mute(int port)
+{
+ down(&lock);
+ outb(0x00, port);
+ up(&lock);
+}
+
+static inline void fmi_unmute(int port)
+{
+ down(&lock);
+ outb(0x08, port);
+ up(&lock);
+}
+
+static inline int fmi_setfreq(struct fmi_device *dev)
+{
+ int myport = dev->port;
+ unsigned long freq = dev->curfreq;
+ int i;
+
+ down(&lock);
+
+ outbits(16, RSF16_ENCODE(freq), myport);
+ outbits(8, 0xC0, myport);
+ for(i=0; i< 100; i++)
+ {
+ udelay(1400);
+ if(current->need_resched)
+ schedule();
+ }
+/* If this becomes allowed use it ...
+ current->state = TASK_UNINTERRUPTIBLE;
+ schedule_timeout(HZ/7);
+*/
+
+ up(&lock);
+ if (dev->curvol) fmi_unmute(myport);
+ return 0;
+}
+
+static inline int fmi_getsigstr(struct fmi_device *dev)
+{
+ int val;
+ int res;
+ int myport = dev->port;
+ int i;
+
+ down(&lock);
+ val = dev->curvol ? 0x08 : 0x00; /* unmute/mute */
+ outb(val, myport);
+ outb(val | 0x10, myport);
+ for(i=0; i< 100; i++)
+ {
+ udelay(1400);
+ if(current->need_resched)
+ schedule();
+ }
+/* If this becomes allowed use it ...
+ current->state = TASK_UNINTERRUPTIBLE;
+ schedule_timeout(HZ/7);
+*/
+ res = (int)inb(myport+1);
+ outb(val, myport);
+
+ up(&lock);
+ return (res & 2) ? 0 : 0xFFFF;
+}
+
+static int fmi_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct fmi_device *fmi=dev->priv;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability v;
+ strcpy(v.name, "SF16-FMx radio");
+ 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;
+ int mult;
+
+ if(copy_from_user(&v, arg,sizeof(v))!=0)
+ return -EFAULT;
+ if(v.tuner) /* Only 1 tuner */
+ return -EINVAL;
+ strcpy(v.name, "FM");
+ mult = (fmi->flags & VIDEO_TUNER_LOW) ? 1 : 1000;
+ v.rangelow = RSF16_MINFREQ/mult;
+ v.rangehigh = RSF16_MAXFREQ/mult;
+ v.flags=fmi->flags;
+ v.mode=VIDEO_MODE_AUTO;
+ v.signal = fmi_getsigstr(fmi);
+ 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;
+ fmi->flags = v.flags & VIDEO_TUNER_LOW;
+ /* Only 1 tuner so no setting needed ! */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long tmp = fmi->curfreq;
+ if (!(fmi->flags & VIDEO_TUNER_LOW))
+ tmp /= 1000;
+ if(copy_to_user(arg, &tmp, sizeof(tmp)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long tmp;
+ if(copy_from_user(&tmp, arg, sizeof(tmp)))
+ return -EFAULT;
+ if (!(fmi->flags & VIDEO_TUNER_LOW))
+ tmp *= 1000;
+ if ( tmp<RSF16_MINFREQ || tmp>RSF16_MAXFREQ )
+ return -EINVAL;
+ /*rounding in steps of 800 to match th freq
+ that will be used */
+ fmi->curfreq = (tmp/800)*800;
+ fmi_setfreq(fmi);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+ v.audio=0;
+ v.volume=0;
+ v.bass=0;
+ v.treble=0;
+ v.flags=( (!fmi->curvol)*VIDEO_AUDIO_MUTE | VIDEO_AUDIO_MUTABLE);
+ strcpy(v.name, "Radio");
+ v.mode=VIDEO_SOUND_STEREO;
+ v.balance=0;
+ v.step=0; /* No volume, just (un)mute */
+ 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;
+ fmi->curvol= v.flags&VIDEO_AUDIO_MUTE ? 0 : 1;
+ fmi->curvol ?
+ fmi_unmute(fmi->port) : fmi_mute(fmi->port);
+ return 0;
+ }
+ case VIDIOCGUNIT:
+ {
+ struct video_unit v;
+ v.video=VIDEO_NO_UNIT;
+ v.vbi=VIDEO_NO_UNIT;
+ v.radio=dev->minor;
+ v.audio=0; /* How do we find out this??? */
+ v.teletext=VIDEO_NO_UNIT;
+ if(copy_to_user(arg, &v, sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int fmi_open(struct video_device *dev, int flags)
+{
+ if(users)
+ return -EBUSY;
+ users++;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void fmi_close(struct video_device *dev)
+{
+ users--;
+ MOD_DEC_USE_COUNT;
+}
+
+static struct fmi_device fmi_unit;
+
+static struct video_device fmi_radio=
+{
+ "SF16FMx radio",
+ VID_TYPE_TUNER,
+ VID_HARDWARE_SF16MI,
+ fmi_open,
+ fmi_close,
+ NULL, /* Can't read (no capture ability) */
+ NULL, /* Can't write */
+ NULL, /* Can't poll */
+ fmi_ioctl,
+ NULL,
+ NULL
+};
+
+static int __init fmi_init(void)
+{
+ if(io==-1)
+ {
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+ return -EINVAL;
+ }
+ if (check_region(io, 2))
+ {
+ printk(KERN_ERR "fmi: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ fmi_unit.port = io;
+ fmi_unit.curvol = 0;
+ fmi_unit.curfreq = 0;
+ fmi_unit.flags = VIDEO_TUNER_LOW;
+ fmi_radio.priv = &fmi_unit;
+
+ init_MUTEX(&lock);
+
+ if(video_register_device(&fmi_radio, VFL_TYPE_RADIO)==-1)
+ return -EINVAL;
+
+ request_region(io, 2, "fmi");
+ printk(KERN_INFO "SF16FMx radio card driver at 0x%x.\n", io);
+ printk(KERN_INFO "(c) 1998 Petr Vandrovec, vandrove@vc.cvut.cz.\n");
+ /* mute card - prevents noisy bootups */
+ fmi_mute(io);
+ return 0;
+}
+
+MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood");
+MODULE_DESCRIPTION("A driver for the SF16MI radio.");
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)");
+
+EXPORT_NO_SYMBOLS;
+
+static void __exit fmi_cleanup_module(void)
+{
+ video_unregister_device(&fmi_radio);
+ release_region(io,2);
+}
+
+module_init(fmi_init);
+module_exit(fmi_cleanup_module);
+
diff --git a/drivers/media/radio/radio-terratec.c b/drivers/media/radio/radio-terratec.c
new file mode 100644
index 000000000..1dda1618e
--- /dev/null
+++ b/drivers/media/radio/radio-terratec.c
@@ -0,0 +1,358 @@
+/* Terratec ActiveRadio ISA Standalone card driver for Linux radio support
+ * (c) 1999 R. Offermanns (rolf@offermanns.de)
+ * based on the aimslab radio driver from M. Kirkwood
+ * many thanks to Michael Becker and Friedhelm Birth (from TerraTec)
+ *
+ *
+ * History:
+ * 1999-05-21 First preview release
+ *
+ * Notes on the hardware:
+ * There are two "main" chips on the card:
+ * - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf)
+ * - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf)
+ * (you can get the datasheet at the above links)
+ *
+ * Frequency control is done digitally -- ie out(port,encodefreq(95.8));
+ * Volume Control is done digitally
+ *
+ * there is a I2C controlled RDS decoder (SAA6588) onboard, which i would like to support someday
+ * (as soon i have understand how to get started :)
+ * If you can help me out with that, please contact me!!
+ *
+ *
+ */
+
+#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_TERRATEC_PORT */
+#include <linux/spinlock.h>
+
+#ifndef CONFIG_RADIO_TERRATEC_PORT
+#define CONFIG_RADIO_TERRATEC_PORT 0x590
+#endif
+
+/**************** this ones are for the terratec *******************/
+#define BASEPORT 0x590
+#define VOLPORT 0x591
+#define WRT_DIS 0x00
+#define CLK_OFF 0x00
+#define IIC_DATA 0x01
+#define IIC_CLK 0x02
+#define DATA 0x04
+#define CLK_ON 0x08
+#define WRT_EN 0x10
+/*******************************************************************/
+
+static int io = CONFIG_RADIO_TERRATEC_PORT;
+static int users = 0;
+static spinlock_t lock;
+
+struct tt_device
+{
+ int port;
+ int curvol;
+ unsigned long curfreq;
+ int muted;
+};
+
+
+/* local things */
+
+static void cardWriteVol(int volume)
+{
+ int i;
+ volume = volume+(volume * 32); // change both channels
+ spin_lock(&lock);
+ for (i=0;i<8;i++)
+ {
+ if (volume & (0x80>>i))
+ outb(0x80, VOLPORT);
+ else outb(0x00, VOLPORT);
+ }
+ spin_unlock(&lock);
+}
+
+
+
+static void tt_mute(struct tt_device *dev)
+{
+ dev->muted = 1;
+ cardWriteVol(0);
+}
+
+static int tt_setvol(struct tt_device *dev, int vol)
+{
+
+// printk(KERN_ERR "setvol called, vol = %d\n", vol);
+
+ if(vol == dev->curvol) { /* requested volume = current */
+ if (dev->muted) { /* user is unmuting the card */
+ dev->muted = 0;
+ cardWriteVol(vol); /* enable card */
+ }
+
+ return 0;
+ }
+
+ if(vol == 0) { /* volume = 0 means mute the card */
+ cardWriteVol(0); /* "turn off card" by setting vol to 0 */
+ dev->curvol = vol; /* track the volume state! */
+ return 0;
+ }
+
+ dev->muted = 0;
+
+ cardWriteVol(vol);
+
+ dev->curvol = vol;
+
+ return 0;
+
+}
+
+
+/* this is the worst part in this driver */
+/* many more or less strange things are going on here, but hey, it works :) */
+
+static int tt_setfreq(struct tt_device *dev, unsigned long freq1)
+{
+ int freq;
+ int i;
+ int p;
+ int temp;
+ long rest;
+
+ unsigned char buffer[25]; /* we have to bit shift 25 registers */
+ freq = freq1/160; /* convert the freq. to a nice to handel value */
+ for(i=24;i>-1;i--)
+ buffer[i]=0;
+
+ rest = freq*10+10700; /* i once had understood what is going on here */
+ /* maybe some wise guy (friedhelm?) can comment this stuff */
+ i=13;
+ p=10;
+ temp=102400;
+ while (rest!=0)
+ {
+ if (rest%temp == rest)
+ buffer[i] = 0;
+ else
+ {
+ buffer[i] = 1;
+ rest = rest-temp;
+ }
+ i--;
+ p--;
+ temp = temp/2;
+ }
+
+ spin_lock(&lock);
+
+ for (i=24;i>-1;i--) /* bit shift the values to the radiocard */
+ {
+ if (buffer[i]==1)
+ {
+ outb(WRT_EN|DATA, BASEPORT);
+ outb(WRT_EN|DATA|CLK_ON , BASEPORT);
+ outb(WRT_EN|DATA, BASEPORT);
+ }
+ else
+ {
+ outb(WRT_EN|0x00, BASEPORT);
+ outb(WRT_EN|0x00|CLK_ON , BASEPORT);
+ }
+ }
+ outb(0x00, BASEPORT);
+
+ spin_unlock(&lock);
+
+ return 0;
+}
+
+int tt_getsigstr(struct tt_device *dev) /* TODO */
+{
+ if (inb(io) & 2) /* bit set = no signal present */
+ return 0;
+ return 1; /* signal present */
+}
+
+
+/* implement the video4linux api */
+
+static int tt_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct tt_device *tt=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;
+ strcpy(v.name, "ActiveRadio");
+ 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=(87*16000);
+ v.rangehigh=(108*16000);
+ v.flags=VIDEO_TUNER_LOW;
+ v.mode=VIDEO_MODE_AUTO;
+ strcpy(v.name, "FM");
+ v.signal=0xFFFF*tt_getsigstr(tt);
+ 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, &tt->curfreq, sizeof(tt->curfreq)))
+ return -EFAULT;
+ return 0;
+ case VIDIOCSFREQ:
+ if(copy_from_user(&tt->curfreq, arg,sizeof(tt->curfreq)))
+ return -EFAULT;
+ tt_setfreq(tt, tt->curfreq);
+ return 0;
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+ memset(&v,0, sizeof(v));
+ v.flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
+ v.volume=tt->curvol * 6554;
+ v.step=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)
+ tt_mute(tt);
+ else
+ tt_setvol(tt,v.volume/6554);
+
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int tt_open(struct video_device *dev, int flags)
+{
+ if(users)
+ return -EBUSY;
+ users++;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void tt_close(struct video_device *dev)
+{
+ users--;
+ MOD_DEC_USE_COUNT;
+}
+
+static struct tt_device terratec_unit;
+
+static struct video_device terratec_radio=
+{
+ "TerraTec ActiveRadio",
+ VID_TYPE_TUNER,
+ VID_HARDWARE_TERRATEC,
+ tt_open,
+ tt_close,
+ NULL, /* Can't read (no capture ability) */
+ NULL, /* Can't write */
+ NULL, /* No poll */
+ tt_ioctl,
+ NULL,
+ NULL
+};
+
+static int __init terratec_init(void)
+{
+ if(io==-1)
+ {
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+ return -EINVAL;
+ }
+ if (check_region(io, 2))
+ {
+ printk(KERN_ERR "TerraTec: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ terratec_radio.priv=&terratec_unit;
+
+ spin_lock_init(&lock);
+
+ if(video_register_device(&terratec_radio, VFL_TYPE_RADIO)==-1)
+ return -EINVAL;
+
+ request_region(io, 2, "terratec");
+ printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver.\n");
+
+ /* mute card - prevents noisy bootups */
+
+ /* this ensures that the volume is all the way down */
+ cardWriteVol(0);
+ terratec_unit.curvol = 0;
+
+ return 0;
+}
+
+MODULE_AUTHOR("R.OFFERMANNS & others");
+MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card.");
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)");
+
+EXPORT_NO_SYMBOLS;
+
+static void __exit terratec_cleanup_module(void)
+{
+ video_unregister_device(&terratec_radio);
+ release_region(io,2);
+ printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver unloaded.\n");
+}
+
+module_init(terratec_init);
+module_exit(terratec_cleanup_module);
+
diff --git a/drivers/media/radio/radio-trust.c b/drivers/media/radio/radio-trust.c
new file mode 100644
index 000000000..4e86bda90
--- /dev/null
+++ b/drivers/media/radio/radio-trust.c
@@ -0,0 +1,350 @@
+/* radio-trust.c - Trust FM Radio card driver for Linux 2.2
+ * by Eric Lammerts <eric@scintilla.utwente.nl>
+ *
+ * Based on radio-aztech.c. Original notes:
+ *
+ * Adapted to support the Video for Linux API by
+ * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by:
+ *
+ * Quay Ly
+ * Donald Song
+ * Jason Lewis (jlewis@twilight.vtc.vsc.edu)
+ * Scott McGrath (smcgrath@twilight.vtc.vsc.edu)
+ * William McGrath (wmcgrath@twilight.vtc.vsc.edu)
+ *
+ * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/
+ */
+
+#include <stdarg.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/videodev.h>
+#include <linux/config.h> /* CONFIG_RADIO_TRUST_PORT */
+
+/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */
+
+#ifndef CONFIG_RADIO_TRUST_PORT
+#define CONFIG_RADIO_TRUST_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_TRUST_PORT;
+static int ioval = 0xf;
+static int users = 0;
+static __u16 curvol;
+static __u16 curbass;
+static __u16 curtreble;
+static unsigned long curfreq;
+static int curstereo;
+static int curmute;
+
+/* i2c addresses */
+#define TDA7318_ADDR 0x88
+#define TSA6060T_ADDR 0xc4
+
+#define TR_DELAY do { inb(io); inb(io); inb(io); } while(0)
+#define TR_SET_SCL outb(ioval |= 2, io)
+#define TR_CLR_SCL outb(ioval &= 0xfd, io)
+#define TR_SET_SDA outb(ioval |= 1, io)
+#define TR_CLR_SDA outb(ioval &= 0xfe, io)
+
+static void write_i2c(int n, ...)
+{
+ unsigned char val, mask;
+ va_list args;
+
+ va_start(args, n);
+
+ /* start condition */
+ TR_SET_SDA;
+ TR_SET_SCL;
+ TR_DELAY;
+ TR_CLR_SDA;
+ TR_CLR_SCL;
+ TR_DELAY;
+
+ for(; n; n--) {
+ val = va_arg(args, unsigned);
+ for(mask = 0x80; mask; mask >>= 1) {
+ if(val & mask)
+ TR_SET_SDA;
+ else
+ TR_CLR_SDA;
+ TR_SET_SCL;
+ TR_DELAY;
+ TR_CLR_SCL;
+ TR_DELAY;
+ }
+ /* acknowledge bit */
+ TR_SET_SDA;
+ TR_SET_SCL;
+ TR_DELAY;
+ TR_CLR_SCL;
+ TR_DELAY;
+ }
+
+ /* stop condition */
+ TR_CLR_SDA;
+ TR_DELAY;
+ TR_SET_SCL;
+ TR_DELAY;
+ TR_SET_SDA;
+ TR_DELAY;
+
+ va_end(args);
+}
+
+static void tr_setvol(__u16 vol)
+{
+ curvol = vol / 2048;
+ write_i2c(2, TDA7318_ADDR, curvol ^ 0x1f);
+}
+
+static int basstreble2chip[15] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8
+};
+
+static void tr_setbass(__u16 bass)
+{
+ curbass = bass / 4370;
+ write_i2c(2, TDA7318_ADDR, 0x60 | basstreble2chip[curbass]);
+}
+
+static void tr_settreble(__u16 treble)
+{
+ curtreble = treble / 4370;
+ write_i2c(2, TDA7318_ADDR, 0x70 | basstreble2chip[curtreble]);
+}
+
+static void tr_setstereo(int stereo)
+{
+ curstereo = !!stereo;
+ ioval = (ioval & 0xfb) | (!curstereo << 2);
+ outb(ioval, io);
+}
+
+static void tr_setmute(int mute)
+{
+ curmute = !!mute;
+ ioval = (ioval & 0xf7) | (curmute << 3);
+ outb(ioval, io);
+}
+
+static int tr_getsigstr(void)
+{
+ int i, v;
+
+ for(i = 0, v = 0; i < 100; i++) v |= inb(io);
+ return (v & 1)? 0 : 0xffff;
+}
+
+static int tr_getstereo(void)
+{
+ /* don't know how to determine it, just return the setting */
+ return curstereo;
+}
+
+static void tr_setfreq(unsigned long f)
+{
+ f /= 160; /* Convert to 10 kHz units */
+ f += 1070; /* Add 10.7 MHz IF */
+
+ write_i2c(5, TSA6060T_ADDR, (f << 1) | 1, f >> 7, 0x60 | ((f >> 15) & 1), 0);
+}
+
+static int tr_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ 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;
+
+ strcpy(v.name, "Trust FM Radio");
+
+ 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 = 87500 * 16;
+ v.rangehigh = 108000 * 16;
+ v.flags = VIDEO_TUNER_LOW;
+ v.mode = VIDEO_MODE_AUTO;
+
+ v.signal = tr_getsigstr();
+ if(tr_getstereo())
+ v.flags |= VIDEO_TUNER_STEREO_ON;
+
+ strcpy(v.name, "FM");
+
+ 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;
+
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ if(copy_to_user(arg, &curfreq, sizeof(curfreq)))
+ return -EFAULT;
+ return 0;
+
+ case VIDIOCSFREQ:
+ {
+ unsigned long f;
+
+ if(copy_from_user(&f, arg, sizeof(curfreq)))
+ return -EFAULT;
+ tr_setfreq(f);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+
+ memset(&v,0, sizeof(v));
+ v.flags = VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME |
+ VIDEO_AUDIO_BASS | VIDEO_AUDIO_TREBLE;
+ v.mode = curstereo? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO;
+ v.volume = curvol * 2048;
+ v.step = 2048;
+ v.bass = curbass * 4370;
+ v.treble = curtreble * 4370;
+
+ strcpy(v.name, "Trust FM 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;
+
+ tr_setvol(v.volume);
+ tr_setbass(v.bass);
+ tr_settreble(v.treble);
+ tr_setstereo(v.mode & VIDEO_SOUND_STEREO);
+ tr_setmute(v.flags & VIDEO_AUDIO_MUTE);
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int tr_open(struct video_device *dev, int flags)
+{
+ if(users)
+ return -EBUSY;
+ users++;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void tr_close(struct video_device *dev)
+{
+ users--;
+ MOD_DEC_USE_COUNT;
+}
+
+static struct video_device trust_radio=
+{
+ "Trust FM Radio",
+ VID_TYPE_TUNER,
+ VID_HARDWARE_TRUST,
+ tr_open,
+ tr_close,
+ NULL, /* Can't read (no capture ability) */
+ NULL, /* Can't write */
+ NULL, /* No poll */
+ tr_ioctl,
+ NULL,
+ NULL
+};
+
+static int __init trust_init(void)
+{
+ if(io == -1) {
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+ return -EINVAL;
+ }
+ if(check_region(io, 2)) {
+ printk(KERN_ERR "trust: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+ if(video_register_device(&trust_radio, VFL_TYPE_RADIO)==-1)
+ return -EINVAL;
+
+ request_region(io, 2, "Trust FM Radio");
+
+ printk(KERN_INFO "Trust FM Radio card driver v1.0.\n");
+
+ write_i2c(2, TDA7318_ADDR, 0x80); /* speaker att. LF = 0 dB */
+ write_i2c(2, TDA7318_ADDR, 0xa0); /* speaker att. RF = 0 dB */
+ write_i2c(2, TDA7318_ADDR, 0xc0); /* speaker att. LR = 0 dB */
+ write_i2c(2, TDA7318_ADDR, 0xe0); /* speaker att. RR = 0 dB */
+ write_i2c(2, TDA7318_ADDR, 0x40); /* stereo 1 input, gain = 18.75 dB */
+
+ tr_setvol(0x8000);
+ tr_setbass(0x8000);
+ tr_settreble(0x8000);
+ tr_setstereo(1);
+
+ /* mute card - prevents noisy bootups */
+ tr_setmute(1);
+
+ return 0;
+}
+
+MODULE_AUTHOR("Eric Lammerts, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
+MODULE_DESCRIPTION("A driver for the Trust FM Radio card.");
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "I/O address of the Trust FM Radio card (0x350 or 0x358)");
+
+EXPORT_NO_SYMBOLS;
+
+static void __exit cleanup_trust_module(void)
+{
+ video_unregister_device(&trust_radio);
+ release_region(io, 2);
+}
+
+module_init(trust_init);
+module_exit(cleanup_trust_module);
diff --git a/drivers/media/radio/radio-typhoon.c b/drivers/media/radio/radio-typhoon.c
new file mode 100644
index 000000000..a0bbf343f
--- /dev/null
+++ b/drivers/media/radio/radio-typhoon.c
@@ -0,0 +1,402 @@
+/* Typhoon Radio Card driver for radio support
+ * (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de>
+ *
+ * Card manufacturer:
+ * http://194.18.155.92/idc/prod2.idc?nr=50753&lang=e
+ *
+ * Notes on the hardware
+ *
+ * This card has two output sockets, one for speakers and one for line.
+ * The speaker output has volume control, but only in four discrete
+ * steps. The line output has neither volume control nor mute.
+ *
+ * The card has auto-stereo according to its manual, although it all
+ * sounds mono to me (even with the Win/DOS drivers). Maybe it's my
+ * antenna - I really don't know for sure.
+ *
+ * Frequency control is done digitally.
+ *
+ * Volume control is done digitally, but there are only four different
+ * possible values. So you should better always turn the volume up and
+ * use line control. I got the best results by connecting line output
+ * to the sound card microphone input. For such a configuration the
+ * volume control has no effect, since volume control only influences
+ * the speaker output.
+ *
+ * There is no explicit mute/unmute. So I set the radio frequency to a
+ * value where I do expect just noise and turn the speaker volume down.
+ * The frequency change is necessary since the card never seems to be
+ * completely silent.
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* check_region, request_region */
+#include <linux/proc_fs.h> /* radio card status report */
+#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_TYPHOON_* */
+
+#define BANNER "Typhoon Radio Card driver v0.1\n"
+
+#ifndef CONFIG_RADIO_TYPHOON_PORT
+#define CONFIG_RADIO_TYPHOON_PORT -1
+#endif
+
+#ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ
+#define CONFIG_RADIO_TYPHOON_MUTEFREQ 0
+#endif
+
+#ifndef CONFIG_PROC_FS
+#undef CONFIG_RADIO_TYPHOON_PROC_FS
+#endif
+
+struct typhoon_device {
+ int users;
+ int iobase;
+ int curvol;
+ int muted;
+ unsigned long curfreq;
+ unsigned long mutefreq;
+};
+
+static void typhoon_setvol_generic(struct typhoon_device *dev, int vol);
+static int typhoon_setfreq_generic(struct typhoon_device *dev,
+ unsigned long frequency);
+static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency);
+static void typhoon_mute(struct typhoon_device *dev);
+static void typhoon_unmute(struct typhoon_device *dev);
+static int typhoon_setvol(struct typhoon_device *dev, int vol);
+static int typhoon_ioctl(struct video_device *dev, unsigned int cmd, void *arg);
+static int typhoon_open(struct video_device *dev, int flags);
+static void typhoon_close(struct video_device *dev);
+#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
+static int typhoon_get_info(char *buf, char **start, off_t offset, int len);
+#endif
+
+static void typhoon_setvol_generic(struct typhoon_device *dev, int vol)
+{
+ vol >>= 14; /* Map 16 bit to 2 bit */
+ vol &= 3;
+ outb_p(vol / 2, dev->iobase); /* Set the volume, high bit. */
+ outb_p(vol % 2, dev->iobase + 2); /* Set the volume, low bit. */
+}
+
+static int typhoon_setfreq_generic(struct typhoon_device *dev,
+ unsigned long frequency)
+{
+ unsigned long outval;
+ unsigned long x;
+
+ /*
+ * The frequency transfer curve is not linear. The best fit I could
+ * get is
+ *
+ * outval = -155 + exp((f + 15.55) * 0.057))
+ *
+ * where frequency f is in MHz. Since we don't have exp in the kernel,
+ * I approximate this function by a third order polynomial.
+ *
+ */
+
+ x = frequency / 160;
+ outval = (x * x + 2500) / 5000;
+ outval = (outval * x + 5000) / 10000;
+ outval -= (10 * x * x + 10433) / 20866;
+ outval += 4 * x - 11505;
+
+ outb_p((outval >> 8) & 0x01, dev->iobase + 4);
+ outb_p(outval >> 9, dev->iobase + 6);
+ outb_p(outval & 0xff, dev->iobase + 8);
+
+ return 0;
+}
+
+static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency)
+{
+ typhoon_setfreq_generic(dev, frequency);
+ dev->curfreq = frequency;
+ return 0;
+}
+
+static void typhoon_mute(struct typhoon_device *dev)
+{
+ if (dev->muted == 1)
+ return;
+ typhoon_setvol_generic(dev, 0);
+ typhoon_setfreq_generic(dev, dev->mutefreq);
+ dev->muted = 1;
+}
+
+static void typhoon_unmute(struct typhoon_device *dev)
+{
+ if (dev->muted == 0)
+ return;
+ typhoon_setfreq_generic(dev, dev->curfreq);
+ typhoon_setvol_generic(dev, dev->curvol);
+ dev->muted = 0;
+}
+
+static int typhoon_setvol(struct typhoon_device *dev, int vol)
+{
+ if (dev->muted && vol != 0) { /* user is unmuting the card */
+ dev->curvol = vol;
+ typhoon_unmute(dev);
+ return 0;
+ }
+ if (vol == dev->curvol) /* requested volume == current */
+ return 0;
+
+ if (vol == 0) { /* volume == 0 means mute the card */
+ typhoon_mute(dev);
+ dev->curvol = vol;
+ return 0;
+ }
+ typhoon_setvol_generic(dev, vol);
+ dev->curvol = vol;
+ return 0;
+}
+
+
+static int typhoon_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct typhoon_device *typhoon = 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;
+ strcpy(v.name, "Typhoon Radio");
+ 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 = 875 * 1600;
+ v.rangehigh = 1080 * 1600;
+ v.flags = VIDEO_TUNER_LOW;
+ v.mode = VIDEO_MODE_AUTO;
+ v.signal = 0xFFFF; /* We can't get the signal strength */
+ strcpy(v.name, "FM");
+ 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, &typhoon->curfreq,
+ sizeof(typhoon->curfreq)))
+ return -EFAULT;
+ return 0;
+ case VIDIOCSFREQ:
+ if (copy_from_user(&typhoon->curfreq, arg,
+ sizeof(typhoon->curfreq)))
+ return -EFAULT;
+ typhoon_setfreq(typhoon, typhoon->curfreq);
+ return 0;
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+ memset(&v, 0, sizeof(v));
+ v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME;
+ v.mode |= VIDEO_SOUND_MONO;
+ v.volume = typhoon->curvol;
+ v.step = 1 << 14;
+ strcpy(v.name, "Typhoon 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)
+ typhoon_mute(typhoon);
+ else
+ typhoon_unmute(typhoon);
+
+ if (v.flags & VIDEO_AUDIO_VOLUME)
+ typhoon_setvol(typhoon, v.volume);
+
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int typhoon_open(struct video_device *dev, int flags)
+{
+ struct typhoon_device *typhoon = dev->priv;
+ if (typhoon->users)
+ return -EBUSY;
+ typhoon->users++;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void typhoon_close(struct video_device *dev)
+{
+ struct typhoon_device *typhoon = dev->priv;
+ typhoon->users--;
+ MOD_DEC_USE_COUNT;
+}
+
+static struct typhoon_device typhoon_unit =
+{
+ 0, /* users */
+ CONFIG_RADIO_TYPHOON_PORT, /* iobase */
+ 0, /* curvol */
+ 0, /* muted */
+ CONFIG_RADIO_TYPHOON_MUTEFREQ, /* curfreq */
+ CONFIG_RADIO_TYPHOON_MUTEFREQ /* mutefreq */
+};
+
+static struct video_device typhoon_radio =
+{
+ "Typhoon Radio",
+ VID_TYPE_TUNER,
+ VID_HARDWARE_TYPHOON,
+ typhoon_open,
+ typhoon_close,
+ NULL, /* Can't read (no capture ability) */
+ NULL, /* Can't write */
+ NULL, /* Can't poll */
+ typhoon_ioctl,
+ NULL,
+ NULL
+};
+
+#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
+
+static int typhoon_get_info(char *buf, char **start, off_t offset, int len)
+{
+ char *out = buf;
+
+ #ifdef MODULE
+ #define MODULEPROCSTRING "Driver loaded as a module"
+ #else
+ #define MODULEPROCSTRING "Driver compiled into kernel"
+ #endif
+
+ /* output must be kept under PAGE_SIZE */
+ out += sprintf(out, BANNER);
+ out += sprintf(out, "Load type: " MODULEPROCSTRING "\n\n");
+ out += sprintf(out, "frequency = %lu kHz\n",
+ typhoon_unit.curfreq >> 4);
+ out += sprintf(out, "volume = %d\n", typhoon_unit.curvol);
+ out += sprintf(out, "mute = %s\n", typhoon_unit.muted ?
+ "on" : "off");
+ out += sprintf(out, "iobase = 0x%x\n", typhoon_unit.iobase);
+ out += sprintf(out, "mute frequency = %lu kHz\n",
+ typhoon_unit.mutefreq >> 4);
+ return out - buf;
+}
+
+#endif /* CONFIG_RADIO_TYPHOON_PROC_FS */
+
+MODULE_AUTHOR("Dr. Henrik Seidel");
+MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio).");
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)");
+MODULE_PARM(mutefreq, "i");
+MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)");
+
+EXPORT_NO_SYMBOLS;
+
+static int io = -1;
+
+#ifdef MODULE
+static unsigned long mutefreq = 0;
+#endif
+
+static int __init typhoon_init(void)
+{
+#ifdef MODULE
+ if (io == -1) {
+ printk(KERN_ERR "radio-typhoon: You must set an I/O address with io=0x316 or io=0x336\n");
+ return -EINVAL;
+ }
+ typhoon_unit.iobase = io;
+
+ if (mutefreq < 87000 || mutefreq > 108500) {
+ printk(KERN_ERR "radio-typhoon: You must set a frequency (in kHz) used when muting the card,\n");
+ printk(KERN_ERR "radio-typhoon: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n");
+ return -EINVAL;
+ }
+ typhoon_unit.mutefreq = mutefreq;
+#endif /* MODULE */
+
+ printk(KERN_INFO BANNER);
+ io = typhoon_unit.iobase;
+ if (check_region(io, 8)) {
+ printk(KERN_ERR "radio-typhoon: port 0x%x already in use\n",
+ typhoon_unit.iobase);
+ return -EBUSY;
+ }
+
+ typhoon_radio.priv = &typhoon_unit;
+ if (video_register_device(&typhoon_radio, VFL_TYPE_RADIO) == -1)
+ return -EINVAL;
+
+ request_region(typhoon_unit.iobase, 8, "typhoon");
+ printk(KERN_INFO "radio-typhoon: port 0x%x.\n", typhoon_unit.iobase);
+ printk(KERN_INFO "radio-typhoon: mute frequency is %lu kHz.\n",
+ typhoon_unit.mutefreq);
+ typhoon_unit.mutefreq <<= 4;
+
+ /* mute card - prevents noisy bootups */
+ typhoon_mute(&typhoon_unit);
+
+#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
+ if (!create_proc_info_entry("driver/radio-typhoon", 0, NULL,
+ typhoon_get_info))
+ printk(KERN_ERR "radio-typhoon: registering /proc/driver/radio-typhoon failed\n");
+#endif
+
+ return 0;
+}
+
+static void __exit typhoon_cleanup_module(void)
+{
+
+#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
+ remove_proc_entry("driver/radio-typhoon", NULL);
+#endif
+
+ video_unregister_device(&typhoon_radio);
+ release_region(io, 8);
+}
+
+module_init(typhoon_init);
+module_exit(typhoon_cleanup_module);
+
diff --git a/drivers/media/radio/radio-zoltrix.c b/drivers/media/radio/radio-zoltrix.c
new file mode 100644
index 000000000..dd688935a
--- /dev/null
+++ b/drivers/media/radio/radio-zoltrix.c
@@ -0,0 +1,412 @@
+/* zoltrix radio plus driver for Linux radio support
+ * (c) 1998 C. van Schaik <carl@leg.uct.ac.za>
+ *
+ * BUGS
+ * Due to the inconsistancy in reading from the signal flags
+ * it is difficult to get an accurate tuned signal.
+ *
+ * It seems that the card is not linear to 0 volume. It cuts off
+ * at a low volume, and it is not possible (at least I have not found)
+ * to get fine volume control over the low volume range.
+ *
+ * Some code derived from code by Romolo Manfredini
+ * romolo@bicnet.it
+ *
+ * 1999-05-06 - (C. van Schaik)
+ * - Make signal strength and stereo scans
+ * kinder to cpu while in delay
+ * 1999-01-05 - (C. van Schaik)
+ * - Changed tuning to 1/160Mhz accuracy
+ * - Added stereo support
+ * (card defaults to stereo)
+ * (can explicitly force mono on the card)
+ * (can detect if station is in stereo)
+ * - Added unmute function
+ * - Reworked ioctl functions
+ */
+
+#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;
+ unsigned int stereo;
+ struct semaphore lock;
+};
+
+
+/* local things */
+
+static void sleep_delay(void)
+{
+ /* Sleep nicely for +/- 10 mS */
+ schedule();
+}
+
+static int zol_setvol(struct zol_device *dev, int vol)
+{
+ dev->curvol = vol;
+ if (dev->muted)
+ return 0;
+
+ down(&dev->lock);
+ if (vol == 0) {
+ outb(0, io);
+ outb(0, io);
+ inb(io + 3); /* Zoltrix needs to be read to confirm */
+ up(&dev->lock);
+ return 0;
+ }
+
+ outb(dev->curvol-1, io);
+ sleep_delay();
+ inb(io + 2);
+ up(&dev->lock);
+ return 0;
+}
+
+static void zol_mute(struct zol_device *dev)
+{
+ dev->muted = 1;
+ down(&dev->lock);
+ outb(0, io);
+ outb(0, io);
+ inb(io + 3); /* Zoltrix needs to be read to confirm */
+ up(&dev->lock);
+}
+
+static void zol_unmute(struct zol_device *dev)
+{
+ dev->muted = 0;
+ zol_setvol(dev, dev->curvol);
+}
+
+static int zol_setfreq(struct zol_device *dev, unsigned long freq)
+{
+ /* tunes the radio to the desired frequency */
+ unsigned long long bitmask, f, m;
+ unsigned int stereo = dev->stereo;
+ int i;
+
+ if (freq == 0)
+ return 1;
+ m = (freq / 160 - 8800) * 2;
+ f = (unsigned long long) m + 0x4d1c;
+
+ bitmask = 0xc480402c10080000ull;
+ i = 45;
+
+ down(&dev->lock);
+
+ outb(0, io);
+ outb(0, io);
+ inb(io + 3); /* Zoltrix needs to be read to confirm */
+
+ outb(0x40, io);
+ outb(0xc0, io);
+
+ bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31));
+ while (i--) {
+ if ((bitmask & 0x8000000000000000ull) != 0) {
+ outb(0x80, io);
+ udelay(50);
+ outb(0x00, io);
+ udelay(50);
+ outb(0x80, io);
+ udelay(50);
+ } else {
+ outb(0xc0, io);
+ udelay(50);
+ outb(0x40, io);
+ udelay(50);
+ outb(0xc0, io);
+ udelay(50);
+ }
+ bitmask *= 2;
+ }
+ /* termination sequence */
+ outb(0x80, io);
+ outb(0xc0, io);
+ outb(0x40, io);
+ udelay(1000);
+ inb(io+2);
+
+ udelay(1000);
+
+ if (dev->muted)
+ {
+ outb(0, io);
+ outb(0, io);
+ inb(io + 3);
+ udelay(1000);
+ }
+
+ up(&dev->lock);
+
+ if(!dev->muted)
+ {
+ zol_setvol(dev, dev->curvol);
+ }
+ return 0;
+}
+
+/* Get signal strength */
+
+int zol_getsigstr(struct zol_device *dev)
+{
+ int a, b;
+
+ down(&dev->lock);
+ outb(0x00, io); /* This stuff I found to do nothing */
+ outb(dev->curvol, io);
+ sleep_delay();
+ sleep_delay();
+
+ a = inb(io);
+ sleep_delay();
+ b = inb(io);
+
+ up(&dev->lock);
+
+ if (a != b)
+ return (0);
+
+ if ((a == 0xcf) || (a == 0xdf) /* I found this out by playing */
+ || (a == 0xef)) /* with a binary scanner on the card io */
+ return (1);
+ return (0);
+}
+
+int zol_is_stereo (struct zol_device *dev)
+{
+ int x1, x2;
+
+ down(&dev->lock);
+
+ outb(0x00, io);
+ outb(dev->curvol, io);
+ sleep_delay();
+ sleep_delay();
+
+ x1 = inb(io);
+ sleep_delay();
+ x2 = inb(io);
+
+ up(&dev->lock);
+
+ if ((x1 == x2) && (x1 == 0xcf))
+ return 1;
+ return 0;
+}
+
+static int zol_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct zol_device *zol = dev->priv;
+
+ switch (cmd) {
+ case VIDIOCGCAP:
+ {
+ struct video_capability v;
+ v.type = VID_TYPE_TUNER;
+ v.channels = 1 + zol->stereo;
+ v.audios = 1;
+ /* No we don't do pictures */
+ v.maxwidth = 0;
+ v.maxheight = 0;
+ v.minwidth = 0;
+ v.minheight = 0;
+ strcpy(v.name, "Zoltrix Radio");
+ 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)))
+ return -EFAULT;
+ if (v.tuner)
+ return -EINVAL;
+ strcpy(v.name, "FM");
+ v.rangelow = (int) (88.0 * 16000);
+ v.rangehigh = (int) (108.0 * 16000);
+ v.flags = zol_is_stereo(zol)
+ ? VIDEO_TUNER_STEREO_ON : 0;
+ v.flags |= VIDEO_TUNER_LOW;
+ v.mode = VIDEO_MODE_AUTO;
+ v.signal = 0xFFFF * zol_getsigstr(zol);
+ 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, &zol->curfreq, sizeof(zol->curfreq)))
+ return -EFAULT;
+ return 0;
+ case VIDIOCSFREQ:
+ if (copy_from_user(&zol->curfreq, arg, sizeof(zol->curfreq)))
+ return -EFAULT;
+ zol_setfreq(zol, zol->curfreq);
+ return 0;
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+ memset(&v, 0, sizeof(v));
+ v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME;
+ v.mode != zol_is_stereo(zol)
+ ? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO;
+ v.volume = zol->curvol * 4096;
+ v.step = 4096;
+ strcpy(v.name, "Zoltrix 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(zol);
+ else
+ {
+ zol_unmute(zol);
+ zol_setvol(zol, v.volume / 4096);
+ }
+
+ if (v.mode & VIDEO_SOUND_STEREO)
+ {
+ zol->stereo = 1;
+ zol_setfreq(zol, zol->curfreq);
+ }
+ if (v.mode & VIDEO_SOUND_MONO)
+ {
+ zol->stereo = 0;
+ zol_setfreq(zol, zol->curfreq);
+ }
+
+ 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 */
+ NULL,
+ zol_ioctl,
+ NULL,
+ NULL
+};
+
+static int __init zoltrix_init(void)
+{
+ if (io == -1) {
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+ return -EINVAL;
+ }
+ if (check_region(io, 2)) {
+ printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+ if ((io != 0x20c) && (io != 0x30c)) {
+ printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n");
+ return -ENXIO;
+ }
+ 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");
+
+ init_MUTEX(&zoltrix_unit.lock);
+
+ /* mute card - prevents noisy bootups */
+
+ /* this ensures that the volume is all the way down */
+
+ outb(0, io);
+ outb(0, io);
+ sleep_delay();
+ sleep_delay();
+ inb(io + 3);
+
+ zoltrix_unit.curvol = 0;
+ zoltrix_unit.stereo = 1;
+
+ return 0;
+}
+
+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;
+
+static void __exit zoltrix_cleanup_module(void)
+{
+ video_unregister_device(&zoltrix_radio);
+ release_region(io, 2);
+}
+
+module_init(zoltrix_init);
+module_exit(zoltrix_cleanup_module);
+
diff --git a/drivers/media/video/Config.in b/drivers/media/video/Config.in
new file mode 100644
index 000000000..617dcda93
--- /dev/null
+++ b/drivers/media/video/Config.in
@@ -0,0 +1,45 @@
+#
+# Multimedia Video device configuration
+#
+mainmenu_option next_comment
+comment 'Video For Linux'
+
+bool ' V4L information in proc filesystem' CONFIG_VIDEO_PROC_FS
+dep_tristate ' I2C on parallel port' CONFIG_I2C_PARPORT $CONFIG_PARPORT $CONFIG_I2C
+
+comment 'Video Adapters'
+if [ "$CONFIG_I2C_ALGOBIT" = "y" -o "$CONFIG_I2C_ALGOBIT" = "m" ]; then
+ dep_tristate ' BT848 Video For Linux' CONFIG_VIDEO_BT848 $CONFIG_VIDEO_DEV $CONFIG_PCI $CONFIG_I2C_ALGOBIT
+fi
+dep_tristate ' Mediavision Pro Movie Studio Video For Linux' CONFIG_VIDEO_PMS $CONFIG_VIDEO_DEV
+if [ "$CONFIG_ALL_PPC" = "y" ]; then
+ dep_tristate ' PlanB Video-In on PowerMac' CONFIG_VIDEO_PLANB $CONFIG_VIDEO_DEV
+fi
+if [ "$CONFIG_PARPORT" != "n" ]; then
+ dep_tristate ' Quickcam BW Video For Linux' CONFIG_VIDEO_BWQCAM $CONFIG_VIDEO_DEV $CONFIG_PARPORT
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ dep_tristate ' QuickCam Colour Video For Linux (EXPERIMENTAL)' CONFIG_VIDEO_CQCAM $CONFIG_VIDEO_DEV $CONFIG_PARPORT
+ fi
+fi
+dep_tristate ' CPiA Video For Linux' CONFIG_VIDEO_CPIA $CONFIG_VIDEO_DEV
+if [ "$CONFIG_VIDEO_CPIA" != "n" ]; then
+ if [ "CONFIG_PARPORT_1284" != "n" ]; then
+ dep_tristate ' CPiA Parallel Port Lowlevel Support' CONFIG_VIDEO_CPIA_PP $CONFIG_VIDEO_CPIA $CONFIG_PARPORT
+ fi
+ if [ "$CONFIG_USB" != "n" ]; then
+ dep_tristate ' CPiA USB Lowlevel Support' CONFIG_VIDEO_CPIA_USB $CONFIG_VIDEO_CPIA $CONFIG_USB
+ fi
+fi
+dep_tristate ' SAA5249 Teletext processor' CONFIG_VIDEO_SAA5249 $CONFIG_VIDEO_DEV $CONFIG_I2C
+dep_tristate ' SAB3036 tuner' CONFIG_TUNER_3036 $CONFIG_VIDEO_DEV $CONFIG_I2C
+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ if [ "$CONFIG_SGI" = "y" ]; then
+ dep_tristate ' SGI Vino Video For Linux (EXPERIMENTAL)' CONFIG_VIDEO_VINO $CONFIG_VIDEO_DEV $CONFIG_SGI
+ fi
+ dep_tristate ' Stradis 4:2:2 MPEG-2 video driver (EXPERIMENTAL)' CONFIG_VIDEO_STRADIS $CONFIG_VIDEO_DEV $CONFIG_PCI
+fi
+dep_tristate ' Zoran ZR36057/36060 Video For Linux' CONFIG_VIDEO_ZORAN $CONFIG_VIDEO_DEV $CONFIG_PCI $CONFIG_I2C
+dep_tristate ' Include support for Iomega Buz' CONFIG_VIDEO_BUZ $CONFIG_VIDEO_ZORAN
+dep_tristate ' Zoran ZR36120/36125 Video For Linux' CONFIG_VIDEO_ZR36120 $CONFIG_VIDEO_DEV $CONFIG_PCI $CONFIG_I2C
+
+endmenu
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
new file mode 100644
index 000000000..fac6990c4
--- /dev/null
+++ b/drivers/media/video/Makefile
@@ -0,0 +1,96 @@
+#
+# Makefile for the kernel character device drivers.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definitions are now inherited from the
+# parent makes..
+#
+
+O_OBJS :=
+OX_OBJS :=
+M_OBJS :=
+MX_OBJS :=
+
+# Object file lists.
+
+obj-y :=
+obj-m :=
+obj-n :=
+obj- :=
+
+SUB_DIRS :=
+MOD_SUB_DIRS := $(SUB_DIRS)
+ALL_SUB_DIRS := $(SUB_DIRS)
+
+O_TARGET := video.o
+
+# All of the (potential) objects that export symbols.
+# This list comes from 'grep -l EXPORT_SYMBOL *.[hc]'.
+
+export-objs := i2c-old.o videodev.o bttv-if.o cpia.o
+
+list-multi := bttv.o zoran.o
+bttv-objs := bttv-driver.o bttv-cards.o bttv-if.o
+zoran-objs := zr36120.o zr36120_i2c.o zr36120_mem.o
+
+obj-$(CONFIG_VIDEO_DEV) += videodev.o
+
+obj-$(CONFIG_BUS_I2C) += i2c-old.o
+obj-$(CONFIG_VIDEO_BT848) += bttv.o msp3400.o \
+ tda7432.o tda8425.o tda985x.o tda9875.o tea6300.o tea6420.o tuner.o
+obj-$(CONFIG_SOUND_TVMIXER) += tvmixer.o
+
+obj-$(CONFIG_VIDEO_ZR36120) += zoran.o i2c-old.o tuner.o saa7110.o saa7111.o saa7185.o
+obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o i2c-old.o
+obj-$(CONFIG_VIDEO_SAA5249) += saa5249.o i2c-old.o
+obj-$(CONFIG_VIDEO_CQCAM) += c-qcam.o
+obj-$(CONFIG_VIDEO_BWQCAM) += bw-qcam.o
+obj-$(CONFIG_VIDEO_ZORAN) += buz.o i2c-old.o saa7110.o saa7111.o saa7185.o
+obj-$(CONFIG_VIDEO_LML33) += bt856.o bt819.o
+obj-$(CONFIG_VIDEO_PMS) += pms.o
+obj-$(CONFIG_VIDEO_PLANB) += planb.o
+obj-$(CONFIG_VIDEO_VINO) += vino.o
+obj-$(CONFIG_VIDEO_STRADIS) += stradis.o
+obj-$(CONFIG_VIDEO_CPIA) += cpia.o
+obj-$(CONFIG_VIDEO_CPIA_PP) += cpia_pp.o
+obj-$(CONFIG_VIDEO_CPIA_USB) += cpia_usb.o
+obj-$(CONFIG_TUNER_3036) += tuner-3036.o
+
+# Extract lists of the multi-part drivers.
+# The 'int-*' lists are the intermediate files used to build the multi's.
+
+multi-y := $(filter $(list-multi), $(obj-y))
+multi-m := $(filter $(list-multi), $(obj-m))
+int-y := $(sort $(foreach m, $(multi-y), $($(basename $(m))-objs)))
+int-m := $(sort $(foreach m, $(multi-m), $($(basename $(m))-objs)))
+
+# Files that are both resident and modular: remove from modular.
+
+obj-m := $(filter-out $(obj-y), $(obj-m))
+int-m := $(filter-out $(int-y), $(int-m))
+
+# Take multi-part drivers out of obj-y and put components in.
+
+obj-y := $(filter-out $(list-multi), $(obj-y)) $(int-y)
+
+# Translate to Rules.make lists.
+
+O_OBJS := $(filter-out $(export-objs), $(obj-y))
+OX_OBJS := $(filter $(export-objs), $(obj-y))
+M_OBJS := $(sort $(filter-out $(export-objs), $(obj-m)))
+MX_OBJS := $(sort $(filter $(export-objs), $(obj-m)))
+MI_OBJS := $(sort $(filter-out $(export-objs), $(int-m)))
+MIX_OBJS := $(sort $(filter $(export-objs), $(int-m)))
+
+include $(TOPDIR)/Rules.make
+
+fastdep:
+
+zoran.o: zr36120.o zr36120_i2c.o zr36120_mem.o
+ $(LD) $(LD_RFLAG) -r -o $@ zr36120.o zr36120_i2c.o zr36120_mem.o
+
+bttv.o: $(bttv-objs)
+ $(LD) $(LD_RFLAG) -r -o $@ $(bttv-objs)
diff --git a/drivers/media/video/audiochip.h b/drivers/media/video/audiochip.h
new file mode 100644
index 000000000..23d1b1259
--- /dev/null
+++ b/drivers/media/video/audiochip.h
@@ -0,0 +1,60 @@
+#ifndef AUDIOCHIP_H
+#define AUDIOCHIP_H
+
+/* ---------------------------------------------------------------------- */
+
+#define MIN(a,b) (((a)>(b))?(b):(a))
+#define MAX(a,b) (((a)>(b))?(a):(b))
+
+/* v4l device was opened in Radio mode */
+#define AUDC_SET_RADIO _IO('m',2)
+/* select from TV,radio,extern,MUTE */
+#define AUDC_SET_INPUT _IOW('m',17,int)
+
+/* all the stuff below is obsolete and just here for reference. I'll
+ * remove it once the driver is tested and works fine.
+ *
+ * Instead creating alot of tiny API's for all kinds of different
+ * chips, we'll just pass throuth the v4l ioctl structs (v4l2 not
+ * yet...). It is a bit less flexible, but most/all used i2c chips
+ * make sense in v4l context only. So I think that's acceptable...
+ */
+
+#if 0
+
+/* TODO (if it is ever [to be] accessible in the V4L[2] spec):
+ * maybe fade? (back/front)
+ * notes:
+ * NEWCHANNEL and SWITCH_MUTE are here because the MSP3400 has a special
+ * routine to go through when it tunes in to a new channel before turning
+ * back on the sound.
+ * Either SET_RADIO, NEWCHANNEL, and SWITCH_MUTE or SET_INPUT need to be
+ * implemented (MSP3400 uses SET_RADIO to select inputs, and SWITCH_MUTE for
+ * channel-change mute -- TEA6300 et al use SET_AUDIO to select input [TV,
+ * radio, external, or MUTE]). If both methods are implemented, you get a
+ * cookie for doing such a good job! :)
+ */
+
+#define AUDC_SET_TVNORM _IOW('m',1,int) /* TV mode + PAL/SECAM/NTSC */
+#define AUDC_NEWCHANNEL _IO('m',3) /* indicate new chan - off mute */
+
+#define AUDC_GET_VOLUME_LEFT _IOR('m',4,__u16)
+#define AUDC_GET_VOLUME_RIGHT _IOR('m',5,__u16)
+#define AUDC_SET_VOLUME_LEFT _IOW('m',6,__u16)
+#define AUDC_SET_VOLUME_RIGHT _IOW('m',7,__u16)
+
+#define AUDC_GET_STEREO _IOR('m',8,__u16)
+#define AUDC_SET_STEREO _IOW('m',9,__u16)
+
+#define AUDC_GET_DC _IOR('m',10,__u16)/* ??? */
+
+#define AUDC_GET_BASS _IOR('m',11,__u16)
+#define AUDC_SET_BASS _IOW('m',12,__u16)
+#define AUDC_GET_TREBLE _IOR('m',13,__u16)
+#define AUDC_SET_TREBLE _IOW('m',14,__u16)
+
+#define AUDC_GET_UNIT _IOR('m',15,int) /* ??? - unimplemented in MSP3400 */
+#define AUDC_SWITCH_MUTE _IO('m',16) /* turn on mute */
+#endif
+
+#endif /* AUDIOCHIP_H */
diff --git a/drivers/media/video/bt848.h b/drivers/media/video/bt848.h
new file mode 100644
index 000000000..340accf31
--- /dev/null
+++ b/drivers/media/video/bt848.h
@@ -0,0 +1,355 @@
+/*
+ bt848.h - Bt848 register offsets
+
+ Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef _BT848_H_
+#define _BT848_H_
+
+#ifndef PCI_VENDOR_ID_BROOKTREE
+#define PCI_VENDOR_ID_BROOKTREE 0x109e
+#endif
+#ifndef PCI_DEVICE_ID_BT848
+#define PCI_DEVICE_ID_BT848 0x350
+#endif
+#ifndef PCI_DEVICE_ID_BT849
+#define PCI_DEVICE_ID_BT849 0x351
+#endif
+#ifndef PCI_DEVICE_ID_BT878
+#define PCI_DEVICE_ID_BT878 0x36e
+#endif
+#ifndef PCI_DEVICE_ID_BT879
+#define PCI_DEVICE_ID_BT879 0x36f
+#endif
+
+
+/* Brooktree 848 registers */
+
+#define BT848_DSTATUS 0x000
+#define BT848_DSTATUS_PRES (1<<7)
+#define BT848_DSTATUS_HLOC (1<<6)
+#define BT848_DSTATUS_FIELD (1<<5)
+#define BT848_DSTATUS_NUML (1<<4)
+#define BT848_DSTATUS_CSEL (1<<3)
+#define BT848_DSTATUS_PLOCK (1<<2)
+#define BT848_DSTATUS_LOF (1<<1)
+#define BT848_DSTATUS_COF (1<<0)
+
+#define BT848_IFORM 0x004
+#define BT848_IFORM_HACTIVE (1<<7)
+#define BT848_IFORM_MUXSEL (3<<5)
+#define BT848_IFORM_MUX0 (2<<5)
+#define BT848_IFORM_MUX1 (3<<5)
+#define BT848_IFORM_MUX2 (1<<5)
+#define BT848_IFORM_XTSEL (3<<3)
+#define BT848_IFORM_XT0 (1<<3)
+#define BT848_IFORM_XT1 (2<<3)
+#define BT848_IFORM_XTAUTO (3<<3)
+#define BT848_IFORM_XTBOTH (3<<3)
+#define BT848_IFORM_NTSC 1
+#define BT848_IFORM_NTSC_J 2
+#define BT848_IFORM_PAL_BDGHI 3
+#define BT848_IFORM_PAL_M 4
+#define BT848_IFORM_PAL_N 5
+#define BT848_IFORM_SECAM 6
+#define BT848_IFORM_PAL_NC 7
+#define BT848_IFORM_AUTO 0
+#define BT848_IFORM_NORM 7
+
+#define BT848_TDEC 0x008
+#define BT848_TDEC_DEC_FIELD (1<<7)
+#define BT848_TDEC_FLDALIGN (1<<6)
+#define BT848_TDEC_DEC_RAT (0x1f)
+
+#define BT848_E_CROP 0x00C
+#define BT848_O_CROP 0x08C
+
+#define BT848_E_VDELAY_LO 0x010
+#define BT848_O_VDELAY_LO 0x090
+
+#define BT848_E_VACTIVE_LO 0x014
+#define BT848_O_VACTIVE_LO 0x094
+
+#define BT848_E_HDELAY_LO 0x018
+#define BT848_O_HDELAY_LO 0x098
+
+#define BT848_E_HACTIVE_LO 0x01C
+#define BT848_O_HACTIVE_LO 0x09C
+
+#define BT848_E_HSCALE_HI 0x020
+#define BT848_O_HSCALE_HI 0x0A0
+
+#define BT848_E_HSCALE_LO 0x024
+#define BT848_O_HSCALE_LO 0x0A4
+
+#define BT848_BRIGHT 0x028
+
+#define BT848_E_CONTROL 0x02C
+#define BT848_O_CONTROL 0x0AC
+#define BT848_CONTROL_LNOTCH (1<<7)
+#define BT848_CONTROL_COMP (1<<6)
+#define BT848_CONTROL_LDEC (1<<5)
+#define BT848_CONTROL_CBSENSE (1<<4)
+#define BT848_CONTROL_CON_MSB (1<<2)
+#define BT848_CONTROL_SAT_U_MSB (1<<1)
+#define BT848_CONTROL_SAT_V_MSB (1<<0)
+
+#define BT848_CONTRAST_LO 0x030
+#define BT848_SAT_U_LO 0x034
+#define BT848_SAT_V_LO 0x038
+#define BT848_HUE 0x03C
+
+#define BT848_E_SCLOOP 0x040
+#define BT848_O_SCLOOP 0x0C0
+#define BT848_SCLOOP_CAGC (1<<6)
+#define BT848_SCLOOP_CKILL (1<<5)
+#define BT848_SCLOOP_HFILT_AUTO (0<<3)
+#define BT848_SCLOOP_HFILT_CIF (1<<3)
+#define BT848_SCLOOP_HFILT_QCIF (2<<3)
+#define BT848_SCLOOP_HFILT_ICON (3<<3)
+
+#define BT848_SCLOOP_PEAK (1<<7)
+#define BT848_SCLOOP_HFILT_MINP (1<<3)
+#define BT848_SCLOOP_HFILT_MEDP (2<<3)
+#define BT848_SCLOOP_HFILT_MAXP (3<<3)
+
+
+#define BT848_OFORM 0x048
+#define BT848_OFORM_RANGE (1<<7)
+#define BT848_OFORM_CORE0 (0<<5)
+#define BT848_OFORM_CORE8 (1<<5)
+#define BT848_OFORM_CORE16 (2<<5)
+#define BT848_OFORM_CORE32 (3<<5)
+
+#define BT848_E_VSCALE_HI 0x04C
+#define BT848_O_VSCALE_HI 0x0CC
+#define BT848_VSCALE_YCOMB (1<<7)
+#define BT848_VSCALE_COMB (1<<6)
+#define BT848_VSCALE_INT (1<<5)
+#define BT848_VSCALE_HI 15
+
+#define BT848_E_VSCALE_LO 0x050
+#define BT848_O_VSCALE_LO 0x0D0
+#define BT848_TEST 0x054
+#define BT848_ADELAY 0x060
+#define BT848_BDELAY 0x064
+
+#define BT848_ADC 0x068
+#define BT848_ADC_RESERVED (2<<6)
+#define BT848_ADC_SYNC_T (1<<5)
+#define BT848_ADC_AGC_EN (1<<4)
+#define BT848_ADC_CLK_SLEEP (1<<3)
+#define BT848_ADC_Y_SLEEP (1<<2)
+#define BT848_ADC_C_SLEEP (1<<1)
+#define BT848_ADC_CRUSH (1<<0)
+
+#define BT848_E_VTC 0x06C
+#define BT848_O_VTC 0x0EC
+#define BT848_VTC_HSFMT (1<<7)
+#define BT848_VTC_VFILT_2TAP 0
+#define BT848_VTC_VFILT_3TAP 1
+#define BT848_VTC_VFILT_4TAP 2
+#define BT848_VTC_VFILT_5TAP 3
+
+#define BT848_SRESET 0x07C
+
+#define BT848_COLOR_FMT 0x0D4
+#define BT848_COLOR_FMT_O_RGB32 (0<<4)
+#define BT848_COLOR_FMT_O_RGB24 (1<<4)
+#define BT848_COLOR_FMT_O_RGB16 (2<<4)
+#define BT848_COLOR_FMT_O_RGB15 (3<<4)
+#define BT848_COLOR_FMT_O_YUY2 (4<<4)
+#define BT848_COLOR_FMT_O_BtYUV (5<<4)
+#define BT848_COLOR_FMT_O_Y8 (6<<4)
+#define BT848_COLOR_FMT_O_RGB8 (7<<4)
+#define BT848_COLOR_FMT_O_YCrCb422 (8<<4)
+#define BT848_COLOR_FMT_O_YCrCb411 (9<<4)
+#define BT848_COLOR_FMT_O_RAW (14<<4)
+#define BT848_COLOR_FMT_E_RGB32 0
+#define BT848_COLOR_FMT_E_RGB24 1
+#define BT848_COLOR_FMT_E_RGB16 2
+#define BT848_COLOR_FMT_E_RGB15 3
+#define BT848_COLOR_FMT_E_YUY2 4
+#define BT848_COLOR_FMT_E_BtYUV 5
+#define BT848_COLOR_FMT_E_Y8 6
+#define BT848_COLOR_FMT_E_RGB8 7
+#define BT848_COLOR_FMT_E_YCrCb422 8
+#define BT848_COLOR_FMT_E_YCrCb411 9
+#define BT848_COLOR_FMT_E_RAW 14
+
+#define BT848_COLOR_FMT_RGB32 0x00
+#define BT848_COLOR_FMT_RGB24 0x11
+#define BT848_COLOR_FMT_RGB16 0x22
+#define BT848_COLOR_FMT_RGB15 0x33
+#define BT848_COLOR_FMT_YUY2 0x44
+#define BT848_COLOR_FMT_BtYUV 0x55
+#define BT848_COLOR_FMT_Y8 0x66
+#define BT848_COLOR_FMT_RGB8 0x77
+#define BT848_COLOR_FMT_YCrCb422 0x88
+#define BT848_COLOR_FMT_YCrCb411 0x99
+#define BT848_COLOR_FMT_RAW 0xee
+
+#define BT848_COLOR_CTL 0x0D8
+#define BT848_COLOR_CTL_EXT_FRMRATE (1<<7)
+#define BT848_COLOR_CTL_COLOR_BARS (1<<6)
+#define BT848_COLOR_CTL_RGB_DED (1<<5)
+#define BT848_COLOR_CTL_GAMMA (1<<4)
+#define BT848_COLOR_CTL_WSWAP_ODD (1<<3)
+#define BT848_COLOR_CTL_WSWAP_EVEN (1<<2)
+#define BT848_COLOR_CTL_BSWAP_ODD (1<<1)
+#define BT848_COLOR_CTL_BSWAP_EVEN (1<<0)
+
+#define BT848_CAP_CTL 0x0DC
+#define BT848_CAP_CTL_DITH_FRAME (1<<4)
+#define BT848_CAP_CTL_CAPTURE_VBI_ODD (1<<3)
+#define BT848_CAP_CTL_CAPTURE_VBI_EVEN (1<<2)
+#define BT848_CAP_CTL_CAPTURE_ODD (1<<1)
+#define BT848_CAP_CTL_CAPTURE_EVEN (1<<0)
+
+#define BT848_VBI_PACK_SIZE 0x0E0
+
+#define BT848_VBI_PACK_DEL 0x0E4
+#define BT848_VBI_PACK_DEL_VBI_HDELAY 0xfc
+#define BT848_VBI_PACK_DEL_EXT_FRAME 2
+#define BT848_VBI_PACK_DEL_VBI_PKT_HI 1
+
+
+#define BT848_INT_STAT 0x100
+#define BT848_INT_MASK 0x104
+
+#define BT848_INT_ETBF (1<<23)
+
+#define BT848_INT_RISCS (0xf<<28)
+#define BT848_INT_RISC_EN (1<<27)
+#define BT848_INT_RACK (1<<25)
+#define BT848_INT_FIELD (1<<24)
+#define BT848_INT_SCERR (1<<19)
+#define BT848_INT_OCERR (1<<18)
+#define BT848_INT_PABORT (1<<17)
+#define BT848_INT_RIPERR (1<<16)
+#define BT848_INT_PPERR (1<<15)
+#define BT848_INT_FDSR (1<<14)
+#define BT848_INT_FTRGT (1<<13)
+#define BT848_INT_FBUS (1<<12)
+#define BT848_INT_RISCI (1<<11)
+#define BT848_INT_GPINT (1<<9)
+#define BT848_INT_I2CDONE (1<<8)
+#define BT848_INT_VPRES (1<<5)
+#define BT848_INT_HLOCK (1<<4)
+#define BT848_INT_OFLOW (1<<3)
+#define BT848_INT_HSYNC (1<<2)
+#define BT848_INT_VSYNC (1<<1)
+#define BT848_INT_FMTCHG (1<<0)
+
+
+#define BT848_GPIO_DMA_CTL 0x10C
+#define BT848_GPIO_DMA_CTL_GPINTC (1<<15)
+#define BT848_GPIO_DMA_CTL_GPINTI (1<<14)
+#define BT848_GPIO_DMA_CTL_GPWEC (1<<13)
+#define BT848_GPIO_DMA_CTL_GPIOMODE (3<<11)
+#define BT848_GPIO_DMA_CTL_GPCLKMODE (1<<10)
+#define BT848_GPIO_DMA_CTL_PLTP23_4 (0<<6)
+#define BT848_GPIO_DMA_CTL_PLTP23_8 (1<<6)
+#define BT848_GPIO_DMA_CTL_PLTP23_16 (2<<6)
+#define BT848_GPIO_DMA_CTL_PLTP23_32 (3<<6)
+#define BT848_GPIO_DMA_CTL_PLTP1_4 (0<<4)
+#define BT848_GPIO_DMA_CTL_PLTP1_8 (1<<4)
+#define BT848_GPIO_DMA_CTL_PLTP1_16 (2<<4)
+#define BT848_GPIO_DMA_CTL_PLTP1_32 (3<<4)
+#define BT848_GPIO_DMA_CTL_PKTP_4 (0<<2)
+#define BT848_GPIO_DMA_CTL_PKTP_8 (1<<2)
+#define BT848_GPIO_DMA_CTL_PKTP_16 (2<<2)
+#define BT848_GPIO_DMA_CTL_PKTP_32 (3<<2)
+#define BT848_GPIO_DMA_CTL_RISC_ENABLE (1<<1)
+#define BT848_GPIO_DMA_CTL_FIFO_ENABLE (1<<0)
+
+#define BT848_I2C 0x110
+#define BT848_I2C_DIV (0xf<<4)
+#define BT848_I2C_SYNC (1<<3)
+#define BT848_I2C_W3B (1<<2)
+#define BT848_I2C_SCL (1<<1)
+#define BT848_I2C_SDA (1<<0)
+
+
+#define BT848_RISC_STRT_ADD 0x114
+#define BT848_GPIO_OUT_EN 0x118
+#define BT848_GPIO_REG_INP 0x11C
+#define BT848_RISC_COUNT 0x120
+#define BT848_GPIO_DATA 0x200
+
+
+/* Bt848 RISC commands */
+
+/* only for the SYNC RISC command */
+#define BT848_FIFO_STATUS_FM1 0x06
+#define BT848_FIFO_STATUS_FM3 0x0e
+#define BT848_FIFO_STATUS_SOL 0x02
+#define BT848_FIFO_STATUS_EOL4 0x01
+#define BT848_FIFO_STATUS_EOL3 0x0d
+#define BT848_FIFO_STATUS_EOL2 0x09
+#define BT848_FIFO_STATUS_EOL1 0x05
+#define BT848_FIFO_STATUS_VRE 0x04
+#define BT848_FIFO_STATUS_VRO 0x0c
+#define BT848_FIFO_STATUS_PXV 0x00
+
+#define BT848_RISC_RESYNC (1<<15)
+
+/* WRITE and SKIP */
+/* disable which bytes of each DWORD */
+#define BT848_RISC_BYTE0 (1<<12)
+#define BT848_RISC_BYTE1 (1<<13)
+#define BT848_RISC_BYTE2 (1<<14)
+#define BT848_RISC_BYTE3 (1<<15)
+#define BT848_RISC_BYTE_ALL (0x0f<<12)
+#define BT848_RISC_BYTE_NONE 0
+/* cause RISCI */
+#define BT848_RISC_IRQ (1<<24)
+/* RISC command is last one in this line */
+#define BT848_RISC_EOL (1<<26)
+/* RISC command is first one in this line */
+#define BT848_RISC_SOL (1<<27)
+
+#define BT848_RISC_WRITE (0x01<<28)
+#define BT848_RISC_SKIP (0x02<<28)
+#define BT848_RISC_WRITEC (0x05<<28)
+#define BT848_RISC_JUMP (0x07<<28)
+#define BT848_RISC_SYNC (0x08<<28)
+
+#define BT848_RISC_WRITE123 (0x09<<28)
+#define BT848_RISC_SKIP123 (0x0a<<28)
+#define BT848_RISC_WRITE1S23 (0x0b<<28)
+
+
+
+/* Bt848A and higher only !! */
+#define BT848_TGLB 0x080
+#define BT848_TGCTRL 0x084
+#define BT848_FCAP 0x0E8
+#define BT848_PLL_F_LO 0x0F0
+#define BT848_PLL_F_HI 0x0F4
+
+#define BT848_PLL_XCI 0x0F8
+#define BT848_PLL_X (1<<7)
+#define BT848_PLL_C (1<<6)
+
+/* Bt878 register */
+
+#define BT878_DEVCTRL 0x40
+#define BT878_EN_TBFX 0x02
+
+#endif
diff --git a/drivers/media/video/bttv-cards.c b/drivers/media/video/bttv-cards.c
new file mode 100644
index 000000000..b2c26e35b
--- /dev/null
+++ b/drivers/media/video/bttv-cards.c
@@ -0,0 +1,835 @@
+/*
+ bttv-cards.c -- this file has card-specific stuff
+
+
+ bttv - Bt848 frame grabber driver
+
+ Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
+ & Marcus Metzler (mocm@thp.uni-koeln.de)
+ (c) 1999,2000 Gerd Knorr <kraxel@goldbach.in-berlin.de>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#define __NO_VERSION__ 1
+
+#include <linux/version.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+
+#include "bttv.h"
+#include "tuner.h"
+
+/* fwd decl */
+static void hauppauge_eeprom(struct bttv *btv);
+static void hauppauge_boot_msp34xx(struct bttv *btv);
+static void init_PXC200(struct bttv *btv);
+static void init_tea5757(struct bttv *btv);
+
+MODULE_PARM(card,"1-4i");
+MODULE_PARM(pll,"1-4i");
+MODULE_PARM(autoload,"i");
+
+static unsigned int card[4] = { -1, -1, -1, -1 };
+static unsigned int pll[4] = { -1, -1, -1, -1 };
+#ifdef MODULE
+static unsigned int autoload = 1;
+#else
+static unsigned int autoload = 0;
+#endif
+
+/* ----------------------------------------------------------------------- */
+/* list of card IDs for bt878+ cards */
+
+static struct CARD {
+ unsigned id;
+ int cardnr;
+ char *name;
+} cards[] __devinitdata = {
+ { 0x00011002, BTTV_HAUPPAUGE878, "ATI TV Wonder" },
+ { 0x00011461, BTTV_AVPHONE98, "AVerMedia TVPhone98" },
+ { 0x00021461, BTTV_AVERMEDIA98, "Avermedia TVCapture 98" },
+ { 0x00031461, BTTV_AVPHONE98, "AVerMedia TVPhone98" },
+ { 0x00041461, BTTV_AVPHONE98, "AVerMedia TVPhone98" },
+ { 0x10b42636, BTTV_HAUPPAUGE878, "STB ???" },
+ { 0x1118153b, BTTV_TERRATVALUE, "Terratec TV Value" },
+ { 0x1123153b, BTTV_TERRATVRADIO, "Terratec TV/Radio+" },
+ { 0x1200bd11, BTTV_PINNACLERAVE, "Pinnacle PCTV Rave" },
+ { 0x13eb0070, BTTV_HAUPPAUGE878, "Hauppauge WinTV" },
+ { 0x18501851, BTTV_CHRONOS_VS2, "Chronos Video Shuttle II" },
+ { 0x18521852, BTTV_TYPHOON_TVIEW, "Typhoon TView TV/FM Tuner" },
+ { 0x217d6606, BTTV_WINFAST2000, "Leadtek WinFast TV 2000" },
+ { 0x263610b4, BTTV_STB2, "STB TV PCI FM, P/N 6000704" },
+ { 0x3000144f, BTTV_MAGICTVIEW063, "TView 99 (CPH063)" },
+ { 0x300014ff, BTTV_MAGICTVIEW061, "TView 99 (CPH061)" },
+ { 0x3002144f, BTTV_MAGICTVIEW061, "Askey Magic TView" },
+ { 0x300214ff, BTTV_PHOEBE_TVMAS, "Phoebe TV Master" },
+ { 0x400a15b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV" },
+ { 0x402010fc, 0 /* no tvcards entry yet */, "I-O Data Co. GV-BCV3/PCI" },
+#if 0 /* probably wrong */
+ { 0x14610002, BTTV_AVERMEDIA98, "Avermedia TVCapture 98" },
+ { 0x6606217d, BTTV_WINFAST2000, "Leadtek WinFast TV 2000" },
+#endif
+ { 0, -1, NULL }
+};
+
+/* ----------------------------------------------------------------------- */
+/* array with description for bt848 / bt878 tv/grabber cards */
+
+struct tvcard bttv_tvcards[] =
+{
+ /* 0x00 */
+ { " *** UNKNOWN *** ",
+ 3, 1, 0, 2, 0, { 2, 3, 1, 1}, { 0, 0, 0, 0, 0},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "MIRO PCTV",
+ 4, 1, 0, 2,15, { 2, 3, 1, 1}, { 2, 0, 0, 0,10},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "Hauppauge old",
+ 4, 1, 0, 2, 7, { 2, 3, 1, 1}, { 0, 1, 2, 3, 4},0,
+ 1,1,0,1,0,0,0,1, PLL_NONE, -1 },
+ { "STB",
+ 3, 1, 0, 2, 7, { 2, 3, 1, 1}, { 4, 0, 2, 3, 1},0,
+ 0,1,1,1,1,0,0,1, PLL_NONE, -1 },
+
+ { "Intel",
+ 3, 1, 0, -1, 7, { 2, 3, 1, 1}, { 0, 1, 2, 3, 4},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "Diamond DTV2000",
+ 3, 1, 0, 2, 3, { 2, 3, 1, 1}, { 0, 1, 0, 1, 3},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "AVerMedia TVPhone",
+ 3, 1, 0, 3,15, { 2, 3, 1, 1}, {12, 4,11,11, 0},0,
+ 1,1,1,1,0,0,0,1, PLL_28, -1 },
+ { "MATRIX-Vision MV-Delta",
+ 5, 1, -1, 3, 0, { 2, 3, 1, 0, 0},{0 }, 0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+
+ /* 0x08 */
+ { "Fly Video II",
+ 3, 1, 0, 2, 0xc00, { 2, 3, 1, 1},
+ { 0, 0xc00, 0x800, 0x400, 0xc00, 0},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "TurboTV",
+ 3, 1, 0, 2, 3, { 2, 3, 1, 1}, { 1, 1, 2, 3, 0},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "Hauppauge new (bt878)",
+ 4, 1, 0, 2, 7, { 2, 0, 1, 1}, { 0, 1, 2, 3, 4},0,
+ 1,1,0,1,0,0,0,1, PLL_28, -1 },
+ { "MIRO PCTV pro",
+ 3, 1, 0, 2, 65551, { 2, 3, 1, 1}, {1,65537, 0, 0,10},0,
+ /* 3, 1, 0, 2, 0x3004F, { 2, 3, 1, 1}, {1, 0x10011, 5, 0,10}, 0x3004F, */
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+
+ { "ADS Technologies Channel Surfer TV",
+ 3, 1, 2, 2, 15, { 2, 3, 1, 1}, { 13, 14, 11, 7, 0, 0},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "AVerMedia TVCapture 98",
+ 3, 4, 0, 2, 15, { 2, 3, 1, 1}, { 13, 14, 11, 7, 0, 0},0,
+ 1,1,1,1,0,0,0,1, PLL_28, -1 },
+ { "Aimslab VHX",
+ 3, 1, 0, 2, 7, { 2, 3, 1, 1}, { 0, 1, 2, 3, 4},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "Zoltrix TV-Max",
+ 3, 1, 0, 2,15, { 2, 3, 1, 1}, {0 , 0, 1 , 0, 10},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+
+ /* 0x10 */
+ { "Pixelview PlayTV (bt878)",
+ 3, 1, 0, 2, 0x01fe00, { 2, 3, 1, 1},
+ { 0x01c000, 0, 0x018000, 0x014000, 0x002000, 0 },0,
+ 1,1,1,1,0,0,0,1, PLL_28, -1 },
+ { "Leadtek WinView 601",
+ 3, 1, 0, 2, 0x8300f8, { 2, 3, 1, 1,0},
+ { 0x4fa007,0xcfa007,0xcfa007,0xcfa007,0xcfa007,0xcfa007},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "AVEC Intercapture",
+ 3, 2, 0, 2, 0, {2, 3, 1, 1}, {1, 0, 0, 0, 0},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "LifeView FlyKit w/o Tuner",
+ 3, 1, -1, -1, 0x8dff00, { 2, 3, 1, 1}, { 0 },0,
+ 0,0,0,0,0,0,0,1, PLL_NONE, -1 },
+
+ { "CEI Raffles Card",
+ 3, 3, 0, 2, 0, {2, 3, 1, 1}, {0, 0, 0, 0 ,0},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "Lucky Star Image World ConferenceTV",
+ 3, 1, 0, 2, 0x00fffe07, { 2, 3, 1, 1}, { 131072, 1, 1638400, 3, 4},0,
+ 1,1,1,1,0,0,0,1, PLL_28, TUNER_PHILIPS_PAL_I },
+ { "Phoebe Tv Master + FM",
+ 3, 1, 0, 2, 0xc00, { 2, 3, 1, 1},{0, 1, 0x800, 0x400, 0xc00, 0},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "Modular Technology MM205 PCTV, bt878",
+ 2, 1, 0, -1, 7, { 2, 3 }, { 0, 0, 0, 0, 0 },0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+
+ /* 0x18 */
+ { "Askey/Typhoon/Anubis Magic TView CPH051/061 (bt878)",
+ 3, 1, 0, 2, 0xe00, { 2, 3, 1, 1}, {0x400, 0x400, 0x400, 0x400, 0},0,
+ 1,1,1,1,0,0,0,1, PLL_28, -1 },
+ { "Terratec/Vobis TV-Boostar",
+ 3, 1, 0, 2, 16777215 , { 2, 3, 1, 1}, { 131072, 1, 1638400, 3,4},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "Newer Hauppauge WinCam (bt878)",
+ 4, 1, 0, 3, 7, { 2, 0, 1, 1}, { 0, 1, 2, 3, 4},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "MAXI TV Video PCI2",
+ 3, 1, 0, 2, 0xffff, { 2, 3, 1, 1}, { 0, 1, 2, 3, 0xc00},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, TUNER_PHILIPS_SECAM },
+
+ { "Terratec TerraTV+",
+ 3, 1, 0, 2, 0x70000, { 2, 3, 1, 1},
+ { 0x20000, 0x30000, 0x00000, 0x10000, 0x40000},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "Imagenation PXC200",
+ 5, 1, -1, 4, 0, { 2, 3, 1, 0, 0}, { 0 }, 0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "FlyVideo 98",
+ 3, 1, 0, 2, 0x8dff00, {2, 3, 1, 1},
+ { 0, 0x8dff00, 0x8df700, 0x8de700, 0x8dff00, 0 },0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "iProTV",
+ 3, 1, 0, 2, 1, { 2, 3, 1, 1}, { 1, 0, 0, 0, 0 },0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+
+ /* 0x20 */
+ { "Intel Create and Share PCI",
+ 4, 1, 0, 2, 7, { 2, 3, 1, 1}, { 4, 4, 4, 4, 4},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "Terratec TerraTValue",
+ 3, 1, 0, 2, 0xffff00, { 2, 3, 1, 1},
+ { 0x500, 0, 0x300, 0x900, 0x900},0,
+ 1,1,1,1,0,0,0,1, PLL_NONE, -1 },
+ { "Leadtek WinFast 2000",
+ 3, 1, 0, 2, 0xfff000, { 2, 3, 1, 1,0},
+ { 0x621000,0x6ddf07,0x621100,0x620000,0xE210000,0x620000},0,
+ 1,1,1,1,1,0,0,1, PLL_28, -1 },
+ { "Chronos Video Shuttle II",
+ 3, 3, 0, 2, 0x1800, { 2, 3, 1, 1}, { 0, 0, 0x1000, 0x1000, 0x0800},0,
+ 1,1,1,1,0,0,0,1, PLL_28, -1 },
+
+ { "Typhoon TView TV/FM Tuner",
+ 3, 3, 0, 2, 0x1800, { 2, 3, 1, 1}, { 0, 0x800, 0, 0, 0x1800, 0 },0,
+ 1,1,1,1,0,0,0,1, PLL_28, -1 },
+ { "PixelView PlayTV pro",
+ 3, 1, 0, 2, 0xff, { 2, 3, 1, 1 },
+ { 0x21, 0x20, 0x24, 0x2c, 0x29, 0x29 }, 0,
+ 0,0,0,0,0,0,0,1, PLL_28, -1 },
+ { "TView99 CPH063",
+ 3, 1, 0, 2, 0x551e00, { 2, 3, 1, 1},
+ { 0x551400, 0x551200, 0, 0, 0, 0x551200 }, 0,
+ 1,1,1,1,0,0,0,1, PLL_28, -1 },
+ { "Pinnacle PCTV Rave",
+ 3, 1, 0, 2, 0x03000F, { 2, 3, 1, 1}, { 2, 0, 0, 0, 1},0,
+ 1,1,1,1,0,0,0,1, PLL_28, -1 },
+
+ /* 0x28 */
+ { "STB2",
+ 3, 1, 0, 2, 7, { 2, 3, 1, 1}, { 4, 0, 2, 3, 1},0,
+ 0,1,1,1,0,1,1,1, PLL_NONE, -1 },
+ { "AVerMedia TVPhone 98",
+ 3, 4, 0, 2, 4, { 2, 3, 1, 1}, { 13, 14, 11, 7, 0, 0},0,
+ 1,1,1,1,0,0,0,1, PLL_28, 5 },
+ { "ProVideo PV951", /* pic16c54 */
+ 3, 1, 0, 2, 0, { 2, 3, 1, 1}, { 0, 0, 0, 0, 0},0,
+ 0,0,0,0,0,0,0,0, PLL_28, 1 },
+ { "Little OnAir TV",
+ 3, 1, 0, 2, 0xe00b, {2, 3, 1, 1},
+ {0xff9ff6, 0xff9ff6, 0xff1ff7, 0, 0xff3ffc},0,
+ 0,0,0,0,0,0,0,0, PLL_NONE, -1 },
+
+ { "Sigma TVII-FM",
+ 2, 1, 0, -1, 3, {2, 3, 1, 1}, {1, 1, 0, 2, 3},0,
+ 0,0,0,0,0,0,0,0, PLL_NONE, -1 },
+ { "MATRIX-Vision MV-Delta 2",
+ 5, 1, -1, 3, 0, { 2, 3, 1, 0, 0},{0 }, 0,
+ 0,0,0,0,0,0,0,0, PLL_28, -1 },
+ { "Zoltrix Genie TV",
+ 3, 1, 0, 2, 0xbcf03f, { 2, 3, 1, 1},
+ { 0xbc803f, 0, 0xbcb03f, 0, 0xbcb03f}, 0,
+ 0,0,0,0,0,0,0,0, PLL_28, 5 },
+ { "Terratec TV/Radio+", /* Radio ?? */
+ 3, 1, 0, 2, 0x1f0000, { 2, 3, 1, 1},
+ { 0xe2ffff, 0xebffff, 0, 0, 0xe0ffff, 0xe2ffff },0,
+ 0,0,0,0,0,0,0,0, PLL_35, 1 },
+
+ /* 0x30 */
+ { "Dynalink Magic TView ",
+ 3, 1, 0, 2, 15, { 2, 3, 1, 1}, {2,0,0,0,1},0,
+ 1,1,1,1,0,0,0,1, PLL_28, -1 },
+};
+const int bttv_num_tvcards = (sizeof(bttv_tvcards)/sizeof(struct tvcard));
+
+/* ----------------------------------------------------------------------- */
+
+static unsigned char eeprom_data[256];
+
+static void __devinit bttv_dump_eeprom(struct bttv *btv,int addr)
+{
+ int i;
+
+ if (bttv_verbose < 2)
+ return;
+ /* for debugging: dump eeprom to syslog */
+ printk(KERN_DEBUG "bttv%d: dump eeprom @ 0x%02x\n",btv->nr,addr);
+ for (i = 0; i < 256;) {
+ printk(KERN_DEBUG " %02x:",i);
+ do {
+ printk(" %02x",eeprom_data[i++]);
+ } while (i % 16);
+ printk("\n");
+ }
+}
+
+static int __devinit bttv_idcard_eeprom(struct bttv *btv)
+{
+ unsigned id;
+ int i,n;
+
+ id = (eeprom_data[252] << 24) |
+ (eeprom_data[253] << 16) |
+ (eeprom_data[254] << 8) |
+ (eeprom_data[255]);
+ if (id == 0 || id == 0xffffffff)
+ return -1;
+
+ /* look for the card */
+ btv->cardid = id;
+ for (n = -1, i = 0; cards[i].id != 0; i++)
+ if (cards[i].id == id)
+ n = i;
+
+ if (n != -1) {
+ /* found it */
+ printk(KERN_INFO "bttv%d: card id: %s (0x%08x) => card=%d\n",
+ btv->nr,cards[n].name,id,cards[n].cardnr);
+ return cards[n].cardnr;
+ } else {
+ /* 404 */
+ printk(KERN_INFO "bttv%d: id: unknown (0x%08x)\n",
+ btv->nr, id);
+ printk(KERN_INFO "please mail id, board name and "
+ "the correct card= insmod option to "
+ "kraxel@goldbach.in-berlin.de\n");
+ return -1;
+ }
+}
+
+#ifndef HAVE_TVAUDIO
+/* can tda9855.c handle this too maybe? */
+static void __devinit init_tda9840(struct bttv *btv)
+{
+ /* Horrible Hack */
+ bttv_I2CWrite(btv, I2C_TDA9840, TDA9840_SW, 0x2a, 1); /* sound mode switching */
+ /* 00 - mute
+ 10 - mono / averaged stereo
+ 2a - stereo
+ 12 - dual A
+ 1a - dual AB
+ 16 - dual BA
+ 1e - dual B
+ 7a - external */
+}
+#endif
+
+void __devinit bttv_idcard(struct bttv *btv)
+{
+ int type,eeprom = 0;
+
+ btwrite(0, BT848_GPIO_OUT_EN);
+
+ /* try to autodetect the card */
+ /* many bt878 cards have a eeprom @ 0xa0 => read ID
+ and try to identify it */
+ if (bttv_I2CRead(btv, I2C_HAUPEE, "eeprom") >= 0) {
+ eeprom = 0xa0;
+ bttv_readee(btv,eeprom_data,0xa0);
+ bttv_dump_eeprom(btv,0xa0); /* DEBUG */
+ type = bttv_idcard_eeprom(btv);
+ if (-1 != type) {
+ btv->type = type;
+ } else if (btv->id <= 849) {
+ /* for unknown bt848, assume old Hauppauge */
+ btv->type=BTTV_HAUPPAUGE;
+ }
+
+ /* STB cards have a eeprom @ 0xae (old bt848) */
+ } else if (bttv_I2CRead(btv, I2C_STBEE, "eeprom")>=0) {
+ btv->type=BTTV_STB;
+ }
+
+ /* let the user override the autodetected type */
+ if (card[btv->nr] >= 0 && card[btv->nr] < bttv_num_tvcards)
+ btv->type=card[btv->nr];
+
+ /* print which card config we are using */
+ sprintf(btv->video_dev.name,"BT%d%s(%.23s)",
+ btv->id,
+ (btv->id==848 && btv->revision==0x12) ? "A" : "",
+ bttv_tvcards[btv->type].name);
+ printk(KERN_INFO "bttv%d: model: %s [%s]\n",btv->nr,btv->video_dev.name,
+ (card[btv->nr] >= 0 && card[btv->nr] < bttv_num_tvcards) ?
+ "insmod option" : "autodetected");
+
+ /* board specific initialisations */
+ if (btv->type == BTTV_MIRO || btv->type == BTTV_MIROPRO) {
+ /* auto detect tuner for MIRO cards */
+ btv->tuner_type=((btread(BT848_GPIO_DATA)>>10)-1)&7;
+#if 0
+ if (btv->type == BTTV_MIROPRO) {
+ if (bttv_verbose)
+ printk(KERN_INFO "Initializing TEA5757...\n");
+ init_tea5757(btv);
+ }
+#endif
+ }
+ if (btv->type == BTTV_HAUPPAUGE || btv->type == BTTV_HAUPPAUGE878) {
+ /* pick up some config infos from the eeprom */
+ if (0xa0 != eeprom) {
+ eeprom = 0xa0;
+ bttv_readee(btv,eeprom_data,0xa0);
+ }
+ hauppauge_eeprom(btv);
+ hauppauge_boot_msp34xx(btv);
+ }
+ if (btv->type == BTTV_PXC200)
+ init_PXC200(btv);
+
+ /* pll configuration */
+ if (!(btv->id==848 && btv->revision==0x11)) {
+ /* defaults from card list */
+ if (PLL_28 == bttv_tvcards[btv->type].pll) {
+ btv->pll.pll_ifreq=28636363;
+ btv->pll.pll_crystal=BT848_IFORM_XT0;
+ }
+ /* insmod options can override */
+ switch (pll[btv->nr]) {
+ case 0: /* none */
+ btv->pll.pll_crystal = 0;
+ btv->pll.pll_ifreq = 0;
+ btv->pll.pll_ofreq = 0;
+ break;
+ case 1: /* 28 MHz */
+ btv->pll.pll_ifreq = 28636363;
+ btv->pll.pll_ofreq = 0;
+ btv->pll.pll_crystal=BT848_IFORM_XT0;
+ break;
+ case 2: /* 35 MHz */
+ btv->pll.pll_ifreq = 35468950;
+ btv->pll.pll_ofreq = 0;
+ btv->pll.pll_crystal=BT848_IFORM_XT1;
+ break;
+ }
+ }
+
+
+ /* tuner configuration */
+ if (-1 != bttv_tvcards[btv->type].tuner_type)
+ btv->tuner_type = bttv_tvcards[btv->type].tuner_type;
+ if (btv->tuner_type != -1)
+ bttv_call_i2c_clients(btv,TUNER_SET_TYPE,&btv->tuner_type);
+
+ /* try to detect audio/fader chips */
+ if (bttv_tvcards[btv->type].msp34xx &&
+ bttv_I2CRead(btv, I2C_MSP3400, "MSP34xx") >=0) {
+ if (autoload)
+ request_module("msp3400");
+ }
+
+#ifndef HAVE_TVAUDIO
+ if (bttv_tvcards[btv->type].tda8425 &&
+ bttv_I2CRead(btv, I2C_TDA8425, "TDA8425") >=0) {
+ if (autoload)
+ request_module("tda8425");
+ }
+
+ if (bttv_tvcards[btv->type].tda9840 &&
+ bttv_I2CRead(btv, I2C_TDA9840, "TDA9840") >=0) {
+ init_tda9840(btv);
+ btv->audio_chip = TDA9840;
+ /* move this to a module too? */
+ init_tda9840(btv);
+ }
+
+ if (bttv_tvcards[btv->type].tda985x &&
+ bttv_I2CRead(btv, I2C_TDA9850, "TDA985x") >=0) {
+ if (autoload)
+ request_module("tda985x");
+ }
+ if (bttv_tvcards[btv->type].tea63xx) {
+ if (autoload)
+ request_module("tea6300");
+ }
+#else
+ if (bttv_tvcards[btv->type].tda8425 ||
+ bttv_tvcards[btv->type].tda9840 ||
+ bttv_tvcards[btv->type].tda985x ||
+ bttv_tvcards[btv->type].tea63xx) {
+ if (autoload)
+ request_module("tvaudio");
+ }
+#endif
+
+ if (bttv_tvcards[btv->type].tda9875 &&
+ bttv_I2CRead(btv, I2C_TDA9875, "TDA9875") >=0) {
+ if (autoload)
+ request_module("tda9875");
+ }
+
+ if (bttv_tvcards[btv->type].tda7432 &&
+ bttv_I2CRead(btv, I2C_TDA7432, "TDA7432") >=0) {
+ if (autoload)
+ request_module("tda7432");
+ }
+
+
+ if (bttv_tvcards[btv->type].tea64xx) {
+ if (autoload)
+ request_module("tea6420");
+ }
+
+ if (bttv_tvcards[btv->type].tuner != -1) {
+ if (autoload)
+ request_module("tuner");
+ }
+}
+
+
+/* ----------------------------------------------------------------------- */
+/* some hauppauge specific stuff */
+
+static struct HAUPPAUGE_TUNER
+{
+ int id;
+ char *name;
+}
+hauppauge_tuner[] __devinitdata =
+{
+ { TUNER_ABSENT, "" },
+ { TUNER_ABSENT, "External" },
+ { TUNER_ABSENT, "Unspecified" },
+ { TUNER_ABSENT, "Philips FI1216" },
+ { TUNER_PHILIPS_SECAM, "Philips FI1216MF" },
+ { TUNER_PHILIPS_NTSC, "Philips FI1236" },
+ { TUNER_ABSENT, "Philips FI1246" },
+ { TUNER_ABSENT, "Philips FI1256" },
+ { TUNER_PHILIPS_PAL, "Philips FI1216 MK2" },
+ { TUNER_PHILIPS_SECAM, "Philips FI1216MF MK2" },
+ { TUNER_PHILIPS_NTSC, "Philips FI1236 MK2" },
+ { TUNER_PHILIPS_PAL_I, "Philips FI1246 MK2" },
+ { TUNER_ABSENT, "Philips FI1256 MK2" },
+ { TUNER_ABSENT, "Temic 4032FY5" },
+ { TUNER_TEMIC_PAL, "Temic 4002FH5" },
+ { TUNER_TEMIC_PAL_I, "Temic 4062FY5" },
+ { TUNER_ABSENT, "Philips FR1216 MK2" },
+ { TUNER_PHILIPS_SECAM, "Philips FR1216MF MK2" },
+ { TUNER_PHILIPS_NTSC, "Philips FR1236 MK2" },
+ { TUNER_PHILIPS_PAL_I, "Philips FR1246 MK2" },
+ { TUNER_ABSENT, "Philips FR1256 MK2" },
+ { TUNER_PHILIPS_PAL, "Philips FM1216" },
+ { TUNER_ABSENT, "Philips FM1216MF" },
+ { TUNER_PHILIPS_NTSC, "Philips FM1236" },
+ { TUNER_PHILIPS_PAL_I, "Philips FM1246" },
+ { TUNER_ABSENT, "Philips FM1256" },
+ { TUNER_TEMIC_4036FY5_NTSC, "Temic 4036FY5" },
+ { TUNER_ABSENT, "Samsung TCPN9082D" },
+ { TUNER_ABSENT, "Samsung TCPM9092P" },
+ { TUNER_TEMIC_PAL, "Temic 4006FH5" },
+ { TUNER_ABSENT, "Samsung TCPN9085D" },
+ { TUNER_ABSENT, "Samsung TCPB9085P" },
+ { TUNER_ABSENT, "Samsung TCPL9091P" },
+ { TUNER_ABSENT, "Temic 4039FR5" },
+ { TUNER_ABSENT, "Philips FQ1216 ME" },
+ { TUNER_TEMIC_PAL_I, "Temic 4066FY5" },
+ { TUNER_ABSENT, "Philips TD1536" },
+ { TUNER_ABSENT, "Philips TD1536D" },
+ { TUNER_ABSENT, "Philips FMR1236" },
+ { TUNER_ABSENT, "Philips FI1256MP" },
+ { TUNER_ABSENT, "Samsung TCPQ9091P" },
+ { TUNER_ABSENT, "Temic 4006FN5" },
+ { TUNER_ABSENT, "Temic 4009FR5" },
+ { TUNER_ABSENT, "Temic 4046FM5" },
+};
+
+static void __devinit hauppauge_eeprom(struct bttv *btv)
+{
+ if (eeprom_data[9] < sizeof(hauppauge_tuner)/sizeof(struct HAUPPAUGE_TUNER))
+ {
+ btv->tuner_type = hauppauge_tuner[eeprom_data[9]].id;
+ if (bttv_verbose)
+ printk("bttv%d: Hauppauge eeprom: tuner=%s (%d)\n",btv->nr,
+ hauppauge_tuner[eeprom_data[9]].name,btv->tuner_type);
+ }
+}
+
+static void __devinit hauppauge_boot_msp34xx(struct bttv *btv)
+{
+ int i;
+
+ /* reset/enable the MSP on some Hauppauge cards */
+ /* Thanks to Kyösti Mälkki (kmalkki@cc.hut.fi)! */
+ btaor(32, ~32, BT848_GPIO_OUT_EN);
+ btaor(0, ~32, BT848_GPIO_DATA);
+ udelay(2500);
+ btaor(32, ~32, BT848_GPIO_DATA);
+
+ if (bttv_verbose)
+ printk("bttv%d: Hauppauge msp34xx: reset line init\n",btv->nr);
+
+ /* look if the msp3400 driver is already registered */
+ for (i = 0; i < I2C_CLIENTS_MAX; i++) {
+ if (btv->i2c_clients[i] != NULL &&
+ btv->i2c_clients[i]->driver->id == I2C_DRIVERID_MSP3400) {
+ return;
+ }
+ }
+
+ /* if not: look for the chip ... */
+ if (bttv_I2CRead(btv, I2C_MSP3400, "MSP34xx")) {
+ /* ... if found re-register to trigger a i2c bus rescan, */
+ /* this time with the msp34xx chip activated */
+ i2c_bit_del_bus(&btv->i2c_adap);
+ i2c_bit_add_bus(&btv->i2c_adap);
+ }
+}
+
+
+/* ----------------------------------------------------------------------- */
+/* Imagenation L-Model PXC200 Framegrabber */
+/* This is basically the same procedure as
+ * used by Alessandro Rubini in his pxc200
+ * driver, but using BTTV functions */
+
+static void __devinit init_PXC200(struct bttv *btv)
+{
+ static const int vals[] = { 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0d,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x00 };
+ int i,tmp;
+
+ /* Initialise GPIO-connevted stuff */
+ btwrite(1<<13,BT848_GPIO_OUT_EN); /* Reset pin only */
+ btwrite(0,BT848_GPIO_DATA);
+ udelay(3);
+ btwrite(1<<13,BT848_GPIO_DATA);
+ /* GPIO inputs are pulled up, so no need to drive
+ * reset pin any longer */
+ btwrite(0,BT848_GPIO_OUT_EN);
+
+ /* we could/should try and reset/control the AD pots? but
+ right now we simply turned off the crushing. Without
+ this the AGC drifts drifts
+ remember the EN is reverse logic -->
+ setting BT848_ADC_AGC_EN disable the AGC
+ tboult@eecs.lehigh.edu
+ */
+ btwrite(BT848_ADC_RESERVED|BT848_ADC_AGC_EN, BT848_ADC);
+
+ /* Initialise MAX517 DAC */
+ printk(KERN_INFO "Setting DAC reference voltage level ...\n");
+ bttv_I2CWrite(btv,0x5E,0,0x80,1);
+
+ /* Initialise 12C508 PIC */
+ /* The I2CWrite and I2CRead commmands are actually to the
+ * same chips - but the R/W bit is included in the address
+ * argument so the numbers are different */
+
+ printk(KERN_INFO "Initialising 12C508 PIC chip ...\n");
+
+ for (i = 0; i < sizeof(vals)/sizeof(int); i++) {
+ tmp=bttv_I2CWrite(btv,0x1E,vals[i],0,1);
+ printk(KERN_INFO "I2C Write(0x08) = %i\nI2C Read () = %x\n\n",
+ tmp,bttv_I2CRead(btv,0x1F,NULL));
+ }
+ printk(KERN_INFO "PXC200 Initialised.\n");
+}
+
+/* ----------------------------------------------------------------------- */
+/* Miro Pro radio stuff -- the tea5757 is connected to some GPIO ports */
+/*
+ * Copyright (c) 1999 Csaba Halasz <qgehali@uni-miskolc.hu>
+ * This code is placed under the terms of the GNU General Public License
+ *
+ * Brutally hacked by Dan Sheridan <dan.sheridan@contact.org.uk> djs52 8/3/00
+ */
+
+/* bus bits on the GPIO port */
+#define TEA_WE 6
+#define TEA_DATA 9
+#define TEA_CLK 8
+#define TEA_MOST 7
+
+#define BUS_LOW(bit) btand(~(1<<TEA_##bit), BT848_GPIO_DATA)
+#define BUS_HIGH(bit) btor((1<<TEA_##bit), BT848_GPIO_DATA)
+#define BUS_IN(bit) ((btread(BT848_GPIO_DATA) >> TEA_##bit) & 1)
+
+/* TEA5757 register bits */
+#define TEA_FREQ 0:14
+#define TEA_BUFFER 15:15
+
+#define TEA_SIGNAL_STRENGTH 16:17
+
+#define TEA_PORT1 18:18
+#define TEA_PORT0 19:19
+
+#define TEA_BAND 20:21
+#define TEA_BAND_FM 0
+#define TEA_BAND_MW 1
+#define TEA_BAND_LW 2
+#define TEA_BAND_SW 3
+
+#define TEA_MONO 22:22
+#define TEA_ALLOW_STEREO 0
+#define TEA_FORCE_MONO 1
+
+#define TEA_SEARCH_DIRECTION 23:23
+#define TEA_SEARCH_DOWN 0
+#define TEA_SEARCH_UP 1
+
+#define TEA_STATUS 24:24
+#define TEA_STATUS_TUNED 0
+#define TEA_STATUS_SEARCHING 1
+
+/* Low-level stuff */
+static int tea_read(struct bttv *btv)
+{
+ int value = 0;
+ long timeout;
+ int i;
+
+ /* better safe than sorry */
+ btaor((1<<TEA_CLK) | (1<<TEA_WE), ~((1<<TEA_CLK) | (1<<TEA_DATA) | (1<<TEA_WE) | (1<<TEA_MOST)), BT848_GPIO_OUT_EN);
+
+ BUS_LOW(WE);
+ BUS_LOW(CLK);
+
+ udelay(10);
+ for(timeout = jiffies + 10 * HZ;
+ BUS_IN(DATA) == 1 && time_before(jiffies, timeout);
+ schedule()); /* 10 s */
+ if (BUS_IN(DATA) == 1) {
+ printk("tea5757: read timeout\n");
+ return -1;
+ }
+ for(timeout = jiffies + HZ/5;
+ BUS_IN(MOST) == 1 && time_before(jiffies, timeout);
+ schedule()); /* 0.2 s */
+ if (bttv_debug) printk("tea5757:");
+ for(i = 0; i < 24; i++)
+ {
+ udelay(10);
+ BUS_HIGH(CLK);
+ udelay(10);
+ if (bttv_debug) printk("%c", (BUS_IN(MOST) == 0)?'T':'-');
+ BUS_LOW(CLK);
+ value <<= 1;
+ value |= (BUS_IN(DATA) == 0)?0:1; /* MSB first */
+ if (bttv_debug) printk("%c", (BUS_IN(MOST) == 0)?'S':'M');
+ }
+ if (bttv_debug) printk("\ntea5757: read 0x%X\n", value);
+ return value;
+}
+
+static int tea_write(struct bttv *btv, int value)
+{
+ int i;
+ int reg = value;
+
+ btaor((1<<TEA_CLK) | (1<<TEA_WE) | (1<<TEA_DATA), ~((1<<TEA_CLK) | (1<<TEA_DATA) | (1<<TEA_WE) | (1<<TEA_MOST)), BT848_GPIO_OUT_EN);
+ if (bttv_debug) printk("tea5757: write 0x%X\n", value);
+ BUS_LOW(CLK);
+ BUS_HIGH(WE);
+ for(i = 0; i < 25; i++)
+ {
+ if (reg & 0x1000000)
+ BUS_HIGH(DATA);
+ else
+ BUS_LOW(DATA);
+ reg <<= 1;
+ BUS_HIGH(CLK);
+ udelay(10);
+ BUS_LOW(CLK);
+ udelay(10);
+ }
+ BUS_LOW(WE); /* unmute !!! */
+ return 0;
+}
+
+void tea5757_set_freq(struct bttv *btv, unsigned short freq)
+{
+ tea_write(btv, 5 * freq + 0x358); /* add 10.7MHz (see docs) */
+ if (bttv_debug) tea_read(btv);
+}
+
+void init_tea5757(struct bttv *btv)
+{
+ BUS_LOW(CLK);
+ BUS_LOW(WE); /* just to be on the safe side... */
+
+ /* software CLK (unused) */
+ btaor(0, BT848_GPIO_DMA_CTL_GPCLKMODE, BT848_GPIO_DMA_CTL);
+ /* normal mode for GPIO */
+ btaor(0, BT848_GPIO_DMA_CTL_GPIOMODE, BT848_GPIO_DMA_CTL);
+}
+
+/* ----------------------------------------------------------------------- */
+/* winview */
+
+void winview_setvol(struct bttv *btv, struct video_audio *v)
+{
+ /* PT2254A programming Jon Tombs, jon@gte.esi.us.es */
+ int bits_out, loops, vol, data;
+
+ /* 32 levels logarithmic */
+ vol = 32 - ((v->volume>>11));
+ /* units */
+ bits_out = (PT2254_DBS_IN_2>>(vol%5));
+ /* tens */
+ bits_out |= (PT2254_DBS_IN_10>>(vol/5));
+ bits_out |= PT2254_L_CHANEL | PT2254_R_CHANEL;
+ data = btread(BT848_GPIO_DATA);
+ data &= ~(WINVIEW_PT2254_CLK| WINVIEW_PT2254_DATA|
+ WINVIEW_PT2254_STROBE);
+ for (loops = 17; loops >= 0 ; loops--) {
+ if (bits_out & (1<<loops))
+ data |= WINVIEW_PT2254_DATA;
+ else
+ data &= ~WINVIEW_PT2254_DATA;
+ btwrite(data, BT848_GPIO_DATA);
+ udelay(5);
+ data |= WINVIEW_PT2254_CLK;
+ btwrite(data, BT848_GPIO_DATA);
+ udelay(5);
+ data &= ~WINVIEW_PT2254_CLK;
+ btwrite(data, BT848_GPIO_DATA);
+ }
+ data |= WINVIEW_PT2254_STROBE;
+ data &= ~WINVIEW_PT2254_DATA;
+ btwrite(data, BT848_GPIO_DATA);
+ udelay(10);
+ data &= ~WINVIEW_PT2254_STROBE;
+ btwrite(data, BT848_GPIO_DATA);
+}
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/bttv-driver.c b/drivers/media/video/bttv-driver.c
new file mode 100644
index 000000000..6c88a757e
--- /dev/null
+++ b/drivers/media/video/bttv-driver.c
@@ -0,0 +1,3272 @@
+/*
+ bttv - Bt848 frame grabber driver
+
+ Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
+ & Marcus Metzler (mocm@thp.uni-koeln.de)
+ (c) 1999,2000 Gerd Knorr <kraxel@goldbach.in-berlin.de>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/poll.h>
+#include <linux/pci.h>
+#include <linux/signal.h>
+#include <asm/io.h>
+#include <linux/ioport.h>
+#include <asm/pgtable.h>
+#include <asm/page.h>
+#include <linux/sched.h>
+#include <asm/segment.h>
+#include <linux/types.h>
+#include <linux/wrapper.h>
+#include <linux/interrupt.h>
+#include <linux/kmod.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#ifdef HACKING
+# include <linux/iobuf.h>
+#endif
+
+#include "bttv.h"
+#include "tuner.h"
+
+#define DEBUG(x) /* Debug driver */
+#define IDEBUG(x) /* Debug interrupt handler */
+#define MIN(a,b) (((a)>(b))?(b):(a))
+#define MAX(a,b) (((a)>(b))?(a):(b))
+
+
+static void bt848_set_risc_jmps(struct bttv *btv, int state);
+
+int bttv_num; /* number of Bt848s in use */
+struct bttv bttvs[BTTV_MAX];
+
+
+/* insmod args */
+MODULE_PARM(triton1,"i");
+MODULE_PARM(radio,"1-4i");
+MODULE_PARM(bigendian,"i");
+MODULE_PARM(fieldnr,"i");
+MODULE_PARM(bttv_verbose,"i");
+MODULE_PARM(bttv_debug,"i");
+MODULE_PARM(gbuffers,"i");
+MODULE_PARM(gbufsize,"i");
+
+MODULE_DESCRIPTION("bttv - v4l driver module for bt848/878 based cards");
+MODULE_AUTHOR("Ralph Metzler & Marcus Metzler & Gerd Knorr");
+
+#if defined(__sparc__) || defined(__powerpc__)
+static unsigned int bigendian=1;
+#else
+static unsigned int bigendian=0;
+#endif
+static int triton1=0;
+static unsigned int radio[BTTV_MAX];
+static unsigned int fieldnr = 0;
+static unsigned int gbuffers = 2;
+static unsigned int gbufsize = BTTV_MAX_FBUF;
+unsigned int bttv_debug = 0;
+unsigned int bttv_verbose = 1;
+
+#define I2C_TIMING (0x7<<4)
+#define I2C_DELAY 10
+
+#define I2C_SET(CTRL,DATA) \
+ { btwrite((CTRL<<1)|(DATA), BT848_I2C); udelay(I2C_DELAY); }
+#define I2C_GET() (btread(BT848_I2C)&1)
+
+#define BURSTOFFSET 76
+#define BTTV_ERRORS 5
+
+
+/*******************************/
+/* Memory management functions */
+/*******************************/
+
+#define MDEBUG(x) do { } while(0) /* Debug memory management */
+
+/* [DaveM] I've recoded most of this so that:
+ * 1) It's easier to tell what is happening
+ * 2) It's more portable, especially for translating things
+ * out of vmalloc mapped areas in the kernel.
+ * 3) Less unnecessary translations happen.
+ *
+ * The code used to assume that the kernel vmalloc mappings
+ * existed in the page tables of every process, this is simply
+ * not guarenteed. We now use pgd_offset_k which is the
+ * defined way to get at the kernel page tables.
+ */
+
+/* Given PGD from the address space's page table, return the kernel
+ * virtual mapping of the physical memory mapped at ADR.
+ */
+static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr)
+{
+ unsigned long ret = 0UL;
+ pmd_t *pmd;
+ pte_t *ptep, pte;
+
+ if (!pgd_none(*pgd)) {
+ pmd = pmd_offset(pgd, adr);
+ if (!pmd_none(*pmd)) {
+ ptep = pte_offset(pmd, adr);
+ pte = *ptep;
+ if(pte_present(pte)) {
+ ret = (unsigned long) page_address(pte_page(pte));
+ ret |= (adr & (PAGE_SIZE - 1));
+ }
+ }
+ }
+ MDEBUG(printk("uv2kva(%lx-->%lx)", adr, ret));
+ return ret;
+}
+
+static inline unsigned long uvirt_to_bus(unsigned long adr)
+{
+ unsigned long kva, ret;
+
+ kva = uvirt_to_kva(pgd_offset(current->mm, adr), adr);
+ ret = virt_to_bus((void *)kva);
+ MDEBUG(printk("uv2b(%lx-->%lx)", adr, ret));
+ return ret;
+}
+
+static inline unsigned long kvirt_to_bus(unsigned long adr)
+{
+ unsigned long va, kva, ret;
+
+ va = VMALLOC_VMADDR(adr);
+ kva = uvirt_to_kva(pgd_offset_k(va), va);
+ ret = virt_to_bus((void *)kva);
+ MDEBUG(printk("kv2b(%lx-->%lx)", adr, ret));
+ return ret;
+}
+
+/* Here we want the physical address of the memory.
+ * This is used when initializing the contents of the
+ * area and marking the pages as reserved.
+ */
+static inline unsigned long kvirt_to_pa(unsigned long adr)
+{
+ unsigned long va, kva, ret;
+
+ va = VMALLOC_VMADDR(adr);
+ kva = uvirt_to_kva(pgd_offset_k(va), va);
+ ret = __pa(kva);
+ MDEBUG(printk("kv2pa(%lx-->%lx)", adr, ret));
+ return ret;
+}
+
+static void * rvmalloc(signed long size)
+{
+ void * mem;
+ unsigned long adr, page;
+
+ mem=vmalloc_32(size);
+ if (mem)
+ {
+ memset(mem, 0, size); /* Clear the ram out, no junk to the user */
+ adr=(unsigned long) mem;
+ while (size > 0)
+ {
+ page = kvirt_to_pa(adr);
+ mem_map_reserve(virt_to_page(__va(page)));
+ adr+=PAGE_SIZE;
+ size-=PAGE_SIZE;
+ }
+ }
+ return mem;
+}
+
+static void rvfree(void * mem, signed long size)
+{
+ unsigned long adr, page;
+
+ if (mem)
+ {
+ adr=(unsigned long) mem;
+ while (size > 0)
+ {
+ page = kvirt_to_pa(adr);
+ mem_map_unreserve(virt_to_page(__va(page)));
+ adr+=PAGE_SIZE;
+ size-=PAGE_SIZE;
+ }
+ vfree(mem);
+ }
+}
+
+
+
+/*
+ * Create the giant waste of buffer space we need for now
+ * until we get DMA to user space sorted out (probably 2.3.x)
+ *
+ * We only create this as and when someone uses mmap
+ */
+
+static int fbuffer_alloc(struct bttv *btv)
+{
+ if(!btv->fbuffer)
+ btv->fbuffer=(unsigned char *) rvmalloc(gbuffers*gbufsize);
+ else
+ printk(KERN_ERR "bttv%d: Double alloc of fbuffer!\n",
+ btv->nr);
+ if(!btv->fbuffer)
+ return -ENOBUFS;
+ return 0;
+}
+
+
+static int __devinit init_bttv_i2c(struct bttv *btv)
+{
+ /* i2c bit_adapter */
+ memcpy(&btv->i2c_adap, &bttv_i2c_adap_template, sizeof(struct i2c_adapter));
+ memcpy(&btv->i2c_algo, &bttv_i2c_algo_template, sizeof(struct i2c_algo_bit_data));
+ memcpy(&btv->i2c_client, &bttv_i2c_client_template, sizeof(struct i2c_client));
+
+ sprintf(btv->i2c_adap.name+strlen(btv->i2c_adap.name),
+ " #%d", btv->nr);
+ btv->i2c_algo.data = btv;
+ btv->i2c_adap.data = btv;
+ btv->i2c_adap.algo_data = &btv->i2c_algo;
+ btv->i2c_client.adapter = &btv->i2c_adap;
+
+ bttv_bit_setscl(btv,1);
+ bttv_bit_setsda(btv,1);
+
+ btv->i2c_ok = i2c_bit_add_bus(&btv->i2c_adap);
+ return btv->i2c_ok;
+}
+
+
+/* ----------------------------------------------------------------------- */
+
+static void audio(struct bttv *btv, int mode, int no_irq_context)
+{
+ btaor(bttv_tvcards[btv->type].gpiomask, ~bttv_tvcards[btv->type].gpiomask,
+ BT848_GPIO_OUT_EN);
+
+ switch (mode)
+ {
+ case AUDIO_MUTE:
+ btv->audio|=AUDIO_MUTE;
+ break;
+ case AUDIO_UNMUTE:
+ btv->audio&=~AUDIO_MUTE;
+ mode=btv->audio;
+ break;
+ case AUDIO_OFF:
+ mode=AUDIO_OFF;
+ break;
+ case AUDIO_ON:
+ mode=btv->audio;
+ break;
+ default:
+ btv->audio&=AUDIO_MUTE;
+ btv->audio|=mode;
+ break;
+ }
+ /* if audio mute or not in H-lock, turn audio off */
+ if ((btv->audio&AUDIO_MUTE))
+ mode=AUDIO_OFF;
+ if ((mode == AUDIO_TUNER) && (btv->radio))
+ mode = AUDIO_RADIO;
+ btaor(bttv_tvcards[btv->type].audiomux[mode],
+ ~bttv_tvcards[btv->type].gpiomask, BT848_GPIO_DATA);
+ if (no_irq_context)
+ bttv_call_i2c_clients(btv,AUDC_SET_INPUT,&(mode));
+}
+
+
+extern inline void bt848_dma(struct bttv *btv, uint state)
+{
+ if (state)
+ btor(3, BT848_GPIO_DMA_CTL);
+ else
+ btand(~3, BT848_GPIO_DMA_CTL);
+}
+
+
+/* If Bt848a or Bt849, use PLL for PAL/SECAM and crystal for NTSC*/
+
+/* Frequency = (F_input / PLL_X) * PLL_I.PLL_F/PLL_C
+ PLL_X = Reference pre-divider (0=1, 1=2)
+ PLL_C = Post divider (0=6, 1=4)
+ PLL_I = Integer input
+ PLL_F = Fractional input
+
+ F_input = 28.636363 MHz:
+ PAL (CLKx2 = 35.46895 MHz): PLL_X = 1, PLL_I = 0x0E, PLL_F = 0xDCF9, PLL_C = 0
+*/
+
+static void set_pll_freq(struct bttv *btv, unsigned int fin, unsigned int fout)
+{
+ unsigned char fl, fh, fi;
+
+ /* prevent overflows */
+ fin/=4;
+ fout/=4;
+
+ fout*=12;
+ fi=fout/fin;
+
+ fout=(fout%fin)*256;
+ fh=fout/fin;
+
+ fout=(fout%fin)*256;
+ fl=fout/fin;
+
+ /*printk("0x%02x 0x%02x 0x%02x\n", fi, fh, fl);*/
+ btwrite(fl, BT848_PLL_F_LO);
+ btwrite(fh, BT848_PLL_F_HI);
+ btwrite(fi|BT848_PLL_X, BT848_PLL_XCI);
+}
+
+static int set_pll(struct bttv *btv)
+{
+ int i;
+ unsigned long tv;
+
+ if (!btv->pll.pll_crystal)
+ return 0;
+
+ if (btv->pll.pll_ifreq == btv->pll.pll_ofreq) {
+ /* no PLL needed */
+ if (btv->pll.pll_current == 0) {
+ /* printk ("bttv%d: PLL: is off\n",btv->nr); */
+ return 0;
+ }
+ printk ("bttv%d: PLL: switching off\n",btv->nr);
+ btwrite(0x00,BT848_TGCTRL);
+ btwrite(0x00,BT848_PLL_XCI);
+ btv->pll.pll_current = 0;
+ return 0;
+ }
+
+ if (btv->pll.pll_ofreq == btv->pll.pll_current) {
+ /* printk("bttv%d: PLL: no change required\n",btv->nr); */
+ return 1;
+ }
+
+ if (bttv_verbose)
+ printk("bttv%d: PLL: %d => %d ... ",btv->nr,
+ btv->pll.pll_ifreq, btv->pll.pll_ofreq);
+
+ set_pll_freq(btv, btv->pll.pll_ifreq, btv->pll.pll_ofreq);
+
+ /* Let other people run while the PLL stabilizes */
+ tv=jiffies+HZ/10; /* .1 seconds */
+ do
+ {
+ schedule();
+ }
+ while(time_before(jiffies,tv));
+
+ for (i=0; i<100; i++)
+ {
+ if ((btread(BT848_DSTATUS)&BT848_DSTATUS_PLOCK))
+ btwrite(0,BT848_DSTATUS);
+ else
+ {
+ btwrite(0x08,BT848_TGCTRL);
+ btv->pll.pll_current = btv->pll.pll_ofreq;
+ if (bttv_verbose)
+ printk("ok\n");
+ return 1;
+ }
+ mdelay(10);
+ }
+ btv->pll.pll_current = 0;
+ if (bttv_verbose)
+ printk("oops\n");
+ return -1;
+}
+
+static void bt848_muxsel(struct bttv *btv, unsigned int input)
+{
+ btaor(bttv_tvcards[btv->type].gpiomask2,~bttv_tvcards[btv->type].gpiomask2,
+ BT848_GPIO_OUT_EN);
+
+ /* This seems to get rid of some synchronization problems */
+ btand(~(3<<5), BT848_IFORM);
+ mdelay(10);
+
+
+ input %= bttv_tvcards[btv->type].video_inputs;
+ if (input==bttv_tvcards[btv->type].svhs)
+ {
+ btor(BT848_CONTROL_COMP, BT848_E_CONTROL);
+ btor(BT848_CONTROL_COMP, BT848_O_CONTROL);
+ }
+ else
+ {
+ btand(~BT848_CONTROL_COMP, BT848_E_CONTROL);
+ btand(~BT848_CONTROL_COMP, BT848_O_CONTROL);
+ }
+ btaor((bttv_tvcards[btv->type].muxsel[input&7]&3)<<5, ~(3<<5), BT848_IFORM);
+ audio(btv, (input!=bttv_tvcards[btv->type].tuner) ?
+ AUDIO_EXTERN : AUDIO_TUNER, 1);
+ btaor(bttv_tvcards[btv->type].muxsel[input]>>4,
+ ~bttv_tvcards[btv->type].gpiomask2, BT848_GPIO_DATA);
+}
+
+
+struct tvnorm
+{
+ u32 Fsc;
+ u16 swidth, sheight; /* scaled standard width, height */
+ u16 totalwidth;
+ u8 adelay, bdelay, iform;
+ u32 scaledtwidth;
+ u16 hdelayx1, hactivex1;
+ u16 vdelay;
+ u8 vbipack;
+};
+
+static struct tvnorm tvnorms[] = {
+ /* PAL-BDGHI */
+ /* max. active video is actually 922, but 924 is divisible by 4 and 3! */
+ /* actually, max active PAL with HSCALE=0 is 948, NTSC is 768 - nil */
+ { 35468950,
+ 924, 576, 1135, 0x7f, 0x72, (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1),
+ 1135, 186, 924, 0x20, 255},
+
+ /* NTSC */
+ { 28636363,
+ 768, 480, 910, 0x68, 0x5d, (BT848_IFORM_NTSC|BT848_IFORM_XT0),
+ 910, 128, 910, 0x1a, 144},
+#if 0
+ /* SECAM EAST */
+ { 35468950,
+ 768, 576, 1135, 0x7f, 0xb0, (BT848_IFORM_SECAM|BT848_IFORM_XT1),
+ 944, 186, 922, 0x20, 255},
+#else
+ /* SECAM L */
+ { 35468950,
+ 924, 576, 1135, 0x7f, 0xb0, (BT848_IFORM_SECAM|BT848_IFORM_XT1),
+ 1135, 186, 922, 0x20, 255},
+#endif
+ /* PAL-NC */
+ { 28636363,
+ 640, 576, 910, 0x68, 0x5d, (BT848_IFORM_PAL_NC|BT848_IFORM_XT0),
+ 780, 130, 734, 0x1a, 144},
+ /* PAL-M */
+ { 28636363,
+ 640, 480, 910, 0x68, 0x5d, (BT848_IFORM_PAL_M|BT848_IFORM_XT0),
+ 780, 135, 754, 0x1a, 144},
+ /* PAL-N */
+ { 35468950,
+ 768, 576, 1135, 0x7f, 0x72, (BT848_IFORM_PAL_N|BT848_IFORM_XT1),
+ 944, 186, 922, 0x20, 144},
+ /* NTSC-Japan */
+ { 28636363,
+ 640, 480, 910, 0x68, 0x5d, (BT848_IFORM_NTSC_J|BT848_IFORM_XT0),
+ 780, 135, 754, 0x16, 144},
+};
+#define TVNORMS (sizeof(tvnorms)/sizeof(tvnorm))
+#define VBI_SPL 2044
+
+/* RISC command to write one VBI data line */
+#define VBI_RISC BT848_RISC_WRITE|VBI_SPL|BT848_RISC_EOL|BT848_RISC_SOL
+
+static void make_vbitab(struct bttv *btv)
+{
+ int i;
+ unsigned int *po=(unsigned int *) btv->vbi_odd;
+ unsigned int *pe=(unsigned int *) btv->vbi_even;
+
+ if (bttv_debug > 1)
+ printk("bttv%d: vbi1: po=%08lx pe=%08lx\n",
+ btv->nr,virt_to_bus(po), virt_to_bus(pe));
+
+ *(po++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); *(po++)=0;
+ for (i=0; i<16; i++)
+ {
+ *(po++)=cpu_to_le32(VBI_RISC);
+ *(po++)=cpu_to_le32(kvirt_to_bus((unsigned long)btv->vbibuf+i*2048));
+ }
+ *(po++)=cpu_to_le32(BT848_RISC_JUMP);
+ *(po++)=cpu_to_le32(virt_to_bus(btv->risc_jmp+4));
+
+ *(pe++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); *(pe++)=0;
+ for (i=16; i<32; i++)
+ {
+ *(pe++)=cpu_to_le32(VBI_RISC);
+ *(pe++)=cpu_to_le32(kvirt_to_bus((unsigned long)btv->vbibuf+i*2048));
+ }
+ *(pe++)=cpu_to_le32(BT848_RISC_JUMP|BT848_RISC_IRQ|(0x01<<16));
+ *(pe++)=cpu_to_le32(virt_to_bus(btv->risc_jmp+10));
+
+ if (bttv_debug > 1)
+ printk("bttv%d: vbi2: po=%08lx pe=%08lx\n",
+ btv->nr,virt_to_bus(po), virt_to_bus(pe));
+}
+
+static int fmtbppx2[16] = {
+ 8, 6, 4, 4, 4, 3, 2, 2, 4, 3, 0, 0, 0, 0, 2, 0
+};
+
+static int palette2fmt[] = {
+ 0,
+ BT848_COLOR_FMT_Y8,
+ BT848_COLOR_FMT_RGB8,
+ BT848_COLOR_FMT_RGB16,
+ BT848_COLOR_FMT_RGB24,
+ BT848_COLOR_FMT_RGB32,
+ BT848_COLOR_FMT_RGB15,
+ BT848_COLOR_FMT_YUY2,
+ BT848_COLOR_FMT_BtYUV,
+ -1,
+ -1,
+ -1,
+ BT848_COLOR_FMT_RAW,
+ BT848_COLOR_FMT_YCrCb422,
+ BT848_COLOR_FMT_YCrCb411,
+ BT848_COLOR_FMT_YCrCb422,
+ BT848_COLOR_FMT_YCrCb411,
+};
+#define PALETTEFMT_MAX (sizeof(palette2fmt)/sizeof(int))
+
+static int make_rawrisctab(struct bttv *btv, unsigned int *ro,
+ unsigned int *re, unsigned int *vbuf)
+{
+ unsigned long line;
+ unsigned long bpl=1024; /* bytes per line */
+ unsigned long vadr=(unsigned long) vbuf;
+
+ *(ro++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1);
+ *(ro++)=cpu_to_le32(0);
+ *(re++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1);
+ *(re++)=cpu_to_le32(0);
+
+ /* In PAL 650 blocks of 256 DWORDs are sampled, but only if VDELAY
+ is 2 and without separate VBI grabbing.
+ We'll have to handle this inside the IRQ handler ... */
+
+ for (line=0; line < 640; line++)
+ {
+ *(ro++)=cpu_to_le32(BT848_RISC_WRITE|bpl|BT848_RISC_SOL|BT848_RISC_EOL);
+ *(ro++)=cpu_to_le32(kvirt_to_bus(vadr));
+ *(re++)=cpu_to_le32(BT848_RISC_WRITE|bpl|BT848_RISC_SOL|BT848_RISC_EOL);
+ *(re++)=cpu_to_le32(kvirt_to_bus(vadr+gbufsize/2));
+ vadr+=bpl;
+ }
+
+ *(ro++)=cpu_to_le32(BT848_RISC_JUMP);
+ *(ro++)=cpu_to_le32(btv->bus_vbi_even);
+ *(re++)=cpu_to_le32(BT848_RISC_JUMP|BT848_RISC_IRQ|(2<<16));
+ *(re++)=cpu_to_le32(btv->bus_vbi_odd);
+
+ return 0;
+}
+
+static int make_prisctab(struct bttv *btv, unsigned int *ro,
+ unsigned int *re,
+ unsigned int *vbuf, unsigned short width,
+ unsigned short height, unsigned short fmt)
+{
+ unsigned long line, lmask;
+ unsigned long bl, blcr, blcb, rcmd;
+ unsigned long todo;
+ unsigned int **rp;
+ int inter;
+ unsigned long cbadr, cradr;
+ unsigned long vadr=(unsigned long) vbuf;
+ int shift, csize;
+
+ if (bttv_debug > 1)
+ printk("bttv%d: prisc1: ro=%08lx re=%08lx\n",
+ btv->nr,virt_to_bus(ro), virt_to_bus(re));
+
+ switch(fmt)
+ {
+ case VIDEO_PALETTE_YUV422P:
+ csize=(width*height)>>1;
+ shift=1;
+ lmask=0;
+ break;
+
+ case VIDEO_PALETTE_YUV411P:
+ csize=(width*height)>>2;
+ shift=2;
+ lmask=0;
+ break;
+
+ case VIDEO_PALETTE_YUV420P:
+ csize=(width*height)>>2;
+ shift=1;
+ lmask=1;
+ break;
+
+ case VIDEO_PALETTE_YUV410P:
+ csize=(width*height)>>4;
+ shift=2;
+ lmask=3;
+ break;
+
+ default:
+ return -1;
+ }
+ cbadr=vadr+(width*height);
+ cradr=cbadr+csize;
+ inter = (height>tvnorms[btv->win.norm].sheight/2) ? 1 : 0;
+
+ *(ro++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM3);
+ *(ro++)=0;
+ *(re++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM3);
+ *(re++)=0;
+
+ for (line=0; line < (height<<(1^inter)); line++)
+ {
+ if(line==height)
+ {
+ vadr+=csize<<1;
+ cbadr=vadr+(width*height);
+ cradr=cbadr+csize;
+ }
+ if (inter)
+ rp= (line&1) ? &re : &ro;
+ else
+ rp= (line>=height) ? &ro : &re;
+
+
+ if(line&lmask)
+ rcmd=BT848_RISC_WRITE1S23|BT848_RISC_SOL;
+ else
+ rcmd=BT848_RISC_WRITE123|BT848_RISC_SOL;
+
+ todo=width;
+ while(todo)
+ {
+ bl=PAGE_SIZE-((PAGE_SIZE-1)&vadr);
+ blcr=(PAGE_SIZE-((PAGE_SIZE-1)&cradr))<<shift;
+ blcb=(PAGE_SIZE-((PAGE_SIZE-1)&cbadr))<<shift;
+ bl=(blcr<bl) ? blcr : bl;
+ bl=(blcb<bl) ? blcb : bl;
+ bl=(bl>todo) ? todo : bl;
+ blcr=bl>>shift;
+ blcb=blcr;
+ /* bl now containts the longest row that can be written */
+ todo-=bl;
+ if(!todo) rcmd|=BT848_RISC_EOL; /* if this is the last EOL */
+
+ *((*rp)++)=cpu_to_le32(rcmd|bl);
+ *((*rp)++)=cpu_to_le32(blcb|(blcr<<16));
+ *((*rp)++)=cpu_to_le32(kvirt_to_bus(vadr));
+ vadr+=bl;
+ if((rcmd&(15<<28))==BT848_RISC_WRITE123)
+ {
+ *((*rp)++)=(kvirt_to_bus(cbadr));
+ cbadr+=blcb;
+ *((*rp)++)=cpu_to_le32(kvirt_to_bus(cradr));
+ cradr+=blcr;
+ }
+
+ rcmd&=~BT848_RISC_SOL; /* only the first has SOL */
+ }
+ }
+
+ *(ro++)=cpu_to_le32(BT848_RISC_JUMP);
+ *(ro++)=cpu_to_le32(btv->bus_vbi_even);
+ *(re++)=cpu_to_le32(BT848_RISC_JUMP|BT848_RISC_IRQ|(2<<16));
+ *(re++)=cpu_to_le32(btv->bus_vbi_odd);
+
+ if (bttv_debug > 1)
+ printk("bttv%d: prisc2: ro=%08lx re=%08lx\n",
+ btv->nr,virt_to_bus(ro), virt_to_bus(re));
+
+ return 0;
+}
+
+static int make_vrisctab(struct bttv *btv, unsigned int *ro,
+ unsigned int *re,
+ unsigned int *vbuf, unsigned short width,
+ unsigned short height, unsigned short palette)
+{
+ unsigned long line;
+ unsigned long bpl; /* bytes per line */
+ unsigned long bl;
+ unsigned long todo;
+ unsigned int **rp;
+ int inter;
+ unsigned long vadr=(unsigned long) vbuf;
+
+ if (palette==VIDEO_PALETTE_RAW)
+ return make_rawrisctab(btv, ro, re, vbuf);
+ if (palette>=VIDEO_PALETTE_PLANAR)
+ return make_prisctab(btv, ro, re, vbuf, width, height, palette);
+
+ if (bttv_debug > 1)
+ printk("bttv%d: vrisc1: ro=%08lx re=%08lx\n",
+ btv->nr,virt_to_bus(ro), virt_to_bus(re));
+
+ inter = (height>tvnorms[btv->win.norm].sheight/2) ? 1 : 0;
+ bpl=width*fmtbppx2[palette2fmt[palette]&0xf]/2;
+
+ *(ro++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1);
+ *(ro++)=cpu_to_le32(0);
+ *(re++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1);
+ *(re++)=cpu_to_le32(0);
+
+ for (line=0; line < (height<<(1^inter)); line++)
+ {
+ if (inter)
+ rp= (line&1) ? &re : &ro;
+ else
+ rp= (line>=height) ? &ro : &re;
+
+ bl=PAGE_SIZE-((PAGE_SIZE-1)&vadr);
+ if (bpl<=bl)
+ {
+ *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL|
+ BT848_RISC_EOL|bpl);
+ *((*rp)++)=cpu_to_le32(kvirt_to_bus(vadr));
+ vadr+=bpl;
+ }
+ else
+ {
+ todo=bpl;
+ *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL|bl);
+ *((*rp)++)=cpu_to_le32(kvirt_to_bus(vadr));
+ vadr+=bl;
+ todo-=bl;
+ while (todo>PAGE_SIZE)
+ {
+ *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|PAGE_SIZE);
+ *((*rp)++)=cpu_to_le32(kvirt_to_bus(vadr));
+ vadr+=PAGE_SIZE;
+ todo-=PAGE_SIZE;
+ }
+ *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_EOL|todo);
+ *((*rp)++)=cpu_to_le32(kvirt_to_bus(vadr));
+ vadr+=todo;
+ }
+ }
+
+ *(ro++)=cpu_to_le32(BT848_RISC_JUMP);
+ *(ro++)=cpu_to_le32(btv->bus_vbi_even);
+ *(re++)=cpu_to_le32(BT848_RISC_JUMP|BT848_RISC_IRQ|(2<<16));
+ *(re++)=cpu_to_le32(btv->bus_vbi_odd);
+
+ if (bttv_debug > 1)
+ printk("bttv%d: vrisc2: ro=%08lx re=%08lx\n",
+ btv->nr,virt_to_bus(ro), virt_to_bus(re));
+
+ return 0;
+}
+
+static unsigned char lmaskt[8] =
+{ 0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80};
+static unsigned char rmaskt[8] =
+{ 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff};
+
+static void clip_draw_rectangle(unsigned char *clipmap, int x, int y, int w, int h)
+{
+ unsigned char lmask, rmask, *p;
+ int W, l, r;
+ int i;
+
+ if (bttv_debug > 1)
+ printk("bttv clip: %dx%d+%d+%d\n",w,h,x,y);
+
+ /* bitmap is fixed width, 128 bytes (1024 pixels represented) */
+ if (x<0)
+ {
+ w+=x;
+ x=0;
+ }
+ if (y<0)
+ {
+ h+=y;
+ y=0;
+ }
+ if (w < 0 || h < 0) /* catch bad clips */
+ return;
+ /* out of range data should just fall through */
+ if (y+h>=625)
+ h=625-y;
+ if (x+w>=1024)
+ w=1024-x;
+
+ l=x>>3;
+ r=(x+w-1)>>3;
+ W=r-l-1;
+ lmask=lmaskt[x&7];
+ rmask=rmaskt[(x+w-1)&7];
+ p=clipmap+128*y+l;
+
+ if (W>0)
+ {
+ for (i=0; i<h; i++, p+=128)
+ {
+ *p|=lmask;
+ memset(p+1, 0xff, W);
+ p[W+1]|=rmask;
+ }
+ } else if (!W) {
+ for (i=0; i<h; i++, p+=128)
+ {
+ p[0]|=lmask;
+ p[1]|=rmask;
+ }
+ } else {
+ for (i=0; i<h; i++, p+=128)
+ p[0]|=lmask&rmask;
+ }
+
+
+}
+
+static void make_clip_tab(struct bttv *btv, struct video_clip *cr, int ncr)
+{
+ int i, line, x, y, bpl, width, height, inter, maxw;
+ unsigned int bpp, dx, sx, **rp, *ro, *re, flags, len;
+ unsigned long adr;
+ unsigned char *clipmap, *clipline, cbit, lastbit, outofmem;
+
+ /* take care: bpp != btv->win.bpp is allowed here */
+ bpp = fmtbppx2[btv->win.color_fmt&0xf]/2;
+ bpl=btv->win.bpl;
+ adr=btv->win.vidadr + btv->win.x * btv->win.bpp + btv->win.y * bpl;
+ inter=(btv->win.interlace&1)^1;
+ width=btv->win.width;
+ height=btv->win.height;
+ if (bttv_debug > 1)
+ printk("bttv%d: clip1: pal=%d size=%dx%d, bpl=%d bpp=%d\n",
+ btv->nr,btv->picture.palette,width,height,bpl,bpp);
+ if(width > 1023)
+ width = 1023; /* sanity check */
+ if(height > 625)
+ height = 625; /* sanity check */
+ ro=btv->risc_scr_odd;
+ re=btv->risc_scr_even;
+
+ if (bttv_debug)
+ printk("bttv%d: clip: ro=%08lx re=%08lx\n",
+ btv->nr,virt_to_bus(ro), virt_to_bus(re));
+
+ if ((clipmap=vmalloc(VIDEO_CLIPMAP_SIZE))==NULL) {
+ /* can't clip, don't generate any risc code */
+ *(ro++)=cpu_to_le32(BT848_RISC_JUMP);
+ *(ro++)=cpu_to_le32(btv->bus_vbi_even);
+ *(re++)=cpu_to_le32(BT848_RISC_JUMP);
+ *(re++)=cpu_to_le32(btv->bus_vbi_odd);
+ }
+ if (ncr < 0) { /* bitmap was pased */
+ memcpy(clipmap, (unsigned char *)cr, VIDEO_CLIPMAP_SIZE);
+ } else { /* convert rectangular clips to a bitmap */
+ memset(clipmap, 0, VIDEO_CLIPMAP_SIZE); /* clear map */
+ for (i=0; i<ncr; i++)
+ clip_draw_rectangle(clipmap, cr[i].x, cr[i].y,
+ cr[i].width, cr[i].height);
+ }
+ /* clip against viewing window AND screen
+ so we do not have to rely on the user program
+ */
+ maxw = (bpl - btv->win.x * btv->win.bpp) / bpp;
+ clip_draw_rectangle(clipmap, (width > maxw) ? maxw : width,
+ 0, 1024, 768);
+ clip_draw_rectangle(clipmap,0,(btv->win.y+height>btv->win.sheight) ?
+ (btv->win.sheight-btv->win.y) : height,1024,768);
+ if (btv->win.x<0)
+ clip_draw_rectangle(clipmap, 0, 0, -(btv->win.x), 768);
+ if (btv->win.y<0)
+ clip_draw_rectangle(clipmap, 0, 0, 1024, -(btv->win.y));
+
+ *(ro++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1);
+ *(ro++)=cpu_to_le32(0);
+ *(re++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1);
+ *(re++)=cpu_to_le32(0);
+
+ /* translate bitmap to risc code */
+ for (line=outofmem=0; line < (height<<inter) && !outofmem; line++)
+ {
+ y = line>>inter;
+ rp= (line&1) ? &re : &ro;
+ clipline = clipmap + (y<<7); /* running pointers ... */
+ lastbit = *clipline & 1;
+ for(x=dx=0,sx=0; x<=width && !outofmem;) {
+ if (0 == (x&7)) {
+ /* check bytes not bits if we can ... */
+ if (lastbit) {
+ while (0xff==*clipline && x<width-8) {
+ x += 8;
+ dx += 8;
+ clipline++;
+ }
+ } else {
+ while (0x00==*clipline && x<width-8) {
+ x += 8;
+ dx += 8;
+ clipline++;
+ }
+ }
+ }
+ cbit = *clipline & (1<<(x&7));
+ if (x < width && !lastbit == !cbit) {
+ dx++;
+ } else {
+ /* generate the dma controller code */
+ len = dx * bpp;
+ flags = ((bpp==4) ? BT848_RISC_BYTE3 : 0);
+ flags |= ((!sx) ? BT848_RISC_SOL : 0);
+ flags |= ((sx + dx == width) ? BT848_RISC_EOL : 0);
+ if (!lastbit) {
+ *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|flags|len);
+ *((*rp)++)=cpu_to_le32(adr + bpp * sx);
+ } else {
+ *((*rp)++)=cpu_to_le32(BT848_RISC_SKIP|flags|len);
+ }
+ lastbit=cbit;
+ sx += dx;
+ dx = 1;
+ if (ro - btv->risc_scr_odd>RISCMEM_LEN/2 - 16)
+ outofmem++;
+ if (re - btv->risc_scr_even>RISCMEM_LEN/2 - 16)
+ outofmem++;
+ }
+ x++;
+ if (0 == (x&7))
+ clipline++;
+ }
+ if ((!inter)||(line&1))
+ adr+=bpl;
+ }
+
+ vfree(clipmap);
+ /* outofmem flag relies on the following code to discard extra data */
+ *(ro++)=cpu_to_le32(BT848_RISC_JUMP);
+ *(ro++)=cpu_to_le32(btv->bus_vbi_even);
+ *(re++)=cpu_to_le32(BT848_RISC_JUMP);
+ *(re++)=cpu_to_le32(btv->bus_vbi_odd);
+
+ if (bttv_debug > 1)
+ printk("bttv%d: clip2: pal=%d size=%dx%d, bpl=%d bpp=%d\n",
+ btv->nr,btv->picture.palette,width,height,bpl,bpp);
+}
+
+/*
+ * Set the registers for the size we have specified. Don't bother
+ * trying to understand this without the BT848 manual in front of
+ * you [AC].
+ *
+ * PS: The manual is free for download in .pdf format from
+ * www.brooktree.com - nicely done those folks.
+ */
+
+static inline void bt848_set_eogeo(struct bttv *btv, struct tvnorm *tvn,
+ int odd, int width, int height)
+{
+ u16 vscale, hscale;
+ u32 xsf, sr;
+ u16 hdelay;
+ u8 crop, vtc;
+ int inter = (height>tvn->sheight/2) ? 0 : 1;
+ int off = odd ? 0x80 : 0x00;
+
+ xsf = (width*tvn->scaledtwidth)/tvn->swidth;
+ hscale = ((tvn->totalwidth*4096UL)/xsf-4096);
+ hdelay = tvn->hdelayx1;
+ hdelay = (hdelay*width)/tvn->swidth;
+ hdelay &= 0x3fe;
+ sr=((tvn->sheight>>inter)*512)/height-512;
+ vscale=(0x10000UL-sr)&0x1fff;
+ crop=((width>>8)&0x03)|((hdelay>>6)&0x0c)|
+ ((tvn->sheight>>4)&0x30)|((tvn->vdelay>>2)&0xc0);
+ vscale |= inter ? (BT848_VSCALE_INT<<8) : 0;
+
+#if 0
+ /* Some people say interpolation looks bad ... */
+ vtc = (width < 193) ? 2 : ((width < 385) ? 1 : 0);
+ if (width < 767)
+ btor(BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off);
+ else
+ btand(~BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off);
+#else
+ vtc = 0;
+ btand(~BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off);
+#endif
+
+ btwrite(vtc, BT848_E_VTC+off);
+ btwrite(hscale>>8, BT848_E_HSCALE_HI+off);
+ btwrite(hscale&0xff, BT848_E_HSCALE_LO+off);
+ btaor((vscale>>8), 0xe0, BT848_E_VSCALE_HI+off);
+ btwrite(vscale&0xff, BT848_E_VSCALE_LO+off);
+ btwrite(width&0xff, BT848_E_HACTIVE_LO+off);
+ btwrite(hdelay&0xff, BT848_E_HDELAY_LO+off);
+ btwrite(tvn->sheight&0xff, BT848_E_VACTIVE_LO+off);
+ btwrite(tvn->vdelay&0xff, BT848_E_VDELAY_LO+off);
+ btwrite(crop, BT848_E_CROP+off);
+}
+
+
+static void bt848_set_geo(struct bttv *btv,
+ int no_irq_context)
+{
+ u16 ewidth, eheight, owidth, oheight;
+ u16 format, bswap;
+ struct tvnorm *tvn;
+
+ tvn=&tvnorms[btv->win.norm];
+
+ btwrite(tvn->adelay, BT848_ADELAY);
+ btwrite(tvn->bdelay, BT848_BDELAY);
+ btaor(tvn->iform,~(BT848_IFORM_NORM|BT848_IFORM_XTBOTH), BT848_IFORM);
+ btwrite(tvn->vbipack, BT848_VBI_PACK_SIZE);
+ btwrite(1, BT848_VBI_PACK_DEL);
+
+ btv->pll.pll_ofreq = tvn->Fsc;
+ if (no_irq_context)
+ set_pll(btv);
+
+ btv->win.interlace = (btv->win.height>tvn->sheight/2) ? 1 : 0;
+
+ if (0 == btv->risc_cap_odd &&
+ 0 == btv->risc_cap_even) {
+ /* overlay only */
+ owidth = btv->win.width;
+ oheight = btv->win.height;
+ ewidth = btv->win.width;
+ eheight = btv->win.height;
+ format = btv->win.color_fmt;
+ bswap = btv->fb_color_ctl;
+ } else if (-1 != btv->gq_grab &&
+ 0 == btv->risc_cap_odd &&
+ !btv->win.interlace &&
+ btv->scr_on) {
+ /* odd field -> overlay, even field -> capture */
+ owidth = btv->win.width;
+ oheight = btv->win.height;
+ ewidth = btv->gbuf[btv->gq_grab].width;
+ eheight = btv->gbuf[btv->gq_grab].height;
+ format = (btv->win.color_fmt & 0xf0) |
+ (btv->gbuf[btv->gq_grab].fmt & 0x0f);
+ bswap = btv->fb_color_ctl & 0x0a;
+ } else {
+ /* capture only */
+ owidth = btv->gbuf[btv->gq_grab].width;
+ oheight = btv->gbuf[btv->gq_grab].height;
+ ewidth = btv->gbuf[btv->gq_grab].width;
+ eheight = btv->gbuf[btv->gq_grab].height;
+ format = btv->gbuf[btv->gq_grab].fmt;
+ bswap = 0;
+ }
+
+ /* program odd + even fields */
+ bt848_set_eogeo(btv, tvn, 1, owidth, oheight);
+ bt848_set_eogeo(btv, tvn, 0, ewidth, eheight);
+
+ btwrite(format, BT848_COLOR_FMT);
+ btwrite(bswap | BT848_COLOR_CTL_GAMMA, BT848_COLOR_CTL);
+}
+
+
+static int bpp2fmt[4] = {
+ BT848_COLOR_FMT_RGB8, BT848_COLOR_FMT_RGB16,
+ BT848_COLOR_FMT_RGB24, BT848_COLOR_FMT_RGB32
+};
+
+static void bt848_set_winsize(struct bttv *btv)
+{
+ unsigned short format;
+
+ if (btv->picture.palette > 0 && btv->picture.palette <= VIDEO_PALETTE_YUV422) {
+ /* format set by VIDIOCSPICT */
+ format = palette2fmt[btv->picture.palette];
+ } else {
+ /* use default for the given color depth */
+ format = (btv->win.depth==15) ? BT848_COLOR_FMT_RGB15 :
+ bpp2fmt[(btv->win.bpp-1)&3];
+ }
+ btv->win.color_fmt = format;
+ if (bigendian &&
+ format == BT848_COLOR_FMT_RGB32) {
+ btv->fb_color_ctl =
+ BT848_COLOR_CTL_WSWAP_ODD |
+ BT848_COLOR_CTL_WSWAP_EVEN |
+ BT848_COLOR_CTL_BSWAP_ODD |
+ BT848_COLOR_CTL_BSWAP_EVEN;
+ } else if (bigendian &&
+ (format == BT848_COLOR_FMT_RGB16 ||
+ format == BT848_COLOR_FMT_RGB15)) {
+ btv->fb_color_ctl =
+ BT848_COLOR_CTL_BSWAP_ODD |
+ BT848_COLOR_CTL_BSWAP_EVEN;
+ } else {
+ btv->fb_color_ctl = 0;
+ }
+
+ /* RGB8 seems to be a 9x5x5 GRB color cube starting at
+ * color 16. Why the h... can't they even mention this in the
+ * data sheet? [AC - because it's a standard format so I guess
+ * it never occurred to them]
+ * Enable dithering in this mode.
+ */
+
+ if (format==BT848_COLOR_FMT_RGB8)
+ btand(~BT848_CAP_CTL_DITH_FRAME, BT848_CAP_CTL);
+ else
+ btor(BT848_CAP_CTL_DITH_FRAME, BT848_CAP_CTL);
+
+ bt848_set_geo(btv,1);
+}
+
+#ifdef HACKING
+/* playing with kiobufs and dma-to-userspace. 2.4.x only
+ Yes, I know: cut+paste programming is ugly.
+ will fix later, this is proof-of-concept right now. */
+static int make_vrisctab_kiobuf(struct bttv *btv, unsigned int *ro,
+ unsigned int *re, struct kiobuf *iobuf,
+ unsigned short width, unsigned short height,
+ unsigned short palette)
+{
+ unsigned long bpl; /* bytes per line */
+ unsigned long bl;
+ unsigned long todo;
+ unsigned long pageaddr;
+ unsigned int **rp;
+ unsigned long line,inter,offset,page;
+
+ inter = (height>tvnorms[btv->win.norm].sheight/2) ? 1 : 0;
+ bpl=width*fmtbppx2[palette2fmt[palette]&0xf]/2;
+
+ *(ro++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1);
+ *(ro++)=cpu_to_le32(0);
+ *(re++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1);
+ *(re++)=cpu_to_le32(0);
+
+ offset = iobuf->offset;
+ page = 0;
+ pageaddr = virt_to_bus(page_address(iobuf->maplist[page]));
+ for (line=0; line < (height<<(1^inter)); line++)
+ {
+ if (inter)
+ rp= (line&1) ? &re : &ro;
+ else
+ rp= (line>=height) ? &ro : &re;
+
+ bl = PAGE_SIZE - offset;
+ if (bpl <= bl) {
+ *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL|
+ BT848_RISC_EOL|bpl);
+ *((*rp)++)=cpu_to_le32(pageaddr+offset);
+ offset+=bpl;
+ } else {
+ todo = bpl;
+ *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL|bl);
+ *((*rp)++)=cpu_to_le32(pageaddr+offset);
+ todo -= bl;
+ offset = 0;
+ page++;
+ pageaddr = virt_to_bus(page_address(iobuf->maplist[page]));
+ while (todo>PAGE_SIZE)
+ {
+ *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|PAGE_SIZE);
+ *((*rp)++)=cpu_to_le32(pageaddr);
+ page++;
+ pageaddr = virt_to_bus(page_address(iobuf->maplist[page]));
+ }
+ *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_EOL|todo);
+ *((*rp)++)=cpu_to_le32(pageaddr);
+ offset += todo;
+ }
+ }
+
+ *(ro++)=cpu_to_le32(BT848_RISC_JUMP);
+ *(ro++)=cpu_to_le32(btv->bus_vbi_even);
+ *(re++)=cpu_to_le32(BT848_RISC_JUMP|BT848_RISC_IRQ|(2<<16));
+ *(re++)=cpu_to_le32(btv->bus_vbi_odd);
+
+ return 0;
+}
+
+static int vgrab_kiobuf(struct bttv *btv, struct bttv_just_hacking *mp,
+ struct kiobuf *iobuf)
+{
+ unsigned int *ro, *re;
+ unsigned long flags;
+
+ if(btv->gbuf[0].stat != GBUFFER_UNUSED)
+ return -EBUSY;
+
+ if(mp->height < 32 || mp->width < 32)
+ return -EINVAL;
+ if (mp->format >= 12 /* more is'nt yet ... PALETTEFMT_MAX */)
+ return -EINVAL;
+
+ if(-1 == palette2fmt[mp->format])
+ return -EINVAL;
+
+ /*
+ * Ok load up the BT848
+ */
+
+ ro=btv->gbuf[0].risc;
+ re=ro+2048;
+ make_vrisctab_kiobuf(btv, ro, re, iobuf, mp->width, mp->height, mp->format);
+
+ if (bttv_debug)
+ printk("bttv%d: cap vgrab_kiobuf: queue %d (%d:%dx%d)\n",
+ btv->nr,0,mp->format,mp->width,mp->height);
+ spin_lock_irqsave(&btv->s_lock, flags);
+ btv->gbuf[0].stat = GBUFFER_GRABBING;
+ btv->gbuf[0].fmt = palette2fmt[mp->format];
+ btv->gbuf[0].width = mp->width;
+ btv->gbuf[0].height = mp->height;
+ btv->gbuf[0].ro = virt_to_bus(ro);
+ btv->gbuf[0].re = virt_to_bus(re);
+
+#if 1
+ if (mp->height <= tvnorms[btv->win.norm].sheight/2 &&
+ mp->format != VIDEO_PALETTE_RAW)
+ btv->gbuf[0].ro = 0;
+#endif
+
+ if (-1 == btv->gq_grab && btv->gq_in == btv->gq_out) {
+ btv->gq_start = 1;
+ btv->risc_jmp[12]=cpu_to_le32(BT848_RISC_JUMP|(0x8<<16)|BT848_RISC_IRQ);
+ }
+ btv->gqueue[btv->gq_in++] = 0;
+ btv->gq_in = btv->gq_in % MAX_GBUFFERS;
+
+ btor(3, BT848_CAP_CTL);
+ btor(3, BT848_GPIO_DMA_CTL);
+ spin_unlock_irqrestore(&btv->s_lock, flags);
+ return 0;
+}
+#endif
+
+/*
+ * Grab into virtual memory.
+ */
+
+static int vgrab(struct bttv *btv, struct video_mmap *mp)
+{
+ unsigned int *ro, *re;
+ unsigned int *vbuf;
+ unsigned long flags;
+
+ if(btv->fbuffer==NULL)
+ {
+ if(fbuffer_alloc(btv))
+ return -ENOBUFS;
+ }
+
+ if(mp->frame >= gbuffers || mp->frame < 0)
+ return -EINVAL;
+ if(btv->gbuf[mp->frame].stat != GBUFFER_UNUSED)
+ return -EBUSY;
+
+ if(mp->height < 32 || mp->width < 32)
+ return -EINVAL;
+ if (mp->format >= PALETTEFMT_MAX)
+ return -EINVAL;
+
+ if (mp->height*mp->width*fmtbppx2[palette2fmt[mp->format]&0x0f]/2
+ > gbufsize)
+ return -EINVAL;
+ if(-1 == palette2fmt[mp->format])
+ return -EINVAL;
+
+ /*
+ * Ok load up the BT848
+ */
+
+ vbuf=(unsigned int *)(btv->fbuffer+gbufsize*mp->frame);
+ ro=btv->gbuf[mp->frame].risc;
+ re=ro+2048;
+ make_vrisctab(btv, ro, re, vbuf, mp->width, mp->height, mp->format);
+
+ if (bttv_debug)
+ printk("bttv%d: cap vgrab: queue %d (%d:%dx%d)\n",
+ btv->nr,mp->frame,mp->format,mp->width,mp->height);
+ spin_lock_irqsave(&btv->s_lock, flags);
+ btv->gbuf[mp->frame].stat = GBUFFER_GRABBING;
+ btv->gbuf[mp->frame].fmt = palette2fmt[mp->format];
+ btv->gbuf[mp->frame].width = mp->width;
+ btv->gbuf[mp->frame].height = mp->height;
+ btv->gbuf[mp->frame].ro = virt_to_bus(ro);
+ btv->gbuf[mp->frame].re = virt_to_bus(re);
+
+#if 1
+ if (mp->height <= tvnorms[btv->win.norm].sheight/2 &&
+ mp->format != VIDEO_PALETTE_RAW)
+ btv->gbuf[mp->frame].ro = 0;
+#endif
+
+ if (-1 == btv->gq_grab && btv->gq_in == btv->gq_out) {
+ btv->gq_start = 1;
+ btv->risc_jmp[12]=cpu_to_le32(BT848_RISC_JUMP|(0x8<<16)|BT848_RISC_IRQ);
+ }
+ btv->gqueue[btv->gq_in++] = mp->frame;
+ btv->gq_in = btv->gq_in % MAX_GBUFFERS;
+
+ btor(3, BT848_CAP_CTL);
+ btor(3, BT848_GPIO_DMA_CTL);
+ spin_unlock_irqrestore(&btv->s_lock, flags);
+ return 0;
+}
+
+static long bttv_write(struct video_device *v, const char *buf, unsigned long count, int nonblock)
+{
+ return -EINVAL;
+}
+
+static long bttv_read(struct video_device *v, char *buf, unsigned long count, int nonblock)
+{
+ struct bttv *btv= (struct bttv *)v;
+ int q,todo;
+ DECLARE_WAITQUEUE(wait, current);
+
+ /* BROKEN: RETURNS VBI WHEN IT SHOULD RETURN GRABBED VIDEO FRAME */
+ todo=count;
+ while (todo && todo>(q=VBIBUF_SIZE-btv->vbip))
+ {
+ if(copy_to_user((void *) buf, (void *) btv->vbibuf+btv->vbip, q))
+ return -EFAULT;
+ todo-=q;
+ buf+=q;
+
+ add_wait_queue(&btv->vbiq, &wait);
+ current->state = TASK_INTERRUPTIBLE;
+ if (todo && q==VBIBUF_SIZE-btv->vbip)
+ {
+ if(nonblock)
+ {
+ remove_wait_queue(&btv->vbiq, &wait);
+ current->state = TASK_RUNNING;
+ if(count==todo)
+ return -EWOULDBLOCK;
+ return count-todo;
+ }
+ schedule();
+ if(signal_pending(current))
+ {
+ remove_wait_queue(&btv->vbiq, &wait);
+ current->state = TASK_RUNNING;
+
+ if(todo==count)
+ return -EINTR;
+ else
+ return count-todo;
+ }
+ }
+ remove_wait_queue(&btv->vbiq, &wait);
+ current->state = TASK_RUNNING;
+ }
+ if (todo)
+ {
+ if(copy_to_user((void *) buf, (void *) btv->vbibuf+btv->vbip, todo))
+ return -EFAULT;
+ btv->vbip+=todo;
+ }
+ return count;
+}
+
+static inline void burst(int on)
+{
+ tvnorms[0].scaledtwidth = 1135 - (on?BURSTOFFSET-2:0);
+ tvnorms[0].hdelayx1 = 186 - (on?BURSTOFFSET :0);
+ tvnorms[2].scaledtwidth = 1135 - (on?BURSTOFFSET-2:0);
+ tvnorms[2].hdelayx1 = 186 - (on?BURSTOFFSET :0);
+}
+
+static void bt848_restart(struct bttv *btv)
+{
+ unsigned long irq_flags;
+
+ if (bttv_verbose)
+ printk("bttv%d: resetting chip\n",btv->nr);
+ btwrite(0xfffffUL, BT848_INT_STAT);
+ btand(~15, BT848_GPIO_DMA_CTL);
+ btwrite(0, BT848_SRESET);
+ btwrite(virt_to_bus(btv->risc_jmp+2),
+ BT848_RISC_STRT_ADD);
+
+ /* enforce pll reprogramming */
+ btv->pll.pll_current = 0;
+ set_pll(btv);
+
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ btv->errors = 0;
+ btv->needs_restart = 0;
+ bt848_set_geo(btv,0);
+ bt848_set_risc_jmps(btv,-1);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+}
+
+/*
+ * Open a bttv card. Right now the flags stuff is just playing
+ */
+
+static int bttv_open(struct video_device *dev, int flags)
+{
+ struct bttv *btv = (struct bttv *)dev;
+ int i,ret;
+
+ ret = -EBUSY;
+
+ MOD_INC_USE_COUNT;
+ down(&btv->lock);
+ if (btv->user)
+ goto out_unlock;
+
+ btv->fbuffer=(unsigned char *) rvmalloc(gbuffers*gbufsize);
+ ret = -ENOMEM;
+ if (!btv->fbuffer)
+ goto out_unlock;
+
+ btv->gq_in = 0;
+ btv->gq_out = 0;
+ btv->gq_grab = -1;
+ for (i = 0; i < gbuffers; i++)
+ btv->gbuf[i].stat = GBUFFER_UNUSED;
+
+ if (btv->needs_restart)
+ bt848_restart(btv);
+ burst(0);
+ set_pll(btv);
+ btv->user++;
+ up(&btv->lock);
+ return 0;
+
+ out_unlock:
+ up(&btv->lock);
+ MOD_DEC_USE_COUNT;
+ return ret;
+}
+
+static void bttv_close(struct video_device *dev)
+{
+ struct bttv *btv=(struct bttv *)dev;
+ unsigned long irq_flags;
+
+ down(&btv->lock);
+ btv->user--;
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ btv->scr_on = 0;
+ btv->risc_cap_odd = 0;
+ btv->risc_cap_even = 0;
+ bt848_set_risc_jmps(btv,-1);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+
+ /*
+ * A word of warning. At this point the chip
+ * is still capturing because its FIFO hasn't emptied
+ * and the DMA control operations are posted PCI
+ * operations.
+ */
+
+ btread(BT848_I2C); /* This fixes the PCI posting delay */
+
+ if (-1 != btv->gq_grab) {
+ /*
+ * This is sucky but right now I can't find a good way to
+ * be sure its safe to free the buffer. We wait 5-6 fields
+ * which is more than sufficient to be sure.
+ */
+ current->state = TASK_UNINTERRUPTIBLE;
+ schedule_timeout(HZ/10); /* Wait 1/10th of a second */
+ }
+
+ /*
+ * We have allowed it to drain.
+ */
+
+ if(btv->fbuffer)
+ rvfree((void *) btv->fbuffer, gbuffers*gbufsize);
+ btv->fbuffer=0;
+ up(&btv->lock);
+ MOD_DEC_USE_COUNT;
+}
+
+
+/***********************************/
+/* ioctls and supporting functions */
+/***********************************/
+
+extern inline void bt848_bright(struct bttv *btv, uint bright)
+{
+ btwrite(bright&0xff, BT848_BRIGHT);
+}
+
+extern inline void bt848_hue(struct bttv *btv, uint hue)
+{
+ btwrite(hue&0xff, BT848_HUE);
+}
+
+extern inline void bt848_contrast(struct bttv *btv, uint cont)
+{
+ unsigned int conthi;
+
+ conthi=(cont>>6)&4;
+ btwrite(cont&0xff, BT848_CONTRAST_LO);
+ btaor(conthi, ~4, BT848_E_CONTROL);
+ btaor(conthi, ~4, BT848_O_CONTROL);
+}
+
+extern inline void bt848_sat_u(struct bttv *btv, unsigned long data)
+{
+ u32 datahi;
+
+ datahi=(data>>7)&2;
+ btwrite(data&0xff, BT848_SAT_U_LO);
+ btaor(datahi, ~2, BT848_E_CONTROL);
+ btaor(datahi, ~2, BT848_O_CONTROL);
+}
+
+static inline void bt848_sat_v(struct bttv *btv, unsigned long data)
+{
+ u32 datahi;
+
+ datahi=(data>>8)&1;
+ btwrite(data&0xff, BT848_SAT_V_LO);
+ btaor(datahi, ~1, BT848_E_CONTROL);
+ btaor(datahi, ~1, BT848_O_CONTROL);
+}
+
+/*
+ * ioctl routine
+ */
+
+
+static int bttv_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct bttv *btv=(struct bttv *)dev;
+ unsigned long irq_flags;
+ int i,ret = 0;
+
+ if (bttv_debug > 1)
+ printk("bttv%d: ioctl 0x%x\n",btv->nr,cmd);
+
+ switch (cmd) {
+ case VIDIOCGCAP:
+ {
+ struct video_capability b;
+ strcpy(b.name,btv->video_dev.name);
+ b.type = VID_TYPE_CAPTURE|
+ ((bttv_tvcards[btv->type].tuner != -1) ? VID_TYPE_TUNER : 0) |
+ VID_TYPE_OVERLAY|
+ VID_TYPE_CLIPPING|
+ VID_TYPE_FRAMERAM|
+ VID_TYPE_SCALES;
+ b.channels = bttv_tvcards[btv->type].video_inputs;
+ b.audios = bttv_tvcards[btv->type].audio_inputs;
+ b.maxwidth = tvnorms[btv->win.norm].swidth;
+ b.maxheight = tvnorms[btv->win.norm].sheight;
+ b.minwidth = 32;
+ b.minheight = 32;
+ if(copy_to_user(arg,&b,sizeof(b)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCGCHAN:
+ {
+ struct video_channel v;
+ if(copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ v.flags=VIDEO_VC_AUDIO;
+ v.tuners=0;
+ v.type=VIDEO_TYPE_CAMERA;
+ v.norm = btv->win.norm;
+ if (v.channel>=bttv_tvcards[btv->type].video_inputs)
+ return -EINVAL;
+ if(v.channel==bttv_tvcards[btv->type].tuner)
+ {
+ strcpy(v.name,"Television");
+ v.flags|=VIDEO_VC_TUNER;
+ v.type=VIDEO_TYPE_TV;
+ v.tuners=1;
+ }
+ else if(v.channel==bttv_tvcards[btv->type].svhs)
+ strcpy(v.name,"S-Video");
+ else
+ sprintf(v.name,"Composite%d",v.channel);
+
+ if(copy_to_user(arg,&v,sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ /*
+ * Each channel has 1 tuner
+ */
+ case VIDIOCSCHAN:
+ {
+ struct video_channel v;
+ if(copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+
+ if (v.channel>bttv_tvcards[btv->type].video_inputs)
+ return -EINVAL;
+ if (v.norm > (sizeof(tvnorms)/sizeof(*tvnorms)))
+ return -EOPNOTSUPP;
+
+ bttv_call_i2c_clients(btv,cmd,&v);
+ down(&btv->lock);
+ bt848_muxsel(btv, v.channel);
+ btv->channel=v.channel;
+ if (btv->win.norm != v.norm) {
+ btv->win.norm = v.norm;
+ make_vbitab(btv);
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ bt848_set_winsize(btv);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+ }
+ up(&btv->lock);
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner v;
+ if(copy_from_user(&v,arg,sizeof(v))!=0)
+ return -EFAULT;
+ if(v.tuner||btv->channel) /* Only tuner 0 */
+ return -EINVAL;
+ strcpy(v.name, "Television");
+ v.rangelow=0;
+ v.rangehigh=0xFFFFFFFF;
+ v.flags=VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM;
+ v.mode = btv->win.norm;
+ v.signal = (btread(BT848_DSTATUS)&BT848_DSTATUS_HLOC) ? 0xFFFF : 0;
+ bttv_call_i2c_clients(btv,cmd,&v);
+ if(copy_to_user(arg,&v,sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ /* We have but one tuner */
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner v;
+ if(copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ /* Only one channel has a tuner */
+ if(v.tuner!=bttv_tvcards[btv->type].tuner)
+ return -EINVAL;
+
+ if(v.mode!=VIDEO_MODE_PAL&&v.mode!=VIDEO_MODE_NTSC
+ &&v.mode!=VIDEO_MODE_SECAM)
+ return -EOPNOTSUPP;
+ bttv_call_i2c_clients(btv,cmd,&v);
+ if (btv->win.norm != v.mode) {
+ btv->win.norm = v.mode;
+ down(&btv->lock);
+ set_pll(btv);
+ make_vbitab(btv);
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ bt848_set_winsize(btv);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+ up(&btv->lock);
+ }
+ return 0;
+ }
+ case VIDIOCGPICT:
+ {
+ struct video_picture p=btv->picture;
+ if(copy_to_user(arg, &p, sizeof(p)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSPICT:
+ {
+ struct video_picture p;
+ if(copy_from_user(&p, arg,sizeof(p)))
+ return -EFAULT;
+ if (p.palette > PALETTEFMT_MAX)
+ return -EINVAL;
+ down(&btv->lock);
+ /* We want -128 to 127 we get 0-65535 */
+ bt848_bright(btv, (p.brightness>>8)-128);
+ /* 0-511 for the colour */
+ bt848_sat_u(btv, p.colour>>7);
+ bt848_sat_v(btv, ((p.colour>>7)*201L)/237);
+ /* -128 to 127 */
+ bt848_hue(btv, (p.hue>>8)-128);
+ /* 0-511 */
+ bt848_contrast(btv, p.contrast>>7);
+ btv->picture = p;
+ up(&btv->lock);
+ return 0;
+ }
+ case VIDIOCSWIN:
+ {
+ struct video_window vw;
+ struct video_clip *vcp = NULL;
+
+ if(copy_from_user(&vw,arg,sizeof(vw)))
+ return -EFAULT;
+
+ down(&btv->lock);
+ if(vw.flags || vw.width < 16 || vw.height < 16)
+ {
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ btv->scr_on = 0;
+ bt848_set_risc_jmps(btv,-1);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+ return -EINVAL;
+ }
+ if (btv->win.bpp < 4)
+ { /* adjust and align writes */
+ vw.x = (vw.x + 3) & ~3;
+ vw.width &= ~3;
+ }
+ if (btv->needs_restart)
+ bt848_restart(btv);
+ btv->win.x=vw.x;
+ btv->win.y=vw.y;
+ btv->win.width=vw.width;
+ btv->win.height=vw.height;
+
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ bt848_set_risc_jmps(btv,0);
+ bt848_set_winsize(btv);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+
+ /*
+ * Do any clips.
+ */
+ if(vw.clipcount<0) {
+ if((vcp=vmalloc(VIDEO_CLIPMAP_SIZE))==NULL)
+ return -ENOMEM;
+ if(copy_from_user(vcp, vw.clips,
+ VIDEO_CLIPMAP_SIZE)) {
+ vfree(vcp);
+ return -EFAULT;
+ }
+ } else if (vw.clipcount) {
+ if((vcp=vmalloc(sizeof(struct video_clip)*
+ (vw.clipcount))) == NULL)
+ return -ENOMEM;
+ if(copy_from_user(vcp,vw.clips,
+ sizeof(struct video_clip)*
+ vw.clipcount)) {
+ vfree(vcp);
+ return -EFAULT;
+ }
+ }
+ make_clip_tab(btv, vcp, vw.clipcount);
+ if (vw.clipcount != 0)
+ vfree(vcp);
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ bt848_set_risc_jmps(btv,-1);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+ up(&btv->lock);
+ return 0;
+ }
+ case VIDIOCGWIN:
+ {
+ struct video_window vw;
+ /* Oh for a COBOL move corresponding .. */
+ vw.x=btv->win.x;
+ vw.y=btv->win.y;
+ vw.width=btv->win.width;
+ vw.height=btv->win.height;
+ vw.chromakey=0;
+ vw.flags=0;
+ if(btv->win.interlace)
+ vw.flags|=VIDEO_WINDOW_INTERLACE;
+ if(copy_to_user(arg,&vw,sizeof(vw)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCCAPTURE:
+ {
+ int v;
+ if(copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ if(btv->win.vidadr == 0)
+ return -EINVAL;
+ if (btv->win.width==0 || btv->win.height==0)
+ return -EINVAL;
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ if (v == 1 && btv->win.vidadr != 0)
+ btv->scr_on = 1;
+ if (v == 0)
+ btv->scr_on = 0;
+ bt848_set_risc_jmps(btv,-1);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+ return 0;
+ }
+ case VIDIOCGFBUF:
+ {
+ struct video_buffer v;
+ v.base=(void *)btv->win.vidadr;
+ v.height=btv->win.sheight;
+ v.width=btv->win.swidth;
+ v.depth=btv->win.depth;
+ v.bytesperline=btv->win.bpl;
+ if(copy_to_user(arg, &v,sizeof(v)))
+ return -EFAULT;
+ return 0;
+
+ }
+ case VIDIOCSFBUF:
+ {
+ struct video_buffer v;
+ if(!capable(CAP_SYS_ADMIN) &&
+ !capable(CAP_SYS_RAWIO))
+ return -EPERM;
+ if(copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ if(v.depth!=8 && v.depth!=15 && v.depth!=16 &&
+ v.depth!=24 && v.depth!=32 && v.width > 16 &&
+ v.height > 16 && v.bytesperline > 16)
+ return -EINVAL;
+ down(&btv->lock);
+ if (v.base)
+ btv->win.vidadr=(unsigned long)v.base;
+ btv->win.sheight=v.height;
+ btv->win.swidth=v.width;
+ btv->win.bpp=((v.depth+7)&0x38)/8;
+ btv->win.depth=v.depth;
+ btv->win.bpl=v.bytesperline;
+
+ /* set sefault color format */
+ switch (btv->win.bpp) {
+ case 8: btv->picture.palette = VIDEO_PALETTE_HI240; break;
+ case 15: btv->picture.palette = VIDEO_PALETTE_RGB555; break;
+ case 16: btv->picture.palette = VIDEO_PALETTE_RGB565; break;
+ case 24: btv->picture.palette = VIDEO_PALETTE_RGB24; break;
+ case 32: btv->picture.palette = VIDEO_PALETTE_RGB32; break;
+ }
+
+ if (bttv_debug)
+ printk("Display at %p is %d by %d, bytedepth %d, bpl %d\n",
+ v.base, v.width,v.height, btv->win.bpp, btv->win.bpl);
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ bt848_set_winsize(btv);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+ up(&btv->lock);
+ return 0;
+ }
+ case VIDIOCKEY:
+ {
+ /* Will be handled higher up .. */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long v=btv->win.freq;
+ if(copy_to_user(arg,&v,sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long v;
+ if(copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ btv->win.freq=v;
+ bttv_call_i2c_clients(btv,cmd,&v);
+#if 0
+ if (btv->type == BTTV_MIROPRO && btv->radio)
+ tea5757_set_freq(btv,v);
+#endif
+ return 0;
+ }
+
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+
+ v=btv->audio_dev;
+ v.flags&=~(VIDEO_AUDIO_MUTE|VIDEO_AUDIO_MUTABLE);
+ v.flags|=VIDEO_AUDIO_MUTABLE;
+ strcpy(v.name,"TV");
+
+ v.mode = VIDEO_SOUND_MONO;
+ bttv_call_i2c_clients(btv,cmd,&v);
+
+ if (btv->type == BTTV_TERRATV) {
+ v.mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO |
+ VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2;
+ }
+#ifndef HAVE_TVAUDIO
+ else if (btv->audio_chip == TDA9840) {
+ /* begin of Horrible Hack <grin@tolna.net> */
+ v.flags|=VIDEO_AUDIO_VOLUME;
+ v.mode = VIDEO_SOUND_MONO;
+ v.mode |= VIDEO_SOUND_STEREO;
+ v.mode |= VIDEO_SOUND_LANG1|VIDEO_SOUND_LANG2;
+ v.volume = 32768; /* fixme */
+ v.step = 4096;
+ }
+#endif
+
+ 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;
+ down(&btv->lock);
+ if(v.flags&VIDEO_AUDIO_MUTE)
+ audio(btv, AUDIO_MUTE, 1);
+ /* One audio source per tuner -- huh? <GA> */
+ if(v.audio<0 || v.audio >= bttv_tvcards[btv->type].audio_inputs) {
+ up(&btv->lock);
+ return -EINVAL;
+ }
+ /* bt848_muxsel(btv,v.audio); */
+ if(!(v.flags&VIDEO_AUDIO_MUTE))
+ audio(btv, AUDIO_UNMUTE, 1);
+
+ bttv_call_i2c_clients(btv,cmd,&v);
+
+ if (btv->type == BTTV_TERRATV) {
+ unsigned int con = 0;
+ btor(0x180000, BT848_GPIO_OUT_EN);
+ if (v.mode & VIDEO_SOUND_LANG2)
+ con = 0x080000;
+ if (v.mode & VIDEO_SOUND_STEREO)
+ con = 0x180000;
+ btaor(con, ~0x180000, BT848_GPIO_DATA);
+
+ } else if (btv->type == BTTV_WINVIEW_601)
+ winview_setvol(btv,&v);
+
+ btv->audio_dev=v;
+ up(&btv->lock);
+ return 0;
+ }
+
+ case VIDIOCSYNC:
+ {
+ DECLARE_WAITQUEUE(wait, current);
+
+ if(copy_from_user((void *)&i,arg,sizeof(int)))
+ return -EFAULT;
+ if (i < 0 || i >= gbuffers)
+ return -EINVAL;
+ switch (btv->gbuf[i].stat) {
+ case GBUFFER_UNUSED:
+ ret = -EINVAL;
+ break;
+ case GBUFFER_GRABBING:
+ add_wait_queue(&btv->capq, &wait);
+ current->state = TASK_INTERRUPTIBLE;
+ while(btv->gbuf[i].stat==GBUFFER_GRABBING) {
+ if (bttv_debug)
+ printk("bttv%d: cap sync: sleep on %d\n",btv->nr,i);
+ schedule();
+ if(signal_pending(current)) {
+ remove_wait_queue(&btv->capq, &wait);
+ current->state = TASK_RUNNING;
+ return -EINTR;
+ }
+ }
+ remove_wait_queue(&btv->capq, &wait);
+ current->state = TASK_RUNNING;
+ /* fall throuth */
+ case GBUFFER_DONE:
+ case GBUFFER_ERROR:
+ ret = (btv->gbuf[i].stat == GBUFFER_ERROR) ? -EIO : 0;
+ if (bttv_debug)
+ printk("bttv%d: cap sync: buffer %d, retval %d\n",btv->nr,i,ret);
+ btv->gbuf[i].stat = GBUFFER_UNUSED;
+ }
+ if (btv->needs_restart) {
+ down(&btv->lock);
+ bt848_restart(btv);
+ up(&btv->lock);
+ }
+ return ret;
+ }
+
+ case BTTV_FIELDNR:
+ if(copy_to_user((void *) arg, (void *) &btv->last_field,
+ sizeof(btv->last_field)))
+ return -EFAULT;
+ break;
+
+ case BTTV_PLLSET: {
+ struct bttv_pll_info p;
+ if(!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if(copy_from_user(&p , (void *) arg, sizeof(btv->pll)))
+ return -EFAULT;
+ down(&btv->lock);
+ btv->pll.pll_ifreq = p.pll_ifreq;
+ btv->pll.pll_ofreq = p.pll_ofreq;
+ btv->pll.pll_crystal = p.pll_crystal;
+ up(&btv->lock);
+ break;
+ }
+
+ case VIDIOCMCAPTURE:
+ {
+ struct video_mmap vm;
+ int ret;
+ if(copy_from_user((void *) &vm, (void *) arg, sizeof(vm)))
+ return -EFAULT;
+ down(&btv->lock);
+ ret = vgrab(btv, &vm);
+ up(&btv->lock);
+ return ret;
+ }
+
+ case VIDIOCGMBUF:
+ {
+ struct video_mbuf vm;
+ memset(&vm, 0 , sizeof(vm));
+ vm.size=gbufsize*gbuffers;
+ vm.frames=gbuffers;
+ for (i = 0; i < gbuffers; i++)
+ vm.offsets[i]=i*gbufsize;
+ if(copy_to_user((void *)arg, (void *)&vm, sizeof(vm)))
+ return -EFAULT;
+ return 0;
+ }
+
+ case VIDIOCGUNIT:
+ {
+ struct video_unit vu;
+ vu.video=btv->video_dev.minor;
+ vu.vbi=btv->vbi_dev.minor;
+ if(btv->radio_dev.minor!=-1)
+ vu.radio=btv->radio_dev.minor;
+ else
+ vu.radio=VIDEO_NO_UNIT;
+ vu.audio=VIDEO_NO_UNIT;
+ vu.teletext=VIDEO_NO_UNIT;
+ if(copy_to_user((void *)arg, (void *)&vu, sizeof(vu)))
+ return -EFAULT;
+ return 0;
+ }
+
+ case BTTV_BURST_ON:
+ {
+ burst(1);
+ return 0;
+ }
+
+ case BTTV_BURST_OFF:
+ {
+ burst(0);
+ return 0;
+ }
+
+ case BTTV_VERSION:
+ {
+ return BTTV_VERSION_CODE;
+ }
+
+ case BTTV_PICNR:
+ {
+ /* return picture;*/
+ return 0;
+ }
+
+#ifdef HACKING
+ /* playing with kiobufs and dma-to-userspace */
+ case BTTV_JUST_HACKING:
+ {
+ DECLARE_WAITQUEUE(wait, current);
+ struct bttv_just_hacking hack;
+ struct kiobuf *iobuf;
+ int err;
+
+ if(copy_from_user((void *) &hack, (void *) arg, sizeof(hack)))
+ return -EFAULT;
+ printk("bttv%d: hack: userland args: %dx%d, fmt=%d, buf=%lx, len=%d\n",
+ btv->nr,hack.width,hack.height,hack.format,
+ hack.buf,hack.len);
+
+ /* pin down */
+ err = alloc_kiovec(1,&iobuf);
+ if (err)
+ goto hack_oops;
+ err = map_user_kiobuf(READ, iobuf, hack.buf, hack.len);
+ if (err)
+ goto hack_oops;
+ err = lock_kiovec(1,&iobuf,1);
+ if (err)
+ goto hack_oops;
+
+ /* have a look */
+ printk("bttv%d: hack: kiobuf: nr_pages=%d, offset=%d, length=%d, locked=%d\n",
+ btv->nr,iobuf->nr_pages,iobuf->offset,iobuf->length,
+ iobuf->locked);
+ printk("bttv%d: hack: pages (bus addr):",btv->nr);
+ for (i = 0; i < iobuf->nr_pages; i++) {
+ printk(" %lx", virt_to_bus(page_address(iobuf->maplist[i])));
+ }
+ printk("\n");
+
+ /* start capture */
+ err = -EINVAL;
+ if (hack.height * hack.width * 2 * /* fixme: *2 */
+ fmtbppx2[palette2fmt[hack.format]&0x0f]/2 > hack.len)
+ goto hack_oops;
+ err = vgrab_kiobuf(btv,&hack,iobuf);
+ if (err)
+ goto hack_oops;
+ printk("bttv%d: hack: capture started\n",btv->nr);
+
+ /* wait */
+ add_wait_queue(&btv->capq, &wait);
+ current->state = TASK_INTERRUPTIBLE;
+ while(btv->gbuf[0].stat==GBUFFER_GRABBING) {
+ if (bttv_debug)
+ printk("bttv%d: hack: cap sync: sleep on %d\n",btv->nr,0);
+ schedule();
+#if 0
+ if(signal_pending(current)) {
+ remove_wait_queue(&btv->capq, &wait);
+ current->state = TASK_RUNNING;
+ return -EINTR;
+ }
+#endif
+ }
+ remove_wait_queue(&btv->capq, &wait);
+ current->state = TASK_RUNNING;
+ printk("bttv%d: hack: capture done\n",btv->nr);
+
+ /* release */
+ err = 0;
+ hack_oops:
+ free_kiovec(1,&iobuf);
+ return 0;
+ }
+#endif
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static int bttv_init_done(struct video_device *dev)
+{
+ return 0;
+}
+
+/*
+ * This maps the vmalloced and reserved fbuffer to user space.
+ *
+ * FIXME:
+ * - PAGE_READONLY should suffice!?
+ * - remap_page_range is kind of inefficient for page by page remapping.
+ * But e.g. pte_alloc() does not work in modules ... :-(
+ */
+
+static int do_bttv_mmap(struct bttv *btv, const char *adr, unsigned long size)
+{
+ unsigned long start=(unsigned long) adr;
+ unsigned long page,pos;
+
+ if (size>gbuffers*gbufsize)
+ return -EINVAL;
+ if (!btv->fbuffer) {
+ if(fbuffer_alloc(btv))
+ return -EINVAL;
+ }
+ pos=(unsigned long) btv->fbuffer;
+ while (size > 0) {
+ page = kvirt_to_pa(pos);
+ if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED))
+ return -EAGAIN;
+ start+=PAGE_SIZE;
+ pos+=PAGE_SIZE;
+ size-=PAGE_SIZE;
+ }
+ return 0;
+}
+
+static int bttv_mmap(struct video_device *dev, const char *adr, unsigned long size)
+{
+ struct bttv *btv=(struct bttv *)dev;
+ int r;
+
+ down(&btv->lock);
+ r=do_bttv_mmap(btv, adr, size);
+ up(&btv->lock);
+ return r;
+}
+
+
+static struct video_device bttv_template=
+{
+ "UNSET",
+ VID_TYPE_TUNER|VID_TYPE_CAPTURE|VID_TYPE_OVERLAY|VID_TYPE_TELETEXT,
+ VID_HARDWARE_BT848,
+ bttv_open,
+ bttv_close,
+ bttv_read,
+ bttv_write,
+ NULL,
+ bttv_ioctl,
+ bttv_mmap,
+ bttv_init_done,
+ NULL,
+ 0,
+ -1
+};
+
+
+static long vbi_read(struct video_device *v, char *buf, unsigned long count,
+ int nonblock)
+{
+ struct bttv *btv=(struct bttv *)(v-2);
+ int q,todo;
+ DECLARE_WAITQUEUE(wait, current);
+
+ todo=count;
+ while (todo && todo>(q=VBIBUF_SIZE-btv->vbip))
+ {
+ if (btv->needs_restart) {
+ down(&btv->lock);
+ bt848_restart(btv);
+ up(&btv->lock);
+ }
+ if(copy_to_user((void *) buf, (void *) btv->vbibuf+btv->vbip, q))
+ return -EFAULT;
+ todo-=q;
+ buf+=q;
+
+ add_wait_queue(&btv->vbiq, &wait);
+ current->state = TASK_INTERRUPTIBLE;
+ if (todo && q==VBIBUF_SIZE-btv->vbip)
+ {
+ if(nonblock)
+ {
+ remove_wait_queue(&btv->vbiq, &wait);
+ current->state = TASK_RUNNING;
+ if(count==todo)
+ return -EWOULDBLOCK;
+ return count-todo;
+ }
+ schedule();
+ if(signal_pending(current))
+ {
+ remove_wait_queue(&btv->vbiq, &wait);
+ current->state = TASK_RUNNING;
+ if(todo==count)
+ return -EINTR;
+ else
+ return count-todo;
+ }
+ }
+ remove_wait_queue(&btv->vbiq, &wait);
+ current->state = TASK_RUNNING;
+ }
+ if (todo)
+ {
+ if(copy_to_user((void *) buf, (void *) btv->vbibuf+btv->vbip, todo))
+ return -EFAULT;
+ btv->vbip+=todo;
+ }
+ return count;
+}
+
+static unsigned int vbi_poll(struct video_device *dev, struct file *file,
+ poll_table *wait)
+{
+ struct bttv *btv=(struct bttv *)(dev-2);
+ unsigned int mask = 0;
+
+ poll_wait(file, &btv->vbiq, wait);
+
+ if (btv->vbip < VBIBUF_SIZE)
+ mask |= (POLLIN | POLLRDNORM);
+
+ return mask;
+}
+
+static int vbi_open(struct video_device *dev, int flags)
+{
+ struct bttv *btv=(struct bttv *)(dev-2);
+ unsigned long irq_flags;
+
+ MOD_INC_USE_COUNT;
+ down(&btv->lock);
+ if (btv->needs_restart)
+ bt848_restart(btv);
+ set_pll(btv);
+ btv->vbip=VBIBUF_SIZE;
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ btv->vbi_on = 1;
+ bt848_set_risc_jmps(btv,-1);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+ up(&btv->lock);
+
+ return 0;
+}
+
+static void vbi_close(struct video_device *dev)
+{
+ struct bttv *btv=(struct bttv *)(dev-2);
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ btv->vbi_on = 0;
+ bt848_set_risc_jmps(btv,-1);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+ MOD_DEC_USE_COUNT;
+}
+
+static int vbi_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct bttv *btv=(struct bttv *)dev;
+
+ switch (cmd) {
+ case VIDIOCGCAP:
+ {
+ struct video_capability b;
+ strcpy(b.name,btv->vbi_dev.name);
+ b.type = ((bttv_tvcards[btv->type].tuner != -1) ? VID_TYPE_TUNER : 0) |
+ VID_TYPE_TELETEXT;
+ b.channels = 0;
+ b.audios = 0;
+ b.maxwidth = 0;
+ b.maxheight = 0;
+ b.minwidth = 0;
+ b.minheight = 0;
+ if(copy_to_user(arg,&b,sizeof(b)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ case VIDIOCSFREQ:
+ return bttv_ioctl((struct video_device *)btv,cmd,arg);
+ case BTTV_VBISIZE:
+ /* make alevt happy :-) */
+ return VBIBUF_SIZE;
+ default:
+ return -EINVAL;
+ }
+}
+
+static struct video_device vbi_template=
+{
+ "bttv vbi",
+ VID_TYPE_CAPTURE|VID_TYPE_TELETEXT,
+ VID_HARDWARE_BT848,
+ vbi_open,
+ vbi_close,
+ vbi_read,
+ bttv_write,
+ vbi_poll,
+ vbi_ioctl,
+ NULL, /* no mmap yet */
+ bttv_init_done,
+ NULL,
+ 0,
+ -1
+};
+
+
+static int radio_open(struct video_device *dev, int flags)
+{
+ struct bttv *btv = (struct bttv *)(dev-1);
+ unsigned long v;
+
+ MOD_INC_USE_COUNT;
+ down(&btv->lock);
+ if (btv->user)
+ goto busy_unlock;
+ btv->user++;
+
+ btv->radio = 1;
+ v = 400*16;
+ bttv_call_i2c_clients(btv,VIDIOCSFREQ,&v);
+ bttv_call_i2c_clients(btv,AUDC_SET_RADIO,&btv->tuner_type);
+ bt848_muxsel(btv,0);
+ up(&btv->lock);
+
+ return 0;
+
+ busy_unlock:
+ up(&btv->lock);
+ MOD_DEC_USE_COUNT;
+ return -EBUSY;
+}
+
+static void radio_close(struct video_device *dev)
+{
+ struct bttv *btv=(struct bttv *)(dev-1);
+
+ down(&btv->lock);
+ btv->user--;
+ btv->radio = 0;
+ up(&btv->lock);
+ MOD_DEC_USE_COUNT;
+}
+
+static long radio_read(struct video_device *v, char *buf, unsigned long count, int nonblock)
+{
+ return -EINVAL;
+}
+
+static int radio_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct bttv *btv=(struct bttv *)(dev-1);
+ switch (cmd) {
+ case VIDIOCGCAP:
+ {
+ struct video_capability v;
+ strcpy(v.name,btv->video_dev.name);
+ 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;
+ break;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner v;
+ if(copy_from_user(&v,arg,sizeof(v))!=0)
+ return -EFAULT;
+ if(v.tuner||btv->channel) /* Only tuner 0 */
+ return -EINVAL;
+ strcpy(v.name, "Radio");
+ v.rangelow=(int)(76*16); /* jp: 76.0MHz - 89.9MHz */
+ v.rangehigh=(int)(108*16); /* eu: 87.5MHz - 108.0MHz */
+ v.flags= 0; /* XXX */
+ v.mode = 0; /* XXX */
+ 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;
+ /* Only channel 0 has a tuner */
+ if(v.tuner!=0 || btv->channel)
+ return -EINVAL;
+ /* XXX anything to do ??? */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ case VIDIOCSFREQ:
+ case VIDIOCGAUDIO:
+ case VIDIOCSAUDIO:
+ bttv_ioctl((struct video_device *)btv,cmd,arg);
+ break;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static struct video_device radio_template=
+{
+ "bttv radio",
+ VID_TYPE_TUNER,
+ VID_HARDWARE_BT848,
+ radio_open,
+ radio_close,
+ radio_read, /* just returns -EINVAL */
+ bttv_write, /* just returns -EINVAL */
+ NULL, /* no poll */
+ radio_ioctl,
+ NULL, /* no mmap */
+ bttv_init_done, /* just returns 0 */
+ NULL,
+ 0,
+ -1
+};
+
+
+#define TRITON_PCON 0x50
+#define TRITON_BUS_CONCURRENCY (1<<0)
+#define TRITON_STREAMING (1<<1)
+#define TRITON_WRITE_BURST (1<<2)
+#define TRITON_PEER_CONCURRENCY (1<<3)
+
+
+static void __devinit handle_chipset(void)
+{
+ struct pci_dev *dev = NULL;
+
+ /* Just in case some nut set this to something dangerous */
+ if (triton1)
+ triton1=BT848_INT_ETBF;
+
+ while ((dev = pci_find_device(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_496, dev)))
+ {
+ /* Beware the SiS 85C496 my friend - rev 49 don't work with a bttv */
+ printk(KERN_WARNING "BT848 and SIS 85C496 chipset don't always work together.\n");
+ }
+
+ while ((dev = pci_find_device(PCI_VENDOR_ID_INTEL,
+ PCI_DEVICE_ID_INTEL_82441, dev)))
+ {
+ unsigned char b;
+ pci_read_config_byte(dev, 0x53, &b);
+ DEBUG(printk(KERN_INFO "bttv: Host bridge: 82441FX Natoma, "));
+ DEBUG(printk("bufcon=0x%02x\n",b));
+ }
+
+ while ((dev = pci_find_device(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82437, dev)))
+ {
+ printk(KERN_INFO "bttv: Host bridge 82437FX Triton PIIX\n");
+ triton1=BT848_INT_ETBF;
+ }
+}
+
+
+static void bt848_set_risc_jmps(struct bttv *btv, int flags)
+{
+ if (-1 == flags) {
+ /* defaults */
+ flags = 0;
+ if (btv->scr_on)
+ flags |= 0x03;
+ if (btv->vbi_on)
+ flags |= 0x0c;
+ }
+
+ if (bttv_debug > 1)
+ printk("bttv%d: set_risc_jmp %08lx:",
+ btv->nr,virt_to_bus(btv->risc_jmp));
+
+ /* Sync to start of odd field */
+ btv->risc_jmp[0]=cpu_to_le32(BT848_RISC_SYNC|BT848_RISC_RESYNC
+ |BT848_FIFO_STATUS_VRE);
+ btv->risc_jmp[1]=cpu_to_le32(0);
+
+ /* Jump to odd vbi sub */
+ btv->risc_jmp[2]=cpu_to_le32(BT848_RISC_JUMP|(0xd<<20));
+ if (flags&8) {
+ if (bttv_debug > 1)
+ printk(" ev=%08lx",virt_to_bus(btv->vbi_odd));
+ btv->risc_jmp[3]=cpu_to_le32(virt_to_bus(btv->vbi_odd));
+ } else {
+ if (bttv_debug > 1)
+ printk(" -----------");
+ btv->risc_jmp[3]=cpu_to_le32(virt_to_bus(btv->risc_jmp+4));
+ }
+
+ /* Jump to odd sub */
+ btv->risc_jmp[4]=cpu_to_le32(BT848_RISC_JUMP|(0xe<<20));
+ if (0 != btv->risc_cap_odd) {
+ if (bttv_debug > 1)
+ printk(" e%d=%08x",btv->gq_grab,btv->risc_cap_odd);
+ flags |= 3;
+ btv->risc_jmp[5]=cpu_to_le32(btv->risc_cap_odd);
+ } else if (flags&2) {
+ if (bttv_debug > 1)
+ printk(" eo=%08lx",virt_to_bus(btv->risc_scr_odd));
+ btv->risc_jmp[5]=cpu_to_le32(virt_to_bus(btv->risc_scr_odd));
+ } else {
+ if (bttv_debug > 1)
+ printk(" -----------");
+ btv->risc_jmp[5]=cpu_to_le32(virt_to_bus(btv->risc_jmp+6));
+ }
+
+
+ /* Sync to start of even field */
+ btv->risc_jmp[6]=cpu_to_le32(BT848_RISC_SYNC|BT848_RISC_RESYNC
+ |BT848_FIFO_STATUS_VRO);
+ btv->risc_jmp[7]=cpu_to_le32(0);
+
+ /* Jump to even vbi sub */
+ btv->risc_jmp[8]=cpu_to_le32(BT848_RISC_JUMP);
+ if (flags&4) {
+ if (bttv_debug > 1)
+ printk(" ov=%08lx",virt_to_bus(btv->vbi_even));
+ btv->risc_jmp[9]=cpu_to_le32(virt_to_bus(btv->vbi_even));
+ } else {
+ if (bttv_debug > 1)
+ printk(" -----------");
+ btv->risc_jmp[9]=cpu_to_le32(virt_to_bus(btv->risc_jmp+10));
+ }
+
+ /* Jump to even sub */
+ btv->risc_jmp[10]=cpu_to_le32(BT848_RISC_JUMP|(8<<20));
+ if (0 != btv->risc_cap_even) {
+ if (bttv_debug > 1)
+ printk(" o%d=%08x",btv->gq_grab,btv->risc_cap_even);
+ flags |= 3;
+ btv->risc_jmp[11]=cpu_to_le32(btv->risc_cap_even);
+ } else if (flags&1) {
+ if (bttv_debug > 1)
+ printk(" oo=%08lx",virt_to_bus(btv->risc_scr_even));
+ btv->risc_jmp[11]=cpu_to_le32(virt_to_bus(btv->risc_scr_even));
+ } else {
+ if (bttv_debug > 1)
+ printk(" -----------");
+ btv->risc_jmp[11]=cpu_to_le32(virt_to_bus(btv->risc_jmp+12));
+ }
+
+ if (btv->gq_start) {
+ btv->risc_jmp[12]=cpu_to_le32(BT848_RISC_JUMP|(0x8<<16)|BT848_RISC_IRQ);
+ } else {
+ btv->risc_jmp[12]=cpu_to_le32(BT848_RISC_JUMP);
+ }
+ btv->risc_jmp[13]=cpu_to_le32(virt_to_bus(btv->risc_jmp));
+
+ /* enable cpaturing and DMA */
+ if (bttv_debug > 1)
+ printk(" flags=0x%x dma=%s\n",
+ flags,(flags&0x0f) ? "on" : "off");
+ btaor(flags, ~0x0f, BT848_CAP_CTL);
+ if (flags&0x0f)
+ bt848_dma(btv, 3);
+ else
+ bt848_dma(btv, 0);
+}
+
+static int __devinit init_video_dev(struct bttv *btv)
+{
+ memcpy(&btv->video_dev,&bttv_template, sizeof(bttv_template));
+ memcpy(&btv->vbi_dev,&vbi_template, sizeof(vbi_template));
+ memcpy(&btv->radio_dev,&radio_template,sizeof(radio_template));
+
+ bttv_idcard(btv);
+ audio(btv, AUDIO_MUTE, 1);
+
+ if(video_register_device(&btv->video_dev,VFL_TYPE_GRABBER)<0)
+ return -1;
+ if(video_register_device(&btv->vbi_dev,VFL_TYPE_VBI)<0)
+ {
+ video_unregister_device(&btv->video_dev);
+ return -1;
+ }
+ if (radio[btv->nr])
+ {
+ if(video_register_device(&btv->radio_dev, VFL_TYPE_RADIO)<0)
+ {
+ video_unregister_device(&btv->vbi_dev);
+ video_unregister_device(&btv->video_dev);
+ return -1;
+ }
+ }
+ return 1;
+}
+
+static int __devinit init_bt848(struct bttv *btv)
+{
+ int j;
+ unsigned long irq_flags;
+
+ btv->user=0;
+ init_MUTEX(&btv->lock);
+
+ /* dump current state of the gpio registers before changing them,
+ * might help to make a new card work */
+ if (bttv_verbose >= 2)
+ printk("bttv%d: gpio: out_enable=0x%x, data=0x%x, in=0x%x\n",
+ btv->nr,
+ btread(BT848_GPIO_OUT_EN),
+ btread(BT848_GPIO_DATA),
+ btread(BT848_GPIO_REG_INP));
+
+ /* reset the bt848 */
+ btwrite(0, BT848_SRESET);
+ DEBUG(printk(KERN_DEBUG "bttv%d: bt848_mem: 0x%lx\n", btv->nr, (unsigned long) btv->bt848_mem));
+
+ /* not registered yet */
+ btv->video_dev.minor = -1;
+ btv->radio_dev.minor = -1;
+ btv->vbi_dev.minor = -1;
+
+ /* default setup for max. PAL size in a 1024xXXX hicolor framebuffer */
+ btv->win.norm=0; /* change this to 1 for NTSC, 2 for SECAM */
+ btv->win.interlace=1;
+ btv->win.x=0;
+ btv->win.y=0;
+ btv->win.width=768; /* 640 */
+ btv->win.height=576; /* 480 */
+ btv->win.bpp=2;
+ btv->win.depth=16;
+ btv->win.color_fmt=BT848_COLOR_FMT_RGB16;
+ btv->win.bpl=1024*btv->win.bpp;
+ btv->win.swidth=1024;
+ btv->win.sheight=768;
+ btv->win.vidadr=0;
+ btv->vbi_on=0;
+ btv->scr_on=0;
+
+ btv->risc_scr_odd=0;
+ btv->risc_scr_even=0;
+ btv->risc_cap_odd=0;
+ btv->risc_cap_even=0;
+ btv->risc_jmp=0;
+ btv->vbibuf=0;
+ btv->field=btv->last_field=0;
+
+ btv->errors=0;
+ btv->needs_restart=0;
+
+ /* i2c */
+ btv->tuner_type=-1;
+ init_bttv_i2c(btv);
+
+ if (!(btv->risc_scr_odd=(unsigned int *) kmalloc(RISCMEM_LEN/2, GFP_KERNEL)))
+ return -1;
+ if (!(btv->risc_scr_even=(unsigned int *) kmalloc(RISCMEM_LEN/2, GFP_KERNEL)))
+ return -1;
+ if (!(btv->risc_jmp =(unsigned int *) kmalloc(2048, GFP_KERNEL)))
+ return -1;
+ btv->vbi_odd=btv->risc_jmp+16;
+ btv->vbi_even=btv->vbi_odd+256;
+ btv->bus_vbi_odd=virt_to_bus(btv->risc_jmp+12);
+ btv->bus_vbi_even=virt_to_bus(btv->risc_jmp+6);
+
+ btwrite(virt_to_bus(btv->risc_jmp+2), BT848_RISC_STRT_ADD);
+ btv->vbibuf=(unsigned char *) vmalloc_32(VBIBUF_SIZE);
+ if (!btv->vbibuf)
+ return -1;
+ if (!(btv->gbuf = kmalloc(sizeof(struct bttv_gbuf)*gbuffers,GFP_KERNEL)))
+ return -1;
+ for (j = 0; j < gbuffers; j++) {
+ if (!(btv->gbuf[j].risc = kmalloc(16384,GFP_KERNEL)))
+ return -1;
+ }
+
+ memset(btv->vbibuf, 0, VBIBUF_SIZE); /* We don't want to return random
+ memory to the user */
+
+ btv->fbuffer=NULL;
+
+ bt848_muxsel(btv, 1);
+ bt848_set_winsize(btv);
+
+/* btwrite(0, BT848_TDEC); */
+ btwrite(0x10, BT848_COLOR_CTL);
+ btwrite(0x00, BT848_CAP_CTL);
+ /* set planar and packed mode trigger points and */
+ /* set rising edge of inverted GPINTR pin as irq trigger */
+ btwrite(BT848_GPIO_DMA_CTL_PKTP_32|
+ BT848_GPIO_DMA_CTL_PLTP1_16|
+ BT848_GPIO_DMA_CTL_PLTP23_16|
+ BT848_GPIO_DMA_CTL_GPINTC|
+ BT848_GPIO_DMA_CTL_GPINTI,
+ BT848_GPIO_DMA_CTL);
+
+ /* select direct input */
+ btwrite(0x00, BT848_GPIO_REG_INP);
+
+ btwrite(BT848_IFORM_MUX1 | BT848_IFORM_XTAUTO | BT848_IFORM_AUTO,
+ BT848_IFORM);
+
+ btwrite(0xd8, BT848_CONTRAST_LO);
+ bt848_bright(btv, 0x10);
+
+ btwrite(0x20, BT848_E_VSCALE_HI);
+ btwrite(0x20, BT848_O_VSCALE_HI);
+ btwrite(/*BT848_ADC_SYNC_T|*/
+ BT848_ADC_RESERVED|BT848_ADC_CRUSH, BT848_ADC);
+
+ btwrite(BT848_CONTROL_LDEC, BT848_E_CONTROL);
+ btwrite(BT848_CONTROL_LDEC, BT848_O_CONTROL);
+
+ btv->picture.colour=254<<7;
+ btv->picture.brightness=128<<8;
+ btv->picture.hue=128<<8;
+ btv->picture.contrast=0xd8<<7;
+
+ btwrite(0x00, BT848_E_SCLOOP);
+ btwrite(0x00, BT848_O_SCLOOP);
+
+ /* clear interrupt status */
+ btwrite(0xfffffUL, BT848_INT_STAT);
+
+ /* set interrupt mask */
+ btwrite(btv->triton1|
+ /*BT848_INT_PABORT|BT848_INT_RIPERR|BT848_INT_PPERR|
+ BT848_INT_FDSR|BT848_INT_FTRGT|BT848_INT_FBUS|*/
+ (fieldnr ? BT848_INT_VSYNC : 0)|
+ BT848_INT_GPINT|
+ BT848_INT_SCERR|
+ BT848_INT_RISCI|BT848_INT_OCERR|BT848_INT_VPRES|
+ BT848_INT_FMTCHG|BT848_INT_HLOCK,
+ BT848_INT_MASK);
+
+ make_vbitab(btv);
+ spin_lock_irqsave(&btv->s_lock, irq_flags);
+ bt848_set_risc_jmps(btv,-1);
+ spin_unlock_irqrestore(&btv->s_lock, irq_flags);
+
+ /*
+ * Now add the template and register the device unit.
+ */
+ init_video_dev(btv);
+
+ return 0;
+}
+
+static void bttv_irq(int irq, void *dev_id, struct pt_regs * regs)
+{
+ u32 stat,astat;
+ u32 dstat;
+ int count,i;
+ struct bttv *btv;
+
+ btv=(struct bttv *)dev_id;
+ count=0;
+ while (1)
+ {
+ /* get/clear interrupt status bits */
+ stat=btread(BT848_INT_STAT);
+ astat=stat&btread(BT848_INT_MASK);
+ if (!astat)
+ return;
+ btwrite(astat,BT848_INT_STAT);
+ IDEBUG(printk ("bttv%d: astat=%08x\n", btv->nr, astat));
+ IDEBUG(printk ("bttv%d: stat=%08x\n", btv->nr, stat));
+
+ /* get device status bits */
+ dstat=btread(BT848_DSTATUS);
+
+ if (astat&BT848_INT_GPINT) {
+ IDEBUG(printk ("bttv%d: IRQ_GPINT\n", btv->nr));
+ wake_up_interruptible(&btv->gpioq);
+ }
+
+ if (astat&BT848_INT_FMTCHG)
+ {
+ IDEBUG(printk ("bttv%d: IRQ_FMTCHG\n", btv->nr));
+ /*btv->win.norm&=
+ (dstat&BT848_DSTATUS_NUML) ? (~1) : (~0); */
+ }
+ if (astat&BT848_INT_VPRES)
+ {
+ IDEBUG(printk ("bttv%d: IRQ_VPRES\n", btv->nr));
+ }
+ if (astat&BT848_INT_VSYNC)
+ {
+ IDEBUG(printk ("bttv%d: IRQ_VSYNC\n", btv->nr));
+ btv->field++;
+ }
+ if (astat&(BT848_INT_SCERR|BT848_INT_OCERR)) {
+ if (bttv_verbose)
+ printk("bttv%d: irq:%s%s risc_count=%08x\n",
+ btv->nr,
+ (astat&BT848_INT_SCERR) ? " SCERR" : "",
+ (astat&BT848_INT_OCERR) ? " OCERR" : "",
+ btread(BT848_RISC_COUNT));
+ btv->errors++;
+ if (btv->errors < BTTV_ERRORS) {
+ spin_lock(&btv->s_lock);
+ btand(~15, BT848_GPIO_DMA_CTL);
+ btwrite(virt_to_bus(btv->risc_jmp+2),
+ BT848_RISC_STRT_ADD);
+ bt848_set_geo(btv,0);
+ bt848_set_risc_jmps(btv,-1);
+ spin_unlock(&btv->s_lock);
+ } else {
+ if (bttv_verbose)
+ printk("bttv%d: aiee: error loops\n",btv->nr);
+ /* cancel all outstanding grab requests */
+ spin_lock(&btv->s_lock);
+ btv->gq_in = 0;
+ btv->gq_out = 0;
+ btv->gq_grab = -1;
+ for (i = 0; i < gbuffers; i++)
+ if (btv->gbuf[i].stat == GBUFFER_GRABBING)
+ btv->gbuf[i].stat = GBUFFER_ERROR;
+ /* disable DMA */
+ btv->risc_cap_odd = 0;
+ btv->risc_cap_even = 0;
+ bt848_set_risc_jmps(btv,0);
+ btv->needs_restart = 1;
+ spin_unlock(&btv->s_lock);
+
+ wake_up_interruptible(&btv->vbiq);
+ wake_up_interruptible(&btv->capq);
+ }
+ }
+ if (astat&BT848_INT_RISCI)
+ {
+ if (bttv_debug > 1)
+ printk("bttv%d: IRQ_RISCI\n",btv->nr);
+
+ /* captured VBI frame */
+ if (stat&(1<<28))
+ {
+ btv->vbip=0;
+ /* inc vbi frame count for detecting drops */
+ (*(u32 *)&(btv->vbibuf[VBIBUF_SIZE - 4]))++;
+ wake_up_interruptible(&btv->vbiq);
+ }
+
+ /* captured full frame */
+ if (stat&(2<<28) && btv->gq_grab != -1)
+ {
+ btv->last_field=btv->field;
+ if (bttv_debug)
+ printk("bttv%d: cap irq: done %d\n",btv->nr,btv->gq_grab);
+ do_gettimeofday(&btv->gbuf[btv->gq_grab].tv);
+ spin_lock(&btv->s_lock);
+ btv->gbuf[btv->gq_grab].stat = GBUFFER_DONE;
+ btv->gq_grab = -1;
+ if (btv->gq_in != btv->gq_out)
+ {
+ btv->gq_grab = btv->gqueue[btv->gq_out++];
+ btv->gq_out = btv->gq_out % MAX_GBUFFERS;
+ if (bttv_debug)
+ printk("bttv%d: cap irq: capture %d\n",btv->nr,btv->gq_grab);
+ btv->risc_cap_odd = btv->gbuf[btv->gq_grab].ro;
+ btv->risc_cap_even = btv->gbuf[btv->gq_grab].re;
+ bt848_set_risc_jmps(btv,-1);
+ bt848_set_geo(btv,0);
+ btwrite(BT848_COLOR_CTL_GAMMA,
+ BT848_COLOR_CTL);
+ } else {
+ btv->risc_cap_odd = 0;
+ btv->risc_cap_even = 0;
+ bt848_set_risc_jmps(btv,-1);
+ bt848_set_geo(btv,0);
+ btwrite(btv->fb_color_ctl | BT848_COLOR_CTL_GAMMA,
+ BT848_COLOR_CTL);
+ }
+ spin_unlock(&btv->s_lock);
+ wake_up_interruptible(&btv->capq);
+ break;
+ }
+ if (stat&(8<<28))
+ {
+ spin_lock(&btv->s_lock);
+ btv->gq_start = 0;
+ btv->gq_grab = btv->gqueue[btv->gq_out++];
+ btv->gq_out = btv->gq_out % MAX_GBUFFERS;
+ if (bttv_debug)
+ printk("bttv%d: cap irq: capture %d [start]\n",btv->nr,btv->gq_grab);
+ btv->risc_cap_odd = btv->gbuf[btv->gq_grab].ro;
+ btv->risc_cap_even = btv->gbuf[btv->gq_grab].re;
+ bt848_set_risc_jmps(btv,-1);
+ bt848_set_geo(btv,0);
+ btwrite(BT848_COLOR_CTL_GAMMA,
+ BT848_COLOR_CTL);
+ spin_unlock(&btv->s_lock);
+ }
+ }
+ if (astat&BT848_INT_OCERR)
+ {
+ IDEBUG(printk ("bttv%d: IRQ_OCERR\n", btv->nr));
+ }
+ if (astat&BT848_INT_PABORT)
+ {
+ IDEBUG(printk ("bttv%d: IRQ_PABORT\n", btv->nr));
+ }
+ if (astat&BT848_INT_RIPERR)
+ {
+ IDEBUG(printk ("bttv%d: IRQ_RIPERR\n", btv->nr));
+ }
+ if (astat&BT848_INT_PPERR)
+ {
+ IDEBUG(printk ("bttv%d: IRQ_PPERR\n", btv->nr));
+ }
+ if (astat&BT848_INT_FDSR)
+ {
+ IDEBUG(printk ("bttv%d: IRQ_FDSR\n", btv->nr));
+ }
+ if (astat&BT848_INT_FTRGT)
+ {
+ IDEBUG(printk ("bttv%d: IRQ_FTRGT\n", btv->nr));
+ }
+ if (astat&BT848_INT_FBUS)
+ {
+ IDEBUG(printk ("bttv%d: IRQ_FBUS\n", btv->nr));
+ }
+ if (astat&BT848_INT_HLOCK)
+ {
+ if ((dstat&BT848_DSTATUS_HLOC) || (btv->radio))
+ audio(btv, AUDIO_ON,0);
+ else
+ audio(btv, AUDIO_OFF,0);
+ }
+
+ if (astat&BT848_INT_I2CDONE)
+ {
+ IDEBUG(printk ("bttv%d: IRQ_I2CDONE\n", btv->nr));
+ }
+ count++;
+ if (count > 10)
+ printk (KERN_WARNING "bttv%d: irq loop %d\n",
+ btv->nr,count);
+ if (count > 20)
+ {
+ btwrite(0, BT848_INT_MASK);
+ printk(KERN_ERR
+ "bttv%d: IRQ lockup, cleared int mask\n", btv->nr);
+ }
+ }
+}
+
+
+
+/*
+ * Scan for a Bt848 card, request the irq and map the io memory
+ */
+
+static void __devinit bttv_remove(struct pci_dev *pci_dev)
+{
+ u8 command;
+ int j;
+ struct bttv *btv = PCI_GET_DRIVER_DATA(pci_dev);
+
+ /* unregister i2c_bus */
+ if (0 == btv->i2c_ok)
+ i2c_bit_del_bus(&btv->i2c_adap);
+
+ /* turn off all capturing, DMA and IRQs */
+ btand(~15, BT848_GPIO_DMA_CTL);
+
+ /* first disable interrupts before unmapping the memory! */
+ btwrite(0, BT848_INT_MASK);
+ btwrite(~0x0UL,BT848_INT_STAT);
+ btwrite(0x0, BT848_GPIO_OUT_EN);
+
+ /* disable PCI bus-mastering */
+ pci_read_config_byte(btv->dev, PCI_COMMAND, &command);
+ /* Should this be &=~ ?? */
+ command&=~PCI_COMMAND_MASTER;
+ pci_write_config_byte(btv->dev, PCI_COMMAND, command);
+
+ /* unmap and free memory */
+ for (j = 0; j < gbuffers; j++)
+ if (btv->gbuf[j].risc)
+ kfree(btv->gbuf[j].risc);
+ if (btv->gbuf)
+ kfree((void *) btv->gbuf);
+
+ if (btv->risc_scr_odd)
+ kfree((void *) btv->risc_scr_odd);
+
+ if (btv->risc_scr_even)
+ kfree((void *) btv->risc_scr_even);
+
+ DEBUG(printk(KERN_DEBUG "free: risc_jmp: 0x%p.\n", btv->risc_jmp));
+ if (btv->risc_jmp)
+ kfree((void *) btv->risc_jmp);
+
+ DEBUG(printk(KERN_DEBUG "bt848_vbibuf: 0x%p.\n", btv->vbibuf));
+ if (btv->vbibuf)
+ vfree((void *) btv->vbibuf);
+
+ free_irq(btv->irq,btv);
+ DEBUG(printk(KERN_DEBUG "bt848_mem: 0x%p.\n", btv->bt848_mem));
+ if (btv->bt848_mem)
+ iounmap(btv->bt848_mem);
+
+ if(btv->video_dev.minor!=-1)
+ video_unregister_device(&btv->video_dev);
+ if(btv->vbi_dev.minor!=-1)
+ video_unregister_device(&btv->vbi_dev);
+ if (radio[btv->nr] && btv->radio_dev.minor != -1)
+ video_unregister_device(&btv->radio_dev);
+
+ release_mem_region(btv->bt848_adr,
+ pci_resource_len(btv->dev,0));
+ /* wake up any waiting processes
+ because shutdown flag is set, no new processes (in this queue)
+ are expected
+ */
+ btv->shutdown=1;
+ wake_up(&btv->gpioq);
+
+ return;
+}
+
+
+static int __devinit bttv_probe(struct pci_dev *dev, const struct pci_device_id *pci_id)
+{
+ int result;
+ unsigned char command;
+ struct bttv *btv;
+#if defined(__powerpc__)
+ unsigned int cmd;
+#endif
+
+ printk(KERN_INFO "bttv: Bt8xx card found (%d).\n", bttv_num);
+
+ btv=&bttvs[bttv_num];
+ btv->dev=dev;
+ btv->nr = bttv_num;
+ btv->bt848_mem=NULL;
+ btv->vbibuf=NULL;
+ btv->risc_jmp=NULL;
+ btv->vbi_odd=NULL;
+ btv->vbi_even=NULL;
+ init_waitqueue_head(&btv->vbiq);
+ init_waitqueue_head(&btv->capq);
+ btv->vbip=VBIBUF_SIZE;
+ btv->s_lock = SPIN_LOCK_UNLOCKED;
+ init_waitqueue_head(&btv->gpioq);
+ btv->shutdown=0;
+
+ btv->id=dev->device;
+ btv->irq=dev->irq;
+ btv->bt848_adr=pci_resource_start(dev,0);
+ if (pci_enable_device(dev))
+ return -EIO;
+ if (!request_mem_region(pci_resource_start(dev,0),
+ pci_resource_len(dev,0),
+ "bttv")) {
+ return -EBUSY;
+ }
+ if (btv->id >= 878)
+ btv->i2c_command = 0x83;
+ else
+ btv->i2c_command=(I2C_TIMING | BT848_I2C_SCL | BT848_I2C_SDA);
+
+ pci_read_config_byte(dev, PCI_CLASS_REVISION, &btv->revision);
+ printk(KERN_INFO "bttv%d: Brooktree Bt%d (rev %d) ",
+ bttv_num,btv->id, btv->revision);
+ printk("bus: %d, devfn: %d, ",dev->bus->number, dev->devfn);
+ printk("irq: %d, ",btv->irq);
+ printk("memory: 0x%lx.\n", btv->bt848_adr);
+
+#if defined(__powerpc__)
+ /* on OpenFirmware machines (PowerMac at least), PCI memory cycle */
+ /* response on cards with no firmware is not enabled by OF */
+ pci_read_config_dword(dev, PCI_COMMAND, &cmd);
+ cmd = (cmd | PCI_COMMAND_MEMORY );
+ pci_write_config_dword(dev, PCI_COMMAND, cmd);
+#endif
+
+#ifdef __sparc__
+ btv->bt848_mem=(unsigned char *)btv->bt848_adr;
+#else
+ btv->bt848_mem=ioremap(btv->bt848_adr, 0x1000);
+#endif
+
+ /* clear interrupt mask */
+ btwrite(0, BT848_INT_MASK);
+
+ result = request_irq(btv->irq, bttv_irq,
+ SA_SHIRQ | SA_INTERRUPT,"bttv",(void *)btv);
+ if (result==-EINVAL)
+ {
+ printk(KERN_ERR "bttv%d: Bad irq number or handler\n",
+ bttv_num);
+ goto fail;
+ }
+ if (result==-EBUSY)
+ {
+ printk(KERN_ERR "bttv%d: IRQ %d busy, change your PnP config in BIOS\n",bttv_num,btv->irq);
+ goto fail;
+ }
+ if (result < 0)
+ goto fail;
+
+ pci_set_master(dev);
+
+ btv->triton1=triton1 ? BT848_INT_ETBF : 0;
+ if (triton1 && btv->id >= 878)
+ {
+ btv->triton1 = 0;
+ printk("bttv: Enabling 430FX compatibilty for bt878\n");
+ pci_read_config_byte(dev, BT878_DEVCTRL, &command);
+ command|=BT878_EN_TBFX;
+ pci_write_config_byte(dev, BT878_DEVCTRL, command);
+ pci_read_config_byte(dev, BT878_DEVCTRL, &command);
+ if (!(command&BT878_EN_TBFX))
+ {
+ printk("bttv: 430FX compatibility could not be enabled\n");
+ free_irq(btv->irq,btv);
+ result = -1;
+ goto fail;
+ }
+ }
+
+ PCI_SET_DRIVER_DATA(dev,btv);
+
+ if(init_bt848(btv) < 0) {
+ bttv_remove(dev);
+ return -EIO;
+ }
+ bttv_num++;
+
+ return 0;
+
+ fail:
+ release_mem_region(pci_resource_start(btv->dev,0),
+ pci_resource_len(btv->dev,0));
+ return result;
+}
+
+static struct pci_device_id bttv_pci_tbl[] __devinitdata = {
+ {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT848,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+ {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT849,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+ {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT878,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+ {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT879,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+ {0,}
+};
+
+MODULE_DEVICE_TABLE(pci, bttv_pci_tbl);
+
+static struct pci_driver bttv_pci_driver = {
+ name: "bttv",
+ id_table: bttv_pci_tbl,
+ probe: bttv_probe,
+ remove: bttv_remove,
+};
+
+int bttv_init_module(void)
+{
+ bttv_num = 0;
+
+ printk(KERN_INFO "bttv: driver version %d.%d.%d loaded\n",
+ (BTTV_VERSION_CODE >> 16) & 0xff,
+ (BTTV_VERSION_CODE >> 8) & 0xff,
+ BTTV_VERSION_CODE & 0xff);
+ if (gbuffers < 2 || gbuffers > MAX_GBUFFERS)
+ gbuffers = 2;
+ if (gbufsize < 0 || gbufsize > BTTV_MAX_FBUF)
+ gbufsize = BTTV_MAX_FBUF;
+ if (bttv_verbose)
+ printk(KERN_INFO "bttv: using %d buffers with %dk (%dk total) for capture\n",
+ gbuffers,gbufsize/1024,gbuffers*gbufsize/1024);
+
+ handle_chipset();
+
+ return pci_module_init(&bttv_pci_driver);
+}
+
+void bttv_cleanup_module(void)
+{
+ pci_unregister_driver(&bttv_pci_driver);
+ return;
+}
+
+module_init(bttv_init_module);
+module_exit(bttv_cleanup_module);
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/bttv-if.c b/drivers/media/video/bttv-if.c
new file mode 100644
index 000000000..a58e441db
--- /dev/null
+++ b/drivers/media/video/bttv-if.c
@@ -0,0 +1,337 @@
+/*
+ bttv-if.c -- interfaces to other kernel modules
+ all the i2c code is here
+ also the gpio interface exported by bttv (used by lirc)
+
+
+ bttv - Bt848 frame grabber driver
+
+ Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
+ & Marcus Metzler (mocm@thp.uni-koeln.de)
+ (c) 1999,2000 Gerd Knorr <kraxel@goldbach.in-berlin.de>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#define __NO_VERSION__ 1
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+
+#include "bttv.h"
+#include "tuner.h"
+
+
+EXPORT_SYMBOL(bttv_get_cardinfo);
+EXPORT_SYMBOL(bttv_get_id);
+EXPORT_SYMBOL(bttv_gpio_enable);
+EXPORT_SYMBOL(bttv_read_gpio);
+EXPORT_SYMBOL(bttv_write_gpio);
+EXPORT_SYMBOL(bttv_get_gpio_queue);
+
+/* ----------------------------------------------------------------------- */
+/* Exported functions - for other modules which want to access the */
+/* gpio ports (IR for example) */
+/* see bttv.h for comments */
+
+int bttv_get_cardinfo(unsigned int card, int *type, int *cardid)
+{
+ if (card >= bttv_num) {
+ return -1;
+ }
+ *type = bttvs[card].type;
+ *cardid = bttvs[card].cardid;
+ return 0;
+}
+
+int bttv_get_id(unsigned int card)
+{
+ printk("bttv_get_id is obsolete, use bttv_get_cardinfo instead\n");
+ if (card >= bttv_num) {
+ return -1;
+ }
+ return bttvs[card].type;
+}
+
+int bttv_gpio_enable(unsigned int card, unsigned long mask, unsigned long data)
+{
+ struct bttv *btv;
+
+ if (card >= bttv_num) {
+ return -EINVAL;
+ }
+
+ btv = &bttvs[card];
+ btaor(data, ~mask, BT848_GPIO_OUT_EN);
+ return 0;
+}
+
+int bttv_read_gpio(unsigned int card, unsigned long *data)
+{
+ struct bttv *btv;
+
+ if (card >= bttv_num) {
+ return -EINVAL;
+ }
+
+ btv = &bttvs[card];
+
+ if(btv->shutdown) {
+ return -ENODEV;
+ }
+
+/* prior setting BT848_GPIO_REG_INP is (probably) not needed
+ because we set direct input on init */
+ *data = btread(BT848_GPIO_DATA);
+ return 0;
+}
+
+int bttv_write_gpio(unsigned int card, unsigned long mask, unsigned long data)
+{
+ struct bttv *btv;
+
+ if (card >= bttv_num) {
+ return -EINVAL;
+ }
+
+ btv = &bttvs[card];
+
+/* prior setting BT848_GPIO_REG_INP is (probably) not needed
+ because direct input is set on init */
+ btaor(data & mask, ~mask, BT848_GPIO_DATA);
+ return 0;
+}
+
+wait_queue_head_t* bttv_get_gpio_queue(unsigned int card)
+{
+ struct bttv *btv;
+
+ if (card >= bttv_num) {
+ return NULL;
+ }
+
+ btv = &bttvs[card];
+ if (bttvs[card].shutdown) {
+ return NULL;
+ }
+ return &btv->gpioq;
+}
+
+
+/* ----------------------------------------------------------------------- */
+/* I2C functions */
+
+void bttv_bit_setscl(void *data, int state)
+{
+ struct bttv *btv = (struct bttv*)data;
+
+ if (state)
+ btv->i2c_state |= 0x02;
+ else
+ btv->i2c_state &= ~0x02;
+ btwrite(btv->i2c_state, BT848_I2C);
+ btread(BT848_I2C);
+}
+
+void bttv_bit_setsda(void *data, int state)
+{
+ struct bttv *btv = (struct bttv*)data;
+
+ if (state)
+ btv->i2c_state |= 0x01;
+ else
+ btv->i2c_state &= ~0x01;
+ btwrite(btv->i2c_state, BT848_I2C);
+ btread(BT848_I2C);
+}
+
+static int bttv_bit_getscl(void *data)
+{
+ struct bttv *btv = (struct bttv*)data;
+ int state;
+
+ state = btread(BT848_I2C) & 0x02 ? 1 : 0;
+ return state;
+}
+
+static int bttv_bit_getsda(void *data)
+{
+ struct bttv *btv = (struct bttv*)data;
+ int state;
+
+ state = btread(BT848_I2C) & 0x01;
+ return state;
+}
+
+static void bttv_inc_use(struct i2c_adapter *adap)
+{
+ MOD_INC_USE_COUNT;
+}
+
+static void bttv_dec_use(struct i2c_adapter *adap)
+{
+ MOD_DEC_USE_COUNT;
+}
+
+static int attach_inform(struct i2c_client *client)
+{
+ struct bttv *btv = (struct bttv*)client->adapter->data;
+ int i;
+
+ for (i = 0; i < I2C_CLIENTS_MAX; i++) {
+ if (btv->i2c_clients[i] == NULL ||
+ btv->i2c_clients[i]->driver->id == client->driver->id) {
+ btv->i2c_clients[i] = client;
+ break;
+ }
+ }
+ if (btv->tuner_type != -1)
+ bttv_call_i2c_clients(btv,TUNER_SET_TYPE,&btv->tuner_type);
+ if (bttv_verbose)
+ printk("bttv%d: i2c attach [%s]\n",btv->nr,client->name);
+ return 0;
+}
+
+static int detach_inform(struct i2c_client *client)
+{
+ struct bttv *btv = (struct bttv*)client->adapter->data;
+ int i;
+
+ if (bttv_verbose)
+ printk("bttv%d: i2c detach [%s]\n",btv->nr,client->name);
+ for (i = 0; i < I2C_CLIENTS_MAX; i++) {
+ if (NULL != btv->i2c_clients[i] &&
+ btv->i2c_clients[i]->driver->id == client->driver->id) {
+ btv->i2c_clients[i] = NULL;
+ break;
+ }
+ }
+ return 0;
+}
+
+void bttv_call_i2c_clients(struct bttv *btv, unsigned int cmd, void *arg)
+{
+ int i;
+
+ for (i = 0; i < I2C_CLIENTS_MAX; i++) {
+ if (NULL == btv->i2c_clients[i])
+ continue;
+ if (NULL == btv->i2c_clients[i]->driver->command)
+ continue;
+ btv->i2c_clients[i]->driver->command(
+ btv->i2c_clients[i],cmd,arg);
+ }
+}
+
+struct i2c_algo_bit_data bttv_i2c_algo_template = {
+ NULL,
+ bttv_bit_setsda,
+ bttv_bit_setscl,
+ bttv_bit_getsda,
+ bttv_bit_getscl,
+ 10, 10, 100,
+};
+
+struct i2c_adapter bttv_i2c_adap_template = {
+ "bt848",
+ I2C_HW_B_BT848,
+ NULL,
+ NULL,
+ bttv_inc_use,
+ bttv_dec_use,
+ attach_inform,
+ detach_inform,
+ NULL,
+};
+
+struct i2c_client bttv_i2c_client_template = {
+ "bttv internal",
+ -1,
+ 0,
+ 0,
+ NULL,
+ NULL
+};
+
+
+/* read I2C */
+int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for)
+{
+ unsigned char buffer = 0;
+
+ if (0 != btv->i2c_ok)
+ return -1;
+ if (bttv_verbose && NULL != probe_for)
+ printk(KERN_INFO "bttv%d: i2c: checking for %s @ 0x%02x... ",
+ btv->nr,probe_for,addr);
+ btv->i2c_client.addr = addr >> 1;
+ if (1 != i2c_master_recv(&btv->i2c_client, &buffer, 1)) {
+ if (NULL != probe_for) {
+ if (bttv_verbose)
+ printk("not found\n");
+ } else
+ printk(KERN_WARNING "bttv%d: i2c read 0x%x: error\n",
+ btv->nr,addr);
+ return -1;
+ }
+ if (bttv_verbose && NULL != probe_for)
+ printk("found\n");
+ return buffer;
+}
+
+/* write I2C */
+int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1,
+ unsigned char b2, int both)
+{
+ unsigned char buffer[2];
+ int bytes = both ? 2 : 1;
+
+ if (0 != btv->i2c_ok)
+ return -1;
+ btv->i2c_client.addr = addr >> 1;
+ buffer[0] = b1;
+ buffer[1] = b2;
+ if (bytes != i2c_master_send(&btv->i2c_client, buffer, bytes))
+ return -1;
+ return 0;
+}
+
+/* read EEPROM content */
+void __devinit bttv_readee(struct bttv *btv, unsigned char *eedata, int addr)
+{
+ int i;
+
+ if (bttv_I2CWrite(btv, addr, 0, -1, 0)<0) {
+ printk(KERN_WARNING "bttv: readee error\n");
+ return;
+ }
+ btv->i2c_client.addr = addr >> 1;
+ for (i=0; i<256; i+=16) {
+ if (16 != i2c_master_recv(&btv->i2c_client,eedata+i,16)) {
+ printk(KERN_WARNING "bttv: readee error\n");
+ break;
+ }
+ }
+}
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/bttv.h b/drivers/media/video/bttv.h
new file mode 100644
index 000000000..6b35d23e5
--- /dev/null
+++ b/drivers/media/video/bttv.h
@@ -0,0 +1,425 @@
+/*
+ bttv - Bt848 frame grabber driver
+
+ Copyright (C) 1996,97 Ralph Metzler (rjkm@thp.uni-koeln.de)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef _BTTV_H_
+#define _BTTV_H_
+
+#define BTTV_VERSION_CODE KERNEL_VERSION(0,7,38)
+
+#ifndef PCI_GET_DRIVER_DATA
+# define PCI_GET_DRIVER_DATA(pdev) ((pdev)->driver_data)
+# define PCI_SET_DRIVER_DATA(pdev,data) (((pdev)->driver_data) = (data))
+#endif /* PCI_GET_DRIVER_DATA */
+
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+
+#include "audiochip.h"
+#include "bt848.h"
+
+#ifdef __KERNEL__
+
+/* fwd decl */
+struct bttv;
+
+
+/* ---------------------------------------------------------- */
+/* exported by bttv-cards.c */
+
+#define BTTV_UNKNOWN 0x00
+#define BTTV_MIRO 0x01
+#define BTTV_HAUPPAUGE 0x02
+#define BTTV_STB 0x03
+#define BTTV_INTEL 0x04
+#define BTTV_DIAMOND 0x05
+#define BTTV_AVERMEDIA 0x06
+#define BTTV_MATRIX_VISION 0x07
+#define BTTV_FLYVIDEO 0x08
+#define BTTV_TURBOTV 0x09
+#define BTTV_HAUPPAUGE878 0x0a
+#define BTTV_MIROPRO 0x0b
+#define BTTV_ADSTECH_TV 0x0c
+#define BTTV_AVERMEDIA98 0x0d
+#define BTTV_VHX 0x0e
+#define BTTV_ZOLTRIX 0x0f
+#define BTTV_PIXVIEWPLAYTV 0x10
+#define BTTV_WINVIEW_601 0x11
+#define BTTV_AVEC_INTERCAP 0x12
+#define BTTV_LIFE_FLYKIT 0x13
+#define BTTV_CEI_RAFFLES 0x14
+#define BTTV_CONFERENCETV 0x15
+#define BTTV_PHOEBE_TVMAS 0x16
+#define BTTV_MODTEC_205 0x17
+#define BTTV_MAGICTVIEW061 0x18
+#define BTTV_VOBIS_BOOSTAR 0x19
+#define BTTV_HAUPPAUG_WCAM 0x1a
+#define BTTV_MAXI 0x1b
+#define BTTV_TERRATV 0x1c
+#define BTTV_PXC200 0x1d
+#define BTTV_FLYVIDEO_98 0x1e
+#define BTTV_IPROTV 0x1f
+#define BTTV_INTEL_C_S_PCI 0x20
+#define BTTV_TERRATVALUE 0x21
+#define BTTV_WINFAST2000 0x22
+#define BTTV_CHRONOS_VS2 0x23
+#define BTTV_TYPHOON_TVIEW 0x24
+#define BTTV_PXELVWPLTVPRO 0x25
+#define BTTV_MAGICTVIEW063 0x26
+#define BTTV_PINNACLERAVE 0x27
+#define BTTV_STB2 0x28
+#define BTTV_AVPHONE98 0x29
+#define BTTV_PV951 0x2a
+#define BTTV_ONAIR_TV 0x2b
+#define BTTV_SIGMA_TVII_FM 0x2c
+#define BTTV_MATRIX_VISION2 0x2d
+#define BTTV_ZOLTRIX_GENIE 0x2e
+#define BTTV_TERRATVRADIO 0x2f
+#define BTTV_DYNALINK 0x30
+
+struct tvcard
+{
+ char *name;
+ int video_inputs;
+ int audio_inputs;
+ int tuner;
+ int svhs;
+ u32 gpiomask;
+ u32 muxsel[8];
+ u32 audiomux[6]; /* Tuner, Radio, external, internal, mute, stereo */
+ u32 gpiomask2; /* GPIO MUX mask */
+
+ /* look for these i2c audio chips */
+ int msp34xx:1;
+ int tda8425:1;
+ int tda9840:1;
+ int tda985x:1;
+ int tea63xx:1;
+ int tea64xx:1;
+ int tda7432:1;
+ int tda9875:1;
+
+ /* other settings */
+ int pll;
+#define PLL_NONE 0
+#define PLL_28 1
+#define PLL_35 2
+
+ int tuner_type;
+};
+
+extern struct tvcard bttv_tvcards[];
+extern const int bttv_num_tvcards;
+
+/* identification / initialization of the card */
+extern void bttv_idcard(struct bttv *btv);
+
+/* card-specific funtions */
+extern void tea5757_set_freq(struct bttv *btv, unsigned short freq);
+extern void winview_setvol(struct bttv *btv, struct video_audio *v);
+
+/* ---------------------------------------------------------- */
+/* exported by bttv-if.c */
+/* interface for gpio access by other modules */
+
+/* returns card type + card ID (for bt878-based ones)
+ for possible values see lines below beginning with #define BTTV_UNKNOWN
+ returns negative value if error ocurred
+*/
+extern int bttv_get_cardinfo(unsigned int card, int *type, int *cardid);
+
+/* obsolete, use bttv_get_cardinfo instead */
+extern int bttv_get_id(unsigned int card);
+
+/* sets GPOE register (BT848_GPIO_OUT_EN) to new value:
+ data | (current_GPOE_value & ~mask)
+ returns negative value if error ocurred
+*/
+extern int bttv_gpio_enable(unsigned int card,
+ unsigned long mask, unsigned long data);
+
+/* fills data with GPDATA register contents
+ returns negative value if error ocurred
+*/
+extern int bttv_read_gpio(unsigned int card, unsigned long *data);
+
+/* sets GPDATA register to new value:
+ (data & mask) | (current_GPDATA_value & ~mask)
+ returns negative value if error ocurred
+*/
+extern int bttv_write_gpio(unsigned int card,
+ unsigned long mask, unsigned long data);
+
+/* returns pointer to task queue which can be used as parameter to
+ interruptible_sleep_on
+ in interrupt handler if BT848_INT_GPINT bit is set - this queue is activated
+ (wake_up_interruptible) and following call to the function bttv_read_gpio
+ should return new value of GPDATA,
+ returns NULL value if error ocurred or queue is not available
+ WARNING: because there is no buffer for GPIO data, one MUST
+ process data ASAP
+*/
+extern wait_queue_head_t* bttv_get_gpio_queue(unsigned int card);
+
+/* i2c */
+struct i2c_algo_bit_data bttv_i2c_algo_template;
+struct i2c_adapter bttv_i2c_adap_template;
+struct i2c_client bttv_i2c_client_template;
+void bttv_bit_setscl(void *data, int state);
+void bttv_bit_setsda(void *data, int state);
+void bttv_call_i2c_clients(struct bttv *btv, unsigned int cmd, void *arg);
+int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for);
+int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1,
+ unsigned char b2, int both);
+void bttv_readee(struct bttv *btv, unsigned char *eedata, int addr);
+
+
+/* ---------------------------------------------------------- */
+/* bttv-driver.c */
+
+/* insmod options */
+extern unsigned int bttv_verbose;
+extern unsigned int bttv_debug;
+
+/* Anybody who uses more than four? */
+#define BTTV_MAX 4
+extern int bttv_num; /* number of Bt848s in use */
+extern struct bttv bttvs[BTTV_MAX];
+
+
+#ifndef O_NONCAP
+#define O_NONCAP O_TRUNC
+#endif
+
+#define MAX_GBUFFERS 64
+#define RISCMEM_LEN (32744*2)
+#define VBI_MAXLINES 16
+#define VBIBUF_SIZE (2048*VBI_MAXLINES*2)
+
+#define BTTV_MAX_FBUF 0x208000
+#define I2C_CLIENTS_MAX 8
+
+struct bttv_window
+{
+ int x, y;
+ ushort width, height;
+ ushort bpp, bpl;
+ ushort swidth, sheight;
+ unsigned long vidadr;
+ ushort freq;
+ int norm;
+ int interlace;
+ int color_fmt;
+ ushort depth;
+};
+
+struct bttv_pll_info {
+ unsigned int pll_ifreq; /* PLL input frequency */
+ unsigned int pll_ofreq; /* PLL output frequency */
+ unsigned int pll_crystal; /* Crystal used for input */
+ unsigned int pll_current; /* Currently programmed ofreq */
+};
+
+struct bttv_gbuf {
+ int stat;
+#define GBUFFER_UNUSED 0
+#define GBUFFER_GRABBING 1
+#define GBUFFER_DONE 2
+#define GBUFFER_ERROR 3
+ struct timeval tv;
+
+ u16 width;
+ u16 height;
+ u16 fmt;
+
+ u32 *risc;
+ unsigned long ro;
+ unsigned long re;
+};
+
+struct bttv {
+ struct video_device video_dev;
+ struct video_device radio_dev;
+ struct video_device vbi_dev;
+ struct video_picture picture; /* Current picture params */
+ struct video_audio audio_dev; /* Current audio params */
+
+ spinlock_t s_lock;
+ struct semaphore lock;
+ int user;
+ int capuser;
+
+ /* i2c */
+ struct i2c_adapter i2c_adap;
+ struct i2c_algo_bit_data i2c_algo;
+ struct i2c_client i2c_client;
+ int i2c_state, i2c_ok;
+ struct i2c_client *i2c_clients[I2C_CLIENTS_MAX];
+
+ int tuner_type;
+ int channel;
+
+ unsigned int nr;
+ unsigned short id;
+ struct pci_dev *dev;
+ unsigned int irq; /* IRQ used by Bt848 card */
+ unsigned char revision;
+ unsigned long bt848_adr; /* bus address of IO mem returned by PCI BIOS */
+ unsigned char *bt848_mem; /* pointer to mapped IO memory */
+ unsigned long busriscmem;
+ u32 *riscmem;
+
+ unsigned char *vbibuf;
+ struct bttv_window win;
+ int fb_color_ctl;
+ int type; /* card type */
+ int cardid;
+ int audio; /* audio mode */
+ int audio_chip; /* set to one of the chips supported by bttv.c */
+ int radio;
+
+ u32 *risc_jmp;
+ u32 *vbi_odd;
+ u32 *vbi_even;
+ u32 bus_vbi_even;
+ u32 bus_vbi_odd;
+ wait_queue_head_t vbiq;
+ wait_queue_head_t capq;
+ int vbip;
+
+ u32 *risc_scr_odd;
+ u32 *risc_scr_even;
+ u32 risc_cap_odd;
+ u32 risc_cap_even;
+ int scr_on;
+ int vbi_on;
+ struct video_clip *cliprecs;
+
+ struct bttv_gbuf *gbuf;
+ int gqueue[MAX_GBUFFERS];
+ int gq_in,gq_out,gq_grab,gq_start;
+ char *fbuffer;
+
+ struct bttv_pll_info pll;
+ unsigned int Fsc;
+ unsigned int field;
+ unsigned int last_field; /* number of last grabbed field */
+ int i2c_command;
+ int triton1;
+
+ int errors;
+ int needs_restart;
+
+ wait_queue_head_t gpioq;
+ int shutdown;
+};
+#endif
+
+#if defined(__powerpc__) /* big-endian */
+extern __inline__ void io_st_le32(volatile unsigned *addr, unsigned val)
+{
+ __asm__ __volatile__ ("stwbrx %1,0,%2" : \
+ "=m" (*addr) : "r" (val), "r" (addr));
+ __asm__ __volatile__ ("eieio" : : : "memory");
+}
+
+#define btwrite(dat,adr) io_st_le32((unsigned *)(btv->bt848_mem+(adr)),(dat))
+#define btread(adr) ld_le32((unsigned *)(btv->bt848_mem+(adr)))
+#else
+#define btwrite(dat,adr) writel((dat), (char *) (btv->bt848_mem+(adr)))
+#define btread(adr) readl(btv->bt848_mem+(adr))
+#endif
+
+#define btand(dat,adr) btwrite((dat) & btread(adr), adr)
+#define btor(dat,adr) btwrite((dat) | btread(adr), adr)
+#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr)
+
+/* bttv ioctls */
+
+#define BTTV_READEE _IOW('v', BASE_VIDIOCPRIVATE+0, char [256])
+#define BTTV_WRITEE _IOR('v', BASE_VIDIOCPRIVATE+1, char [256])
+#define BTTV_FIELDNR _IOR('v' , BASE_VIDIOCPRIVATE+2, unsigned int)
+#define BTTV_PLLSET _IOW('v' , BASE_VIDIOCPRIVATE+3, struct bttv_pll_info)
+#define BTTV_BURST_ON _IOR('v' , BASE_VIDIOCPRIVATE+4, int)
+#define BTTV_BURST_OFF _IOR('v' , BASE_VIDIOCPRIVATE+5, int)
+#define BTTV_VERSION _IOR('v' , BASE_VIDIOCPRIVATE+6, int)
+#define BTTV_PICNR _IOR('v' , BASE_VIDIOCPRIVATE+7, int)
+#define BTTV_VBISIZE _IOR('v' , BASE_VIDIOCPRIVATE+8, int)
+
+#define AUDIO_TUNER 0x00
+#define AUDIO_RADIO 0x01
+#define AUDIO_EXTERN 0x02
+#define AUDIO_INTERN 0x03
+#define AUDIO_OFF 0x04
+#define AUDIO_ON 0x05
+#define AUDIO_MUTE 0x80
+#define AUDIO_UNMUTE 0x81
+
+#define TDA9850 0x01
+#define TDA9840 0x02
+#define TDA8425 0x03
+#define TEA6300 0x04
+
+#define I2C_TSA5522 0xc2
+#define I2C_TDA7432 0x8a
+#define I2C_TDA8425 0x82
+#define I2C_TDA9840 0x84
+#define I2C_TDA9850 0xb6 /* also used by 9855,9873 */
+#define I2C_TDA9875 0xb0
+#define I2C_HAUPEE 0xa0
+#define I2C_STBEE 0xae
+#define I2C_VHX 0xc0
+#define I2C_MSP3400 0x80
+#define I2C_TEA6300 0x80
+#define I2C_DPL3518 0x84
+
+#ifndef HAVE_TVAUDIO
+#define TDA9840_SW 0x00
+#define TDA9840_LVADJ 0x02
+#define TDA9840_STADJ 0x03
+#define TDA9840_TEST 0x04
+#endif
+
+#define PT2254_L_CHANEL 0x10
+#define PT2254_R_CHANEL 0x08
+#define PT2254_DBS_IN_2 0x400
+#define PT2254_DBS_IN_10 0x20000
+#define WINVIEW_PT2254_CLK 0x40
+#define WINVIEW_PT2254_DATA 0x20
+#define WINVIEW_PT2254_STROBE 0x80
+
+struct bttv_just_hacking {
+ int height,width; /* size */
+ unsigned int format; /* should be VIDEO_PALETTE_* */
+ long buf;
+ int len;
+};
+
+#define BTTV_JUST_HACKING _IOR('v' , BASE_VIDIOCPRIVATE+31,struct bttv_just_hacking)
+
+#endif
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/buz.c b/drivers/media/video/buz.c
new file mode 100644
index 000000000..ca3cb4f47
--- /dev/null
+++ b/drivers/media/video/buz.c
@@ -0,0 +1,3479 @@
+#define MAX_KMALLOC_MEM (512*1024)
+/*
+ buz - Iomega Buz driver version 1.0
+
+ Copyright (C) 1999 Rainer Johanni <Rainer@Johanni.de>
+
+ based on
+
+ buz.0.0.3 Copyright (C) 1998 Dave Perks <dperks@ibm.net>
+
+ and
+
+ bttv - Bt848 frame grabber driver
+
+ Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
+ & Marcus Metzler (mocm@thp.uni-koeln.de)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+#include <linux/signal.h>
+#include <asm/io.h>
+#include <asm/pgtable.h>
+#include <asm/page.h>
+#include <linux/sched.h>
+#include <asm/segment.h>
+#include <linux/types.h>
+#include <linux/wrapper.h>
+#include <linux/spinlock.h>
+#include <linux/vmalloc.h>
+
+#include <linux/videodev.h>
+
+#include <linux/version.h>
+#include <asm/uaccess.h>
+
+#include <linux/i2c-old.h>
+#include "buz.h"
+#include <linux/video_decoder.h>
+#include <linux/video_encoder.h>
+
+#define IRQ_MASK ( ZR36057_ISR_GIRQ0 | /* ZR36057_ISR_GIRQ1 | ZR36057_ISR_CodRepIRQ | */ ZR36057_ISR_JPEGRepIRQ )
+#define GPIO_MASK 0xdf
+
+/*
+
+ BUZ
+
+ GPIO0 = 1, take board out of reset
+ GPIO1 = 1, take JPEG codec out of sleep mode
+ GPIO3 = 1, deassert FRAME# to 36060
+
+
+ GIRQ0 signals a vertical sync of the video signal
+ GIRQ1 signals that ZR36060's DATERR# line is asserted.
+
+ SAA7111A
+
+ In their infinite wisdom, the Iomega engineers decided to
+ use the same input line for composite and S-Video Color,
+ although there are two entries not connected at all!
+ Through this ingenious strike, it is not possible to
+ keep two running video sources connected at the same time
+ to Composite and S-VHS input!
+
+ mode 0 - N/C
+ mode 1 - S-Video Y
+ mode 2 - noise or something I don't know
+ mode 3 - Composite and S-Video C
+ mode 4 - N/C
+ mode 5 - S-Video (gain C independently selectable of gain Y)
+ mode 6 - N/C
+ mode 7 - S-Video (gain C adapted to gain Y)
+ */
+
+#define MAJOR_VERSION 1 /* driver major version */
+#define MINOR_VERSION 0 /* driver minor version */
+
+#define BUZ_NAME "Iomega BUZ V-1.0" /* name of the driver */
+
+#define DEBUG(x) /* Debug driver */
+#define IDEBUG(x) /* Debug interrupt handler */
+#define IOCTL_DEBUG(x)
+
+
+/* The parameters for this driver */
+
+/*
+ The video mem address of the video card.
+ The driver has a little database for some videocards
+ to determine it from there. If your video card is not in there
+ you have either to give it to the driver as a parameter
+ or set in in a VIDIOCSFBUF ioctl
+ */
+
+static unsigned long vidmem = 0; /* Video memory base address */
+
+/* Special purposes only: */
+
+static int triton = 0; /* 0=no, 1=yes */
+static int natoma = 0; /* 0=no, 1=yes */
+
+/*
+ Number and size of grab buffers for Video 4 Linux
+ The vast majority of applications should not need more than 2,
+ the very popular BTTV driver actually does ONLY have 2.
+ Time sensitive applications might need more, the maximum
+ is VIDEO_MAX_FRAME (defined in <linux/videodev.h>).
+
+ The size is set so that the maximum possible request
+ can be satisfied. Decrease it, if bigphys_area alloc'd
+ memory is low. If you don't have the bigphys_area patch,
+ set it to 128 KB. Will you allow only to grab small
+ images with V4L, but that's better than nothing.
+
+ v4l_bufsize has to be given in KB !
+
+ */
+
+static int v4l_nbufs = 2;
+static int v4l_bufsize = 128; /* Everybody should be able to work with this setting */
+
+/*
+ Default input and video norm at startup of the driver.
+ */
+
+static int default_input = 0; /* 0=Composite, 1=S-VHS */
+static int default_norm = 0; /* 0=PAL, 1=NTSC */
+
+MODULE_PARM(vidmem, "i");
+MODULE_PARM(triton, "i");
+MODULE_PARM(natoma, "i");
+MODULE_PARM(v4l_nbufs, "i");
+MODULE_PARM(v4l_bufsize, "i");
+MODULE_PARM(default_input, "i");
+MODULE_PARM(default_norm, "i");
+
+/* Anybody who uses more than four? */
+#define BUZ_MAX 4
+
+static int zoran_num; /* number of Buzs in use */
+static struct zoran zoran[BUZ_MAX];
+
+/* forward references */
+
+static void v4l_fbuffer_free(struct zoran *zr);
+static void jpg_fbuffer_free(struct zoran *zr);
+static void zoran_feed_stat_com(struct zoran *zr);
+
+
+
+/*
+ * Allocate the V4L grab buffers
+ *
+ * These have to be pysically contiguous.
+ * If v4l_bufsize <= MAX_KMALLOC_MEM we use kmalloc
+ */
+
+static int v4l_fbuffer_alloc(struct zoran *zr)
+{
+ int i, off;
+ unsigned char *mem;
+
+ for (i = 0; i < v4l_nbufs; i++) {
+ if (zr->v4l_gbuf[i].fbuffer)
+ printk(KERN_WARNING "%s: v4l_fbuffer_alloc: buffer %d allready allocated ?\n", zr->name, i);
+
+ if (v4l_bufsize <= MAX_KMALLOC_MEM) {
+ /* Use kmalloc */
+
+ mem = (unsigned char *) kmalloc(v4l_bufsize, GFP_KERNEL);
+ if (mem == 0) {
+ printk(KERN_ERR "%s: kmalloc for V4L bufs failed\n", zr->name);
+ v4l_fbuffer_free(zr);
+ return -ENOBUFS;
+ }
+ zr->v4l_gbuf[i].fbuffer = mem;
+ zr->v4l_gbuf[i].fbuffer_phys = virt_to_phys(mem);
+ zr->v4l_gbuf[i].fbuffer_bus = virt_to_bus(mem);
+ for (off = 0; off < v4l_bufsize; off += PAGE_SIZE)
+ mem_map_reserve(virt_to_page(mem + off));
+ DEBUG(printk(BUZ_INFO ": V4L frame %d mem 0x%x (bus: 0x%x=%d)\n", i, mem, virt_to_bus(mem), virt_to_bus(mem)));
+ } else {
+ return -ENOBUFS;
+ }
+ }
+
+ return 0;
+}
+
+/* free the V4L grab buffers */
+static void v4l_fbuffer_free(struct zoran *zr)
+{
+ int i, off;
+ unsigned char *mem;
+
+ for (i = 0; i < v4l_nbufs; i++) {
+ if (!zr->v4l_gbuf[i].fbuffer)
+ continue;
+
+ mem = zr->v4l_gbuf[i].fbuffer;
+ for (off = 0; off < v4l_bufsize; off += PAGE_SIZE)
+ mem_map_unreserve(virt_to_page(mem + off));
+ kfree((void *) zr->v4l_gbuf[i].fbuffer);
+ zr->v4l_gbuf[i].fbuffer = NULL;
+ }
+}
+
+/*
+ * Allocate the MJPEG grab buffers.
+ *
+ * If the requested buffer size is smaller than MAX_KMALLOC_MEM,
+ * kmalloc is used to request a physically contiguous area,
+ * else we allocate the memory in framgents with get_free_page.
+ *
+ * If a Natoma chipset is present and this is a revision 1 zr36057,
+ * each MJPEG buffer needs to be physically contiguous.
+ * (RJ: This statement is from Dave Perks' original driver,
+ * I could never check it because I have a zr36067)
+ * The driver cares about this because it reduces the buffer
+ * size to MAX_KMALLOC_MEM in that case (which forces contiguous allocation).
+ *
+ * RJ: The contents grab buffers needs never be accessed in the driver.
+ * Therefore there is no need to allocate them with vmalloc in order
+ * to get a contiguous virtual memory space.
+ * I don't understand why many other drivers first allocate them with
+ * vmalloc (which uses internally also get_free_page, but delivers you
+ * virtual addresses) and then again have to make a lot of efforts
+ * to get the physical address.
+ *
+ */
+
+static int jpg_fbuffer_alloc(struct zoran *zr)
+{
+ int i, j, off, alloc_contig;
+ unsigned long mem;
+
+ /* Decide if we should alloc contiguous or fragmented memory */
+ /* This has to be identical in jpg_fbuffer_alloc and jpg_fbuffer_free */
+
+ alloc_contig = (zr->jpg_bufsize < MAX_KMALLOC_MEM);
+
+ for (i = 0; i < zr->jpg_nbufs; i++) {
+ if (zr->jpg_gbuf[i].frag_tab)
+ printk(KERN_WARNING "%s: jpg_fbuffer_alloc: buffer %d allready allocated ???\n", zr->name, i);
+
+ /* Allocate fragment table for this buffer */
+
+ mem = get_free_page(GFP_KERNEL);
+ if (mem == 0) {
+ printk(KERN_ERR "%s: jpg_fbuffer_alloc: get_free_page (frag_tab) failed for buffer %d\n", zr->name, i);
+ jpg_fbuffer_free(zr);
+ return -ENOBUFS;
+ }
+ memset((void *) mem, 0, PAGE_SIZE);
+ zr->jpg_gbuf[i].frag_tab = (u32 *) mem;
+ zr->jpg_gbuf[i].frag_tab_bus = virt_to_bus((void *) mem);
+
+ if (alloc_contig) {
+ mem = (unsigned long) kmalloc(zr->jpg_bufsize, GFP_KERNEL);
+ if (mem == 0) {
+ jpg_fbuffer_free(zr);
+ return -ENOBUFS;
+ }
+ zr->jpg_gbuf[i].frag_tab[0] = virt_to_bus((void *) mem);
+ zr->jpg_gbuf[i].frag_tab[1] = ((zr->jpg_bufsize / 4) << 1) | 1;
+ for (off = 0; off < zr->jpg_bufsize; off += PAGE_SIZE)
+ mem_map_reserve(virt_to_page(mem + off));
+ } else {
+ /* jpg_bufsize is alreay page aligned */
+ for (j = 0; j < zr->jpg_bufsize / PAGE_SIZE; j++) {
+ mem = get_free_page(GFP_KERNEL);
+ if (mem == 0) {
+ jpg_fbuffer_free(zr);
+ return -ENOBUFS;
+ }
+ zr->jpg_gbuf[i].frag_tab[2 * j] = virt_to_bus((void *) mem);
+ zr->jpg_gbuf[i].frag_tab[2 * j + 1] = (PAGE_SIZE / 4) << 1;
+ mem_map_reserve(virt_to_page(mem));
+ }
+
+ zr->jpg_gbuf[i].frag_tab[2 * j - 1] |= 1;
+ }
+ }
+
+ DEBUG(printk("jpg_fbuffer_alloc: %d KB allocated\n",
+ (zr->jpg_nbufs * zr->jpg_bufsize) >> 10));
+ zr->jpg_buffers_allocated = 1;
+ return 0;
+}
+
+/* free the MJPEG grab buffers */
+static void jpg_fbuffer_free(struct zoran *zr)
+{
+ int i, j, off, alloc_contig;
+ unsigned char *mem;
+
+ /* Decide if we should alloc contiguous or fragmented memory */
+ /* This has to be identical in jpg_fbuffer_alloc and jpg_fbuffer_free */
+
+ alloc_contig = (zr->jpg_bufsize < MAX_KMALLOC_MEM);
+
+ for (i = 0; i < zr->jpg_nbufs; i++) {
+ if (!zr->jpg_gbuf[i].frag_tab)
+ continue;
+
+ if (alloc_contig) {
+ if (zr->jpg_gbuf[i].frag_tab[0]) {
+ mem = (unsigned char *) bus_to_virt(zr->jpg_gbuf[i].frag_tab[0]);
+ for (off = 0; off < zr->jpg_bufsize; off += PAGE_SIZE)
+ mem_map_unreserve(virt_to_page(mem + off));
+ kfree((void *) mem);
+ zr->jpg_gbuf[i].frag_tab[0] = 0;
+ zr->jpg_gbuf[i].frag_tab[1] = 0;
+ }
+ } else {
+ for (j = 0; j < zr->jpg_bufsize / PAGE_SIZE; j++) {
+ if (!zr->jpg_gbuf[i].frag_tab[2 * j])
+ break;
+ mem_map_unreserve(virt_to_page(bus_to_virt(zr->jpg_gbuf[i].frag_tab[2 * j])));
+ free_page((unsigned long) bus_to_virt(zr->jpg_gbuf[i].frag_tab[2 * j]));
+ zr->jpg_gbuf[i].frag_tab[2 * j] = 0;
+ zr->jpg_gbuf[i].frag_tab[2 * j + 1] = 0;
+ }
+ }
+
+ free_page((unsigned long) zr->jpg_gbuf[i].frag_tab);
+ zr->jpg_gbuf[i].frag_tab = NULL;
+ }
+ zr->jpg_buffers_allocated = 0;
+}
+
+
+/* ----------------------------------------------------------------------- */
+
+/* I2C functions */
+
+#define I2C_DELAY 10
+
+
+/* software I2C functions */
+
+static void i2c_setlines(struct i2c_bus *bus, int ctrl, int data)
+{
+ struct zoran *zr = (struct zoran *) bus->data;
+ btwrite((data << 1) | ctrl, ZR36057_I2CBR);
+ btread(ZR36057_I2CBR);
+ udelay(I2C_DELAY);
+}
+
+static int i2c_getdataline(struct i2c_bus *bus)
+{
+ struct zoran *zr = (struct zoran *) bus->data;
+ return (btread(ZR36057_I2CBR) >> 1) & 1;
+}
+
+static void attach_inform(struct i2c_bus *bus, int id)
+{
+ DEBUG(struct zoran *zr = (struct zoran *) bus->data);
+ DEBUG(printk(BUZ_DEBUG "-%u: i2c attach %02x\n", zr->id, id));
+}
+
+static void detach_inform(struct i2c_bus *bus, int id)
+{
+ DEBUG(struct zoran *zr = (struct zoran *) bus->data);
+ DEBUG(printk(BUZ_DEBUG "-%u: i2c detach %02x\n", zr->id, id));
+}
+
+static struct i2c_bus zoran_i2c_bus_template =
+{
+ "zr36057",
+ I2C_BUSID_BT848,
+ NULL,
+
+ SPIN_LOCK_UNLOCKED,
+
+ attach_inform,
+ detach_inform,
+
+ i2c_setlines,
+ i2c_getdataline,
+ NULL,
+ NULL,
+};
+
+
+/* ----------------------------------------------------------------------- */
+
+static void GPIO(struct zoran *zr, unsigned bit, unsigned value)
+{
+ u32 reg;
+ u32 mask;
+
+ mask = 1 << (24 + bit);
+ reg = btread(ZR36057_GPPGCR1) & ~mask;
+ if (value) {
+ reg |= mask;
+ }
+ btwrite(reg, ZR36057_GPPGCR1);
+ /* Stop any PCI posting on the GPIO bus */
+ btread(ZR36057_I2CBR);
+}
+
+
+/*
+ * Set the registers for the size we have specified. Don't bother
+ * trying to understand this without the ZR36057 manual in front of
+ * you [AC].
+ *
+ * PS: The manual is free for download in .pdf format from
+ * www.zoran.com - nicely done those folks.
+ */
+
+struct tvnorm {
+ u16 Wt, Wa, Ht, Ha, HStart, VStart;
+};
+
+static struct tvnorm tvnorms[] =
+{
+ /* PAL-BDGHI */
+ {864, 720, 625, 576, 31, 16},
+ /* NTSC */
+ {858, 720, 525, 480, 21, 8},
+};
+#define TVNORMS (sizeof(tvnorms) / sizeof(tvnorm))
+
+static int format2bpp(int format)
+{
+ int bpp;
+
+ /* Determine the number of bytes per pixel for the video format requested */
+
+ switch (format) {
+
+ case VIDEO_PALETTE_YUV422:
+ bpp = 2;
+ break;
+
+ case VIDEO_PALETTE_RGB555:
+ bpp = 2;
+ break;
+
+ case VIDEO_PALETTE_RGB565:
+ bpp = 2;
+ break;
+
+ case VIDEO_PALETTE_RGB24:
+ bpp = 3;
+ break;
+
+ case VIDEO_PALETTE_RGB32:
+ bpp = 4;
+ break;
+
+ default:
+ bpp = 0;
+ }
+
+ return bpp;
+}
+
+/*
+ * set geometry
+ */
+static void zr36057_set_vfe(struct zoran *zr, int video_width, int video_height,
+ unsigned int video_format)
+{
+ struct tvnorm *tvn;
+ unsigned HStart, HEnd, VStart, VEnd;
+ unsigned DispMode;
+ unsigned VidWinWid, VidWinHt;
+ unsigned hcrop1, hcrop2, vcrop1, vcrop2;
+ unsigned Wa, We, Ha, He;
+ unsigned X, Y, HorDcm, VerDcm;
+ u32 reg;
+ unsigned mask_line_size;
+
+ if (zr->params.norm < 0 || zr->params.norm > 1) {
+ printk(KERN_ERR "%s: set_vfe: video_norm = %d not valid\n", zr->name, zr->params.norm);
+ return;
+ }
+ if (video_width < BUZ_MIN_WIDTH || video_height < BUZ_MIN_HEIGHT) {
+ printk(KERN_ERR "%s: set_vfe: w=%d h=%d not valid\n", zr->name, video_width, video_height);
+ return;
+ }
+ tvn = &tvnorms[zr->params.norm];
+
+ Wa = tvn->Wa;
+ Ha = tvn->Ha;
+
+ /* if window has more than half of active height,
+ switch on interlacing - we want the full information */
+
+ zr->video_interlace = (video_height > Ha / 2);
+
+/**** zr36057 ****/
+
+ /* horizontal */
+ VidWinWid = video_width;
+ X = (VidWinWid * 64 + tvn->Wa - 1) / tvn->Wa;
+ We = (VidWinWid * 64) / X;
+ HorDcm = 64 - X;
+ hcrop1 = 2 * ((tvn->Wa - We) / 4);
+ hcrop2 = tvn->Wa - We - hcrop1;
+ HStart = tvn->HStart | 1;
+ HEnd = HStart + tvn->Wa - 1;
+ HStart += hcrop1;
+ HEnd -= hcrop2;
+ reg = ((HStart & ZR36057_VFEHCR_Hmask) << ZR36057_VFEHCR_HStart)
+ | ((HEnd & ZR36057_VFEHCR_Hmask) << ZR36057_VFEHCR_HEnd);
+ reg |= ZR36057_VFEHCR_HSPol;
+ btwrite(reg, ZR36057_VFEHCR);
+
+ /* Vertical */
+ DispMode = !zr->video_interlace;
+ VidWinHt = DispMode ? video_height : video_height / 2;
+ Y = (VidWinHt * 64 * 2 + tvn->Ha - 1) / tvn->Ha;
+ He = (VidWinHt * 64) / Y;
+ VerDcm = 64 - Y;
+ vcrop1 = (tvn->Ha / 2 - He) / 2;
+ vcrop2 = tvn->Ha / 2 - He - vcrop1;
+ VStart = tvn->VStart;
+ VEnd = VStart + tvn->Ha / 2 - 1;
+ VStart += vcrop1;
+ VEnd -= vcrop2;
+ reg = ((VStart & ZR36057_VFEVCR_Vmask) << ZR36057_VFEVCR_VStart)
+ | ((VEnd & ZR36057_VFEVCR_Vmask) << ZR36057_VFEVCR_VEnd);
+ reg |= ZR36057_VFEVCR_VSPol;
+ btwrite(reg, ZR36057_VFEVCR);
+
+ /* scaler and pixel format */
+ reg = 0 // ZR36057_VFESPFR_ExtFl /* Trying to live without ExtFl */
+ | (HorDcm << ZR36057_VFESPFR_HorDcm)
+ | (VerDcm << ZR36057_VFESPFR_VerDcm)
+ | (DispMode << ZR36057_VFESPFR_DispMode)
+ | ZR36057_VFESPFR_LittleEndian;
+ /* RJ: I don't know, why the following has to be the opposite
+ of the corresponding ZR36060 setting, but only this way
+ we get the correct colors when uncompressing to the screen */
+ reg |= ZR36057_VFESPFR_VCLKPol;
+ /* RJ: Don't know if that is needed for NTSC also */
+ reg |= ZR36057_VFESPFR_TopField;
+ switch (video_format) {
+
+ case VIDEO_PALETTE_YUV422:
+ reg |= ZR36057_VFESPFR_YUV422;
+ break;
+
+ case VIDEO_PALETTE_RGB555:
+ reg |= ZR36057_VFESPFR_RGB555 | ZR36057_VFESPFR_ErrDif;
+ break;
+
+ case VIDEO_PALETTE_RGB565:
+ reg |= ZR36057_VFESPFR_RGB565 | ZR36057_VFESPFR_ErrDif;
+ break;
+
+ case VIDEO_PALETTE_RGB24:
+ reg |= ZR36057_VFESPFR_RGB888 | ZR36057_VFESPFR_Pack24;
+ break;
+
+ case VIDEO_PALETTE_RGB32:
+ reg |= ZR36057_VFESPFR_RGB888;
+ break;
+
+ default:
+ printk(KERN_INFO "%s: Unknown color_fmt=%x\n", zr->name, video_format);
+ return;
+
+ }
+ if (HorDcm >= 48) {
+ reg |= 3 << ZR36057_VFESPFR_HFilter; /* 5 tap filter */
+ } else if (HorDcm >= 32) {
+ reg |= 2 << ZR36057_VFESPFR_HFilter; /* 4 tap filter */
+ } else if (HorDcm >= 16) {
+ reg |= 1 << ZR36057_VFESPFR_HFilter; /* 3 tap filter */
+ }
+ btwrite(reg, ZR36057_VFESPFR);
+
+ /* display configuration */
+
+ reg = (16 << ZR36057_VDCR_MinPix)
+ | (VidWinHt << ZR36057_VDCR_VidWinHt)
+ | (VidWinWid << ZR36057_VDCR_VidWinWid);
+ if (triton)
+ reg &= ~ZR36057_VDCR_Triton;
+ else
+ reg |= ZR36057_VDCR_Triton;
+ btwrite(reg, ZR36057_VDCR);
+
+ /* Write overlay clipping mask data, but don't enable overlay clipping */
+ /* RJ: since this makes only sense on the screen, we use
+ zr->window.width instead of video_width */
+
+ mask_line_size = (BUZ_MAX_WIDTH + 31) / 32;
+ reg = virt_to_bus(zr->overlay_mask);
+ btwrite(reg, ZR36057_MMTR);
+ reg = virt_to_bus(zr->overlay_mask + mask_line_size);
+ btwrite(reg, ZR36057_MMBR);
+ reg = mask_line_size - (zr->window.width + 31) / 32;
+ if (DispMode == 0)
+ reg += mask_line_size;
+ reg <<= ZR36057_OCR_MaskStride;
+ btwrite(reg, ZR36057_OCR);
+
+}
+
+/*
+ * Switch overlay on or off
+ */
+
+static void zr36057_overlay(struct zoran *zr, int on)
+{
+ int fmt, bpp;
+ u32 reg;
+
+ if (on) {
+ /* do the necessary settings ... */
+
+ btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR); /* switch it off first */
+
+ switch (zr->buffer.depth) {
+ case 15:
+ fmt = VIDEO_PALETTE_RGB555;
+ bpp = 2;
+ break;
+ case 16:
+ fmt = VIDEO_PALETTE_RGB565;
+ bpp = 2;
+ break;
+ case 24:
+ fmt = VIDEO_PALETTE_RGB24;
+ bpp = 3;
+ break;
+ case 32:
+ fmt = VIDEO_PALETTE_RGB32;
+ bpp = 4;
+ break;
+ default:
+ fmt = 0;
+ bpp = 0;
+ }
+
+ zr36057_set_vfe(zr, zr->window.width, zr->window.height, fmt);
+
+ /* Start and length of each line MUST be 4-byte aligned.
+ This should be allready checked before the call to this routine.
+ All error messages are internal driver checking only! */
+
+ /* video display top and bottom registers */
+
+ reg = (u32) zr->buffer.base
+ + zr->window.x * bpp
+ + zr->window.y * zr->buffer.bytesperline;
+ btwrite(reg, ZR36057_VDTR);
+ if (reg & 3)
+ printk(KERN_ERR "%s: zr36057_overlay: video_address not aligned\n", zr->name);
+ if (zr->video_interlace)
+ reg += zr->buffer.bytesperline;
+ btwrite(reg, ZR36057_VDBR);
+
+ /* video stride, status, and frame grab register */
+
+ reg = zr->buffer.bytesperline - zr->window.width * bpp;
+ if (zr->video_interlace)
+ reg += zr->buffer.bytesperline;
+ if (reg & 3)
+ printk(KERN_ERR "%s: zr36057_overlay: video_stride not aligned\n", zr->name);
+ reg = (reg << ZR36057_VSSFGR_DispStride);
+ reg |= ZR36057_VSSFGR_VidOvf; /* clear overflow status */
+ btwrite(reg, ZR36057_VSSFGR);
+
+ /* Set overlay clipping */
+
+ if (zr->window.clipcount)
+ btor(ZR36057_OCR_OvlEnable, ZR36057_OCR);
+
+ /* ... and switch it on */
+
+ btor(ZR36057_VDCR_VidEn, ZR36057_VDCR);
+ } else {
+ /* Switch it off */
+
+ btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR);
+ }
+}
+
+/*
+ * The overlay mask has one bit for each pixel on a scan line,
+ * and the maximum window size is BUZ_MAX_WIDTH * BUZ_MAX_HEIGHT pixels.
+ */
+static void write_overlay_mask(struct zoran *zr, struct video_clip *vp, int count)
+{
+ unsigned mask_line_size = (BUZ_MAX_WIDTH + 31) / 32;
+ u32 *mask;
+ int x, y, width, height;
+ unsigned i, j, k;
+ u32 reg;
+
+ /* fill mask with one bits */
+ memset(zr->overlay_mask, ~0, mask_line_size * 4 * BUZ_MAX_HEIGHT);
+ reg = 0;
+
+ for (i = 0; i < count; ++i) {
+ /* pick up local copy of clip */
+ x = vp[i].x;
+ y = vp[i].y;
+ width = vp[i].width;
+ height = vp[i].height;
+
+ /* trim clips that extend beyond the window */
+ if (x < 0) {
+ width += x;
+ x = 0;
+ }
+ if (y < 0) {
+ height += y;
+ y = 0;
+ }
+ if (x + width > zr->window.width) {
+ width = zr->window.width - x;
+ }
+ if (y + height > zr->window.height) {
+ height = zr->window.height - y;
+ }
+ /* ignore degenerate clips */
+ if (height <= 0) {
+ continue;
+ }
+ if (width <= 0) {
+ continue;
+ }
+ /* apply clip for each scan line */
+ for (j = 0; j < height; ++j) {
+ /* reset bit for each pixel */
+ /* this can be optimized later if need be */
+ mask = zr->overlay_mask + (y + j) * mask_line_size;
+ for (k = 0; k < width; ++k) {
+ mask[(x + k) / 32] &= ~((u32) 1 << (x + k) % 32);
+ }
+ }
+ }
+}
+
+/* Enable/Disable uncompressed memory grabbing of the 36057 */
+
+static void zr36057_set_memgrab(struct zoran *zr, int mode)
+{
+ if (mode) {
+ if (btread(ZR36057_VSSFGR) & (ZR36057_VSSFGR_SnapShot | ZR36057_VSSFGR_FrameGrab))
+ printk(KERN_WARNING "%s: zr36057_set_memgrab_on with SnapShot or FrameGrab on ???\n", zr->name);
+
+ /* switch on VSync interrupts */
+
+ btwrite(IRQ_MASK, ZR36057_ISR); // Clear Interrupts
+
+ btor(ZR36057_ICR_GIRQ0, ZR36057_ICR);
+
+ /* enable SnapShot */
+
+ btor(ZR36057_VSSFGR_SnapShot, ZR36057_VSSFGR);
+
+ /* Set zr36057 video front end and enable video */
+
+#ifdef XAWTV_HACK
+ zr36057_set_vfe(zr, zr->gwidth > 720 ? 720 : zr->gwidth, zr->gheight, zr->gformat);
+#else
+ zr36057_set_vfe(zr, zr->gwidth, zr->gheight, zr->gformat);
+#endif
+
+ zr->v4l_memgrab_active = 1;
+ } else {
+ zr->v4l_memgrab_active = 0;
+
+ /* switch off VSync interrupts */
+
+ btand(~ZR36057_ICR_GIRQ0, ZR36057_ICR);
+
+ /* reenable grabbing to screen if it was running */
+
+ if (zr->v4l_overlay_active) {
+ zr36057_overlay(zr, 1);
+ } else {
+ btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR);
+ btand(~ZR36057_VSSFGR_SnapShot, ZR36057_VSSFGR);
+ }
+ }
+}
+
+static int wait_grab_pending(struct zoran *zr)
+{
+ unsigned long flags;
+
+ /* wait until all pending grabs are finished */
+
+ if (!zr->v4l_memgrab_active)
+ return 0;
+
+ while (zr->v4l_pend_tail != zr->v4l_pend_head) {
+ interruptible_sleep_on(&zr->v4l_capq);
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ }
+
+ spin_lock_irqsave(&zr->lock, flags);
+ zr36057_set_memgrab(zr, 0);
+ spin_unlock_irqrestore(&zr->lock, flags);
+
+ return 0;
+}
+
+/*
+ * V4L Buffer grabbing
+ */
+
+static int v4l_grab(struct zoran *zr, struct video_mmap *mp)
+{
+ unsigned long flags;
+ int res, bpp;
+
+ /*
+ * There is a long list of limitations to what is allowed to be grabbed
+ * We don't output error messages her, since some programs (e.g. xawtv)
+ * just try several settings to find out what is valid or not.
+ */
+
+ /* No grabbing outside the buffer range! */
+
+ if (mp->frame >= v4l_nbufs || mp->frame < 0)
+ return -EINVAL;
+
+ /* Check size and format of the grab wanted */
+
+ if (mp->height < BUZ_MIN_HEIGHT || mp->width < BUZ_MIN_WIDTH)
+ return -EINVAL;
+ if (mp->height > BUZ_MAX_HEIGHT || mp->width > BUZ_MAX_WIDTH)
+ return -EINVAL;
+
+ bpp = format2bpp(mp->format);
+ if (bpp == 0)
+ return -EINVAL;
+
+ /* Check against available buffer size */
+
+ if (mp->height * mp->width * bpp > v4l_bufsize)
+ return -EINVAL;
+
+ /* The video front end needs 4-byte alinged line sizes */
+
+ if ((bpp == 2 && (mp->width & 1)) || (bpp == 3 && (mp->width & 3)))
+ return -EINVAL;
+
+ /*
+ * To minimize the time spent in the IRQ routine, we avoid setting up
+ * the video front end there.
+ * If this grab has different parameters from a running streaming capture
+ * we stop the streaming capture and start it over again.
+ */
+
+ if (zr->v4l_memgrab_active &&
+ (zr->gwidth != mp->width || zr->gheight != mp->height || zr->gformat != mp->format)) {
+ res = wait_grab_pending(zr);
+ if (res)
+ return res;
+ }
+ zr->gwidth = mp->width;
+ zr->gheight = mp->height;
+ zr->gformat = mp->format;
+ zr->gbpl = bpp * zr->gwidth;
+
+
+ spin_lock_irqsave(&zr->lock, flags);
+
+ /* make sure a grab isn't going on currently with this buffer */
+
+ switch (zr->v4l_gbuf[mp->frame].state) {
+
+ default:
+ case BUZ_STATE_PEND:
+ res = -EBUSY; /* what are you doing? */
+ break;
+
+ case BUZ_STATE_USER:
+ case BUZ_STATE_DONE:
+ /* since there is at least one unused buffer there's room for at least one more pend[] entry */
+ zr->v4l_pend[zr->v4l_pend_head++ & V4L_MASK_FRAME] = mp->frame;
+ zr->v4l_gbuf[mp->frame].state = BUZ_STATE_PEND;
+ res = 0;
+ break;
+
+ }
+
+ /* put the 36057 into frame grabbing mode */
+
+ if (!res && !zr->v4l_memgrab_active)
+ zr36057_set_memgrab(zr, 1);
+
+ spin_unlock_irqrestore(&zr->lock, flags);
+
+ return res;
+}
+
+/*
+ * Sync on a V4L buffer
+ */
+
+static int v4l_sync(struct zoran *zr, int frame)
+{
+ unsigned long flags;
+
+
+ /* check passed-in frame number */
+ if (frame >= v4l_nbufs || frame < 0) {
+ printk(KERN_ERR "%s: v4l_sync: frame %d is invalid\n", zr->name, frame);
+ return -EINVAL;
+ }
+ /* Check if is buffer was queued at all */
+
+ if (zr->v4l_gbuf[frame].state == BUZ_STATE_USER) {
+// printk(KERN_ERR "%s: v4l_sync: Trying to sync on a buffer which was not queued?\n", zr->name);
+ return -EINVAL;
+ }
+ /* wait on this buffer to get ready */
+
+ while (zr->v4l_gbuf[frame].state == BUZ_STATE_PEND) {
+ interruptible_sleep_on(&zr->v4l_capq);
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ }
+
+ /* buffer should now be in BUZ_STATE_DONE */
+
+ if (zr->v4l_gbuf[frame].state != BUZ_STATE_DONE)
+ printk(KERN_ERR "%s: v4l_sync - internal error\n", zr->name);
+
+ /* Check if streaming capture has finished */
+
+ spin_lock_irqsave(&zr->lock, flags);
+
+ if (zr->v4l_pend_tail == zr->v4l_pend_head)
+ zr36057_set_memgrab(zr, 0);
+
+ spin_unlock_irqrestore(&zr->lock, flags);
+
+ return 0;
+}
+/*****************************************************************************
+ * *
+ * Set up the Buz-specific MJPEG part *
+ * *
+ *****************************************************************************/
+
+/*
+ * Wait til post office is no longer busy
+ */
+
+static int post_office_wait(struct zoran *zr)
+{
+ u32 por;
+ u32 ct=0;
+
+ while (((por = btread(ZR36057_POR)) & (ZR36057_POR_POPen | ZR36057_POR_POTime)) == ZR36057_POR_POPen) {
+ ct++;
+ if(ct>100000)
+ {
+ printk(KERN_ERR "%s: timeout on post office.\n", zr->name);
+ return -1;
+ }
+ /* wait for something to happen */
+ }
+ if ((por & ZR36057_POR_POPen) != 0) {
+ printk(KERN_WARNING "%s: pop pending %08x\n", zr->name, por);
+ return -1;
+ }
+ if ((por & (ZR36057_POR_POTime | ZR36057_POR_POPen)) != 0) {
+ printk(KERN_WARNING "%s: pop timeout %08x\n", zr->name, por);
+ return -1;
+ }
+ return 0;
+}
+
+static int post_office_write(struct zoran *zr, unsigned guest, unsigned reg, unsigned value)
+{
+ u32 por;
+
+ post_office_wait(zr);
+ por = ZR36057_POR_PODir | ZR36057_POR_POTime | ((guest & 7) << 20) | ((reg & 7) << 16) | (value & 0xFF);
+ btwrite(por, ZR36057_POR);
+ return post_office_wait(zr);
+}
+
+static int post_office_read(struct zoran *zr, unsigned guest, unsigned reg)
+{
+ u32 por;
+
+ post_office_wait(zr);
+ por = ZR36057_POR_POTime | ((guest & 7) << 20) | ((reg & 7) << 16);
+ btwrite(por, ZR36057_POR);
+ if (post_office_wait(zr) < 0) {
+ return -1;
+ }
+ return btread(ZR36057_POR) & 0xFF;
+}
+
+static int zr36060_write_8(struct zoran *zr, unsigned reg, unsigned val)
+{
+ if (post_office_wait(zr)
+ || post_office_write(zr, 0, 1, reg >> 8)
+ || post_office_write(zr, 0, 2, reg)) {
+ return -1;
+ }
+ return post_office_write(zr, 0, 3, val);
+}
+
+static int zr36060_write_16(struct zoran *zr, unsigned reg, unsigned val)
+{
+ if (zr36060_write_8(zr, reg + 0, val >> 8)) {
+ return -1;
+ }
+ return zr36060_write_8(zr, reg + 1, val >> 0);
+}
+
+static int zr36060_write_24(struct zoran *zr, unsigned reg, unsigned val)
+{
+ if (zr36060_write_8(zr, reg + 0, val >> 16)) {
+ return -1;
+ }
+ return zr36060_write_16(zr, reg + 1, val >> 0);
+}
+
+static int zr36060_write_32(struct zoran *zr, unsigned reg, unsigned val)
+{
+ if (zr36060_write_16(zr, reg + 0, val >> 16)) {
+ return -1;
+ }
+ return zr36060_write_16(zr, reg + 2, val >> 0);
+}
+
+static u32 zr36060_read_8(struct zoran *zr, unsigned reg)
+{
+ if (post_office_wait(zr)
+ || post_office_write(zr, 0, 1, reg >> 8)
+ || post_office_write(zr, 0, 2, reg)) {
+ return -1;
+ }
+ return post_office_read(zr, 0, 3) & 0xFF;
+}
+
+static int zr36060_reset(struct zoran *zr)
+{
+ return post_office_write(zr, 3, 0, 0);
+}
+
+static void zr36060_sleep(struct zoran *zr, int sleep)
+{
+ GPIO(zr, 1, !sleep);
+}
+
+
+static void zr36060_set_jpg(struct zoran *zr, enum zoran_codec_mode mode)
+{
+ struct tvnorm *tvn;
+ u32 reg;
+ int size;
+
+ reg = (1 << 0) /* CodeMstr */
+ |(0 << 2) /* CFIS=0 */
+ |(0 << 6) /* Endian=0 */
+ |(0 << 7); /* Code16=0 */
+ zr36060_write_8(zr, 0x002, reg);
+
+ switch (mode) {
+
+ case BUZ_MODE_MOTION_DECOMPRESS:
+ case BUZ_MODE_STILL_DECOMPRESS:
+ reg = 0x00; /* Codec mode = decompression */
+ break;
+
+ case BUZ_MODE_MOTION_COMPRESS:
+ case BUZ_MODE_STILL_COMPRESS:
+ default:
+ reg = 0xa4; /* Codec mode = compression with variable scale factor */
+ break;
+
+ }
+ zr36060_write_8(zr, 0x003, reg);
+
+ reg = 0x00; /* reserved, mbz */
+ zr36060_write_8(zr, 0x004, reg);
+
+ reg = 0xff; /* 510 bits/block */
+ zr36060_write_8(zr, 0x005, reg);
+
+ /* JPEG markers */
+ reg = (zr->params.jpeg_markers) & 0x38; /* DRI, DQT, DHT */
+ if (zr->params.COM_len)
+ reg |= JPEG_MARKER_COM;
+ if (zr->params.APP_len)
+ reg |= JPEG_MARKER_APP;
+ zr36060_write_8(zr, 0x006, reg);
+
+ reg = (0 << 3) /* DATERR=0 */
+ |(0 << 2) /* END=0 */
+ |(0 << 1) /* EOI=0 */
+ |(0 << 0); /* EOAV=0 */
+ zr36060_write_8(zr, 0x007, reg);
+
+ /* code volume */
+
+ /* Target field size in pixels: */
+ tvn = &tvnorms[zr->params.norm];
+ size = (tvn->Ha / 2) * (tvn->Wa) / (zr->params.HorDcm) / (zr->params.VerDcm);
+
+ /* Target compressed field size in bits: */
+ size = size * 16; /* uncompressed size in bits */
+ size = size * zr->params.quality / 400; /* quality = 100 is a compression ratio 1:4 */
+
+ /* Lower limit (arbitrary, 1 KB) */
+ if (size < 8192)
+ size = 8192;
+
+ /* Upper limit: 7/8 of the code buffers */
+ if (size * zr->params.field_per_buff > zr->jpg_bufsize * 7)
+ size = zr->jpg_bufsize * 7 / zr->params.field_per_buff;
+
+ reg = size;
+ zr36060_write_32(zr, 0x009, reg);
+
+ /* how do we set initial SF as a function of quality parameter? */
+ reg = 0x0100; /* SF=1.0 */
+ zr36060_write_16(zr, 0x011, reg);
+
+ reg = 0x00ffffff; /* AF=max */
+ zr36060_write_24(zr, 0x013, reg);
+
+ reg = 0x0000; /* test */
+ zr36060_write_16(zr, 0x024, reg);
+}
+
+static void zr36060_set_video(struct zoran *zr, enum zoran_codec_mode mode)
+{
+ struct tvnorm *tvn;
+ u32 reg;
+
+ reg = (0 << 7) /* Video8=0 */
+ |(0 << 6) /* Range=0 */
+ |(0 << 3) /* FlDet=0 */
+ |(1 << 2) /* FlVedge=1 */
+ |(0 << 1) /* FlExt=0 */
+ |(0 << 0); /* SyncMstr=0 */
+
+ /* According to ZR36067 documentation, FlDet should correspond
+ to the odd_even flag of the ZR36067 */
+ if (zr->params.odd_even)
+ reg |= (1 << 3);
+
+ if (mode != BUZ_MODE_STILL_DECOMPRESS) {
+ /* limit pixels to range 16..235 as per CCIR-601 */
+ reg |= (1 << 6); /* Range=1 */
+ }
+ zr36060_write_8(zr, 0x030, reg);
+
+ reg = (0 << 7) /* VCLKPol=0 */
+ |(0 << 6) /* PValPol=0 */
+ |(1 << 5) /* PoePol=1 */
+ |(0 << 4) /* SImgPol=0 */
+ |(0 << 3) /* BLPol=0 */
+ |(0 << 2) /* FlPol=0 */
+ |(0 << 1) /* HSPol=0, sync on falling edge */
+ |(1 << 0); /* VSPol=1 */
+ zr36060_write_8(zr, 0x031, reg);
+
+ switch (zr->params.HorDcm) {
+ default:
+ case 1:
+ reg = (0 << 0);
+ break; /* HScale = 0 */
+
+ case 2:
+ reg = (1 << 0);
+ break; /* HScale = 1 */
+
+ case 4:
+ reg = (2 << 0);
+ break; /* HScale = 2 */
+ }
+ if (zr->params.VerDcm == 2)
+ reg |= (1 << 2);
+ zr36060_write_8(zr, 0x032, reg);
+
+ reg = 0x80; /* BackY */
+ zr36060_write_8(zr, 0x033, reg);
+
+ reg = 0xe0; /* BackU */
+ zr36060_write_8(zr, 0x034, reg);
+
+ reg = 0xe0; /* BackV */
+ zr36060_write_8(zr, 0x035, reg);
+
+ /* sync generator */
+
+ tvn = &tvnorms[zr->params.norm];
+
+ reg = tvn->Ht - 1; /* Vtotal */
+ zr36060_write_16(zr, 0x036, reg);
+
+ reg = tvn->Wt - 1; /* Htotal */
+ zr36060_write_16(zr, 0x038, reg);
+
+ reg = 6 - 1; /* VsyncSize */
+ zr36060_write_8(zr, 0x03a, reg);
+
+ reg = 100 - 1; /* HsyncSize */
+ zr36060_write_8(zr, 0x03b, reg);
+
+ reg = tvn->VStart - 1; /* BVstart */
+ zr36060_write_8(zr, 0x03c, reg);
+
+ reg += tvn->Ha / 2; /* BVend */
+ zr36060_write_16(zr, 0x03e, reg);
+
+ reg = tvn->HStart - 1; /* BHstart */
+ zr36060_write_8(zr, 0x03d, reg);
+
+ reg += tvn->Wa; /* BHend */
+ zr36060_write_16(zr, 0x040, reg);
+
+ /* active area */
+ reg = zr->params.img_y + tvn->VStart; /* Vstart */
+ zr36060_write_16(zr, 0x042, reg);
+
+ reg += zr->params.img_height; /* Vend */
+ zr36060_write_16(zr, 0x044, reg);
+
+ reg = zr->params.img_x + tvn->HStart; /* Hstart */
+ zr36060_write_16(zr, 0x046, reg);
+
+ reg += zr->params.img_width; /* Hend */
+ zr36060_write_16(zr, 0x048, reg);
+
+ /* subimage area */
+ reg = zr->params.img_y + tvn->VStart; /* SVstart */
+ zr36060_write_16(zr, 0x04a, reg);
+
+ reg += zr->params.img_height; /* SVend */
+ zr36060_write_16(zr, 0x04c, reg);
+
+ reg = zr->params.img_x + tvn->HStart; /* SHstart */
+ zr36060_write_16(zr, 0x04e, reg);
+
+ reg += zr->params.img_width; /* SHend */
+ zr36060_write_16(zr, 0x050, reg);
+}
+
+static void zr36060_set_jpg_SOF(struct zoran *zr)
+{
+ u32 reg;
+
+
+ reg = 0xffc0; /* SOF marker */
+ zr36060_write_16(zr, 0x060, reg);
+
+ reg = 17; /* SOF length */
+ zr36060_write_16(zr, 0x062, reg);
+
+ reg = 8; /* precision 8 bits */
+ zr36060_write_8(zr, 0x064, reg);
+
+ reg = zr->params.img_height / zr->params.VerDcm; /* image height */
+ zr36060_write_16(zr, 0x065, reg);
+
+ reg = zr->params.img_width / zr->params.HorDcm; /* image width */
+ zr36060_write_16(zr, 0x067, reg);
+
+ reg = 3; /* 3 color components */
+ zr36060_write_8(zr, 0x069, reg);
+
+ reg = 0x002100; /* Y component */
+ zr36060_write_24(zr, 0x06a, reg);
+
+ reg = 0x011101; /* U component */
+ zr36060_write_24(zr, 0x06d, reg);
+
+ reg = 0x021101; /* V component */
+ zr36060_write_24(zr, 0x070, reg);
+}
+
+static void zr36060_set_jpg_SOS(struct zoran *zr)
+{
+ u32 reg;
+
+
+ reg = 0xffda; /* SOS marker */
+ zr36060_write_16(zr, 0x07a, reg);
+
+ reg = 12; /* SOS length */
+ zr36060_write_16(zr, 0x07c, reg);
+
+ reg = 3; /* 3 color components */
+ zr36060_write_8(zr, 0x07e, reg);
+
+ reg = 0x0000; /* Y component */
+ zr36060_write_16(zr, 0x07f, reg);
+
+ reg = 0x0111; /* U component */
+ zr36060_write_16(zr, 0x081, reg);
+
+ reg = 0x0211; /* V component */
+ zr36060_write_16(zr, 0x083, reg);
+
+ reg = 0x003f00; /* Start, end spectral scans */
+ zr36060_write_24(zr, 0x085, reg);
+}
+
+static void zr36060_set_jpg_DRI(struct zoran *zr)
+{
+ u32 reg;
+
+
+ reg = 0xffdd; /* DRI marker */
+ zr36060_write_16(zr, 0x0c0, reg);
+
+ reg = 4; /* DRI length */
+ zr36060_write_16(zr, 0x0c2, reg);
+
+ reg = 8; /* length in MCUs */
+ zr36060_write_16(zr, 0x0c4, reg);
+}
+
+static void zr36060_set_jpg_DQT(struct zoran *zr)
+{
+ unsigned i;
+ unsigned adr;
+ static const u8 dqt[] =
+ {
+ 0xff, 0xdb, /* DHT marker */
+ 0x00, 0x84, /* DHT length */
+ 0x00, /* table ID 0 */
+ 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e,
+ 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28,
+ 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25,
+ 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33,
+ 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44,
+ 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57,
+ 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71,
+ 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63,
+ 0x01, /* table ID 1 */
+ 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a,
+ 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63
+ };
+
+ /* write fixed quantitization tables */
+ adr = 0x0cc;
+ for (i = 0; i < sizeof(dqt); ++i) {
+ zr36060_write_8(zr, adr++, dqt[i]);
+ }
+}
+
+static void zr36060_set_jpg_DHT(struct zoran *zr)
+{
+ unsigned i;
+ unsigned adr;
+ static const u8 dht[] =
+ {
+ 0xff, 0xc4, /* DHT marker */
+ 0x01, 0xa2, /* DHT length */
+ 0x00, /* table class 0, ID 0 */
+ 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, /* # codes of length 1..8 */
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* # codes of length 8..16 */
+ 0x00, /* values for codes of length 2 */
+ 0x01, 0x02, 0x03, 0x04, 0x05, /* values for codes of length 3 */
+ 0x06, /* values for codes of length 4 */
+ 0x07, /* values for codes of length 5 */
+ 0x08, /* values for codes of length 6 */
+ 0x09, /* values for codes of length 7 */
+ 0x0a, /* values for codes of length 8 */
+ 0x0b, /* values for codes of length 9 */
+ 0x01, /* table class 0, ID 1 */
+ 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* # codes of length 1..8 */
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, /* # codes of length 9..16 */
+ 0x00, 0x01, 0x02, /* values for codes of length 2 */
+ 0x03, /* values for codes of length 3 */
+ 0x04, /* values for codes of length 4 */
+ 0x05, /* values for codes of length 5 */
+ 0x06, /* values for codes of length 6 */
+ 0x07, /* values for codes of length 7 */
+ 0x08, /* values for codes of length 8 */
+ 0x09, /* values for codes of length 9 */
+ 0x0a, /* values for codes of length 10 */
+ 0x0b, /* values for codes of length 11 */
+ 0x10,
+ 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03,
+ 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d,
+ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
+ 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
+ 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
+ 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
+ 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
+ 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
+ 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
+ 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
+ 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+ 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
+ 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+ 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
+ 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
+ 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
+ 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
+ 0xf9, 0xfa, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04,
+ 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00,
+ 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11,
+ 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
+ 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08,
+ 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23,
+ 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a,
+ 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18,
+ 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35,
+ 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45,
+ 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55,
+ 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+ 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75,
+ 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84,
+ 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93,
+ 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2,
+ 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
+ 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
+ 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
+ 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6,
+ 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5,
+ 0xf6, 0xf7, 0xf8, 0xf9, 0xfa
+ };
+
+ /* write fixed Huffman tables */
+ adr = 0x1d4;
+ for (i = 0; i < sizeof(dht); ++i) {
+ zr36060_write_8(zr, adr++, dht[i]);
+ }
+}
+
+static void zr36060_set_jpg_APP(struct zoran *zr)
+{
+ unsigned adr;
+ int len, i;
+ u32 reg;
+
+
+ len = zr->params.APP_len;
+ if (len < 0)
+ len = 0;
+ if (len > 60)
+ len = 60;
+
+ i = zr->params.APPn;
+ if (i < 0)
+ i = 0;
+ if (i > 15)
+ i = 15;
+
+ reg = 0xffe0 + i; /* APPn marker */
+ zr36060_write_16(zr, 0x380, reg);
+
+ reg = len + 2; /* APPn len */
+ zr36060_write_16(zr, 0x382, reg);
+
+ /* write APPn data */
+ adr = 0x384;
+ for (i = 0; i < 60; i++) {
+ zr36060_write_8(zr, adr++, (i < len ? zr->params.APP_data[i] : 0));
+ }
+}
+
+static void zr36060_set_jpg_COM(struct zoran *zr)
+{
+ unsigned adr;
+ int len, i;
+ u32 reg;
+
+
+ len = zr->params.COM_len;
+ if (len < 0)
+ len = 0;
+ if (len > 60)
+ len = 60;
+
+ reg = 0xfffe; /* COM marker */
+ zr36060_write_16(zr, 0x3c0, reg);
+
+ reg = len + 2; /* COM len */
+ zr36060_write_16(zr, 0x3c2, reg);
+
+ /* write COM data */
+ adr = 0x3c4;
+ for (i = 0; i < 60; i++) {
+ zr36060_write_8(zr, adr++, (i < len ? zr->params.COM_data[i] : 0));
+ }
+}
+
+static void zr36060_set_cap(struct zoran *zr, enum zoran_codec_mode mode)
+{
+ unsigned i;
+ u32 reg;
+
+ zr36060_reset(zr);
+ mdelay(10);
+
+ reg = (0 << 7) /* Load=0 */
+ |(1 << 0); /* SynRst=1 */
+ zr36060_write_8(zr, 0x000, reg);
+
+ zr36060_set_jpg(zr, mode);
+ zr36060_set_video(zr, mode);
+ zr36060_set_jpg_SOF(zr);
+ zr36060_set_jpg_SOS(zr);
+ zr36060_set_jpg_DRI(zr);
+ zr36060_set_jpg_DQT(zr);
+ zr36060_set_jpg_DHT(zr);
+ zr36060_set_jpg_APP(zr);
+ zr36060_set_jpg_COM(zr);
+
+ reg = (1 << 7) /* Load=1 */
+ |(0 << 0); /* SynRst=0 */
+ zr36060_write_8(zr, 0x000, reg);
+
+ /* wait for codec to unbusy */
+ for (i = 0; i < 1000; ++i) {
+ reg = zr36060_read_8(zr, 0x001);
+ if ((reg & (1 << 7)) == 0) {
+ DEBUG(printk(KERN_DEBUG "060: loaded, loops=%u\n", i));
+ return;
+ }
+ udelay(1000);
+ }
+ printk(KERN_INFO "060: stuck busy, statux=%02x\n", reg);
+}
+
+static void zr36057_set_jpg(struct zoran *zr, enum zoran_codec_mode mode)
+{
+ struct tvnorm *tvn;
+ u32 reg;
+ int i;
+
+ tvn = &tvnorms[zr->params.norm];
+
+ /* assert P_Reset */
+ btwrite(0, ZR36057_JPC);
+
+ /* re-initialize DMA ring stuff */
+ zr->jpg_que_head = 0;
+ zr->jpg_dma_head = 0;
+ zr->jpg_dma_tail = 0;
+ zr->jpg_que_tail = 0;
+ zr->jpg_seq_num = 0;
+ for (i = 0; i < BUZ_NUM_STAT_COM; ++i) {
+ zr->stat_com[i] = 1; /* mark as unavailable to zr36057 */
+ }
+ for (i = 0; i < zr->jpg_nbufs; i++) {
+ zr->jpg_gbuf[i].state = BUZ_STATE_USER; /* nothing going on */
+ }
+
+ /* MJPEG compression mode */
+ switch (mode) {
+
+ case BUZ_MODE_MOTION_COMPRESS:
+ default:
+ reg = ZR36057_JMC_MJPGCmpMode;
+ break;
+
+ case BUZ_MODE_MOTION_DECOMPRESS:
+ reg = ZR36057_JMC_MJPGExpMode;
+ reg |= ZR36057_JMC_SyncMstr;
+ /* RJ: The following is experimental - improves the output to screen */
+ if (zr->params.VFIFO_FB)
+ reg |= ZR36057_JMC_VFIFO_FB;
+ break;
+
+ case BUZ_MODE_STILL_COMPRESS:
+ reg = ZR36057_JMC_JPGCmpMode;
+ break;
+
+ case BUZ_MODE_STILL_DECOMPRESS:
+ reg = ZR36057_JMC_JPGExpMode;
+ break;
+
+ }
+ reg |= ZR36057_JMC_JPG;
+ if (zr->params.field_per_buff == 1)
+ reg |= ZR36057_JMC_Fld_per_buff;
+ btwrite(reg, ZR36057_JMC);
+
+ /* vertical */
+ btor(ZR36057_VFEVCR_VSPol, ZR36057_VFEVCR);
+ reg = (6 << ZR36057_VSP_VsyncSize) | (tvn->Ht << ZR36057_VSP_FrmTot);
+ btwrite(reg, ZR36057_VSP);
+ reg = ((zr->params.img_y + tvn->VStart) << ZR36057_FVAP_NAY)
+ | (zr->params.img_height << ZR36057_FVAP_PAY);
+ btwrite(reg, ZR36057_FVAP);
+
+ /* horizontal */
+ btor(ZR36057_VFEHCR_HSPol, ZR36057_VFEHCR);
+ reg = ((tvn->Wt - 100) << ZR36057_HSP_HsyncStart) | (tvn->Wt << ZR36057_HSP_LineTot);
+ btwrite(reg, ZR36057_HSP);
+ reg = ((zr->params.img_x + tvn->HStart) << ZR36057_FHAP_NAX)
+ | (zr->params.img_width << ZR36057_FHAP_PAX);
+ btwrite(reg, ZR36057_FHAP);
+
+ /* field process parameters */
+ if (zr->params.odd_even)
+ reg = ZR36057_FPP_Odd_Even;
+ else
+ reg = 0;
+ btwrite(reg, ZR36057_FPP);
+
+ /* Set proper VCLK Polarity, else colors will be wrong during playback */
+ btor(ZR36057_VFESPFR_VCLKPol, ZR36057_VFESPFR);
+
+ /* code base address and FIFO threshold */
+ reg = virt_to_bus(zr->stat_com);
+ btwrite(reg, ZR36057_JCBA);
+ reg = 0x50;
+ btwrite(reg, ZR36057_JCFT);
+
+ /* JPEG codec guest ID */
+ reg = (1 << ZR36057_JCGI_JPEGuestID) | (0 << ZR36057_JCGI_JPEGuestReg);
+ btwrite(reg, ZR36057_JCGI);
+
+ /* Code transfer guest ID */
+ reg = (0 << ZR36057_MCTCR_CodGuestID) | (3 << ZR36057_MCTCR_CodGuestReg);
+ reg |= ZR36057_MCTCR_CFlush;
+ btwrite(reg, ZR36057_MCTCR);
+
+ /* deassert P_Reset */
+ btwrite(ZR36057_JPC_P_Reset, ZR36057_JPC);
+}
+
+static void zr36057_enable_jpg(struct zoran *zr, enum zoran_codec_mode mode)
+{
+ static int zero = 0;
+ static int one = 1;
+
+ switch (mode) {
+
+ case BUZ_MODE_MOTION_COMPRESS:
+ zr36060_set_cap(zr, mode);
+ zr36057_set_jpg(zr, mode);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_ENABLE_OUTPUT, &one);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_INPUT, &zero);
+
+ /* deassert P_Reset, assert Code transfer enable */
+ btwrite(IRQ_MASK, ZR36057_ISR);
+ btand(~ZR36057_MCTCR_CFlush, ZR36057_MCTCR);
+ break;
+
+ case BUZ_MODE_MOTION_DECOMPRESS:
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_ENABLE_OUTPUT, &zero);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_INPUT, &one);
+ zr36060_set_cap(zr, mode);
+ zr36057_set_jpg(zr, mode);
+
+ /* deassert P_Reset, assert Code transfer enable */
+ btwrite(IRQ_MASK, ZR36057_ISR);
+ btand(~ZR36057_MCTCR_CFlush, ZR36057_MCTCR);
+ break;
+
+ case BUZ_MODE_IDLE:
+ default:
+ /* shut down processing */
+ btor(ZR36057_MCTCR_CFlush, ZR36057_MCTCR);
+ btwrite(ZR36057_JPC_P_Reset, ZR36057_JPC);
+ btand(~ZR36057_JMC_VFIFO_FB, ZR36057_JMC);
+ btand(~ZR36057_JMC_SyncMstr, ZR36057_JMC);
+ btand(~ZR36057_JMC_Go_en, ZR36057_JMC);
+ btwrite(0, ZR36057_ISR);
+ zr36060_reset(zr);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_ENABLE_OUTPUT, &one);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_INPUT, &zero);
+ break;
+
+ }
+ zr->codec_mode = mode;
+}
+
+/*
+ * Queue a MJPEG buffer for capture/playback
+ */
+
+static int jpg_qbuf(struct zoran *zr, int frame, enum zoran_codec_mode mode)
+{
+ unsigned long flags;
+ int res;
+
+ /* Check if buffers are allocated */
+
+ if (!zr->jpg_buffers_allocated) {
+ printk(KERN_ERR "%s: jpg_qbuf: buffers not yet allocated\n", zr->name);
+ return -ENOMEM;
+ }
+ /* Does the user want to stop streaming? */
+
+ if (frame < 0) {
+ if (zr->codec_mode == mode) {
+ zr36057_enable_jpg(zr, BUZ_MODE_IDLE);
+ return 0;
+ } else {
+ printk(KERN_ERR "%s: jpg_qbuf - stop streaming but not in streaming mode\n", zr->name);
+ return -EINVAL;
+ }
+ }
+ /* No grabbing outside the buffer range! */
+
+ if (frame >= zr->jpg_nbufs) {
+ printk(KERN_ERR "%s: jpg_qbuf: buffer %d out of range\n", zr->name, frame);
+ return -EINVAL;
+ }
+ /* what is the codec mode right now? */
+
+ if (zr->codec_mode == BUZ_MODE_IDLE) {
+ /* Ok load up the zr36060 and go */
+ zr36057_enable_jpg(zr, mode);
+ } else if (zr->codec_mode != mode) {
+ /* wrong codec mode active - invalid */
+ printk(KERN_ERR "%s: jpg_qbuf - codec in wrong mode\n", zr->name);
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&zr->lock, flags);
+
+ /* make sure a grab isn't going on currently with this buffer */
+
+ switch (zr->jpg_gbuf[frame].state) {
+
+ default:
+ case BUZ_STATE_DMA:
+ case BUZ_STATE_PEND:
+ case BUZ_STATE_DONE:
+ res = -EBUSY; /* what are you doing? */
+ break;
+
+ case BUZ_STATE_USER:
+ /* since there is at least one unused buffer there's room for at least one more pend[] entry */
+ zr->jpg_pend[zr->jpg_que_head++ & BUZ_MASK_FRAME] = frame;
+ zr->jpg_gbuf[frame].state = BUZ_STATE_PEND;
+ zoran_feed_stat_com(zr);
+ res = 0;
+ break;
+
+ }
+
+ spin_unlock_irqrestore(&zr->lock, flags);
+
+ /* Start the zr36060 when the first frame is queued */
+ if (zr->jpg_que_head == 1) {
+ btor(ZR36057_JMC_Go_en, ZR36057_JMC);
+ btwrite(ZR36057_JPC_P_Reset | ZR36057_JPC_CodTrnsEn | ZR36057_JPC_Active, ZR36057_JPC);
+ }
+ return res;
+}
+
+/*
+ * Sync on a MJPEG buffer
+ */
+
+static int jpg_sync(struct zoran *zr, struct zoran_sync *bs)
+{
+ unsigned long flags;
+ int frame;
+
+ if (zr->codec_mode != BUZ_MODE_MOTION_DECOMPRESS &&
+ zr->codec_mode != BUZ_MODE_MOTION_COMPRESS) {
+ return -EINVAL;
+ }
+ while (zr->jpg_que_tail == zr->jpg_dma_tail) {
+ interruptible_sleep_on(&zr->jpg_capq);
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ }
+
+ spin_lock_irqsave(&zr->lock, flags);
+
+ frame = zr->jpg_pend[zr->jpg_que_tail++ & BUZ_MASK_FRAME];
+
+ /* buffer should now be in BUZ_STATE_DONE */
+
+ if (zr->jpg_gbuf[frame].state != BUZ_STATE_DONE)
+ printk(KERN_ERR "%s: jpg_sync - internal error\n", zr->name);
+
+ *bs = zr->jpg_gbuf[frame].bs;
+ zr->jpg_gbuf[frame].state = BUZ_STATE_USER;
+
+ spin_unlock_irqrestore(&zr->lock, flags);
+
+ return 0;
+}
+
+/* when this is called the spinlock must be held */
+static void zoran_feed_stat_com(struct zoran *zr)
+{
+ /* move frames from pending queue to DMA */
+
+ int frame, i, max_stat_com;
+
+ max_stat_com = (zr->params.TmpDcm == 1) ? BUZ_NUM_STAT_COM : (BUZ_NUM_STAT_COM >> 1);
+
+ while ((zr->jpg_dma_head - zr->jpg_dma_tail) < max_stat_com
+ && zr->jpg_dma_head != zr->jpg_que_head) {
+
+ frame = zr->jpg_pend[zr->jpg_dma_head & BUZ_MASK_FRAME];
+ if (zr->params.TmpDcm == 1) {
+ /* fill 1 stat_com entry */
+ i = zr->jpg_dma_head & BUZ_MASK_STAT_COM;
+ zr->stat_com[i] = zr->jpg_gbuf[frame].frag_tab_bus;
+ } else {
+ /* fill 2 stat_com entries */
+ i = (zr->jpg_dma_head & 1) * 2;
+ zr->stat_com[i] = zr->jpg_gbuf[frame].frag_tab_bus;
+ zr->stat_com[i + 1] = zr->jpg_gbuf[frame].frag_tab_bus;
+ }
+ zr->jpg_gbuf[frame].state = BUZ_STATE_DMA;
+ zr->jpg_dma_head++;
+
+ }
+}
+
+/* when this is called the spinlock must be held */
+static void zoran_reap_stat_com(struct zoran *zr)
+{
+ /* move frames from DMA queue to done queue */
+
+ int i;
+ u32 stat_com;
+ unsigned int seq;
+ unsigned int dif;
+ int frame;
+ struct zoran_gbuffer *gbuf;
+
+ /* In motion decompress we don't have a hardware frame counter,
+ we just count the interrupts here */
+
+ if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS)
+ zr->jpg_seq_num++;
+
+ while (zr->jpg_dma_tail != zr->jpg_dma_head) {
+ if (zr->params.TmpDcm == 1)
+ i = zr->jpg_dma_tail & BUZ_MASK_STAT_COM;
+ else
+ i = (zr->jpg_dma_tail & 1) * 2 + 1;
+
+ stat_com = zr->stat_com[i];
+
+ if ((stat_com & 1) == 0) {
+ return;
+ }
+ frame = zr->jpg_pend[zr->jpg_dma_tail & BUZ_MASK_FRAME];
+ gbuf = &zr->jpg_gbuf[frame];
+ get_fast_time(&gbuf->bs.timestamp);
+
+ if (zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) {
+ gbuf->bs.length = (stat_com & 0x7fffff) >> 1;
+
+ /* update sequence number with the help of the counter in stat_com */
+
+ seq = stat_com >> 24;
+ dif = (seq - zr->jpg_seq_num) & 0xff;
+ zr->jpg_seq_num += dif;
+ } else {
+ gbuf->bs.length = 0;
+ }
+ gbuf->bs.seq = zr->params.TmpDcm == 2 ? (zr->jpg_seq_num >> 1) : zr->jpg_seq_num;
+ gbuf->state = BUZ_STATE_DONE;
+
+ zr->jpg_dma_tail++;
+ }
+}
+
+static void zoran_irq(int irq, void *dev_id, struct pt_regs *regs)
+{
+ u32 stat, astat;
+ int count;
+ struct zoran *zr;
+ unsigned long flags;
+
+ zr = (struct zoran *) dev_id;
+ count = 0;
+
+ spin_lock_irqsave(&zr->lock, flags);
+ while (1) {
+ /* get/clear interrupt status bits */
+ stat = btread(ZR36057_ISR);
+ astat = stat & IRQ_MASK;
+ if (!astat) {
+ break;
+ }
+ btwrite(astat, ZR36057_ISR);
+ IDEBUG(printk(BUZ_DEBUG "-%u: astat %08x stat %08x\n", zr->id, astat, stat));
+
+#if (IRQ_MASK & ZR36057_ISR_GIRQ0)
+ if (astat & ZR36057_ISR_GIRQ0) {
+
+ /* Interrupts may still happen when zr->v4l_memgrab_active is switched off.
+ We simply ignore them */
+
+ if (zr->v4l_memgrab_active) {
+
+/* A lot more checks should be here ... */
+ if ((btread(ZR36057_VSSFGR) & ZR36057_VSSFGR_SnapShot) == 0)
+ printk(KERN_WARNING "%s: BuzIRQ with SnapShot off ???\n", zr->name);
+
+ if (zr->v4l_grab_frame != NO_GRAB_ACTIVE) {
+ /* There is a grab on a frame going on, check if it has finished */
+
+ if ((btread(ZR36057_VSSFGR) & ZR36057_VSSFGR_FrameGrab) == 0) {
+ /* it is finished, notify the user */
+
+ zr->v4l_gbuf[zr->v4l_grab_frame].state = BUZ_STATE_DONE;
+ zr->v4l_grab_frame = NO_GRAB_ACTIVE;
+ zr->v4l_grab_seq++;
+ zr->v4l_pend_tail++;
+ }
+ }
+ if (zr->v4l_grab_frame == NO_GRAB_ACTIVE)
+ wake_up_interruptible(&zr->v4l_capq);
+
+ /* Check if there is another grab queued */
+
+ if (zr->v4l_grab_frame == NO_GRAB_ACTIVE &&
+ zr->v4l_pend_tail != zr->v4l_pend_head) {
+
+ int frame = zr->v4l_pend[zr->v4l_pend_tail & V4L_MASK_FRAME];
+ u32 reg;
+
+ zr->v4l_grab_frame = frame;
+
+ /* Set zr36057 video front end and enable video */
+
+ /* Buffer address */
+
+ reg = zr->v4l_gbuf[frame].fbuffer_bus;
+ btwrite(reg, ZR36057_VDTR);
+ if (zr->video_interlace)
+ reg += zr->gbpl;
+ btwrite(reg, ZR36057_VDBR);
+
+ /* video stride, status, and frame grab register */
+
+#ifdef XAWTV_HACK
+ reg = (zr->gwidth > 720) ? ((zr->gwidth & ~3) - 720) * zr->gbpl / zr->gwidth : 0;
+#else
+ reg = 0;
+#endif
+ if (zr->video_interlace)
+ reg += zr->gbpl;
+ reg = (reg << ZR36057_VSSFGR_DispStride);
+ reg |= ZR36057_VSSFGR_VidOvf;
+ reg |= ZR36057_VSSFGR_SnapShot;
+ reg |= ZR36057_VSSFGR_FrameGrab;
+ btwrite(reg, ZR36057_VSSFGR);
+
+ btor(ZR36057_VDCR_VidEn, ZR36057_VDCR);
+ }
+ }
+ }
+#endif /* (IRQ_MASK & ZR36057_ISR_GIRQ0) */
+
+#if (IRQ_MASK & ZR36057_ISR_GIRQ1)
+ if (astat & ZR36057_ISR_GIRQ1) {
+ unsigned csr = zr36060_read_8(zr, 0x001);
+ unsigned isr = zr36060_read_8(zr, 0x008);
+
+ IDEBUG(printk(KERN_DEBUG "%s: ZR36057_ISR_GIRQ1 60_code=%02x 60_intr=%02x\n",
+ zr->name, csr, isr));
+
+ btand(~ZR36057_ICR_GIRQ1, ZR36057_ICR);
+ zoran_reap_stat_com(zr);
+ zoran_feed_stat_com(zr);
+ }
+#endif /* (IRQ_MASK & ZR36057_ISR_GIRQ1) */
+
+#if (IRQ_MASK & ZR36057_ISR_CodRepIRQ)
+ if (astat & ZR36057_ISR_CodRepIRQ) {
+ IDEBUG(printk(KERN_DEBUG "%s: ZR36057_ISR_CodRepIRQ\n", zr->name));
+ btand(~ZR36057_ICR_CodRepIRQ, ZR36057_ICR);
+ }
+#endif /* (IRQ_MASK & ZR36057_ISR_CodRepIRQ) */
+
+#if (IRQ_MASK & ZR36057_ISR_JPEGRepIRQ)
+ if ((astat & ZR36057_ISR_JPEGRepIRQ) &&
+ (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS ||
+ zr->codec_mode == BUZ_MODE_MOTION_COMPRESS)) {
+ zoran_reap_stat_com(zr);
+ zoran_feed_stat_com(zr);
+ wake_up_interruptible(&zr->jpg_capq);
+ }
+#endif /* (IRQ_MASK & ZR36057_ISR_JPEGRepIRQ) */
+
+ count++;
+ if (count > 10) {
+ printk(KERN_WARNING "%s: irq loop %d\n", zr->name, count);
+ if (count > 20) {
+ btwrite(0, ZR36057_ICR);
+ printk(KERN_ERR "%s: IRQ lockup, cleared int mask\n", zr->name);
+ break;
+ }
+ }
+ }
+ spin_unlock_irqrestore(&zr->lock, flags);
+}
+
+/* Check a zoran_params struct for correctness, insert default params */
+
+static int zoran_check_params(struct zoran *zr, struct zoran_params *params)
+{
+ int err = 0, err0 = 0;
+
+ /* insert constant params */
+
+ params->major_version = MAJOR_VERSION;
+ params->minor_version = MINOR_VERSION;
+
+ /* Check input and norm */
+
+ if (params->input != 0 && params->input != 1) {
+ err++;
+ }
+ if (params->norm != VIDEO_MODE_PAL && params->norm != VIDEO_MODE_NTSC) {
+ err++;
+ }
+ /* Check decimation, set default values for decimation = 1, 2, 4 */
+
+ switch (params->decimation) {
+ case 1:
+
+ params->HorDcm = 1;
+ params->VerDcm = 1;
+ params->TmpDcm = 1;
+ params->field_per_buff = 2;
+
+ params->img_x = 0;
+ params->img_y = 0;
+ params->img_width = 720;
+ params->img_height = tvnorms[params->norm].Ha / 2;
+ break;
+
+ case 2:
+
+ params->HorDcm = 2;
+ params->VerDcm = 1;
+ params->TmpDcm = 2;
+ params->field_per_buff = 1;
+
+ params->img_x = 8;
+ params->img_y = 0;
+ params->img_width = 704;
+ params->img_height = tvnorms[params->norm].Ha / 2;
+ break;
+
+ case 4:
+
+ params->HorDcm = 4;
+ params->VerDcm = 2;
+ params->TmpDcm = 2;
+ params->field_per_buff = 1;
+
+ params->img_x = 8;
+ params->img_y = 0;
+ params->img_width = 704;
+ params->img_height = tvnorms[params->norm].Ha / 2;
+ break;
+
+ case 0:
+
+ /* We have to check the data the user has set */
+
+ if (params->HorDcm != 1 && params->HorDcm != 2 && params->HorDcm != 4)
+ err0++;
+ if (params->VerDcm != 1 && params->VerDcm != 2)
+ err0++;
+ if (params->TmpDcm != 1 && params->TmpDcm != 2)
+ err0++;
+ if (params->field_per_buff != 1 && params->field_per_buff != 2)
+ err0++;
+
+ if (params->img_x < 0)
+ err0++;
+ if (params->img_y < 0)
+ err0++;
+ if (params->img_width < 0)
+ err0++;
+ if (params->img_height < 0)
+ err0++;
+ if (params->img_x + params->img_width > 720)
+ err0++;
+ if (params->img_y + params->img_height > tvnorms[params->norm].Ha / 2)
+ err0++;
+ if (params->img_width % (16 * params->HorDcm) != 0)
+ err0++;
+ if (params->img_height % (8 * params->VerDcm) != 0)
+ err0++;
+
+ if (err0) {
+ err++;
+ }
+ break;
+
+ default:
+ err++;
+ break;
+ }
+
+ if (params->quality > 100)
+ params->quality = 100;
+ if (params->quality < 5)
+ params->quality = 5;
+
+ if (params->APPn < 0)
+ params->APPn = 0;
+ if (params->APPn > 15)
+ params->APPn = 15;
+ if (params->APP_len < 0)
+ params->APP_len = 0;
+ if (params->APP_len > 60)
+ params->APP_len = 60;
+ if (params->COM_len < 0)
+ params->COM_len = 0;
+ if (params->COM_len > 60)
+ params->COM_len = 60;
+
+ if (err)
+ return -EINVAL;
+
+ return 0;
+
+}
+static void zoran_open_init_params(struct zoran *zr)
+{
+ int i;
+
+ /* Per default, map the V4L Buffers */
+
+ zr->map_mjpeg_buffers = 0;
+
+ /* User must explicitly set a window */
+
+ zr->window_set = 0;
+
+ zr->window.x = 0;
+ zr->window.y = 0;
+ zr->window.width = 0;
+ zr->window.height = 0;
+ zr->window.chromakey = 0;
+ zr->window.flags = 0;
+ zr->window.clips = NULL;
+ zr->window.clipcount = 0;
+
+ zr->video_interlace = 0;
+
+ zr->v4l_memgrab_active = 0;
+ zr->v4l_overlay_active = 0;
+
+ zr->v4l_grab_frame = NO_GRAB_ACTIVE;
+ zr->v4l_grab_seq = 0;
+
+ zr->gwidth = 0;
+ zr->gheight = 0;
+ zr->gformat = 0;
+ zr->gbpl = 0;
+
+ /* DMA ring stuff for V4L */
+
+ zr->v4l_pend_tail = 0;
+ zr->v4l_pend_head = 0;
+ for (i = 0; i < v4l_nbufs; i++) {
+ zr->v4l_gbuf[i].state = BUZ_STATE_USER; /* nothing going on */
+ }
+
+ /* Set necessary params and call zoran_check_params to set the defaults */
+
+ zr->params.decimation = 1;
+
+ zr->params.quality = 50; /* default compression factor 8 */
+ zr->params.odd_even = 1;
+
+ zr->params.APPn = 0;
+ zr->params.APP_len = 0; /* No APPn marker */
+ for (i = 0; i < 60; i++)
+ zr->params.APP_data[i] = 0;
+
+ zr->params.COM_len = 0; /* No COM marker */
+ for (i = 0; i < 60; i++)
+ zr->params.COM_data[i] = 0;
+
+ zr->params.VFIFO_FB = 0;
+
+ memset(zr->params.reserved, 0, sizeof(zr->params.reserved));
+
+ zr->params.jpeg_markers = JPEG_MARKER_DHT | JPEG_MARKER_DQT;
+
+ i = zoran_check_params(zr, &zr->params);
+ if (i)
+ printk(KERN_ERR "%s: zoran_open_init_params internal error\n", zr->name);
+}
+
+/*
+ * Open a buz card. Right now the flags stuff is just playing
+ */
+
+static int zoran_open(struct video_device *dev, int flags)
+{
+ struct zoran *zr = (struct zoran *) dev;
+
+ DEBUG(printk(KERN_INFO ": zoran_open\n"));
+
+ switch (flags) {
+
+ case 0:
+ if (zr->user)
+ return -EBUSY;
+ zr->user++;
+
+ if (v4l_fbuffer_alloc(zr) < 0) {
+ zr->user--;
+ return -ENOMEM;
+ }
+ /* default setup */
+
+ zoran_open_init_params(zr);
+
+ zr36057_enable_jpg(zr, BUZ_MODE_IDLE);
+
+ btwrite(IRQ_MASK, ZR36057_ISR); // Clears interrupts
+
+ btor(ZR36057_ICR_IntPinEn, ZR36057_ICR);
+
+ break;
+
+ default:
+ return -EBUSY;
+
+ }
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void zoran_close(struct video_device *dev)
+{
+ struct zoran *zr = (struct zoran *) dev;
+
+ DEBUG(printk(KERN_INFO ": zoran_close\n"));
+
+ /* disable interrupts */
+ btand(~ZR36057_ICR_IntPinEn, ZR36057_ICR);
+
+ /* wake up sleeping beauties */
+ wake_up_interruptible(&zr->v4l_capq);
+ wake_up_interruptible(&zr->jpg_capq);
+
+ zr36057_enable_jpg(zr, BUZ_MODE_IDLE);
+ zr36057_set_memgrab(zr, 0);
+ if (zr->v4l_overlay_active)
+ zr36057_overlay(zr, 0);
+
+ zr->user--;
+
+ v4l_fbuffer_free(zr);
+ jpg_fbuffer_free(zr);
+ zr->jpg_nbufs = 0;
+
+ MOD_DEC_USE_COUNT;
+ DEBUG(printk(KERN_INFO ": zoran_close done\n"));
+}
+
+
+static long zoran_read(struct video_device *dev, char *buf, unsigned long count, int nonblock)
+{
+ return -EINVAL;
+}
+
+static long zoran_write(struct video_device *dev, const char *buf, unsigned long count, int nonblock)
+{
+ return -EINVAL;
+}
+
+/*
+ * ioctl routine
+ */
+
+
+static int zoran_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct zoran *zr = (struct zoran *) dev;
+
+ switch (cmd) {
+
+ case VIDIOCGCAP:
+ {
+ struct video_capability b;
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCGCAP\n"));
+ strncpy(b.name, zr->video_dev.name, sizeof(b.name));
+ b.type = VID_TYPE_CAPTURE |
+ VID_TYPE_OVERLAY |
+ VID_TYPE_CLIPPING |
+ VID_TYPE_FRAMERAM |
+ VID_TYPE_SCALES;
+ /* theoretically we could also flag VID_TYPE_SUBCAPTURE
+ but this is not even implemented in the BTTV driver */
+
+ b.channels = 2; /* composite, svhs */
+ b.audios = 0;
+ b.maxwidth = BUZ_MAX_WIDTH;
+ b.maxheight = BUZ_MAX_HEIGHT;
+ b.minwidth = BUZ_MIN_WIDTH;
+ b.minheight = BUZ_MIN_HEIGHT;
+ if (copy_to_user(arg, &b, sizeof(b))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ case VIDIOCGCHAN:
+ {
+ struct video_channel v;
+
+ if (copy_from_user(&v, arg, sizeof(v))) {
+ return -EFAULT;
+ }
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCGCHAN for channel %d\n", v.channel));
+ switch (v.channel) {
+ case 0:
+ strcpy(v.name, "Composite");
+ break;
+ case 1:
+ strcpy(v.name, "SVHS");
+ break;
+ default:
+ return -EINVAL;
+ }
+ v.tuners = 0;
+ v.flags = 0;
+ v.type = VIDEO_TYPE_CAMERA;
+ v.norm = zr->params.norm;
+ if (copy_to_user(arg, &v, sizeof(v))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ /* RJ: the documentation at http://roadrunner.swansea.linux.org.uk/v4lapi.shtml says:
+
+ * "The VIDIOCSCHAN ioctl takes an integer argument and switches the capture to this input."
+ * ^^^^^^^
+ * The famos BTTV driver has it implemented with a struct video_channel argument
+ * and we follow it for compatibility reasons
+ *
+ * BTW: this is the only way the user can set the norm!
+ */
+
+ case VIDIOCSCHAN:
+ {
+ struct video_channel v;
+ int input;
+ int on, res;
+
+ if (copy_from_user(&v, arg, sizeof(v))) {
+ return -EFAULT;
+ }
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCSCHAN: channel=%d, norm=%d\n", v.channel, v.norm));
+ switch (v.channel) {
+ case 0:
+ input = 3;
+ break;
+ case 1:
+ input = 7;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (v.norm != VIDEO_MODE_PAL
+ && v.norm != VIDEO_MODE_NTSC) {
+ return -EINVAL;
+ }
+ zr->params.norm = v.norm;
+ zr->params.input = v.channel;
+
+ /* We switch overlay off and on since a change in the norm
+ needs different VFE settings */
+
+ on = zr->v4l_overlay_active && !zr->v4l_memgrab_active;
+ if (on)
+ zr36057_overlay(zr, 0);
+
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &input);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &zr->params.norm);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_NORM, &zr->params.norm);
+
+ if (on)
+ zr36057_overlay(zr, 1);
+
+ /* Make sure the changes come into effect */
+ res = wait_grab_pending(zr);
+ if (res)
+ return res;
+
+ return 0;
+ }
+
+ case VIDIOCGTUNER:
+ case VIDIOCSTUNER:
+ return -EINVAL;
+
+ case VIDIOCGPICT:
+ {
+ struct video_picture p = zr->picture;
+
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCGPICT\n"));
+ p.depth = zr->buffer.depth;
+ switch (zr->buffer.depth) {
+ case 15:
+ p.palette = VIDEO_PALETTE_RGB555;
+ break;
+
+ case 16:
+ p.palette = VIDEO_PALETTE_RGB565;
+ break;
+
+ case 24:
+ p.palette = VIDEO_PALETTE_RGB24;
+ break;
+
+ case 32:
+ p.palette = VIDEO_PALETTE_RGB32;
+ break;
+ }
+
+ if (copy_to_user(arg, &p, sizeof(p))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ case VIDIOCSPICT:
+ {
+ struct video_picture p;
+
+ if (copy_from_user(&p, arg, sizeof(p))) {
+ return -EFAULT;
+ }
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_PICTURE, &p);
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCSPICT bri=%d hue=%d col=%d con=%d dep=%d pal=%d\n",
+ p.brightness, p.hue, p.colour, p.contrast, p.depth, p.palette));
+ /* The depth and palette values have no meaning to us,
+ should we return -EINVAL if they don't fit ? */
+ zr->picture = p;
+ return 0;
+ }
+
+ case VIDIOCCAPTURE:
+ {
+ int v, res;
+
+ if (copy_from_user(&v, arg, sizeof(v))) {
+ return -EFAULT;
+ }
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCCAPTURE: %d\n", v));
+ /* If there is nothing to do, return immediatly */
+
+ if ((v && zr->v4l_overlay_active) || (!v && !zr->v4l_overlay_active))
+ return 0;
+
+ if (v == 0) {
+ zr->v4l_overlay_active = 0;
+ if (!zr->v4l_memgrab_active)
+ zr36057_overlay(zr, 0);
+ /* When a grab is running, the video simply won't be switched on any more */
+ } else {
+ if (!zr->buffer_set || !zr->window_set) {
+ return -EINVAL;
+ }
+ zr->v4l_overlay_active = 1;
+ if (!zr->v4l_memgrab_active)
+ zr36057_overlay(zr, 1);
+ /* When a grab is running, the video will be switched on when grab is finished */
+ }
+ /* Make sure the changes come into effect */
+ res = wait_grab_pending(zr);
+ if (res)
+ return res;
+ return 0;
+ }
+
+ case VIDIOCGWIN:
+ {
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCGWIN\n"));
+ if (copy_to_user(arg, &zr->window, sizeof(zr->window))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ case VIDIOCSWIN:
+ {
+ struct video_clip *vcp;
+ struct video_window vw;
+ int on, end, res;
+
+ if (copy_from_user(&vw, arg, sizeof(vw))) {
+ return -EFAULT;
+ }
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCSWIN: x=%d y=%d w=%d h=%d clipcount=%d\n", vw.x, vw.y, vw.width, vw.height, vw.clipcount));
+ if (!zr->buffer_set) {
+ return -EINVAL;
+ }
+ /*
+ * The video front end needs 4-byte alinged line sizes, we correct that
+ * silently here if necessary
+ */
+
+ if (zr->buffer.depth == 15 || zr->buffer.depth == 16) {
+ end = (vw.x + vw.width) & ~1; /* round down */
+ vw.x = (vw.x + 1) & ~1; /* round up */
+ vw.width = end - vw.x;
+ }
+ if (zr->buffer.depth == 24) {
+ end = (vw.x + vw.width) & ~3; /* round down */
+ vw.x = (vw.x + 3) & ~3; /* round up */
+ vw.width = end - vw.x;
+ }
+#if 0
+ // At least xawtv seems to care about the following - just leave it away
+ /*
+ * Also corrected silently (as long as window fits at all):
+ * video not fitting the screen
+ */
+#if 0
+ if (vw.x < 0 || vw.y < 0 || vw.x + vw.width > zr->buffer.width ||
+ vw.y + vw.height > zr->buffer.height) {
+ printk(BUZ_ERR ": VIDIOCSWIN: window does not fit frame buffer: %dx%d+%d*%d\n",
+ vw.width, vw.height, vw.x, vw.y);
+ return -EINVAL;
+ }
+#else
+ if (vw.x < 0)
+ vw.x = 0;
+ if (vw.y < 0)
+ vw.y = 0;
+ if (vw.x + vw.width > zr->buffer.width)
+ vw.width = zr->buffer.width - vw.x;
+ if (vw.y + vw.height > zr->buffer.height)
+ vw.height = zr->buffer.height - vw.y;
+#endif
+#endif
+
+ /* Check for vaild parameters */
+ if (vw.width < BUZ_MIN_WIDTH || vw.height < BUZ_MIN_HEIGHT ||
+ vw.width > BUZ_MAX_WIDTH || vw.height > BUZ_MAX_HEIGHT) {
+ return -EINVAL;
+ }
+#ifdef XAWTV_HACK
+ if (vw.width > 720)
+ vw.width = 720;
+#endif
+
+ zr->window.x = vw.x;
+ zr->window.y = vw.y;
+ zr->window.width = vw.width;
+ zr->window.height = vw.height;
+ zr->window.chromakey = 0;
+ zr->window.flags = 0; // RJ: Is this intended for interlace on/off ?
+
+ zr->window.clips = NULL;
+ zr->window.clipcount = vw.clipcount;
+
+ /*
+ * If an overlay is running, we have to switch it off
+ * and switch it on again in order to get the new settings in effect.
+ *
+ * We also want to avoid that the overlay mask is written
+ * when an overlay is running.
+ */
+
+ on = zr->v4l_overlay_active && !zr->v4l_memgrab_active;
+ if (on)
+ zr36057_overlay(zr, 0);
+
+ /*
+ * Write the overlay mask if clips are wanted.
+ */
+ if (vw.clipcount) {
+ vcp = vmalloc(sizeof(struct video_clip) * (vw.clipcount + 4));
+ if (vcp == NULL) {
+ return -ENOMEM;
+ }
+ if (copy_from_user(vcp, vw.clips, sizeof(struct video_clip) * vw.clipcount)) {
+ vfree(vcp);
+ return -EFAULT;
+ }
+ write_overlay_mask(zr, vcp, vw.clipcount);
+ vfree(vcp);
+ }
+ if (on)
+ zr36057_overlay(zr, 1);
+ zr->window_set = 1;
+
+ /* Make sure the changes come into effect */
+ res = wait_grab_pending(zr);
+ if (res)
+ return res;
+
+ return 0;
+ }
+
+ case VIDIOCGFBUF:
+ {
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCGFBUF\n"));
+ if (copy_to_user(arg, &zr->buffer, sizeof(zr->buffer))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ case VIDIOCSFBUF:
+ {
+ struct video_buffer v;
+
+ if (!capable(CAP_SYS_ADMIN)
+ || !capable(CAP_SYS_RAWIO))
+ return -EPERM;
+
+ if (copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCSFBUF: base=0x%x w=%d h=%d depth=%d bpl=%d\n", (u32) v.base, v.width, v.height, v.depth, v.bytesperline));
+ if (zr->v4l_overlay_active) {
+ /* Has the user gotten crazy ... ? */
+ return -EINVAL;
+ }
+ if (v.depth != 15
+ && v.depth != 16
+ && v.depth != 24
+ && v.depth != 32) {
+ return -EINVAL;
+ }
+ if (v.height <= 0 || v.width <= 0 || v.bytesperline <= 0) {
+ return -EINVAL;
+ }
+ if (v.bytesperline & 3) {
+ return -EINVAL;
+ }
+ if (v.base) {
+ zr->buffer.base = (void *) ((unsigned long) v.base & ~3);
+ }
+ zr->buffer.height = v.height;
+ zr->buffer.width = v.width;
+ zr->buffer.depth = v.depth;
+ zr->buffer.bytesperline = v.bytesperline;
+
+ if (zr->buffer.base)
+ zr->buffer_set = 1;
+ zr->window_set = 0; /* The user should set new window parameters */
+ return 0;
+ }
+
+ /* RJ: what is VIDIOCKEY intended to do ??? */
+
+ case VIDIOCGFREQ:
+ case VIDIOCSFREQ:
+ case VIDIOCGAUDIO:
+ case VIDIOCSAUDIO:
+ return -EINVAL;
+
+ case VIDIOCSYNC:
+ {
+ int v;
+
+ if (copy_from_user(&v, arg, sizeof(v))) {
+ return -EFAULT;
+ }
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCSYNC %d\n", v));
+ return v4l_sync(zr, v);
+ }
+
+ case VIDIOCMCAPTURE:
+ {
+ struct video_mmap vm;
+
+ if (copy_from_user((void *) &vm, (void *) arg, sizeof(vm))) {
+ return -EFAULT;
+ }
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCMCAPTURE frame=%d geom=%dx%d fmt=%d\n",
+ vm.frame, vm.height, vm.width, vm.format));
+ return v4l_grab(zr, &vm);
+ }
+
+ case VIDIOCGMBUF:
+ {
+ struct video_mbuf vm;
+ int i;
+
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCGMBUF\n"));
+
+ vm.size = v4l_nbufs * v4l_bufsize;
+ vm.frames = v4l_nbufs;
+ for (i = 0; i < v4l_nbufs; i++) {
+ vm.offsets[i] = i * v4l_bufsize;
+ }
+
+ /* The next mmap will map the V4L buffers */
+ zr->map_mjpeg_buffers = 0;
+
+ if (copy_to_user(arg, &vm, sizeof(vm))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ case VIDIOCGUNIT:
+ {
+ struct video_unit vu;
+
+ IOCTL_DEBUG(printk("buz ioctl VIDIOCGUNIT\n"));
+ vu.video = zr->video_dev.minor;
+ vu.vbi = VIDEO_NO_UNIT;
+ vu.radio = VIDEO_NO_UNIT;
+ vu.audio = VIDEO_NO_UNIT;
+ vu.teletext = VIDEO_NO_UNIT;
+ if (copy_to_user(arg, &vu, sizeof(vu)))
+ return -EFAULT;
+ return 0;
+ }
+
+ /*
+ * RJ: In principal we could support subcaptures for V4L grabbing.
+ * Not even the famous BTTV driver has them, however.
+ * If there should be a strong demand, one could consider
+ * to implement them.
+ */
+ case VIDIOCGCAPTURE:
+ case VIDIOCSCAPTURE:
+ return -EINVAL;
+
+ case BUZIOC_G_PARAMS:
+ {
+ IOCTL_DEBUG(printk("buz ioctl BUZIOC_G_PARAMS\n"));
+ if (copy_to_user(arg, &(zr->params), sizeof(zr->params)))
+ return -EFAULT;
+ return 0;
+ }
+
+ case BUZIOC_S_PARAMS:
+ {
+ struct zoran_params bp;
+ int input, on;
+
+ if (zr->codec_mode != BUZ_MODE_IDLE) {
+ return -EINVAL;
+ }
+ if (copy_from_user(&bp, arg, sizeof(bp))) {
+ return -EFAULT;
+ }
+ IOCTL_DEBUG(printk("buz ioctl BUZIOC_S_PARAMS\n"));
+
+ /* Check the params first before overwriting our internal values */
+
+ if (zoran_check_params(zr, &bp))
+ return -EINVAL;
+
+ zr->params = bp;
+
+ /* Make changes of input and norm go into effect immediatly */
+
+ /* We switch overlay off and on since a change in the norm
+ needs different VFE settings */
+
+ on = zr->v4l_overlay_active && !zr->v4l_memgrab_active;
+ if (on)
+ zr36057_overlay(zr, 0);
+
+ input = zr->params.input == 0 ? 3 : 7;
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &input);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &zr->params.norm);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_NORM, &zr->params.norm);
+
+ if (on)
+ zr36057_overlay(zr, 1);
+
+ if (copy_to_user(arg, &bp, sizeof(bp))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ case BUZIOC_REQBUFS:
+ {
+ struct zoran_requestbuffers br;
+
+ if (zr->jpg_buffers_allocated) {
+ return -EINVAL;
+ }
+ if (copy_from_user(&br, arg, sizeof(br))) {
+ return -EFAULT;
+ }
+ IOCTL_DEBUG(printk("buz ioctl BUZIOC_REQBUFS count = %lu size=%lu\n",
+ br.count, br.size));
+ /* Enforce reasonable lower and upper limits */
+ if (br.count < 4)
+ br.count = 4; /* Could be choosen smaller */
+ if (br.count > BUZ_MAX_FRAME)
+ br.count = BUZ_MAX_FRAME;
+ br.size = PAGE_ALIGN(br.size);
+ if (br.size < 8192)
+ br.size = 8192; /* Arbitrary */
+ /* br.size is limited by 1 page for the stat_com tables to a Maximum of 2 MB */
+ if (br.size > (512 * 1024))
+ br.size = (512 * 1024); /* 512 K should be enough */
+ if (zr->need_contiguous && br.size > MAX_KMALLOC_MEM)
+ br.size = MAX_KMALLOC_MEM;
+
+ zr->jpg_nbufs = br.count;
+ zr->jpg_bufsize = br.size;
+
+ if (jpg_fbuffer_alloc(zr))
+ return -ENOMEM;
+
+ /* The next mmap will map the MJPEG buffers */
+ zr->map_mjpeg_buffers = 1;
+
+ if (copy_to_user(arg, &br, sizeof(br))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ case BUZIOC_QBUF_CAPT:
+ {
+ int nb;
+
+ if (copy_from_user((void *) &nb, (void *) arg, sizeof(int))) {
+ return -EFAULT;
+ }
+ IOCTL_DEBUG(printk("buz ioctl BUZIOC_QBUF_CAPT %d\n", nb));
+ return jpg_qbuf(zr, nb, BUZ_MODE_MOTION_COMPRESS);
+ }
+
+ case BUZIOC_QBUF_PLAY:
+ {
+ int nb;
+
+ if (copy_from_user((void *) &nb, (void *) arg, sizeof(int))) {
+ return -EFAULT;
+ }
+ IOCTL_DEBUG(printk("buz ioctl BUZIOC_QBUF_PLAY %d\n", nb));
+ return jpg_qbuf(zr, nb, BUZ_MODE_MOTION_DECOMPRESS);
+ }
+
+ case BUZIOC_SYNC:
+ {
+ struct zoran_sync bs;
+ int res;
+
+ IOCTL_DEBUG(printk("buz ioctl BUZIOC_SYNC\n"));
+ res = jpg_sync(zr, &bs);
+ if (copy_to_user(arg, &bs, sizeof(bs))) {
+ return -EFAULT;
+ }
+ return res;
+ }
+
+ case BUZIOC_G_STATUS:
+ {
+ struct zoran_status bs;
+ int norm, input, status;
+
+ if (zr->codec_mode != BUZ_MODE_IDLE) {
+ return -EINVAL;
+ }
+ if (copy_from_user(&bs, arg, sizeof(bs))) {
+ return -EFAULT;
+ }
+ IOCTL_DEBUG(printk("buz ioctl BUZIOC_G_STATUS\n"));
+ switch (bs.input) {
+ case 0:
+ input = 3;
+ break;
+ case 1:
+ input = 7;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Set video norm to VIDEO_MODE_AUTO */
+
+ norm = VIDEO_MODE_AUTO;
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &input);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &norm);
+
+ /* sleep 1 second */
+
+ schedule_timeout(HZ);
+
+ /* Get status of video decoder */
+
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_GET_STATUS, &status);
+ bs.signal = (status & DECODER_STATUS_GOOD) ? 1 : 0;
+ bs.norm = (status & DECODER_STATUS_NTSC) ? VIDEO_MODE_NTSC : VIDEO_MODE_PAL;
+ bs.color = (status & DECODER_STATUS_COLOR) ? 1 : 0;
+
+ /* restore previous input and norm */
+ input = zr->params.input == 0 ? 3 : 7;
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &input);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &zr->params.norm);
+
+ if (copy_to_user(arg, &bs, sizeof(bs))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ default:
+ return -ENOIOCTLCMD;
+
+ }
+ return 0;
+}
+
+
+/*
+ * This maps the buffers to user space.
+ *
+ * Depending on the state of zr->map_mjpeg_buffers
+ * the V4L or the MJPEG buffers are mapped
+ *
+ */
+
+static int zoran_mmap(struct video_device *dev, const char *adr, unsigned long size)
+{
+ struct zoran *zr = (struct zoran *) dev;
+ unsigned long start = (unsigned long) adr;
+ unsigned long page, pos, todo, fraglen;
+ int i, j;
+
+ if (zr->map_mjpeg_buffers) {
+ /* Map the MJPEG buffers */
+
+ if (!zr->jpg_buffers_allocated) {
+ return -ENOMEM;
+ }
+ if (size > zr->jpg_nbufs * zr->jpg_bufsize) {
+ return -EINVAL;
+ }
+
+ for (i = 0; i < zr->jpg_nbufs; i++) {
+ for (j = 0; j < zr->jpg_bufsize / PAGE_SIZE; j++) {
+ fraglen = (zr->jpg_gbuf[i].frag_tab[2 * j + 1] & ~1) << 1;
+ todo = size;
+ if (todo > fraglen)
+ todo = fraglen;
+ pos = (unsigned long) zr->jpg_gbuf[i].frag_tab[2 * j];
+ page = virt_to_phys(bus_to_virt(pos)); /* should just be pos on i386 */
+ if (remap_page_range(start, page, todo, PAGE_SHARED)) {
+ printk(KERN_ERR "%s: zoran_mmap(V4L): remap_page_range failed\n", zr->name);
+ return -EAGAIN;
+ }
+ size -= todo;
+ start += todo;
+ if (size == 0)
+ break;
+ if (zr->jpg_gbuf[i].frag_tab[2 * j + 1] & 1)
+ break; /* was last fragment */
+ }
+ if (size == 0)
+ break;
+ }
+ } else {
+ /* Map the V4L buffers */
+
+ if (size > v4l_nbufs * v4l_bufsize) {
+ return -EINVAL;
+ }
+
+ for (i = 0; i < v4l_nbufs; i++) {
+ todo = size;
+ if (todo > v4l_bufsize)
+ todo = v4l_bufsize;
+ page = zr->v4l_gbuf[i].fbuffer_phys;
+ DEBUG(printk("V4L remap page range %d 0x%x %d to 0x%x\n", i, page, todo, start));
+ if (remap_page_range(start, page, todo, PAGE_SHARED)) {
+ printk(KERN_ERR "%s: zoran_mmap(V4L): remap_page_range failed\n", zr->name);
+ return -EAGAIN;
+ }
+ size -= todo;
+ start += todo;
+ if (size == 0)
+ break;
+ }
+ }
+ return 0;
+}
+
+static int zoran_init_done(struct video_device *dev)
+{
+ return 0;
+}
+
+static struct video_device zoran_template =
+{
+ BUZ_NAME,
+ VID_TYPE_CAPTURE | VID_TYPE_OVERLAY | VID_TYPE_CLIPPING | VID_TYPE_FRAMERAM |
+ VID_TYPE_SCALES | VID_TYPE_SUBCAPTURE,
+ VID_HARDWARE_ZR36067,
+ zoran_open,
+ zoran_close,
+ zoran_read,
+ zoran_write,
+ NULL,
+ zoran_ioctl,
+ zoran_mmap,
+ zoran_init_done,
+ NULL,
+ 0,
+ 0
+};
+
+static int zr36057_init(int i)
+{
+ struct zoran *zr = &zoran[i];
+ unsigned long mem;
+ unsigned mem_needed;
+ int j;
+ int rev;
+
+ /* reset zr36057 */
+ btwrite(0, ZR36057_SPGPPCR);
+ mdelay(10);
+
+ /* default setup of all parameters which will persist beetween opens */
+
+ zr->user = 0;
+
+ init_waitqueue_head(&zr->v4l_capq);
+ init_waitqueue_head(&zr->jpg_capq);
+
+ zr->map_mjpeg_buffers = 0; /* Map V4L buffers by default */
+
+ zr->jpg_nbufs = 0;
+ zr->jpg_bufsize = 0;
+ zr->jpg_buffers_allocated = 0;
+
+ zr->buffer_set = 0; /* Flag if frame buffer has been set */
+ zr->buffer.base = (void *) vidmem;
+ zr->buffer.width = 0;
+ zr->buffer.height = 0;
+ zr->buffer.depth = 0;
+ zr->buffer.bytesperline = 0;
+
+ zr->params.norm = default_norm ? 1 : 0; /* Avoid nonsense settings from user */
+ zr->params.input = default_input ? 1 : 0; /* Avoid nonsense settings from user */
+ zr->video_interlace = 0;
+
+ /* Should the following be reset at every open ? */
+
+ zr->picture.colour = 32768;
+ zr->picture.brightness = 32768;
+ zr->picture.hue = 32768;
+ zr->picture.contrast = 32768;
+ zr->picture.whiteness = 0;
+ zr->picture.depth = 0;
+ zr->picture.palette = 0;
+
+ for (j = 0; j < VIDEO_MAX_FRAME; j++) {
+ zr->v4l_gbuf[i].fbuffer = 0;
+ zr->v4l_gbuf[i].fbuffer_phys = 0;
+ zr->v4l_gbuf[i].fbuffer_bus = 0;
+ }
+
+ zr->stat_com = 0;
+
+ /* default setup (will be repeated at every open) */
+
+ zoran_open_init_params(zr);
+
+ /* allocate memory *before* doing anything to the hardware in case allocation fails */
+
+ /* STAT_COM table and overlay mask */
+
+ mem_needed = (BUZ_NUM_STAT_COM + ((BUZ_MAX_WIDTH + 31) / 32) * BUZ_MAX_HEIGHT) * 4;
+ mem = (unsigned long) kmalloc(mem_needed, GFP_KERNEL);
+ if (!mem) {
+ return -ENOMEM;
+ }
+ memset((void *) mem, 0, mem_needed);
+
+ zr->stat_com = (u32 *) mem;
+ for (j = 0; j < BUZ_NUM_STAT_COM; j++) {
+ zr->stat_com[j] = 1; /* mark as unavailable to zr36057 */
+ }
+ zr->overlay_mask = (u32 *) (mem + BUZ_NUM_STAT_COM * 4);
+
+ /* Initialize zr->jpg_gbuf */
+
+ for (j = 0; j < BUZ_MAX_FRAME; j++) {
+ zr->jpg_gbuf[j].frag_tab = 0;
+ zr->jpg_gbuf[j].frag_tab_bus = 0;
+ zr->jpg_gbuf[j].state = BUZ_STATE_USER;
+ zr->jpg_gbuf[j].bs.frame = j;
+ }
+
+ /* take zr36057 out of reset now */
+ btwrite(ZR36057_SPGPPCR_SoftReset, ZR36057_SPGPPCR);
+ mdelay(10);
+
+ /* stop all DMA processes */
+ btwrite(ZR36057_MCTCR_CFlush, ZR36057_MCTCR);
+ btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR);
+ /* assert P_Reset */
+ btwrite(0, ZR36057_JPC);
+
+ switch(zr->board)
+ {
+ case BOARD_BUZ:
+
+ /* set up GPIO direction */
+ btwrite(ZR36057_SPGPPCR_SoftReset | 0, ZR36057_SPGPPCR);
+
+ /* Set up guest bus timing - Guests 0..3 Tdur=12, Trec=3 */
+ btwrite((GPIO_MASK << 24) | 0x8888, ZR36057_GPPGCR1);
+ mdelay(10);
+
+ /* reset video decoder */
+
+ GPIO(zr, 0, 0);
+ mdelay(10);
+ GPIO(zr, 0, 1);
+ mdelay(10);
+
+ /* reset JPEG codec */
+ zr36060_sleep(zr, 0);
+ mdelay(10);
+ zr36060_reset(zr);
+ mdelay(10);
+
+ /* display codec revision */
+ if ((rev=zr36060_read_8(zr, 0x022)) == 0x33) {
+ printk(KERN_INFO "%s: Zoran ZR36060 (rev %d)\n",
+ zr->name, zr36060_read_8(zr, 0x023));
+ } else {
+ printk(KERN_ERR "%s: Zoran ZR36060 not found (Rev=%d)\n", zr->name, rev);
+ kfree((void *) zr->stat_com);
+ return -1;
+ }
+ break;
+
+ case BOARD_LML33:
+// btwrite(btread(ZR36057_SPGPPCR)&~ZR36057_SPGPPCR_SoftReset , ZR36057_SPGPPCR);
+// udelay(100);
+// btwrite(btread(ZR36057_SPGPPCR)|ZR36057_SPGPPCR_SoftReset , ZR36057_SPGPPCR);
+// udelay(1000);
+
+ /*
+ * Set up the GPIO direction
+ */
+ btwrite(btread(ZR36057_SPGPPCR_SoftReset)|0 , ZR36057_SPGPPCR);
+ /* Set up guest bus timing - Guests 0..2 Tdur=12, Trec=3 */
+ btwrite(0xFF00F888, ZR36057_GPPGCR1);
+ mdelay(10);
+ GPIO(zr, 5, 0); /* Analog video bypass */
+ udelay(3000);
+ GPIO(zr, 0, 0); /* Reset 819 */
+ udelay(3000);
+ GPIO(zr, 0, 1); /* 819 back */
+ udelay(3000);
+ /* reset JPEG codec */
+ zr36060_sleep(zr, 0);
+ udelay(3000);
+ zr36060_reset(zr);
+ udelay(3000);
+
+ /* display codec revision */
+ if ((rev=zr36060_read_8(zr, 0x022)) == 0x33) {
+ printk(KERN_INFO "%s: Zoran ZR36060 (rev %d)\n",
+ zr->name, zr36060_read_8(zr, 0x023));
+ } else {
+ printk(KERN_ERR "%s: Zoran ZR36060 not found (rev=%d)\n", zr->name, rev);
+ kfree((void *) zr->stat_com);
+ return -1;
+ }
+ break;
+ }
+ /* i2c */
+ memcpy(&zr->i2c, &zoran_i2c_bus_template, sizeof(struct i2c_bus));
+ sprintf(zr->i2c.name, "zoran%u", zr->id);
+ zr->i2c.data = zr;
+ if (i2c_register_bus(&zr->i2c) < 0) {
+ kfree((void *) zr->stat_com);
+ return -1;
+ }
+ /*
+ * Now add the template and register the device unit.
+ */
+ memcpy(&zr->video_dev, &zoran_template, sizeof(zoran_template));
+ sprintf(zr->video_dev.name, "zoran%u", zr->id);
+ if (video_register_device(&zr->video_dev, VFL_TYPE_GRABBER) < 0) {
+ i2c_unregister_bus(&zr->i2c);
+ kfree((void *) zr->stat_com);
+ return -1;
+ }
+ /* toggle JPEG codec sleep to sync PLL */
+ zr36060_sleep(zr, 1);
+ mdelay(10);
+ zr36060_sleep(zr, 0);
+ mdelay(10);
+
+ /* Enable bus-mastering */
+ pci_set_master(zr->pci_dev);
+
+ j = zr->params.input == 0 ? 3 : 7;
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &j);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &zr->params.norm);
+ i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_NORM, &zr->params.norm);
+
+ /* set individual interrupt enables (without GIRQ0)
+ but don't global enable until zoran_open() */
+
+ btwrite(IRQ_MASK & ~ZR36057_ISR_GIRQ0, ZR36057_ICR);
+
+ if(request_irq(zr->pci_dev->irq, zoran_irq,
+ SA_SHIRQ | SA_INTERRUPT, zr->name, (void *) zr)<0)
+ {
+ printk(KERN_ERR "%s: Can't assign irq.\n", zr->name);
+ video_unregister_device(&zr->video_dev);
+ i2c_unregister_bus(&zr->i2c);
+ kfree((void *) zr->stat_com);
+ return -1;
+ }
+ zr->initialized = 1;
+ return 0;
+}
+
+
+
+static void release_zoran(void)
+{
+ u8 command;
+ int i;
+ struct zoran *zr;
+
+ for (i = 0; i < zoran_num; i++) {
+ zr = &zoran[i];
+
+ if (!zr->initialized)
+ continue;
+
+ /* unregister i2c_bus */
+ i2c_unregister_bus((&zr->i2c));
+
+ /* disable PCI bus-mastering */
+ pci_read_config_byte(zr->pci_dev, PCI_COMMAND, &command);
+ command &= ~PCI_COMMAND_MASTER;
+ pci_write_config_byte(zr->pci_dev, PCI_COMMAND, command);
+
+ /* put chip into reset */
+ btwrite(0, ZR36057_SPGPPCR);
+
+ free_irq(zr->pci_dev->irq, zr);
+
+ /* unmap and free memory */
+
+ kfree((void *) zr->stat_com);
+
+ iounmap(zr->zr36057_mem);
+
+ video_unregister_device(&zr->video_dev);
+ }
+}
+
+/*
+ * Scan for a Buz card (actually for the PCI controller ZR36057),
+ * request the irq and map the io memory
+ */
+
+static int find_zr36057(void)
+{
+ unsigned char latency;
+ struct zoran *zr;
+ struct pci_dev *dev = NULL;
+
+ zoran_num = 0;
+
+ while (zoran_num < BUZ_MAX
+ && (dev = pci_find_device(PCI_VENDOR_ID_ZORAN, PCI_DEVICE_ID_ZORAN_36057, dev)) != NULL) {
+ zr = &zoran[zoran_num];
+ zr->pci_dev = dev;
+ zr->zr36057_mem = NULL;
+ zr->id = zoran_num;
+ sprintf(zr->name, "zoran%u", zr->id);
+
+ spin_lock_init(&zr->lock);
+
+ if (pci_enable_device(dev))
+ continue;
+
+ zr->zr36057_adr = pci_resource_start(zr->pci_dev, 0);
+ pci_read_config_byte(zr->pci_dev, PCI_CLASS_REVISION, &zr->revision);
+ if (zr->revision < 2) {
+ printk(KERN_INFO "%s: Zoran ZR36057 (rev %d) irq: %d, memory: 0x%08x.\n",
+ zr->name, zr->revision, zr->pci_dev->irq, zr->zr36057_adr);
+ } else {
+ unsigned short ss_vendor_id, ss_id;
+
+ ss_vendor_id = zr->pci_dev->subsystem_vendor;
+ ss_id = zr->pci_dev->subsystem_device;
+ printk(KERN_INFO "%s: Zoran ZR36067 (rev %d) irq: %d, memory: 0x%08x\n",
+ zr->name, zr->revision, zr->pci_dev->irq, zr->zr36057_adr);
+ printk(KERN_INFO "%s: subsystem vendor=0x%04x id=0x%04x\n",
+ zr->name, ss_vendor_id, ss_id);
+ if(ss_vendor_id==0xFF10 && ss_id == 0xDE41)
+ {
+ zr->board = BOARD_LML33;
+ printk(KERN_INFO "%s: LML33 detected.\n", zr->name);
+ }
+ }
+
+ zr->zr36057_mem = ioremap(zr->zr36057_adr, 0x1000);
+ if (!zr->zr36057_mem) {
+ printk(KERN_ERR "%s: ioremap failed\n", zr->name);
+ /* XXX handle error */
+ }
+
+ /* set PCI latency timer */
+ pci_read_config_byte(zr->pci_dev, PCI_LATENCY_TIMER, &latency);
+ if (latency != 48) {
+ printk(KERN_INFO "%s: Changing PCI latency from %d to 48.\n", zr->name, latency);
+ latency = 48;
+ pci_write_config_byte(zr->pci_dev, PCI_LATENCY_TIMER, latency);
+ }
+ zoran_num++;
+ }
+ if (zoran_num == 0)
+ printk(KERN_INFO "zoran: no cards found.\n");
+
+ return zoran_num;
+}
+
+static void handle_chipset(void)
+{
+ if(pci_pci_problems&PCIPCI_FAIL)
+ {
+ printk(KERN_WARNING "buz: This configuration is known to have PCI to PCI DMA problems\n");
+ printk(KERN_WARNING "buz: You may not be able to use overlay mode.\n");
+ }
+
+
+ if(pci_pci_problems&PCIPCI_TRITON)
+ {
+ printk("buz: Enabling Triton support.\n");
+ triton = 1;
+ }
+
+ if(pci_pci_problems&PCIPCI_NATOMA)
+ {
+ printk("buz: Enabling Natoma workaround.\n");
+ natoma = 1;
+ }
+}
+
+#ifdef MODULE
+int init_module(void)
+#else
+int init_zoran_cards(struct video_init *unused)
+#endif
+{
+ int i;
+
+
+ printk(KERN_INFO "Zoran driver 1.00 (c) 1999 Rainer Johanni, Dave Perks.\n");
+
+ /* Look for Buz cards */
+
+ if (find_zr36057() <= 0) {
+ return -EIO;
+ }
+ printk(KERN_INFO"zoran: %d zoran card(s) found\n", zoran_num);
+
+ if (zoran_num == 0)
+ return -ENXIO;
+
+
+ /* check the parameters we have been given, adjust if necessary */
+
+ if (v4l_nbufs < 0)
+ v4l_nbufs = 0;
+ if (v4l_nbufs > VIDEO_MAX_FRAME)
+ v4l_nbufs = VIDEO_MAX_FRAME;
+ /* The user specfies the in KB, we want them in byte (and page aligned) */
+ v4l_bufsize = PAGE_ALIGN(v4l_bufsize * 1024);
+ if (v4l_bufsize < 32768)
+ v4l_bufsize = 32768;
+ /* 2 MB is arbitrary but sufficient for the maximum possible images */
+ if (v4l_bufsize > 2048 * 1024)
+ v4l_bufsize = 2048 * 1024;
+
+ printk(KERN_INFO "zoran: using %d V4L buffers of size %d KB\n", v4l_nbufs, v4l_bufsize >> 10);
+
+ /* Use parameter for vidmem or try to find a video card */
+
+ if (vidmem) {
+ printk(KERN_INFO "zoran: Using supplied video memory base address @ 0x%lx\n", vidmem);
+ }
+
+ /* check if we have a Triton or Natome chipset */
+
+ handle_chipset();
+
+ /* take care of Natoma chipset and a revision 1 zr36057 */
+
+ for (i = 0; i < zoran_num; i++) {
+ if (natoma && zoran[i].revision <= 1) {
+ zoran[i].need_contiguous = 1;
+ printk(KERN_INFO "%s: ZR36057/Natome bug, max. buffer size is 128K\n", zoran[i].name);
+ } else {
+ zoran[i].need_contiguous = 0;
+ }
+ }
+
+ /* initialize the Buzs */
+
+ /* We have to know which ones must be released if an error occurs */
+ for (i = 0; i < zoran_num; i++)
+ zoran[i].initialized = 0;
+
+ for (i = 0; i < zoran_num; i++) {
+ if (zr36057_init(i) < 0) {
+ release_zoran();
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+
+
+#ifdef MODULE
+
+void cleanup_module(void)
+{
+ release_zoran();
+}
+
+#endif
diff --git a/drivers/media/video/buz.h b/drivers/media/video/buz.h
new file mode 100644
index 000000000..06b794a09
--- /dev/null
+++ b/drivers/media/video/buz.h
@@ -0,0 +1,319 @@
+/*
+ buz - Iomega Buz driver
+
+ Copyright (C) 1999 Rainer Johanni <Rainer@Johanni.de>
+
+ based on
+
+ buz.0.0.3 Copyright (C) 1998 Dave Perks <dperks@ibm.net>
+
+ and
+
+ bttv - Bt848 frame grabber driver
+ Copyright (C) 1996,97 Ralph Metzler (rjkm@thp.uni-koeln.de)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _BUZ_H_
+#define _BUZ_H_
+
+/* The Buz only supports a maximum width of 720, but some V4L
+ applications (e.g. xawtv are more happy with 768).
+ If XAWTV_HACK is defined, we try to fake a device with bigger width */
+
+#define XAWTV_HACK
+
+#ifdef XAWTV_HACK
+#define BUZ_MAX_WIDTH 768 /* never display more than 768 pixels */
+#else
+#define BUZ_MAX_WIDTH 720 /* never display more than 720 pixels */
+#endif
+#define BUZ_MAX_HEIGHT 576 /* never display more than 576 rows */
+#define BUZ_MIN_WIDTH 32 /* never display less than 32 pixels */
+#define BUZ_MIN_HEIGHT 24 /* never display less than 24 rows */
+
+struct zoran_requestbuffers {
+ unsigned long count; /* Number of buffers for MJPEG grabbing */
+ unsigned long size; /* Size PER BUFFER in bytes */
+};
+
+struct zoran_sync {
+ unsigned long frame; /* number of buffer that has been free'd */
+ unsigned long length; /* number of code bytes in buffer (capture only) */
+ unsigned long seq; /* frame sequence number */
+ struct timeval timestamp; /* timestamp */
+};
+
+struct zoran_status {
+ int input; /* Input channel, has to be set prior to BUZIOC_G_STATUS */
+ int signal; /* Returned: 1 if valid video signal detected */
+ int norm; /* Returned: VIDEO_MODE_PAL or VIDEO_MODE_NTSC */
+ int color; /* Returned: 1 if color signal detected */
+};
+
+struct zoran_params {
+
+ /* The following parameters can only be queried */
+
+ int major_version; /* Major version number of driver */
+ int minor_version; /* Minor version number of driver */
+
+ /* Main control parameters */
+
+ int input; /* Input channel: 0 = Composite, 1 = S-VHS */
+ int norm; /* Norm: VIDEO_MODE_PAL or VIDEO_MODE_NTSC */
+ int decimation; /* decimation of captured video,
+ enlargement of video played back.
+ Valid values are 1, 2, 4 or 0.
+ 0 is a special value where the user
+ has full control over video scaling */
+
+ /* The following parameters only have to be set if decimation==0,
+ for other values of decimation they provide the data how the image is captured */
+
+ int HorDcm; /* Horizontal decimation: 1, 2 or 4 */
+ int VerDcm; /* Vertical decimation: 1 or 2 */
+ int TmpDcm; /* Temporal decimation: 1 or 2,
+ if TmpDcm==2 in capture every second frame is dropped,
+ in playback every frame is played twice */
+ int field_per_buff; /* Number of fields per buffer: 1 or 2 */
+ int img_x; /* start of image in x direction */
+ int img_y; /* start of image in y direction */
+ int img_width; /* image width BEFORE decimation,
+ must be a multiple of HorDcm*16 */
+ int img_height; /* image height BEFORE decimation,
+ must be a multiple of VerDcm*8 */
+
+ /* --- End of parameters for decimation==0 only --- */
+
+ /* JPEG control parameters */
+
+ int quality; /* Measure for quality of compressed images.
+ Scales linearly with the size of the compressed images.
+ Must be beetween 0 and 100, 100 is a compression
+ ratio of 1:4 */
+
+ int odd_even; /* Which field should come first ??? */
+
+ int APPn; /* Number of APP segment to be written, must be 0..15 */
+ int APP_len; /* Length of data in JPEG APPn segment */
+ char APP_data[60]; /* Data in the JPEG APPn segment. */
+
+ int COM_len; /* Length of data in JPEG COM segment */
+ char COM_data[60]; /* Data in JPEG COM segment */
+
+ unsigned long jpeg_markers; /* Which markers should go into the JPEG output.
+ Unless you exactly know what you do, leave them untouched.
+ Inluding less markers will make the resulting code
+ smaller, but there will be fewer aplications
+ which can read it.
+ The presence of the APP and COM marker is
+ influenced by APP0_len and COM_len ONLY! */
+#define JPEG_MARKER_DHT (1<<3) /* Define Huffman Tables */
+#define JPEG_MARKER_DQT (1<<4) /* Define Quantization Tables */
+#define JPEG_MARKER_DRI (1<<5) /* Define Restart Interval */
+#define JPEG_MARKER_COM (1<<6) /* Comment segment */
+#define JPEG_MARKER_APP (1<<7) /* App segment, driver will allways use APP0 */
+
+ int VFIFO_FB; /* Flag for enabling Video Fifo Feedback.
+ If this flag is turned on and JPEG decompressing
+ is going to the screen, the decompress process
+ is stopped every time the Video Fifo is full.
+ This enables a smooth decompress to the screen
+ but the video output signal will get scrambled */
+
+ /* Misc */
+
+ char reserved[312]; /* Makes 512 bytes for this structure */
+};
+
+/*
+ Private IOCTL to set up for displaying MJPEG
+ */
+#define BUZIOC_G_PARAMS _IOR ('v', BASE_VIDIOCPRIVATE+0, struct zoran_params)
+#define BUZIOC_S_PARAMS _IOWR('v', BASE_VIDIOCPRIVATE+1, struct zoran_params)
+#define BUZIOC_REQBUFS _IOWR('v', BASE_VIDIOCPRIVATE+2, struct zoran_requestbuffers)
+#define BUZIOC_QBUF_CAPT _IOW ('v', BASE_VIDIOCPRIVATE+3, int)
+#define BUZIOC_QBUF_PLAY _IOW ('v', BASE_VIDIOCPRIVATE+4, int)
+#define BUZIOC_SYNC _IOR ('v', BASE_VIDIOCPRIVATE+5, struct zoran_sync)
+#define BUZIOC_G_STATUS _IOWR('v', BASE_VIDIOCPRIVATE+6, struct zoran_status)
+
+
+#ifdef __KERNEL__
+
+#define BUZ_NUM_STAT_COM 4
+#define BUZ_MASK_STAT_COM 3
+
+#define BUZ_MAX_FRAME 256 /* Must be a power of 2 */
+#define BUZ_MASK_FRAME 255 /* Must be BUZ_MAX_FRAME-1 */
+
+#if VIDEO_MAX_FRAME <= 32
+#define V4L_MAX_FRAME 32
+#elif VIDEO_MAX_FRAME <= 64
+#define V4L_MAX_FRAME 64
+#else
+#error "Too many video frame buffers to handle"
+#endif
+#define V4L_MASK_FRAME (V4L_MAX_FRAME - 1)
+
+
+#include "zr36057.h"
+
+enum zoran_codec_mode {
+ BUZ_MODE_IDLE, /* nothing going on */
+ BUZ_MODE_MOTION_COMPRESS, /* grabbing frames */
+ BUZ_MODE_MOTION_DECOMPRESS, /* playing frames */
+ BUZ_MODE_STILL_COMPRESS, /* still frame conversion */
+ BUZ_MODE_STILL_DECOMPRESS /* still frame conversion */
+};
+
+enum zoran_buffer_state {
+ BUZ_STATE_USER, /* buffer is owned by application */
+ BUZ_STATE_PEND, /* buffer is queued in pend[] ready to feed to I/O */
+ BUZ_STATE_DMA, /* buffer is queued in dma[] for I/O */
+ BUZ_STATE_DONE /* buffer is ready to return to application */
+};
+
+struct zoran_gbuffer {
+ u32 *frag_tab; /* addresses of frag table */
+ u32 frag_tab_bus; /* same value cached to save time in ISR */
+ enum zoran_buffer_state state; /* non-zero if corresponding buffer is in use in grab queue */
+ struct zoran_sync bs; /* DONE: info to return to application */
+};
+
+struct v4l_gbuffer {
+ char *fbuffer; /* virtual address of frame buffer */
+ unsigned long fbuffer_phys; /* physical address of frame buffer */
+ unsigned long fbuffer_bus; /* bus address of frame buffer */
+ enum zoran_buffer_state state; /* state: unused/pending/done */
+};
+
+struct zoran {
+ struct video_device video_dev;
+ struct i2c_bus i2c;
+
+ int initialized; /* flag if zoran has been correctly initalized */
+ int user; /* number of current users (0 or 1) */
+
+ unsigned short id; /* number of this device */
+ char name[32]; /* name of this device */
+ struct pci_dev *pci_dev; /* PCI device */
+ unsigned char revision; /* revision of zr36057 */
+ int board; /* Board type */
+#define BOARD_BUZ 0
+#define BOARD_LML33 1
+ unsigned int zr36057_adr; /* bus address of IO mem returned by PCI BIOS */
+ unsigned char *zr36057_mem; /* pointer to mapped IO memory */
+
+ int map_mjpeg_buffers; /* Flag which bufferset will map by next mmap() */
+
+ spinlock_t lock; /* Spinlock */
+
+ /* Video for Linux parameters */
+
+ struct video_picture picture; /* Current picture params */
+ struct video_buffer buffer; /* Current buffer params */
+ struct video_window window; /* Current window params */
+ int buffer_set, window_set; /* Flags if the above structures are set */
+ int video_interlace; /* Image on screen is interlaced */
+
+ u32 *overlay_mask;
+
+ wait_queue_head_t v4l_capq; /* wait here for grab to finish */
+
+ int v4l_overlay_active; /* Overlay grab is activated */
+ int v4l_memgrab_active; /* Memory grab is activated */
+
+ int v4l_grab_frame; /* Frame number being currently grabbed */
+#define NO_GRAB_ACTIVE (-1)
+ int v4l_grab_seq; /* Number of frames grabbed */
+ int gwidth; /* Width of current memory capture */
+ int gheight; /* Height of current memory capture */
+ int gformat; /* Format of ... */
+ int gbpl; /* byte per line of ... */
+
+ /* V4L grab queue of frames pending */
+
+ unsigned v4l_pend_head;
+ unsigned v4l_pend_tail;
+ int v4l_pend[V4L_MAX_FRAME];
+
+ struct v4l_gbuffer v4l_gbuf[VIDEO_MAX_FRAME]; /* V4L buffers' info */
+
+ /* Buz MJPEG parameters */
+
+ unsigned long jpg_nbufs; /* Number of buffers */
+ unsigned long jpg_bufsize; /* Size of mjpeg buffers in bytes */
+ int jpg_buffers_allocated; /* Flag if buffers are allocated */
+ int need_contiguous; /* Flag if contiguous buffers are needed */
+
+ enum zoran_codec_mode codec_mode; /* status of codec */
+ struct zoran_params params; /* structure with a lot of things to play with */
+
+ wait_queue_head_t jpg_capq; /* wait here for grab to finish */
+
+ /* grab queue counts/indices, mask with BUZ_MASK_STAT_COM before using as index */
+ /* (dma_head - dma_tail) is number active in DMA, must be <= BUZ_NUM_STAT_COM */
+ /* (value & BUZ_MASK_STAT_COM) corresponds to index in stat_com table */
+ unsigned long jpg_que_head; /* Index where to put next buffer which is queued */
+ unsigned long jpg_dma_head; /* Index of next buffer which goes into stat_com */
+ unsigned long jpg_dma_tail; /* Index of last buffer in stat_com */
+ unsigned long jpg_que_tail; /* Index of last buffer in queue */
+ unsigned long jpg_seq_num; /* count of frames since grab/play started */
+
+ /* zr36057's code buffer table */
+ u32 *stat_com; /* stat_com[i] is indexed by dma_head/tail & BUZ_MASK_STAT_COM */
+
+ /* (value & BUZ_MASK_FRAME) corresponds to index in pend[] queue */
+ int jpg_pend[BUZ_MAX_FRAME];
+
+ /* array indexed by frame number */
+ struct zoran_gbuffer jpg_gbuf[BUZ_MAX_FRAME]; /* MJPEG buffers' info */
+};
+
+#endif
+
+/*The following should be done in more portable way. It depends on define
+ of _ALPHA_BUZ in the Makefile. */
+
+#ifdef _ALPHA_BUZ
+#define btwrite(dat,adr) writel((dat),(char *) (zr->zr36057_adr+(adr)))
+#define btread(adr) readl(zr->zr36057_adr+(adr))
+#else
+#define btwrite(dat,adr) writel((dat), (char *) (zr->zr36057_mem+(adr)))
+#define btread(adr) readl(zr->zr36057_mem+(adr))
+#endif
+
+#define btand(dat,adr) btwrite((dat) & btread(adr), adr)
+#define btor(dat,adr) btwrite((dat) | btread(adr), adr)
+#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr)
+
+#define I2C_TSA5522 0xc2
+#define I2C_TDA9850 0xb6
+#define I2C_HAUPEE 0xa0
+#define I2C_STBEE 0xae
+#define I2C_SAA7111 0x48
+#define I2C_SAA7185 0x88
+
+#define TDA9850_CON1 0x04
+#define TDA9850_CON2 0x05
+#define TDA9850_CON3 0x06
+#define TDA9850_CON4 0x07
+#define TDA9850_ALI1 0x08
+#define TDA9850_ALI2 0x09
+#define TDA9850_ALI3 0x0a
+
+#endif
diff --git a/drivers/media/video/bw-qcam.c b/drivers/media/video/bw-qcam.c
new file mode 100644
index 000000000..17f7d25dc
--- /dev/null
+++ b/drivers/media/video/bw-qcam.c
@@ -0,0 +1,1066 @@
+/*
+ * QuickCam Driver For Video4Linux.
+ *
+ * This version only works as a module.
+ *
+ * Video4Linux conversion work by Alan Cox.
+ * Parport compatibility by Phil Blundell.
+ * Busy loop avoidance by Mark Cooke.
+ *
+ * Module parameters:
+ *
+ * maxpoll=<1 - 5000>
+ *
+ * When polling the QuickCam for a response, busy-wait for a
+ * maximum of this many loops. The default of 250 gives little
+ * impact on interactive response.
+ *
+ * NOTE: If this parameter is set too high, the processor
+ * will busy wait until this loop times out, and then
+ * slowly poll for a further 5 seconds before failing
+ * the transaction. You have been warned.
+ *
+ * yieldlines=<1 - 250>
+ *
+ * When acquiring a frame from the camera, the data gathering
+ * loop will yield back to the scheduler after completing
+ * this many lines. The default of 4 provides a trade-off
+ * between increased frame acquisition time and impact on
+ * interactive response.
+ */
+
+/* qcam-lib.c -- Library for programming with the Connectix QuickCam.
+ * See the included documentation for usage instructions and details
+ * of the protocol involved. */
+
+
+/* Version 0.5, August 4, 1996 */
+/* Version 0.7, August 27, 1996 */
+/* Version 0.9, November 17, 1996 */
+
+
+/******************************************************************
+
+Copyright (C) 1996 by Scott Laird
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL SCOTT LAIRD BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+******************************************************************/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/parport.h>
+#include <linux/sched.h>
+#include <linux/version.h>
+#include <linux/videodev.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include "bw-qcam.h"
+
+static unsigned int maxpoll=250; /* Maximum busy-loop count for qcam I/O */
+static unsigned int yieldlines=4; /* Yield after this many during capture */
+
+#if LINUX_VERSION_CODE >= 0x020117
+MODULE_PARM(maxpoll,"i");
+MODULE_PARM(yieldlines,"i");
+#endif
+
+extern __inline__ int read_lpstatus(struct qcam_device *q)
+{
+ return parport_read_status(q->pport);
+}
+
+extern __inline__ int read_lpcontrol(struct qcam_device *q)
+{
+ return parport_read_control(q->pport);
+}
+
+extern __inline__ int read_lpdata(struct qcam_device *q)
+{
+ return parport_read_data(q->pport);
+}
+
+extern __inline__ void write_lpdata(struct qcam_device *q, int d)
+{
+ parport_write_data(q->pport, d);
+}
+
+extern __inline__ void write_lpcontrol(struct qcam_device *q, int d)
+{
+ parport_write_control(q->pport, d);
+}
+
+static int qc_waithand(struct qcam_device *q, int val);
+static int qc_command(struct qcam_device *q, int command);
+static int qc_readparam(struct qcam_device *q);
+static int qc_setscanmode(struct qcam_device *q);
+static int qc_readbytes(struct qcam_device *q, char buffer[]);
+
+static struct video_device qcam_template;
+
+static int qc_calibrate(struct qcam_device *q)
+{
+ /*
+ * Bugfix by Hanno Mueller hmueller@kabel.de, Mai 21 96
+ * The white balance is an individiual value for each
+ * quickcam.
+ */
+
+ int value;
+ int count = 0;
+
+ qc_command(q, 27); /* AutoAdjustOffset */
+ qc_command(q, 0); /* Dummy Parameter, ignored by the camera */
+
+ /* GetOffset (33) will read 255 until autocalibration */
+ /* is finished. After that, a value of 1-254 will be */
+ /* returned. */
+
+ do {
+ qc_command(q, 33);
+ value = qc_readparam(q);
+ mdelay(1);
+ schedule();
+ count++;
+ } while (value == 0xff && count<2048);
+
+ q->whitebal = value;
+ return value;
+}
+
+/* Initialize the QuickCam driver control structure. This is where
+ * defaults are set for people who don't have a config file.*/
+
+static struct qcam_device *qcam_init(struct parport *port)
+{
+ struct qcam_device *q;
+
+ q = kmalloc(sizeof(struct qcam_device), GFP_KERNEL);
+ if(q==NULL)
+ return NULL;
+
+ q->pport = port;
+ q->pdev = parport_register_device(port, "bw-qcam", NULL, NULL,
+ NULL, 0, NULL);
+ if (q->pdev == NULL)
+ {
+ printk(KERN_ERR "bw-qcam: couldn't register for %s.\n",
+ port->name);
+ kfree(q);
+ return NULL;
+ }
+
+ memcpy(&q->vdev, &qcam_template, sizeof(qcam_template));
+
+ init_MUTEX(&q->lock);
+
+ q->port_mode = (QC_ANY | QC_NOTSET);
+ q->width = 320;
+ q->height = 240;
+ q->bpp = 4;
+ q->transfer_scale = 2;
+ q->contrast = 192;
+ q->brightness = 180;
+ q->whitebal = 105;
+ q->top = 1;
+ q->left = 14;
+ q->mode = -1;
+ q->status = QC_PARAM_CHANGE;
+ return q;
+}
+
+
+/* qc_command is probably a bit of a misnomer -- it's used to send
+ * bytes *to* the camera. Generally, these bytes are either commands
+ * or arguments to commands, so the name fits, but it still bugs me a
+ * bit. See the documentation for a list of commands. */
+
+static int qc_command(struct qcam_device *q, int command)
+{
+ int n1, n2;
+ int cmd;
+
+ write_lpdata(q, command);
+ write_lpcontrol(q, 6);
+
+ n1 = qc_waithand(q, 1);
+
+ write_lpcontrol(q, 0xe);
+ n2 = qc_waithand(q, 0);
+
+ cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4);
+ return cmd;
+}
+
+static int qc_readparam(struct qcam_device *q)
+{
+ int n1, n2;
+ int cmd;
+
+ write_lpcontrol(q, 6);
+ n1 = qc_waithand(q, 1);
+
+ write_lpcontrol(q, 0xe);
+ n2 = qc_waithand(q, 0);
+
+ cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4);
+ return cmd;
+}
+
+/* qc_waithand busy-waits for a handshake signal from the QuickCam.
+ * Almost all communication with the camera requires handshaking. */
+
+static int qc_waithand(struct qcam_device *q, int val)
+{
+ int status;
+ int runs=0;
+
+ if (val)
+ {
+ while (!((status = read_lpstatus(q)) & 8))
+ {
+ /* 1000 is enough spins on the I/O for all normal
+ cases, at that point we start to poll slowly
+ until the camera wakes up. However, we are
+ busy blocked until the camera responds, so
+ setting it lower is much better for interactive
+ response. */
+
+ if(runs++>maxpoll)
+ {
+ current->state=TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/200);
+ }
+ if(runs>(maxpoll+1000)) /* 5 seconds */
+ return -1;
+ }
+ }
+ else
+ {
+ while (((status = read_lpstatus(q)) & 8))
+ {
+ /* 1000 is enough spins on the I/O for all normal
+ cases, at that point we start to poll slowly
+ until the camera wakes up. However, we are
+ busy blocked until the camera responds, so
+ setting it lower is much better for interactive
+ response. */
+
+ if(runs++>maxpoll)
+ {
+ current->state=TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/200);
+ }
+ if(runs++>(maxpoll+1000)) /* 5 seconds */
+ return -1;
+ }
+ }
+
+ return status;
+}
+
+/* Waithand2 is used when the qcam is in bidirectional mode, and the
+ * handshaking signal is CamRdy2 (bit 0 of data reg) instead of CamRdy1
+ * (bit 3 of status register). It also returns the last value read,
+ * since this data is useful. */
+
+static unsigned int qc_waithand2(struct qcam_device *q, int val)
+{
+ unsigned int status;
+ int runs=0;
+
+ do
+ {
+ status = read_lpdata(q);
+ /* 1000 is enough spins on the I/O for all normal
+ cases, at that point we start to poll slowly
+ until the camera wakes up. However, we are
+ busy blocked until the camera responds, so
+ setting it lower is much better for interactive
+ response. */
+
+ if(runs++>maxpoll)
+ {
+ current->state=TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/200);
+ }
+ if(runs++>(maxpoll+1000)) /* 5 seconds */
+ return 0;
+ }
+ while ((status & 1) != val);
+
+ return status;
+}
+
+
+/* Try to detect a QuickCam. It appears to flash the upper 4 bits of
+ the status register at 5-10 Hz. This is only used in the autoprobe
+ code. Be aware that this isn't the way Connectix detects the
+ camera (they send a reset and try to handshake), but this should be
+ almost completely safe, while their method screws up my printer if
+ I plug it in before the camera. */
+
+static int qc_detect(struct qcam_device *q)
+{
+ int reg, lastreg;
+ int count = 0;
+ int i;
+
+ lastreg = reg = read_lpstatus(q) & 0xf0;
+
+ for (i = 0; i < 500; i++)
+ {
+ reg = read_lpstatus(q) & 0xf0;
+ if (reg != lastreg)
+ count++;
+ lastreg = reg;
+ mdelay(2);
+ }
+
+
+#if 0
+ /* Force camera detection during testing. Sometimes the camera
+ won't be flashing these bits. Possibly unloading the module
+ in the middle of a grab? Or some timeout condition?
+ I've seen this parameter as low as 19 on my 450Mhz box - mpc */
+ printk("Debugging: QCam detection counter <30-200 counts as detected>: %d\n", count);
+ return 1;
+#endif
+
+ /* Be (even more) liberal in what you accept... */
+
+/* if (count > 30 && count < 200) */
+ if (count > 20 && count < 300)
+ return 1; /* found */
+ else
+ return 0; /* not found */
+}
+
+
+/* Reset the QuickCam. This uses the same sequence the Windows
+ * QuickPic program uses. Someone with a bi-directional port should
+ * check that bi-directional mode is detected right, and then
+ * implement bi-directional mode in qc_readbyte(). */
+
+static void qc_reset(struct qcam_device *q)
+{
+ switch (q->port_mode & QC_FORCE_MASK)
+ {
+ case QC_FORCE_UNIDIR:
+ q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_UNIDIR;
+ break;
+
+ case QC_FORCE_BIDIR:
+ q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_BIDIR;
+ break;
+
+ case QC_ANY:
+ write_lpcontrol(q, 0x20);
+ write_lpdata(q, 0x75);
+
+ if (read_lpdata(q) != 0x75) {
+ q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_BIDIR;
+ } else {
+ q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_UNIDIR;
+ }
+ break;
+ }
+
+ write_lpcontrol(q, 0xb);
+ udelay(250);
+ write_lpcontrol(q, 0xe);
+ qc_setscanmode(q); /* in case port_mode changed */
+}
+
+
+/* Decide which scan mode to use. There's no real requirement that
+ * the scanmode match the resolution in q->height and q-> width -- the
+ * camera takes the picture at the resolution specified in the
+ * "scanmode" and then returns the image at the resolution specified
+ * with the resolution commands. If the scan is bigger than the
+ * requested resolution, the upper-left hand corner of the scan is
+ * returned. If the scan is smaller, then the rest of the image
+ * returned contains garbage. */
+
+static int qc_setscanmode(struct qcam_device *q)
+{
+ int old_mode = q->mode;
+
+ switch (q->transfer_scale)
+ {
+ case 1:
+ q->mode = 0;
+ break;
+ case 2:
+ q->mode = 4;
+ break;
+ case 4:
+ q->mode = 8;
+ break;
+ }
+
+ switch (q->bpp)
+ {
+ case 4:
+ break;
+ case 6:
+ q->mode += 2;
+ break;
+ }
+
+ switch (q->port_mode & QC_MODE_MASK)
+ {
+ case QC_BIDIR:
+ q->mode += 1;
+ break;
+ case QC_NOTSET:
+ case QC_UNIDIR:
+ break;
+ }
+
+ if (q->mode != old_mode)
+ q->status |= QC_PARAM_CHANGE;
+
+ return 0;
+}
+
+
+/* Reset the QuickCam and program for brightness, contrast,
+ * white-balance, and resolution. */
+
+void qc_set(struct qcam_device *q)
+{
+ int val;
+ int val2;
+
+ qc_reset(q);
+
+ /* Set the brightness. Yes, this is repetitive, but it works.
+ * Shorter versions seem to fail subtly. Feel free to try :-). */
+ /* I think the problem was in qc_command, not here -- bls */
+
+ qc_command(q, 0xb);
+ qc_command(q, q->brightness);
+
+ val = q->height / q->transfer_scale;
+ qc_command(q, 0x11);
+ qc_command(q, val);
+ if ((q->port_mode & QC_MODE_MASK) == QC_UNIDIR && q->bpp == 6) {
+ /* The normal "transfers per line" calculation doesn't seem to work
+ as expected here (and yet it works fine in qc_scan). No idea
+ why this case is the odd man out. Fortunately, Laird's original
+ working version gives me a good way to guess at working values.
+ -- bls */
+ val = q->width;
+ val2 = q->transfer_scale * 4;
+ } else {
+ val = q->width * q->bpp;
+ val2 = (((q->port_mode & QC_MODE_MASK) == QC_BIDIR) ? 24 : 8) *
+ q->transfer_scale;
+ }
+ val = (val + val2 - 1) / val2;
+ qc_command(q, 0x13);
+ qc_command(q, val);
+
+ /* Setting top and left -- bls */
+ qc_command(q, 0xd);
+ qc_command(q, q->top);
+ qc_command(q, 0xf);
+ qc_command(q, q->left / 2);
+
+ qc_command(q, 0x19);
+ qc_command(q, q->contrast);
+ qc_command(q, 0x1f);
+ qc_command(q, q->whitebal);
+
+ /* Clear flag that we must update the grabbing parameters on the camera
+ before we grab the next frame */
+ q->status &= (~QC_PARAM_CHANGE);
+}
+
+/* Qc_readbytes reads some bytes from the QC and puts them in
+ the supplied buffer. It returns the number of bytes read,
+ or -1 on error. */
+
+extern __inline__ int qc_readbytes(struct qcam_device *q, char buffer[])
+{
+ int ret=1;
+ unsigned int hi, lo;
+ unsigned int hi2, lo2;
+ static int state = 0;
+
+ if (buffer == NULL)
+ {
+ state = 0;
+ return 0;
+ }
+
+ switch (q->port_mode & QC_MODE_MASK)
+ {
+ case QC_BIDIR: /* Bi-directional Port */
+ write_lpcontrol(q, 0x26);
+ lo = (qc_waithand2(q, 1) >> 1);
+ hi = (read_lpstatus(q) >> 3) & 0x1f;
+ write_lpcontrol(q, 0x2e);
+ lo2 = (qc_waithand2(q, 0) >> 1);
+ hi2 = (read_lpstatus(q) >> 3) & 0x1f;
+ switch (q->bpp)
+ {
+ case 4:
+ buffer[0] = lo & 0xf;
+ buffer[1] = ((lo & 0x70) >> 4) | ((hi & 1) << 3);
+ buffer[2] = (hi & 0x1e) >> 1;
+ buffer[3] = lo2 & 0xf;
+ buffer[4] = ((lo2 & 0x70) >> 4) | ((hi2 & 1) << 3);
+ buffer[5] = (hi2 & 0x1e) >> 1;
+ ret = 6;
+ break;
+ case 6:
+ buffer[0] = lo & 0x3f;
+ buffer[1] = ((lo & 0x40) >> 6) | (hi << 1);
+ buffer[2] = lo2 & 0x3f;
+ buffer[3] = ((lo2 & 0x40) >> 6) | (hi2 << 1);
+ ret = 4;
+ break;
+ }
+ break;
+
+ case QC_UNIDIR: /* Unidirectional Port */
+ write_lpcontrol(q, 6);
+ lo = (qc_waithand(q, 1) & 0xf0) >> 4;
+ write_lpcontrol(q, 0xe);
+ hi = (qc_waithand(q, 0) & 0xf0) >> 4;
+
+ switch (q->bpp)
+ {
+ case 4:
+ buffer[0] = lo;
+ buffer[1] = hi;
+ ret = 2;
+ break;
+ case 6:
+ switch (state)
+ {
+ case 0:
+ buffer[0] = (lo << 2) | ((hi & 0xc) >> 2);
+ q->saved_bits = (hi & 3) << 4;
+ state = 1;
+ ret = 1;
+ break;
+ case 1:
+ buffer[0] = lo | q->saved_bits;
+ q->saved_bits = hi << 2;
+ state = 2;
+ ret = 1;
+ break;
+ case 2:
+ buffer[0] = ((lo & 0xc) >> 2) | q->saved_bits;
+ buffer[1] = ((lo & 3) << 4) | hi;
+ state = 0;
+ ret = 2;
+ break;
+ }
+ break;
+ }
+ break;
+ }
+ return ret;
+}
+
+/* requests a scan from the camera. It sends the correct instructions
+ * to the camera and then reads back the correct number of bytes. In
+ * previous versions of this routine the return structure contained
+ * the raw output from the camera, and there was a 'qc_convertscan'
+ * function that converted that to a useful format. In version 0.3 I
+ * rolled qc_convertscan into qc_scan and now I only return the
+ * converted scan. The format is just an one-dimensional array of
+ * characters, one for each pixel, with 0=black up to n=white, where
+ * n=2^(bit depth)-1. Ask me for more details if you don't understand
+ * this. */
+
+long qc_capture(struct qcam_device * q, char *buf, unsigned long len)
+{
+ int i, j, k, yield;
+ int bytes;
+ int linestotrans, transperline;
+ int divisor;
+ int pixels_per_line;
+ int pixels_read = 0;
+ int got=0;
+ char buffer[6];
+ int shift=8-q->bpp;
+ char invert;
+
+ if (q->mode == -1)
+ return -ENXIO;
+
+ qc_command(q, 0x7);
+ qc_command(q, q->mode);
+
+ if ((q->port_mode & QC_MODE_MASK) == QC_BIDIR)
+ {
+ write_lpcontrol(q, 0x2e); /* turn port around */
+ write_lpcontrol(q, 0x26);
+ (void) qc_waithand(q, 1);
+ write_lpcontrol(q, 0x2e);
+ (void) qc_waithand(q, 0);
+ }
+
+ /* strange -- should be 15:63 below, but 4bpp is odd */
+ invert = (q->bpp == 4) ? 16 : 63;
+
+ linestotrans = q->height / q->transfer_scale;
+ pixels_per_line = q->width / q->transfer_scale;
+ transperline = q->width * q->bpp;
+ divisor = (((q->port_mode & QC_MODE_MASK) == QC_BIDIR) ? 24 : 8) *
+ q->transfer_scale;
+ transperline = (transperline + divisor - 1) / divisor;
+
+ for (i = 0, yield = yieldlines; i < linestotrans; i++)
+ {
+ for (pixels_read = j = 0; j < transperline; j++)
+ {
+ bytes = qc_readbytes(q, buffer);
+ for (k = 0; k < bytes && (pixels_read + k) < pixels_per_line; k++)
+ {
+ int o;
+ if (buffer[k] == 0 && invert == 16)
+ {
+ /* 4bpp is odd (again) -- inverter is 16, not 15, but output
+ must be 0-15 -- bls */
+ buffer[k] = 16;
+ }
+ o=i*pixels_per_line + pixels_read + k;
+ if(o<len)
+ {
+ got++;
+ put_user((invert - buffer[k])<<shift, buf+o);
+ }
+ }
+ pixels_read += bytes;
+ }
+ (void) qc_readbytes(q, 0); /* reset state machine */
+
+ /* Grabbing an entire frame from the quickcam is a lengthy
+ process. We don't (usually) want to busy-block the
+ processor for the entire frame. yieldlines is a module
+ parameter. If we yield every line, the minimum frame
+ time will be 240 / 200 = 1.2 seconds. The compile-time
+ default is to yield every 4 lines. */
+ if (i >= yield) {
+ current->state=TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/200);
+ yield = i + yieldlines;
+ }
+ }
+
+ if ((q->port_mode & QC_MODE_MASK) == QC_BIDIR)
+ {
+ write_lpcontrol(q, 2);
+ write_lpcontrol(q, 6);
+ udelay(3);
+ write_lpcontrol(q, 0xe);
+ }
+ if(got<len)
+ return got;
+ return len;
+}
+
+/*
+ * Video4linux interfacing
+ */
+
+static int qcam_open(struct video_device *dev, int flags)
+{
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void qcam_close(struct video_device *dev)
+{
+ MOD_DEC_USE_COUNT;
+}
+
+static int qcam_init_done(struct video_device *dev)
+{
+ return 0;
+}
+
+static long qcam_write(struct video_device *v, const char *buf, unsigned long count, int noblock)
+{
+ return -EINVAL;
+}
+
+static int qcam_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct qcam_device *qcam=(struct qcam_device *)dev;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability b;
+ strcpy(b.name, "Quickcam");
+ b.type = VID_TYPE_CAPTURE|VID_TYPE_SCALES|VID_TYPE_MONOCHROME;
+ b.channels = 1;
+ b.audios = 0;
+ b.maxwidth = 320;
+ b.maxheight = 240;
+ b.minwidth = 80;
+ b.minheight = 60;
+ if(copy_to_user(arg, &b,sizeof(b)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCGCHAN:
+ {
+ struct video_channel v;
+ if(copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ if(v.channel!=0)
+ return -EINVAL;
+ v.flags=0;
+ v.tuners=0;
+ /* Good question.. its composite or SVHS so.. */
+ v.type = VIDEO_TYPE_CAMERA;
+ strcpy(v.name, "Camera");
+ if(copy_to_user(arg, &v, sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSCHAN:
+ {
+ int v;
+ if(copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ if(v!=0)
+ return -EINVAL;
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner v;
+ if(copy_from_user(&v, arg, sizeof(v))!=0)
+ return -EFAULT;
+ if(v.tuner)
+ return -EINVAL;
+ strcpy(v.name, "Format");
+ v.rangelow=0;
+ v.rangehigh=0;
+ v.flags= 0;
+ v.mode = VIDEO_MODE_AUTO;
+ if(copy_to_user(arg,&v,sizeof(v))!=0)
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner v;
+ if(copy_from_user(&v, arg, sizeof(v))!=0)
+ return -EFAULT;
+ if(v.tuner)
+ return -EINVAL;
+ if(v.mode!=VIDEO_MODE_AUTO)
+ return -EINVAL;
+ return 0;
+ }
+ case VIDIOCGPICT:
+ {
+ struct video_picture p;
+ p.colour=0x8000;
+ p.hue=0x8000;
+ p.brightness=qcam->brightness<<8;
+ p.contrast=qcam->contrast<<8;
+ p.whiteness=qcam->whitebal<<8;
+ p.depth=qcam->bpp;
+ p.palette=VIDEO_PALETTE_GREY;
+ if(copy_to_user(arg, &p, sizeof(p)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSPICT:
+ {
+ struct video_picture p;
+ if(copy_from_user(&p, arg, sizeof(p)))
+ return -EFAULT;
+ if(p.palette!=VIDEO_PALETTE_GREY)
+ return -EINVAL;
+ if(p.depth!=4 && p.depth!=6)
+ return -EINVAL;
+
+ /*
+ * Now load the camera.
+ */
+
+ qcam->brightness = p.brightness>>8;
+ qcam->contrast = p.contrast>>8;
+ qcam->whitebal = p.whiteness>>8;
+ qcam->bpp = p.depth;
+
+ down(&qcam->lock);
+ qc_setscanmode(qcam);
+ up(&qcam->lock);
+ qcam->status |= QC_PARAM_CHANGE;
+
+ return 0;
+ }
+ case VIDIOCSWIN:
+ {
+ struct video_window vw;
+ if(copy_from_user(&vw, arg,sizeof(vw)))
+ return -EFAULT;
+ if(vw.flags)
+ return -EINVAL;
+ if(vw.clipcount)
+ return -EINVAL;
+ if(vw.height<60||vw.height>240)
+ return -EINVAL;
+ if(vw.width<80||vw.width>320)
+ return -EINVAL;
+
+ qcam->width = 320;
+ qcam->height = 240;
+ qcam->transfer_scale = 4;
+
+ if(vw.width>=160 && vw.height>=120)
+ {
+ qcam->transfer_scale = 2;
+ }
+ if(vw.width>=320 && vw.height>=240)
+ {
+ qcam->width = 320;
+ qcam->height = 240;
+ qcam->transfer_scale = 1;
+ }
+ down(&qcam->lock);
+ qc_setscanmode(qcam);
+ up(&qcam->lock);
+
+ /* We must update the camera before we grab. We could
+ just have changed the grab size */
+ qcam->status |= QC_PARAM_CHANGE;
+
+ /* Ok we figured out what to use from our wide choice */
+ return 0;
+ }
+ case VIDIOCGWIN:
+ {
+ struct video_window vw;
+ vw.x=0;
+ vw.y=0;
+ vw.width=qcam->width/qcam->transfer_scale;
+ vw.height=qcam->height/qcam->transfer_scale;
+ vw.chromakey=0;
+ vw.flags=0;
+ if(copy_to_user(arg, &vw, sizeof(vw)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCCAPTURE:
+ return -EINVAL;
+ case VIDIOCGFBUF:
+ return -EINVAL;
+ case VIDIOCSFBUF:
+ return -EINVAL;
+ case VIDIOCKEY:
+ return 0;
+ case VIDIOCGFREQ:
+ return -EINVAL;
+ case VIDIOCSFREQ:
+ return -EINVAL;
+ case VIDIOCGAUDIO:
+ return -EINVAL;
+ case VIDIOCSAUDIO:
+ return -EINVAL;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static long qcam_read(struct video_device *v, char *buf, unsigned long count, int noblock)
+{
+ struct qcam_device *qcam=(struct qcam_device *)v;
+ int len;
+ parport_claim_or_block(qcam->pdev);
+
+ down(&qcam->lock);
+
+ qc_reset(qcam);
+
+ /* Update the camera parameters if we need to */
+ if (qcam->status & QC_PARAM_CHANGE)
+ qc_set(qcam);
+
+ len=qc_capture(qcam, buf,count);
+
+ up(&qcam->lock);
+
+ parport_release(qcam->pdev);
+ return len;
+}
+
+static struct video_device qcam_template=
+{
+ "Connectix Quickcam",
+ VID_TYPE_CAPTURE,
+ VID_HARDWARE_QCAM_BW,
+ qcam_open,
+ qcam_close,
+ qcam_read,
+ qcam_write,
+ NULL,
+ qcam_ioctl,
+ NULL,
+ qcam_init_done,
+ NULL,
+ 0,
+ 0
+};
+
+#define MAX_CAMS 4
+static struct qcam_device *qcams[MAX_CAMS];
+static unsigned int num_cams = 0;
+
+int init_bwqcam(struct parport *port)
+{
+ struct qcam_device *qcam;
+
+ if (num_cams == MAX_CAMS)
+ {
+ printk(KERN_ERR "Too many Quickcams (max %d)\n", MAX_CAMS);
+ return -ENOSPC;
+ }
+
+ qcam=qcam_init(port);
+ if(qcam==NULL)
+ return -ENODEV;
+
+ parport_claim_or_block(qcam->pdev);
+
+ qc_reset(qcam);
+
+ if(qc_detect(qcam)==0)
+ {
+ parport_release(qcam->pdev);
+ parport_unregister_device(qcam->pdev);
+ kfree(qcam);
+ return -ENODEV;
+ }
+ qc_calibrate(qcam);
+
+ parport_release(qcam->pdev);
+
+ printk(KERN_INFO "Connectix Quickcam on %s\n", qcam->pport->name);
+
+ if(video_register_device(&qcam->vdev, VFL_TYPE_GRABBER)==-1)
+ {
+ parport_unregister_device(qcam->pdev);
+ kfree(qcam);
+ return -ENODEV;
+ }
+
+ qcams[num_cams++] = qcam;
+
+ return 0;
+}
+
+void close_bwqcam(struct qcam_device *qcam)
+{
+ video_unregister_device(&qcam->vdev);
+ parport_unregister_device(qcam->pdev);
+ kfree(qcam);
+}
+
+/* The parport parameter controls which parports will be scanned.
+ * Scanning all parports causes some printers to print a garbage page.
+ * -- March 14, 1999 Billy Donahue <billy@escape.com> */
+#ifdef MODULE
+static char *parport[MAX_CAMS] = { NULL, };
+MODULE_PARM(parport, "1-" __MODULE_STRING(MAX_CAMS) "s");
+#endif
+
+#ifdef MODULE
+int init_module(void)
+{
+ struct parport *port;
+ int n;
+ if(parport[0] && strncmp(parport[0], "auto", 4)){
+ /* user gave parport parameters */
+ for(n=0; parport[n] && n<MAX_CAMS; n++){
+ char *ep;
+ unsigned long r;
+ r = simple_strtoul(parport[n], &ep, 0);
+ if(ep == parport[n]){
+ printk(KERN_ERR
+ "bw-qcam: bad port specifier \"%s\"\n",
+ parport[n]);
+ continue;
+ }
+ for (port=parport_enumerate(); port; port=port->next){
+ if(r!=port->number)
+ continue;
+ init_bwqcam(port);
+ break;
+ }
+ }
+ return (num_cams)?0:-ENODEV;
+ }
+ /* no parameter or "auto" */
+ for (port = parport_enumerate(); port; port=port->next)
+ init_bwqcam(port);
+
+ /* Do some sanity checks on the module parameters. */
+ if (maxpoll > 5000) {
+ printk("Connectix Quickcam max-poll was above 5000. Using 5000.\n");
+ maxpoll = 5000;
+ }
+
+ if (yieldlines < 1) {
+ printk("Connectix Quickcam yieldlines was less than 1. Using 1.\n");
+ yieldlines = 1;
+ }
+
+ return (num_cams)?0:-ENODEV;
+}
+
+void cleanup_module(void)
+{
+ unsigned int i;
+ for (i = 0; i < num_cams; i++)
+ close_bwqcam(qcams[i]);
+}
+#else
+int __init init_bw_qcams(struct video_init *unused)
+{
+ struct parport *port;
+
+ for (port = parport_enumerate(); port; port=port->next)
+ init_bwqcam(port);
+ return 0;
+}
+#endif
diff --git a/drivers/media/video/bw-qcam.h b/drivers/media/video/bw-qcam.h
new file mode 100644
index 000000000..723e8ad9e
--- /dev/null
+++ b/drivers/media/video/bw-qcam.h
@@ -0,0 +1,68 @@
+/*
+ * Video4Linux bw-qcam driver
+ *
+ * Derived from code..
+ */
+
+/******************************************************************
+
+Copyright (C) 1996 by Scott Laird
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL SCOTT LAIRD BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+******************************************************************/
+
+/* One from column A... */
+#define QC_NOTSET 0
+#define QC_UNIDIR 1
+#define QC_BIDIR 2
+#define QC_SERIAL 3
+
+/* ... and one from column B */
+#define QC_ANY 0x00
+#define QC_FORCE_UNIDIR 0x10
+#define QC_FORCE_BIDIR 0x20
+#define QC_FORCE_SERIAL 0x30
+/* in the port_mode member */
+
+#define QC_MODE_MASK 0x07
+#define QC_FORCE_MASK 0x70
+
+#define MAX_HEIGHT 243
+#define MAX_WIDTH 336
+
+/* Bit fields for status flags */
+#define QC_PARAM_CHANGE 0x01 /* Camera status change has occurred */
+
+struct qcam_device {
+ struct video_device vdev;
+ struct pardevice *pdev;
+ struct parport *pport;
+ struct semaphore lock;
+ int width, height;
+ int bpp;
+ int mode;
+ int contrast, brightness, whitebal;
+ int port_mode;
+ int transfer_scale;
+ int top, left;
+ int status;
+ unsigned int saved_bits;
+};
diff --git a/drivers/media/video/c-qcam.c b/drivers/media/video/c-qcam.c
new file mode 100644
index 000000000..fa96cc925
--- /dev/null
+++ b/drivers/media/video/c-qcam.c
@@ -0,0 +1,901 @@
+/*
+ * Video4Linux Colour QuickCam driver
+ * Copyright 1997-2000 Philip Blundell <philb@gnu.org>
+ *
+ * Module parameters:
+ *
+ * parport=auto -- probe all parports (default)
+ * parport=0 -- parport0 becomes qcam1
+ * parport=2,0,1 -- parports 2,0,1 are tried in that order
+ *
+ * probe=0 -- do no probing, assume camera is present
+ * probe=1 -- use IEEE-1284 autoprobe data only (default)
+ * probe=2 -- probe aggressively for cameras
+ *
+ * force_rgb=1 -- force data format to RGB (default is BGR)
+ *
+ * The parport parameter controls which parports will be scanned.
+ * Scanning all parports causes some printers to print a garbage page.
+ * -- March 14, 1999 Billy Donahue <billy@escape.com>
+ *
+ * Fixed data format to BGR, added force_rgb parameter. Added missing
+ * parport_unregister_driver() on module removal.
+ * -- May 28, 2000 Claudio Matsuoka <claudio@conectiva.com>
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/parport.h>
+#include <linux/sched.h>
+#include <linux/version.h>
+#include <linux/videodev.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+struct qcam_device {
+ struct video_device vdev;
+ struct pardevice *pdev;
+ struct parport *pport;
+ int width, height;
+ int ccd_width, ccd_height;
+ int mode;
+ int contrast, brightness, whitebal;
+ int top, left;
+ unsigned int bidirectional;
+ struct semaphore lock;
+};
+
+/* cameras maximum */
+#define MAX_CAMS 4
+
+/* The three possible QuickCam modes */
+#define QC_MILLIONS 0x18
+#define QC_BILLIONS 0x10
+#define QC_THOUSANDS 0x08 /* with VIDEC compression (not supported) */
+
+/* The three possible decimations */
+#define QC_DECIMATION_1 0
+#define QC_DECIMATION_2 2
+#define QC_DECIMATION_4 4
+
+#define BANNER "Colour QuickCam for Video4Linux v0.05"
+
+static int parport[MAX_CAMS] = { [1 ... MAX_CAMS-1] = -1 };
+static int probe = 2;
+static int force_rgb = 0;
+
+static inline void qcam_set_ack(struct qcam_device *qcam, unsigned int i)
+{
+ /* note: the QC specs refer to the PCAck pin by voltage, not
+ software level. PC ports have builtin inverters. */
+ parport_frob_control(qcam->pport, 8, i?8:0);
+}
+
+static inline unsigned int qcam_ready1(struct qcam_device *qcam)
+{
+ return (parport_read_status(qcam->pport) & 0x8)?1:0;
+}
+
+static inline unsigned int qcam_ready2(struct qcam_device *qcam)
+{
+ return (parport_read_data(qcam->pport) & 0x1)?1:0;
+}
+
+static unsigned int qcam_await_ready1(struct qcam_device *qcam,
+ int value)
+{
+ unsigned long oldjiffies = jiffies;
+ unsigned int i;
+
+ for (oldjiffies = jiffies; (jiffies - oldjiffies) < (HZ/25); )
+ if (qcam_ready1(qcam) == value)
+ return 0;
+
+ /* If the camera didn't respond within 1/25 second, poll slowly
+ for a while. */
+ for (i = 0; i < 50; i++)
+ {
+ if (qcam_ready1(qcam) == value)
+ return 0;
+ current->state=TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/10);
+ }
+
+ /* Probably somebody pulled the plug out. Not much we can do. */
+ printk(KERN_ERR "c-qcam: ready1 timeout (%d) %x %x\n", value,
+ parport_read_status(qcam->pport),
+ parport_read_control(qcam->pport));
+ return 1;
+}
+
+static unsigned int qcam_await_ready2(struct qcam_device *qcam, int value)
+{
+ unsigned long oldjiffies = jiffies;
+ unsigned int i;
+
+ for (oldjiffies = jiffies; (jiffies - oldjiffies) < (HZ/25); )
+ if (qcam_ready2(qcam) == value)
+ return 0;
+
+ /* If the camera didn't respond within 1/25 second, poll slowly
+ for a while. */
+ for (i = 0; i < 50; i++)
+ {
+ if (qcam_ready2(qcam) == value)
+ return 0;
+ current->state=TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/10);
+ }
+
+ /* Probably somebody pulled the plug out. Not much we can do. */
+ printk(KERN_ERR "c-qcam: ready2 timeout (%d) %x %x %x\n", value,
+ parport_read_status(qcam->pport),
+ parport_read_control(qcam->pport),
+ parport_read_data(qcam->pport));
+ return 1;
+}
+
+static int qcam_read_data(struct qcam_device *qcam)
+{
+ unsigned int idata;
+ qcam_set_ack(qcam, 0);
+ if (qcam_await_ready1(qcam, 1)) return -1;
+ idata = parport_read_status(qcam->pport) & 0xf0;
+ qcam_set_ack(qcam, 1);
+ if (qcam_await_ready1(qcam, 0)) return -1;
+ idata |= (parport_read_status(qcam->pport) >> 4);
+ return idata;
+}
+
+static int qcam_write_data(struct qcam_device *qcam, unsigned int data)
+{
+ unsigned int idata;
+ parport_write_data(qcam->pport, data);
+ idata = qcam_read_data(qcam);
+ if (data != idata)
+ {
+ printk(KERN_WARNING "cqcam: sent %x but received %x\n", data,
+ idata);
+ return 1;
+ }
+ return 0;
+}
+
+static inline int qcam_set(struct qcam_device *qcam, unsigned int cmd, unsigned int data)
+{
+ if (qcam_write_data(qcam, cmd))
+ return -1;
+ if (qcam_write_data(qcam, data))
+ return -1;
+ return 0;
+}
+
+static inline int qcam_get(struct qcam_device *qcam, unsigned int cmd)
+{
+ if (qcam_write_data(qcam, cmd))
+ return -1;
+ return qcam_read_data(qcam);
+}
+
+static int qc_detect(struct qcam_device *qcam)
+{
+ unsigned int stat, ostat, i, count = 0;
+
+ /* The probe routine below is not very reliable. The IEEE-1284
+ probe takes precedence. */
+ /* XXX Currently parport provides no way to distinguish between
+ "the IEEE probe was not done" and "the probe was done, but
+ no device was found". Fix this one day. */
+ if (qcam->pport->probe_info[0].class == PARPORT_CLASS_MEDIA
+ && qcam->pport->probe_info[0].model
+ && !strcmp(qcam->pdev->port->probe_info[0].model,
+ "Color QuickCam 2.0")) {
+ printk(KERN_DEBUG "QuickCam: Found by IEEE1284 probe.\n");
+ return 1;
+ }
+
+ if (probe < 2)
+ return 0;
+
+ parport_write_control(qcam->pport, 0xc);
+
+ /* look for a heartbeat */
+ ostat = stat = parport_read_status(qcam->pport);
+ for (i=0; i<250; i++)
+ {
+ mdelay(1);
+ stat = parport_read_status(qcam->pport);
+ if (ostat != stat)
+ {
+ if (++count >= 3) return 1;
+ ostat = stat;
+ }
+ }
+
+ /* Reset the camera and try again */
+ parport_write_control(qcam->pport, 0xc);
+ parport_write_control(qcam->pport, 0x8);
+ mdelay(1);
+ parport_write_control(qcam->pport, 0xc);
+ mdelay(1);
+ count = 0;
+
+ ostat = stat = parport_read_status(qcam->pport);
+ for (i=0; i<250; i++)
+ {
+ mdelay(1);
+ stat = parport_read_status(qcam->pport);
+ if (ostat != stat)
+ {
+ if (++count >= 3) return 1;
+ ostat = stat;
+ }
+ }
+
+ /* no (or flatline) camera, give up */
+ return 0;
+}
+
+static void qc_reset(struct qcam_device *qcam)
+{
+ parport_write_control(qcam->pport, 0xc);
+ parport_write_control(qcam->pport, 0x8);
+ mdelay(1);
+ parport_write_control(qcam->pport, 0xc);
+ mdelay(1);
+}
+
+/* Reset the QuickCam and program for brightness, contrast,
+ * white-balance, and resolution. */
+
+static void qc_setup(struct qcam_device *q)
+{
+ qc_reset(q);
+
+ /* Set the brightness. */
+ qcam_set(q, 11, q->brightness);
+
+ /* Set the height and width. These refer to the actual
+ CCD area *before* applying the selected decimation. */
+ qcam_set(q, 17, q->ccd_height);
+ qcam_set(q, 19, q->ccd_width / 2);
+
+ /* Set top and left. */
+ qcam_set(q, 0xd, q->top);
+ qcam_set(q, 0xf, q->left);
+
+ /* Set contrast and white balance. */
+ qcam_set(q, 0x19, q->contrast);
+ qcam_set(q, 0x1f, q->whitebal);
+
+ /* Set the speed. */
+ qcam_set(q, 45, 2);
+}
+
+/* Read some bytes from the camera and put them in the buffer.
+ nbytes should be a multiple of 3, because bidirectional mode gives
+ us three bytes at a time. */
+
+static unsigned int qcam_read_bytes(struct qcam_device *q, unsigned char *buf, unsigned int nbytes)
+{
+ unsigned int bytes = 0;
+
+ qcam_set_ack(q, 0);
+ if (q->bidirectional)
+ {
+ /* It's a bidirectional port */
+ while (bytes < nbytes)
+ {
+ unsigned int lo1, hi1, lo2, hi2;
+ unsigned char r, g, b;
+
+ if (qcam_await_ready2(q, 1)) return bytes;
+ lo1 = parport_read_data(q->pport) >> 1;
+ hi1 = ((parport_read_status(q->pport) >> 3) & 0x1f) ^ 0x10;
+ qcam_set_ack(q, 1);
+ if (qcam_await_ready2(q, 0)) return bytes;
+ lo2 = parport_read_data(q->pport) >> 1;
+ hi2 = ((parport_read_status(q->pport) >> 3) & 0x1f) ^ 0x10;
+ qcam_set_ack(q, 0);
+ r = (lo1 | ((hi1 & 1)<<7));
+ g = ((hi1 & 0x1e)<<3) | ((hi2 & 0x1e)>>1);
+ b = (lo2 | ((hi2 & 1)<<7));
+ if (force_rgb) {
+ buf[bytes++] = r;
+ buf[bytes++] = g;
+ buf[bytes++] = b;
+ } else {
+ buf[bytes++] = b;
+ buf[bytes++] = g;
+ buf[bytes++] = r;
+ }
+ }
+ }
+ else
+ {
+ /* It's a unidirectional port */
+ int i = 0, n = bytes;
+ unsigned char rgb[3];
+
+ while (bytes < nbytes)
+ {
+ unsigned int hi, lo;
+
+ if (qcam_await_ready1(q, 1)) return bytes;
+ hi = (parport_read_status(q->pport) & 0xf0);
+ qcam_set_ack(q, 1);
+ if (qcam_await_ready1(q, 0)) return bytes;
+ lo = (parport_read_status(q->pport) & 0xf0);
+ qcam_set_ack(q, 0);
+ /* flip some bits */
+ rgb[(i = bytes++ % 3)] = (hi | (lo >> 4)) ^ 0x88;
+ if (i >= 2) {
+get_fragment:
+ if (force_rgb) {
+ buf[n++] = rgb[0];
+ buf[n++] = rgb[1];
+ buf[n++] = rgb[2];
+ } else {
+ buf[n++] = rgb[2];
+ buf[n++] = rgb[1];
+ buf[n++] = rgb[0];
+ }
+ }
+ }
+ if (i) {
+ i = 0;
+ goto get_fragment;
+ }
+ }
+ return bytes;
+}
+
+#define BUFSZ 150
+
+static long qc_capture(struct qcam_device *q, char *buf, unsigned long len)
+{
+ unsigned lines, pixelsperline, bitsperxfer;
+ unsigned int is_bi_dir = q->bidirectional;
+ size_t wantlen, outptr = 0;
+ char tmpbuf[BUFSZ];
+
+ if (verify_area(VERIFY_WRITE, buf, len))
+ return -EFAULT;
+
+ /* Wait for camera to become ready */
+ for (;;)
+ {
+ int i = qcam_get(q, 41);
+ if (i == -1) {
+ qc_setup(q);
+ return -EIO;
+ }
+ if ((i & 0x80) == 0)
+ break;
+ else
+ schedule();
+ }
+
+ if (qcam_set(q, 7, (q->mode | (is_bi_dir?1:0)) + 1))
+ return -EIO;
+
+ lines = q->height;
+ pixelsperline = q->width;
+ bitsperxfer = (is_bi_dir) ? 24 : 8;
+
+ if (is_bi_dir)
+ {
+ /* Turn the port around */
+ parport_data_reverse(q->pport);
+ mdelay(3);
+ qcam_set_ack(q, 0);
+ if (qcam_await_ready1(q, 1)) {
+ qc_setup(q);
+ return -EIO;
+ }
+ qcam_set_ack(q, 1);
+ if (qcam_await_ready1(q, 0)) {
+ qc_setup(q);
+ return -EIO;
+ }
+ }
+
+ wantlen = lines * pixelsperline * 24 / 8;
+
+ while (wantlen)
+ {
+ size_t t, s;
+ s = (wantlen > BUFSZ)?BUFSZ:wantlen;
+ t = qcam_read_bytes(q, tmpbuf, s);
+ if (outptr < len)
+ {
+ size_t sz = len - outptr;
+ if (sz > t) sz = t;
+ if (__copy_to_user(buf+outptr, tmpbuf, sz))
+ break;
+ outptr += sz;
+ }
+ wantlen -= t;
+ if (t < s)
+ break;
+ if (current->need_resched)
+ schedule();
+ }
+
+ len = outptr;
+
+ if (wantlen)
+ {
+ printk("qcam: short read.\n");
+ if (is_bi_dir)
+ parport_data_forward(q->pport);
+ qc_setup(q);
+ return len;
+ }
+
+ if (is_bi_dir)
+ {
+ int l;
+ do {
+ l = qcam_read_bytes(q, tmpbuf, 3);
+ if (current->need_resched)
+ schedule();
+ } while (l && (tmpbuf[0] == 0x7e || tmpbuf[1] == 0x7e || tmpbuf[2] == 0x7e));
+ if (force_rgb) {
+ if (tmpbuf[0] != 0xe || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xf)
+ printk("qcam: bad EOF\n");
+ } else {
+ if (tmpbuf[0] != 0xf || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xe)
+ printk("qcam: bad EOF\n");
+ }
+ qcam_set_ack(q, 0);
+ if (qcam_await_ready1(q, 1))
+ {
+ printk("qcam: no ack after EOF\n");
+ parport_data_forward(q->pport);
+ qc_setup(q);
+ return len;
+ }
+ parport_data_forward(q->pport);
+ mdelay(3);
+ qcam_set_ack(q, 1);
+ if (qcam_await_ready1(q, 0))
+ {
+ printk("qcam: no ack to port turnaround\n");
+ qc_setup(q);
+ return len;
+ }
+ }
+ else
+ {
+ int l;
+ do {
+ l = qcam_read_bytes(q, tmpbuf, 1);
+ if (current->need_resched)
+ schedule();
+ } while (l && tmpbuf[0] == 0x7e);
+ l = qcam_read_bytes(q, tmpbuf+1, 2);
+ if (force_rgb) {
+ if (tmpbuf[0] != 0xe || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xf)
+ printk("qcam: bad EOF\n");
+ } else {
+ if (tmpbuf[0] != 0xf || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xe)
+ printk("qcam: bad EOF\n");
+ }
+ }
+
+ qcam_write_data(q, 0);
+ return len;
+}
+
+/*
+ * Video4linux interfacing
+ */
+
+static int qcam_open(struct video_device *dev, int flags)
+{
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void qcam_close(struct video_device *dev)
+{
+ MOD_DEC_USE_COUNT;
+}
+
+static int qcam_init_done(struct video_device *dev)
+{
+ return 0;
+}
+
+static long qcam_write(struct video_device *v, const char *buf, unsigned long count, int noblock)
+{
+ return -EINVAL;
+}
+
+static int qcam_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct qcam_device *qcam=(struct qcam_device *)dev;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability b;
+ strcpy(b.name, "Quickcam");
+ b.type = VID_TYPE_CAPTURE|VID_TYPE_SCALES;
+ b.channels = 1;
+ b.audios = 0;
+ b.maxwidth = 320;
+ b.maxheight = 240;
+ b.minwidth = 80;
+ b.minheight = 60;
+ if(copy_to_user(arg, &b,sizeof(b)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCGCHAN:
+ {
+ struct video_channel v;
+ if(copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ if(v.channel!=0)
+ return -EINVAL;
+ v.flags=0;
+ v.tuners=0;
+ /* Good question.. its composite or SVHS so.. */
+ v.type = VIDEO_TYPE_CAMERA;
+ strcpy(v.name, "Camera");
+ if(copy_to_user(arg, &v, sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSCHAN:
+ {
+ int v;
+ if(copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ if(v!=0)
+ return -EINVAL;
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner v;
+ if(copy_from_user(&v, arg, sizeof(v))!=0)
+ return -EFAULT;
+ if(v.tuner)
+ return -EINVAL;
+ strcpy(v.name, "Format");
+ v.rangelow=0;
+ v.rangehigh=0;
+ v.flags= 0;
+ v.mode = VIDEO_MODE_AUTO;
+ if(copy_to_user(arg,&v,sizeof(v))!=0)
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner v;
+ if(copy_from_user(&v, arg, sizeof(v))!=0)
+ return -EFAULT;
+ if(v.tuner)
+ return -EINVAL;
+ if(v.mode!=VIDEO_MODE_AUTO)
+ return -EINVAL;
+ return 0;
+ }
+ case VIDIOCGPICT:
+ {
+ struct video_picture p;
+ p.colour=0x8000;
+ p.hue=0x8000;
+ p.brightness=qcam->brightness<<8;
+ p.contrast=qcam->contrast<<8;
+ p.whiteness=qcam->whitebal<<8;
+ p.depth=24;
+ p.palette=VIDEO_PALETTE_RGB24;
+ if(copy_to_user(arg, &p, sizeof(p)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSPICT:
+ {
+ struct video_picture p;
+ if(copy_from_user(&p, arg, sizeof(p)))
+ return -EFAULT;
+
+ /*
+ * Sanity check args
+ */
+ if (p.depth != 24 || p.palette != VIDEO_PALETTE_RGB24)
+ return -EINVAL;
+
+ /*
+ * Now load the camera.
+ */
+ qcam->brightness = p.brightness>>8;
+ qcam->contrast = p.contrast>>8;
+ qcam->whitebal = p.whiteness>>8;
+
+ down(&qcam->lock);
+ parport_claim_or_block(qcam->pdev);
+ qc_setup(qcam);
+ parport_release(qcam->pdev);
+ up(&qcam->lock);
+ return 0;
+ }
+ case VIDIOCSWIN:
+ {
+ struct video_window vw;
+
+ if(copy_from_user(&vw, arg,sizeof(vw)))
+ return -EFAULT;
+ if(vw.flags)
+ return -EINVAL;
+ if(vw.clipcount)
+ return -EINVAL;
+ if(vw.height<60||vw.height>240)
+ return -EINVAL;
+ if(vw.width<80||vw.width>320)
+ return -EINVAL;
+
+ qcam->width = 80;
+ qcam->height = 60;
+ qcam->mode = QC_DECIMATION_4;
+
+ if(vw.width>=160 && vw.height>=120)
+ {
+ qcam->width = 160;
+ qcam->height = 120;
+ qcam->mode = QC_DECIMATION_2;
+ }
+ if(vw.width>=320 && vw.height>=240)
+ {
+ qcam->width = 320;
+ qcam->height = 240;
+ qcam->mode = QC_DECIMATION_1;
+ }
+ qcam->mode |= QC_MILLIONS;
+#if 0
+ if(vw.width>=640 && vw.height>=480)
+ {
+ qcam->width = 640;
+ qcam->height = 480;
+ qcam->mode = QC_BILLIONS | QC_DECIMATION_1;
+ }
+#endif
+ /* Ok we figured out what to use from our
+ wide choice */
+ down(&qcam->lock);
+ parport_claim_or_block(qcam->pdev);
+ qc_setup(qcam);
+ parport_release(qcam->pdev);
+ up(&qcam->lock);
+ return 0;
+ }
+ case VIDIOCGWIN:
+ {
+ struct video_window vw;
+ vw.x=0;
+ vw.y=0;
+ vw.width=qcam->width;
+ vw.height=qcam->height;
+ vw.chromakey=0;
+ vw.flags=0;
+ if(copy_to_user(arg, &vw, sizeof(vw)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCCAPTURE:
+ return -EINVAL;
+ case VIDIOCGFBUF:
+ return -EINVAL;
+ case VIDIOCSFBUF:
+ return -EINVAL;
+ case VIDIOCKEY:
+ return 0;
+ case VIDIOCGFREQ:
+ return -EINVAL;
+ case VIDIOCSFREQ:
+ return -EINVAL;
+ case VIDIOCGAUDIO:
+ return -EINVAL;
+ case VIDIOCSAUDIO:
+ return -EINVAL;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static long qcam_read(struct video_device *v, char *buf, unsigned long count, int noblock)
+{
+ struct qcam_device *qcam=(struct qcam_device *)v;
+ int len;
+
+ down(&qcam->lock);
+ parport_claim_or_block(qcam->pdev);
+ /* Probably should have a semaphore against multiple users */
+ len = qc_capture(qcam, buf,count);
+ parport_release(qcam->pdev);
+ up(&qcam->lock);
+ return len;
+}
+
+/* video device template */
+static struct video_device qcam_template=
+{
+ "Colour QuickCam",
+ VID_TYPE_CAPTURE,
+ VID_HARDWARE_QCAM_C,
+ qcam_open,
+ qcam_close,
+ qcam_read,
+ qcam_write,
+ NULL,
+ qcam_ioctl,
+ NULL,
+ qcam_init_done,
+ NULL,
+ 0,
+ 0
+};
+
+/* Initialize the QuickCam driver control structure. */
+
+static struct qcam_device *qcam_init(struct parport *port)
+{
+ struct qcam_device *q;
+
+ q = kmalloc(sizeof(struct qcam_device), GFP_KERNEL);
+ if(q==NULL)
+ return NULL;
+
+ q->pport = port;
+ q->pdev = parport_register_device(port, "c-qcam", NULL, NULL,
+ NULL, 0, NULL);
+
+ q->bidirectional = (q->pport->modes & PARPORT_MODE_TRISTATE)?1:0;
+
+ if (q->pdev == NULL)
+ {
+ printk(KERN_ERR "c-qcam: couldn't register for %s.\n",
+ port->name);
+ kfree(q);
+ return NULL;
+ }
+
+ memcpy(&q->vdev, &qcam_template, sizeof(qcam_template));
+
+ init_MUTEX(&q->lock);
+ q->width = q->ccd_width = 320;
+ q->height = q->ccd_height = 240;
+ q->mode = QC_MILLIONS | QC_DECIMATION_1;
+ q->contrast = 192;
+ q->brightness = 240;
+ q->whitebal = 128;
+ q->top = 1;
+ q->left = 14;
+ return q;
+}
+
+static struct qcam_device *qcams[MAX_CAMS];
+static unsigned int num_cams = 0;
+
+int init_cqcam(struct parport *port)
+{
+ struct qcam_device *qcam;
+
+ if (parport[0] != -1)
+ {
+ /* The user gave specific instructions */
+ int i, found = 0;
+ for (i = 0; i < MAX_CAMS && parport[i] != -1; i++)
+ {
+ if (parport[0] == port->number)
+ found = 1;
+ }
+ if (!found)
+ return -ENODEV;
+ }
+
+ if (num_cams == MAX_CAMS)
+ return -ENOSPC;
+
+ qcam = qcam_init(port);
+ if (qcam==NULL)
+ return -ENODEV;
+
+ parport_claim_or_block(qcam->pdev);
+
+ qc_reset(qcam);
+
+ if (probe && qc_detect(qcam)==0)
+ {
+ parport_release(qcam->pdev);
+ parport_unregister_device(qcam->pdev);
+ kfree(qcam);
+ return -ENODEV;
+ }
+
+ qc_setup(qcam);
+
+ parport_release(qcam->pdev);
+
+ if (video_register_device(&qcam->vdev, VFL_TYPE_GRABBER)==-1)
+ {
+ printk(KERN_ERR "Unable to register Colour QuickCam on %s\n",
+ qcam->pport->name);
+ parport_unregister_device(qcam->pdev);
+ kfree(qcam);
+ return -ENODEV;
+ }
+
+ printk(KERN_INFO "video%d: Colour QuickCam found on %s\n",
+ qcam->vdev.minor, qcam->pport->name);
+
+ qcams[num_cams++] = qcam;
+
+ return 0;
+}
+
+void close_cqcam(struct qcam_device *qcam)
+{
+ video_unregister_device(&qcam->vdev);
+ parport_unregister_device(qcam->pdev);
+ kfree(qcam);
+}
+
+static void cq_attach(struct parport *port)
+{
+ init_cqcam(port);
+}
+
+static void cq_detach(struct parport *port)
+{
+ /* Write this some day. */
+}
+
+static struct parport_driver cqcam_driver = {
+ "cqcam",
+ cq_attach,
+ cq_detach,
+ NULL
+};
+
+static int __init cqcam_init (void)
+{
+ printk(BANNER "\n");
+
+ return parport_register_driver(&cqcam_driver);
+}
+
+static void __exit cqcam_cleanup (void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_cams; i++)
+ close_cqcam(qcams[i]);
+
+ parport_unregister_driver(&cqcam_driver);
+}
+
+MODULE_AUTHOR("Philip Blundell <philb@gnu.org>");
+MODULE_DESCRIPTION(BANNER);
+MODULE_PARM_DESC(parport ,"parport=<auto|n[,n]...> for port detection method\n\
+probe=<0|1|2> for camera detection method\n\
+force_rgb=<0|1> for RGB data format (default BGR)");
+MODULE_PARM(parport, "1-" __MODULE_STRING(MAX_CAMS) "i");
+MODULE_PARM(probe, "i");
+MODULE_PARM(force_rgb, "i");
+
+module_init(cqcam_init);
+module_exit(cqcam_cleanup);
diff --git a/drivers/media/video/cpia.c b/drivers/media/video/cpia.c
new file mode 100644
index 000000000..d7d007f01
--- /dev/null
+++ b/drivers/media/video/cpia.c
@@ -0,0 +1,3324 @@
+/*
+ * cpia CPiA driver
+ *
+ * Supports CPiA based Video Camera's.
+ *
+ * (C) Copyright 1999-2000 Peter Pregler,
+ * (C) Copyright 1999-2000 Scott J. Bertin,
+ * (C) Copyright 1999-2000 Johannes Erdfelt, jerdfelt@valinux.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* #define _CPIA_DEBUG_ define for verbose debug output */
+#include <linux/config.h>
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/vmalloc.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/ctype.h>
+#include <linux/pagemap.h>
+#include <asm/io.h>
+#include <asm/semaphore.h>
+#include <linux/wrapper.h>
+
+#ifdef CONFIG_KMOD
+#include <linux/kmod.h>
+#endif
+
+#include "cpia.h"
+
+#ifdef CONFIG_VIDEO_CPIA_PP
+extern int cpia_pp_init(void);
+#endif
+#ifdef CONFIG_VIDEO_CPIA_USB
+extern int cpia_usb_init(void);
+#endif
+
+#ifdef MODULE
+MODULE_AUTHOR("Scott J. Bertin <sbertin@mindspring.com> & Peter Pregler <Peter_Pregler@email.com> & Johannes Erdfelt <jerdfelt@valinux.com>");
+MODULE_DESCRIPTION("V4L-driver for Vision CPiA based cameras");
+MODULE_SUPPORTED_DEVICE("video");
+#endif
+
+#define ABOUT "V4L-Driver for Vision CPiA based cameras"
+
+#ifndef VID_HARDWARE_CPIA
+#define VID_HARDWARE_CPIA 24 /* FIXME -> from linux/videodev.h */
+#endif
+
+#define CPIA_MODULE_CPIA (0<<5)
+#define CPIA_MODULE_SYSTEM (1<<5)
+#define CPIA_MODULE_VP_CTRL (5<<5)
+#define CPIA_MODULE_CAPTURE (6<<5)
+#define CPIA_MODULE_DEBUG (7<<5)
+
+#define INPUT (DATA_IN << 8)
+#define OUTPUT (DATA_OUT << 8)
+
+#define CPIA_COMMAND_GetCPIAVersion (INPUT | CPIA_MODULE_CPIA | 1)
+#define CPIA_COMMAND_GetPnPID (INPUT | CPIA_MODULE_CPIA | 2)
+#define CPIA_COMMAND_GetCameraStatus (INPUT | CPIA_MODULE_CPIA | 3)
+#define CPIA_COMMAND_GotoHiPower (OUTPUT | CPIA_MODULE_CPIA | 4)
+#define CPIA_COMMAND_GotoLoPower (OUTPUT | CPIA_MODULE_CPIA | 5)
+#define CPIA_COMMAND_GotoSuspend (OUTPUT | CPIA_MODULE_CPIA | 7)
+#define CPIA_COMMAND_GotoPassThrough (OUTPUT | CPIA_MODULE_CPIA | 8)
+#define CPIA_COMMAND_ModifyCameraStatus (OUTPUT | CPIA_MODULE_CPIA | 10)
+
+#define CPIA_COMMAND_ReadVCRegs (INPUT | CPIA_MODULE_SYSTEM | 1)
+#define CPIA_COMMAND_WriteVCReg (OUTPUT | CPIA_MODULE_SYSTEM | 2)
+#define CPIA_COMMAND_ReadMCPorts (INPUT | CPIA_MODULE_SYSTEM | 3)
+#define CPIA_COMMAND_WriteMCPort (OUTPUT | CPIA_MODULE_SYSTEM | 4)
+#define CPIA_COMMAND_SetBaudRate (OUTPUT | CPIA_MODULE_SYSTEM | 5)
+#define CPIA_COMMAND_SetECPTiming (OUTPUT | CPIA_MODULE_SYSTEM | 6)
+#define CPIA_COMMAND_ReadIDATA (INPUT | CPIA_MODULE_SYSTEM | 7)
+#define CPIA_COMMAND_WriteIDATA (OUTPUT | CPIA_MODULE_SYSTEM | 8)
+#define CPIA_COMMAND_GenericCall (OUTPUT | CPIA_MODULE_SYSTEM | 9)
+#define CPIA_COMMAND_I2CStart (OUTPUT | CPIA_MODULE_SYSTEM | 10)
+#define CPIA_COMMAND_I2CStop (OUTPUT | CPIA_MODULE_SYSTEM | 11)
+#define CPIA_COMMAND_I2CWrite (OUTPUT | CPIA_MODULE_SYSTEM | 12)
+#define CPIA_COMMAND_I2CRead (INPUT | CPIA_MODULE_SYSTEM | 13)
+
+#define CPIA_COMMAND_GetVPVersion (INPUT | CPIA_MODULE_VP_CTRL | 1)
+#define CPIA_COMMAND_SetColourParams (OUTPUT | CPIA_MODULE_VP_CTRL | 3)
+#define CPIA_COMMAND_SetExposure (OUTPUT | CPIA_MODULE_VP_CTRL | 4)
+#define CPIA_COMMAND_SetColourBalance (OUTPUT | CPIA_MODULE_VP_CTRL | 6)
+#define CPIA_COMMAND_SetSensorFPS (OUTPUT | CPIA_MODULE_VP_CTRL | 7)
+#define CPIA_COMMAND_SetVPDefaults (OUTPUT | CPIA_MODULE_VP_CTRL | 8)
+#define CPIA_COMMAND_SetApcor (OUTPUT | CPIA_MODULE_VP_CTRL | 9)
+#define CPIA_COMMAND_SetFlickerCtrl (OUTPUT | CPIA_MODULE_VP_CTRL | 10)
+#define CPIA_COMMAND_SetVLOffset (OUTPUT | CPIA_MODULE_VP_CTRL | 11)
+#define CPIA_COMMAND_GetColourParams (INPUT | CPIA_MODULE_VP_CTRL | 16)
+#define CPIA_COMMAND_GetColourBalance (INPUT | CPIA_MODULE_VP_CTRL | 17)
+#define CPIA_COMMAND_GetExposure (INPUT | CPIA_MODULE_VP_CTRL | 18)
+#define CPIA_COMMAND_SetSensorMatrix (OUTPUT | CPIA_MODULE_VP_CTRL | 19)
+#define CPIA_COMMAND_ColourBars (OUTPUT | CPIA_MODULE_VP_CTRL | 25)
+#define CPIA_COMMAND_ReadVPRegs (INPUT | CPIA_MODULE_VP_CTRL | 30)
+#define CPIA_COMMAND_WriteVPReg (OUTPUT | CPIA_MODULE_VP_CTRL | 31)
+
+#define CPIA_COMMAND_GrabFrame (OUTPUT | CPIA_MODULE_CAPTURE | 1)
+#define CPIA_COMMAND_UploadFrame (OUTPUT | CPIA_MODULE_CAPTURE | 2)
+#define CPIA_COMMAND_SetGrabMode (OUTPUT | CPIA_MODULE_CAPTURE | 3)
+#define CPIA_COMMAND_InitStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 4)
+#define CPIA_COMMAND_FiniStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 5)
+#define CPIA_COMMAND_StartStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 6)
+#define CPIA_COMMAND_EndStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 7)
+#define CPIA_COMMAND_SetFormat (OUTPUT | CPIA_MODULE_CAPTURE | 8)
+#define CPIA_COMMAND_SetROI (OUTPUT | CPIA_MODULE_CAPTURE | 9)
+#define CPIA_COMMAND_SetCompression (OUTPUT | CPIA_MODULE_CAPTURE | 10)
+#define CPIA_COMMAND_SetCompressionTarget (OUTPUT | CPIA_MODULE_CAPTURE | 11)
+#define CPIA_COMMAND_SetYUVThresh (OUTPUT | CPIA_MODULE_CAPTURE | 12)
+#define CPIA_COMMAND_SetCompressionParams (OUTPUT | CPIA_MODULE_CAPTURE | 13)
+#define CPIA_COMMAND_DiscardFrame (OUTPUT | CPIA_MODULE_CAPTURE | 14)
+
+#define CPIA_COMMAND_OutputRS232 (OUTPUT | CPIA_MODULE_DEBUG | 1)
+#define CPIA_COMMAND_AbortProcess (OUTPUT | CPIA_MODULE_DEBUG | 4)
+#define CPIA_COMMAND_SetDramPage (OUTPUT | CPIA_MODULE_DEBUG | 5)
+#define CPIA_COMMAND_StartDramUpload (OUTPUT | CPIA_MODULE_DEBUG | 6)
+#define CPIA_COMMAND_StartDummyDtream (OUTPUT | CPIA_MODULE_DEBUG | 8)
+#define CPIA_COMMAND_AbortStream (OUTPUT | CPIA_MODULE_DEBUG | 9)
+#define CPIA_COMMAND_DownloadDRAM (OUTPUT | CPIA_MODULE_DEBUG | 10)
+
+enum {
+ FRAME_READY, /* Ready to grab into */
+ FRAME_GRABBING, /* In the process of being grabbed into */
+ FRAME_DONE, /* Finished grabbing, but not been synced yet */
+ FRAME_UNUSED, /* Unused (no MCAPTURE) */
+};
+
+#define COMMAND_NONE 0x0000
+#define COMMAND_SETCOMPRESSION 0x0001
+#define COMMAND_SETCOMPRESSIONTARGET 0x0002
+#define COMMAND_SETCOLOURPARAMS 0x0004
+#define COMMAND_SETFORMAT 0x0008
+#define COMMAND_PAUSE 0x0010
+#define COMMAND_RESUME 0x0020
+#define COMMAND_SETYUVTHRESH 0x0040
+#define COMMAND_SETECPTIMING 0x0080
+#define COMMAND_SETCOMPRESSIONPARAMS 0x0100
+#define COMMAND_SETEXPOSURE 0x0200
+#define COMMAND_SETCOLOURBALANCE 0x0400
+#define COMMAND_SETSENSORFPS 0x0800
+#define COMMAND_SETAPCOR 0x1000
+#define COMMAND_SETFLICKERCTRL 0x2000
+#define COMMAND_SETVLOFFSET 0x4000
+
+/* Developer's Guide Table 5 p 3-34
+ * indexed by [mains][sensorFps.baserate][sensorFps.divisor]*/
+static u8 flicker_jumps[2][2][4] =
+{ { { 76, 38, 19, 9 }, { 92, 46, 23, 11 } },
+ { { 64, 32, 16, 8 }, { 76, 38, 19, 9} }
+};
+
+/* forward declaration of local function */
+static void reset_camera_struct(struct cam_data *cam);
+
+/**********************************************************************
+ *
+ * Memory management
+ *
+ * This is a shameless copy from the USB-cpia driver (linux kernel
+ * version 2.3.29 or so, I have no idea what this code actually does ;).
+ * Actually it seems to be a copy of a shameless copy of the bttv-driver.
+ * Or that is a copy of a shameless copy of ... (To the powers: is there
+ * no generic kernel-function to do this sort of stuff?)
+ *
+ * Yes, it was a shameless copy from the bttv-driver. IIRC, Alan says
+ * there will be one, but apparentely not yet - jerdfelt
+ *
+ **********************************************************************/
+
+/* Given PGD from the address space's page table, return the kernel
+ * virtual mapping of the physical memory mapped at ADR.
+ */
+static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr)
+{
+ unsigned long ret = 0UL;
+ pmd_t *pmd;
+ pte_t *ptep, pte;
+
+ if (!pgd_none(*pgd)) {
+ pmd = pmd_offset(pgd, adr);
+ if (!pmd_none(*pmd)) {
+ ptep = pte_offset(pmd, adr);
+ pte = *ptep;
+ if (pte_present(pte)) {
+ ret = (unsigned long) page_address(pte_page(pte));
+ ret |= (adr & (PAGE_SIZE-1));
+ }
+ }
+ }
+ return ret;
+}
+
+/* Here we want the physical address of the memory.
+ * This is used when initializing the contents of the
+ * area and marking the pages as reserved.
+ */
+static inline unsigned long kvirt_to_pa(unsigned long adr)
+{
+ unsigned long va, kva, ret;
+
+ va = VMALLOC_VMADDR(adr);
+ kva = uvirt_to_kva(pgd_offset_k(va), va);
+ ret = __pa(kva);
+ return ret;
+}
+
+static void *rvmalloc(unsigned long size)
+{
+ void *mem;
+ unsigned long adr, page;
+
+ /* Round it off to PAGE_SIZE */
+ size += (PAGE_SIZE - 1);
+ size &= ~(PAGE_SIZE - 1);
+
+ mem = vmalloc_32(size);
+ if (!mem)
+ return NULL;
+
+ memset(mem, 0, size); /* Clear the ram out, no junk to the user */
+ adr = (unsigned long) mem;
+ while (size > 0) {
+ page = kvirt_to_pa(adr);
+ mem_map_reserve(virt_to_page(__va(page)));
+ adr += PAGE_SIZE;
+ if (size > PAGE_SIZE)
+ size -= PAGE_SIZE;
+ else
+ size = 0;
+ }
+
+ return mem;
+}
+
+static void rvfree(void *mem, unsigned long size)
+{
+ unsigned long adr, page;
+
+ if (!mem)
+ return;
+
+ size += (PAGE_SIZE - 1);
+ size &= ~(PAGE_SIZE - 1);
+
+ adr = (unsigned long) mem;
+ while (size > 0) {
+ page = kvirt_to_pa(adr);
+ mem_map_unreserve(virt_to_page(__va(page)));
+ adr += PAGE_SIZE;
+ if (size > PAGE_SIZE)
+ size -= PAGE_SIZE;
+ else
+ size = 0;
+ }
+ vfree(mem);
+}
+
+/**********************************************************************
+ *
+ * /proc interface
+ *
+ **********************************************************************/
+#ifdef CONFIG_PROC_FS
+static struct proc_dir_entry *cpia_proc_root=NULL;
+
+static int cpia_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = page;
+ int len, tmp;
+ struct cam_data *cam = data;
+ char tmpstr[20];
+
+ /* IMPORTANT: This output MUST be kept under PAGE_SIZE
+ * or we need to get more sophisticated. */
+
+ out += sprintf(out, "read-only\n-----------------------\n");
+ out += sprintf(out, "V4L Driver version: %d.%d.%d\n",
+ CPIA_MAJ_VER, CPIA_MIN_VER, CPIA_PATCH_VER);
+ out += sprintf(out, "CPIA Version: %d.%02d (%d.%d)\n",
+ cam->params.version.firmwareVersion,
+ cam->params.version.firmwareRevision,
+ cam->params.version.vcVersion,
+ cam->params.version.vcRevision);
+ out += sprintf(out, "CPIA PnP-ID: %04x:%04x:%04x\n",
+ cam->params.pnpID.vendor, cam->params.pnpID.product,
+ cam->params.pnpID.deviceRevision);
+ out += sprintf(out, "VP-Version: %d.%d %04x\n",
+ cam->params.vpVersion.vpVersion,
+ cam->params.vpVersion.vpRevision,
+ cam->params.vpVersion.cameraHeadID);
+
+ out += sprintf(out, "system_state: %#04x\n",
+ cam->params.status.systemState);
+ out += sprintf(out, "grab_state: %#04x\n",
+ cam->params.status.grabState);
+ out += sprintf(out, "stream_state: %#04x\n",
+ cam->params.status.streamState);
+ out += sprintf(out, "fatal_error: %#04x\n",
+ cam->params.status.fatalError);
+ out += sprintf(out, "cmd_error: %#04x\n",
+ cam->params.status.cmdError);
+ out += sprintf(out, "debug_flags: %#04x\n",
+ cam->params.status.debugFlags);
+ out += sprintf(out, "vp_status: %#04x\n",
+ cam->params.status.vpStatus);
+ out += sprintf(out, "error_code: %#04x\n",
+ cam->params.status.errorCode);
+ out += sprintf(out, "video_size: %s\n",
+ cam->params.format.videoSize == VIDEOSIZE_CIF ?
+ "CIF " : "QCIF");
+ out += sprintf(out, "sub_sample: %s\n",
+ cam->params.format.subSample == SUBSAMPLE_420 ?
+ "420" : "422");
+ out += sprintf(out, "yuv_order: %s\n",
+ cam->params.format.yuvOrder == YUVORDER_YUYV ?
+ "YUYV" : "UYVY");
+ out += sprintf(out, "roi: (%3d, %3d) to (%3d, %3d)\n",
+ cam->params.roi.colStart*8,
+ cam->params.roi.rowStart*4,
+ cam->params.roi.colEnd*8,
+ cam->params.roi.rowEnd*4);
+ out += sprintf(out, "actual_fps: %3d\n", cam->fps);
+ out += sprintf(out, "transfer_rate: %4dkB/s\n",
+ cam->transfer_rate);
+
+ out += sprintf(out, "\nread-write\n");
+ out += sprintf(out, "----------------------- current min"
+ " max default comment\n");
+ out += sprintf(out, "brightness: %8d %8d %8d %8d\n",
+ cam->params.colourParams.brightness, 0, 100, 50);
+ if (cam->params.version.firmwareVersion == 1 &&
+ cam->params.version.firmwareRevision == 2)
+ /* 1-02 firmware limits contrast to 80 */
+ tmp = 80;
+ else
+ tmp = 96;
+
+ out += sprintf(out, "contrast: %8d %8d %8d %8d"
+ " steps of 8\n",
+ cam->params.colourParams.contrast, 0, tmp, 48);
+ out += sprintf(out, "saturation: %8d %8d %8d %8d\n",
+ cam->params.colourParams.saturation, 0, 100, 50);
+ tmp = (25000+5000*cam->params.sensorFps.baserate)/
+ (1<<cam->params.sensorFps.divisor);
+ out += sprintf(out, "sensor_fps: %4d.%03d %8d %8d %8d\n",
+ tmp/1000, tmp%1000, 3, 30, 15);
+ out += sprintf(out, "stream_start_line: %8d %8d %8d %8d\n",
+ 2*cam->params.streamStartLine, 0,
+ cam->params.format.videoSize == VIDEOSIZE_CIF ? 288:144,
+ cam->params.format.videoSize == VIDEOSIZE_CIF ? 240:120);
+ out += sprintf(out, "ecp_timing: %8s %8s %8s %8s\n",
+ cam->params.ecpTiming ? "slow" : "normal", "slow",
+ "normal", "normal");
+
+ if (cam->params.colourBalance.balanceModeIsAuto) {
+ sprintf(tmpstr, "auto");
+ } else {
+ sprintf(tmpstr, "manual");
+ }
+ out += sprintf(out, "color_balance_mode: %8s %8s %8s"
+ " %8s\n", tmpstr, "manual", "auto", "auto");
+ out += sprintf(out, "red_gain: %8d %8d %8d %8d\n",
+ cam->params.colourBalance.redGain, 0, 212, 32);
+ out += sprintf(out, "green_gain: %8d %8d %8d %8d\n",
+ cam->params.colourBalance.greenGain, 0, 212, 6);
+ out += sprintf(out, "blue_gain: %8d %8d %8d %8d\n",
+ cam->params.colourBalance.blueGain, 0, 212, 92);
+
+ if (cam->params.version.firmwareVersion == 1 &&
+ cam->params.version.firmwareRevision == 2)
+ /* 1-02 firmware limits gain to 2 */
+ sprintf(tmpstr, "%8d %8d", 1, 2);
+ else
+ sprintf(tmpstr, "1,2,4,8");
+
+ if (cam->params.exposure.gainMode == 0)
+ out += sprintf(out, "max_gain: unknown %18s"
+ " %8d\n", tmpstr, 2);
+ else
+ out += sprintf(out, "max_gain: %8d %18s %8d\n",
+ 1<<(cam->params.exposure.gainMode-1), tmpstr, 2);
+
+ switch(cam->params.exposure.expMode) {
+ case 1:
+ case 3:
+ sprintf(tmpstr, "manual");
+ break;
+ case 2:
+ sprintf(tmpstr, "auto");
+ break;
+ default:
+ sprintf(tmpstr, "unknown");
+ break;
+ }
+ out += sprintf(out, "exposure_mode: %8s %8s %8s"
+ " %8s\n", tmpstr, "manual", "auto", "auto");
+ out += sprintf(out, "centre_weight: %8s %8s %8s %8s\n",
+ (2-cam->params.exposure.centreWeight) ? "on" : "off",
+ "off", "on", "on");
+ out += sprintf(out, "gain: %8d %8d max_gain %8d 1,2,4,8 possible\n",
+ 1<<cam->params.exposure.gain, 1, 1);
+ if (cam->params.version.firmwareVersion == 1 &&
+ cam->params.version.firmwareRevision == 2)
+ /* 1-02 firmware limits fineExp to 127 */
+ tmp = 255;
+ else
+ tmp = 511;
+
+ out += sprintf(out, "fine_exp: %8d %8d %8d %8d\n",
+ cam->params.exposure.fineExp*2, 0, tmp, 0);
+ if (cam->params.version.firmwareVersion == 1 &&
+ cam->params.version.firmwareRevision == 2)
+ /* 1-02 firmware limits coarseExpHi to 0 */
+ tmp = 255;
+ else
+ tmp = 65535;
+
+ out += sprintf(out, "coarse_exp: %8d %8d %8d"
+ " %8d\n", cam->params.exposure.coarseExpLo+
+ 256*cam->params.exposure.coarseExpHi, 0, tmp, 185);
+ out += sprintf(out, "red_comp: %8d %8d %8d %8d\n",
+ cam->params.exposure.redComp, 220, 255, 220);
+ out += sprintf(out, "green1_comp: %8d %8d %8d %8d\n",
+ cam->params.exposure.green1Comp, 214, 255, 214);
+ out += sprintf(out, "green2_comp: %8d %8d %8d %8d\n",
+ cam->params.exposure.green2Comp, 214, 255, 214);
+ out += sprintf(out, "blue_comp: %8d %8d %8d %8d\n",
+ cam->params.exposure.blueComp, 230, 255, 230);
+
+ out += sprintf(out, "apcor_gain1: %#8x %#8x %#8x %#8x\n",
+ cam->params.apcor.gain1, 0, 0xff, 0x1c);
+ out += sprintf(out, "apcor_gain2: %#8x %#8x %#8x %#8x\n",
+ cam->params.apcor.gain2, 0, 0xff, 0x1a);
+ out += sprintf(out, "apcor_gain4: %#8x %#8x %#8x %#8x\n",
+ cam->params.apcor.gain4, 0, 0xff, 0x2d);
+ out += sprintf(out, "apcor_gain8: %#8x %#8x %#8x %#8x\n",
+ cam->params.apcor.gain8, 0, 0xff, 0x2a);
+ out += sprintf(out, "vl_offset_gain1: %8d %8d %8d %8d\n",
+ cam->params.vlOffset.gain1, 0, 255, 24);
+ out += sprintf(out, "vl_offset_gain2: %8d %8d %8d %8d\n",
+ cam->params.vlOffset.gain2, 0, 255, 28);
+ out += sprintf(out, "vl_offset_gain4: %8d %8d %8d %8d\n",
+ cam->params.vlOffset.gain4, 0, 255, 30);
+ out += sprintf(out, "vl_offset_gain8: %8d %8d %8d %8d\n",
+ cam->params.vlOffset.gain8, 0, 255, 30);
+ out += sprintf(out, "flicker_control: %8s %8s %8s %8s\n",
+ cam->params.flickerControl.flickerMode ? "on" : "off",
+ "off", "on", "off");
+ out += sprintf(out, "mains_frequency: %8d %8d %8d %8d"
+ " only 50/60\n",
+ cam->mainsFreq ? 60 : 50, 50, 60, 50);
+ out += sprintf(out, "allowable_overexposure: %8d %8d %8d %8d\n",
+ cam->params.flickerControl.allowableOverExposure, 0,
+ 255, 0);
+ out += sprintf(out, "compression_mode: ");
+ switch(cam->params.compression.mode) {
+ case CPIA_COMPRESSION_NONE:
+ out += sprintf(out, "%8s", "none");
+ break;
+ case CPIA_COMPRESSION_AUTO:
+ out += sprintf(out, "%8s", "auto");
+ break;
+ case CPIA_COMPRESSION_MANUAL:
+ out += sprintf(out, "%8s", "manual");
+ break;
+ default:
+ out += sprintf(out, "%8s", "unknown");
+ break;
+ }
+ out += sprintf(out, " none,auto,manual auto\n");
+ out += sprintf(out, "decimation_enable: %8s %8s %8s %8s\n",
+ cam->params.compression.decimation ==
+ DECIMATION_ENAB ? "on":"off", "off", "off",
+ "off");
+ out += sprintf(out, "compression_target: %9s %9s %9s %9s\n",
+ cam->params.compressionTarget.frTargeting ==
+ CPIA_COMPRESSION_TARGET_FRAMERATE ?
+ "framerate":"quality",
+ "framerate", "quality", "quality");
+ out += sprintf(out, "target_framerate: %8d %8d %8d %8d\n",
+ cam->params.compressionTarget.targetFR, 0, 30, 7);
+ out += sprintf(out, "target_quality: %8d %8d %8d %8d\n",
+ cam->params.compressionTarget.targetQ, 0, 255, 10);
+ out += sprintf(out, "y_threshold: %8d %8d %8d %8d\n",
+ cam->params.yuvThreshold.yThreshold, 0, 31, 15);
+ out += sprintf(out, "uv_threshold: %8d %8d %8d %8d\n",
+ cam->params.yuvThreshold.uvThreshold, 0, 31, 15);
+ out += sprintf(out, "hysteresis: %8d %8d %8d %8d\n",
+ cam->params.compressionParams.hysteresis, 0, 255, 3);
+ out += sprintf(out, "threshold_max: %8d %8d %8d %8d\n",
+ cam->params.compressionParams.threshMax, 0, 255, 11);
+ out += sprintf(out, "small_step: %8d %8d %8d %8d\n",
+ cam->params.compressionParams.smallStep, 0, 255, 1);
+ out += sprintf(out, "large_step: %8d %8d %8d %8d\n",
+ cam->params.compressionParams.largeStep, 0, 255, 3);
+ out += sprintf(out, "decimation_hysteresis: %8d %8d %8d %8d\n",
+ cam->params.compressionParams.decimationHysteresis,
+ 0, 255, 2);
+ out += sprintf(out, "fr_diff_step_thresh: %8d %8d %8d %8d\n",
+ cam->params.compressionParams.frDiffStepThresh,
+ 0, 255, 5);
+ out += sprintf(out, "q_diff_step_thresh: %8d %8d %8d %8d\n",
+ cam->params.compressionParams.qDiffStepThresh,
+ 0, 255, 3);
+ out += sprintf(out, "decimation_thresh_mod: %8d %8d %8d %8d\n",
+ cam->params.compressionParams.decimationThreshMod,
+ 0, 255, 2);
+
+ len = out - page;
+ len -= off;
+ if (len < count) {
+ *eof = 1;
+ if (len <= 0) return 0;
+ } else
+ len = count;
+
+ *start = page + off;
+ return len;
+}
+
+static int cpia_write_proc(struct file *file, const char *buffer,
+ unsigned long count, void *data)
+{
+ struct cam_data *cam = data;
+ struct cam_params new_params;
+ int retval, find_colon;
+ int size = count;
+ unsigned long val;
+ u32 command_flags = 0;
+ u8 new_mains;
+
+ if (down_interruptible(&cam->param_lock))
+ return -ERESTARTSYS;
+
+ /*
+ * Skip over leading whitespace
+ */
+ while (count && isspace(*buffer)) {
+ --count;
+ ++buffer;
+ }
+
+ memcpy(&new_params, &cam->params, sizeof(struct cam_params));
+ new_mains = cam->mainsFreq;
+
+#define MATCH(x) \
+ ({ \
+ int _len = strlen(x), _ret, _colon_found; \
+ _ret = (_len <= count && strncmp(buffer, x, _len) == 0); \
+ if (_ret) { \
+ buffer += _len; \
+ count -= _len; \
+ if (find_colon) { \
+ _colon_found = 0; \
+ while (count && (*buffer == ' ' || *buffer == '\t' || \
+ (!_colon_found && *buffer == ':'))) { \
+ if (*buffer == ':') \
+ _colon_found = 1; \
+ --count; \
+ ++buffer; \
+ } \
+ if (!count || !_colon_found) \
+ retval = -EINVAL; \
+ find_colon = 0; \
+ } \
+ } \
+ _ret; \
+ })
+#define FIRMWARE_VERSION(x,y) (new_params.version.firmwareVersion == (x) && \
+ new_params.version.firmwareRevision == (y))
+#define VALUE \
+ ({ \
+ char *_p; \
+ unsigned long int _ret; \
+ _ret = simple_strtoul(buffer, &_p, 0); \
+ if (_p == buffer) \
+ retval = -EINVAL; \
+ else { \
+ count -= _p - buffer; \
+ buffer = _p; \
+ } \
+ _ret; \
+ })
+
+ retval = 0;
+ while (count && !retval) {
+ find_colon = 1;
+ if (MATCH("brightness")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 100)
+ new_params.colourParams.brightness = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOLOURPARAMS;
+ } else if (MATCH("contrast")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 100) {
+ /* contrast is in steps of 8, so round*/
+ val = ((val + 3) / 8) * 8;
+ /* 1-02 firmware limits contrast to 80*/
+ if (FIRMWARE_VERSION(1,2) && val > 80)
+ val = 80;
+
+ new_params.colourParams.contrast = val;
+ } else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOLOURPARAMS;
+ } else if (MATCH("saturation")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 100)
+ new_params.colourParams.saturation = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOLOURPARAMS;
+ } else if (MATCH("sensor_fps")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ /* find values so that sensorFPS is minimized,
+ * but >= val */
+ if (val > 30)
+ retval = -EINVAL;
+ else if (val > 25) {
+ new_params.sensorFps.divisor = 0;
+ new_params.sensorFps.baserate = 1;
+ } else if (val > 15) {
+ new_params.sensorFps.divisor = 0;
+ new_params.sensorFps.baserate = 0;
+ } else if (val > 12) {
+ new_params.sensorFps.divisor = 1;
+ new_params.sensorFps.baserate = 1;
+ } else if (val > 7) {
+ new_params.sensorFps.divisor = 1;
+ new_params.sensorFps.baserate = 0;
+ } else if (val > 6) {
+ new_params.sensorFps.divisor = 2;
+ new_params.sensorFps.baserate = 1;
+ } else if (val > 3) {
+ new_params.sensorFps.divisor = 2;
+ new_params.sensorFps.baserate = 0;
+ } else {
+ new_params.sensorFps.divisor = 3;
+ /* Either base rate would work here */
+ new_params.sensorFps.baserate = 1;
+ }
+ new_params.flickerControl.coarseJump =
+ flicker_jumps[new_mains]
+ [new_params.sensorFps.baserate]
+ [new_params.sensorFps.divisor];
+ if (new_params.flickerControl.flickerMode)
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ }
+ command_flags |= COMMAND_SETSENSORFPS;
+ } else if (MATCH("stream_start_line")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ int max_line = 288;
+
+ if (new_params.format.videoSize == VIDEOSIZE_QCIF)
+ max_line = 144;
+ if (val <= max_line)
+ new_params.streamStartLine = val/2;
+ else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("ecp_timing")) {
+ if (!retval && MATCH("normal"))
+ new_params.ecpTiming = 0;
+ else if (!retval && MATCH("slow"))
+ new_params.ecpTiming = 1;
+ else
+ retval = -EINVAL;
+
+ command_flags |= COMMAND_SETECPTIMING;
+ } else if (MATCH("color_balance_mode")) {
+ if (!retval && MATCH("manual"))
+ new_params.colourBalance.balanceModeIsAuto = 0;
+ else if (!retval && MATCH("auto"))
+ new_params.colourBalance.balanceModeIsAuto = 1;
+ else
+ retval = -EINVAL;
+
+ command_flags |= COMMAND_SETCOLOURBALANCE;
+ } else if (MATCH("red_gain")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 212)
+ new_params.colourBalance.redGain = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOLOURBALANCE;
+ } else if (MATCH("green_gain")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 212)
+ new_params.colourBalance.greenGain = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOLOURBALANCE;
+ } else if (MATCH("blue_gain")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 212)
+ new_params.colourBalance.blueGain = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOLOURBALANCE;
+ } else if (MATCH("max_gain")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ /* 1-02 firmware limits gain to 2 */
+ if (FIRMWARE_VERSION(1,2) && val > 2)
+ val = 2;
+ switch(val) {
+ case 1:
+ new_params.exposure.gainMode = 1;
+ break;
+ case 2:
+ new_params.exposure.gainMode = 2;
+ break;
+ case 4:
+ new_params.exposure.gainMode = 3;
+ break;
+ case 8:
+ new_params.exposure.gainMode = 4;
+ break;
+ default:
+ retval = -EINVAL;
+ break;
+ }
+ }
+ command_flags |= COMMAND_SETEXPOSURE;
+ } else if (MATCH("exposure_mode")) {
+ if (!retval && MATCH("auto"))
+ new_params.exposure.expMode = 2;
+ else if (!retval && MATCH("manual")) {
+ if (new_params.exposure.expMode == 2)
+ new_params.exposure.expMode = 3;
+ new_params.flickerControl.flickerMode = 0;
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ } else
+ retval = -EINVAL;
+
+ command_flags |= COMMAND_SETEXPOSURE;
+ } else if (MATCH("centre_weight")) {
+ if (!retval && MATCH("on"))
+ new_params.exposure.centreWeight = 1;
+ else if (!retval && MATCH("off"))
+ new_params.exposure.centreWeight = 2;
+ else
+ retval = -EINVAL;
+
+ command_flags |= COMMAND_SETEXPOSURE;
+ } else if (MATCH("gain")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ switch(val) {
+ case 1:
+ new_params.exposure.gain = 0;
+ new_params.exposure.expMode = 1;
+ new_params.flickerControl.flickerMode = 0;
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ break;
+ case 2:
+ new_params.exposure.gain = 1;
+ new_params.exposure.expMode = 1;
+ new_params.flickerControl.flickerMode = 0;
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ break;
+ case 4:
+ new_params.exposure.gain = 2;
+ new_params.exposure.expMode = 1;
+ new_params.flickerControl.flickerMode = 0;
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ break;
+ case 8:
+ new_params.exposure.gain = 3;
+ new_params.exposure.expMode = 1;
+ new_params.flickerControl.flickerMode = 0;
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ break;
+ default:
+ retval = -EINVAL;
+ break;
+ }
+ command_flags |= COMMAND_SETEXPOSURE;
+ if (new_params.exposure.gain >
+ new_params.exposure.gainMode-1)
+ retval = -EINVAL;
+ }
+ } else if (MATCH("fine_exp")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val < 256) {
+ /* 1-02 firmware limits fineExp to 127*/
+ if (FIRMWARE_VERSION(1,2) && val > 127)
+ val = 127;
+ new_params.exposure.fineExp = val;
+ new_params.exposure.expMode = 1;
+ command_flags |= COMMAND_SETEXPOSURE;
+ new_params.flickerControl.flickerMode = 0;
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ } else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("coarse_exp")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val < 65536) {
+ /* 1-02 firmware limits
+ * coarseExp to 255 */
+ if (FIRMWARE_VERSION(1,2) && val > 255)
+ val = 255;
+ new_params.exposure.coarseExpLo =
+ val & 0xff;
+ new_params.exposure.coarseExpHi =
+ val >> 8;
+ new_params.exposure.expMode = 1;
+ command_flags |= COMMAND_SETEXPOSURE;
+ new_params.flickerControl.flickerMode = 0;
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ } else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("red_comp")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val >= 220 && val <= 255) {
+ new_params.exposure.redComp = val;
+ command_flags |= COMMAND_SETEXPOSURE;
+ } else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("green1_comp")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val >= 214 && val <= 255) {
+ new_params.exposure.green1Comp = val;
+ command_flags |= COMMAND_SETEXPOSURE;
+ } else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("green2_comp")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val >= 214 && val <= 255) {
+ new_params.exposure.green2Comp = val;
+ command_flags |= COMMAND_SETEXPOSURE;
+ } else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("blue_comp")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val >= 230 && val <= 255) {
+ new_params.exposure.blueComp = val;
+ command_flags |= COMMAND_SETEXPOSURE;
+ } else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("apcor_gain1")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ command_flags |= COMMAND_SETAPCOR;
+ if (val <= 0xff)
+ new_params.apcor.gain1 = val;
+ else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("apcor_gain2")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ command_flags |= COMMAND_SETAPCOR;
+ if (val <= 0xff)
+ new_params.apcor.gain2 = val;
+ else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("apcor_gain4")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ command_flags |= COMMAND_SETAPCOR;
+ if (val <= 0xff)
+ new_params.apcor.gain4 = val;
+ else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("apcor_gain8")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ command_flags |= COMMAND_SETAPCOR;
+ if (val <= 0xff)
+ new_params.apcor.gain8 = val;
+ else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("vl_offset_gain1")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.vlOffset.gain1 = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETVLOFFSET;
+ } else if (MATCH("vl_offset_gain2")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.vlOffset.gain2 = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETVLOFFSET;
+ } else if (MATCH("vl_offset_gain4")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.vlOffset.gain4 = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETVLOFFSET;
+ } else if (MATCH("vl_offset_gain8")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.vlOffset.gain8 = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETVLOFFSET;
+ } else if (MATCH("flicker_control")) {
+ if (!retval && MATCH("on")) {
+ new_params.flickerControl.flickerMode = 1;
+ new_params.exposure.expMode = 2;
+ command_flags |= COMMAND_SETEXPOSURE;
+ } else if (!retval && MATCH("off"))
+ new_params.flickerControl.flickerMode = 0;
+ else
+ retval = -EINVAL;
+
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ } else if (MATCH("mains_frequency")) {
+ if (!retval && MATCH("50")) {
+ new_mains = 0;
+ new_params.flickerControl.coarseJump =
+ flicker_jumps[new_mains]
+ [new_params.sensorFps.baserate]
+ [new_params.sensorFps.divisor];
+ if (new_params.flickerControl.flickerMode)
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ } else if (!retval && MATCH("60")) {
+ new_mains = 1;
+ new_params.flickerControl.coarseJump =
+ flicker_jumps[new_mains]
+ [new_params.sensorFps.baserate]
+ [new_params.sensorFps.divisor];
+ if (new_params.flickerControl.flickerMode)
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ } else
+ retval = -EINVAL;
+ } else if (MATCH("allowable_overexposure")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff) {
+ new_params.flickerControl.
+ allowableOverExposure = val;
+ command_flags |= COMMAND_SETFLICKERCTRL;
+ } else
+ retval = -EINVAL;
+ }
+ } else if (MATCH("compression_mode")) {
+ if (!retval && MATCH("none"))
+ new_params.compression.mode =
+ CPIA_COMPRESSION_NONE;
+ else if (!retval && MATCH("auto"))
+ new_params.compression.mode =
+ CPIA_COMPRESSION_AUTO;
+ else if (!retval && MATCH("manual"))
+ new_params.compression.mode =
+ CPIA_COMPRESSION_MANUAL;
+ else
+ retval = -EINVAL;
+
+ command_flags |= COMMAND_SETCOMPRESSION;
+ } else if (MATCH("decimation_enable")) {
+ if (!retval && MATCH("off"))
+ new_params.compression.decimation = 0;
+ else
+ retval = -EINVAL;
+
+ command_flags |= COMMAND_SETCOMPRESSION;
+ } else if (MATCH("compression_target")) {
+ if (!retval && MATCH("quality"))
+ new_params.compressionTarget.frTargeting =
+ CPIA_COMPRESSION_TARGET_QUALITY;
+ else if (!retval && MATCH("framerate"))
+ new_params.compressionTarget.frTargeting =
+ CPIA_COMPRESSION_TARGET_FRAMERATE;
+ else
+ retval = -EINVAL;
+
+ command_flags |= COMMAND_SETCOMPRESSIONTARGET;
+ } else if (MATCH("target_framerate")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval)
+ new_params.compressionTarget.targetFR = val;
+ command_flags |= COMMAND_SETCOMPRESSIONTARGET;
+ } else if (MATCH("target_quality")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval)
+ new_params.compressionTarget.targetQ = val;
+
+ command_flags |= COMMAND_SETCOMPRESSIONTARGET;
+ } else if (MATCH("y_threshold")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val < 32)
+ new_params.yuvThreshold.yThreshold = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETYUVTHRESH;
+ } else if (MATCH("uv_threshold")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val < 32)
+ new_params.yuvThreshold.uvThreshold = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETYUVTHRESH;
+ } else if (MATCH("hysteresis")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.compressionParams.hysteresis = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
+ } else if (MATCH("threshold_max")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.compressionParams.threshMax = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
+ } else if (MATCH("small_step")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.compressionParams.smallStep = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
+ } else if (MATCH("large_step")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.compressionParams.largeStep = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
+ } else if (MATCH("decimation_hysteresis")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.compressionParams.decimationHysteresis = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
+ } else if (MATCH("fr_diff_step_thresh")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.compressionParams.frDiffStepThresh = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
+ } else if (MATCH("q_diff_step_thresh")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.compressionParams.qDiffStepThresh = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
+ } else if (MATCH("decimation_thresh_mod")) {
+ if (!retval)
+ val = VALUE;
+
+ if (!retval) {
+ if (val <= 0xff)
+ new_params.compressionParams.decimationThreshMod = val;
+ else
+ retval = -EINVAL;
+ }
+ command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
+ } else {
+ DBG("No match found\n");
+ retval = -EINVAL;
+ }
+
+ if (!retval) {
+ while (count && isspace(*buffer) && *buffer != '\n') {
+ --count;
+ ++buffer;
+ }
+ if (count) {
+ if (*buffer != '\n' && *buffer != ';')
+ retval = -EINVAL;
+ else {
+ --count;
+ ++buffer;
+ }
+ }
+ }
+ }
+#undef MATCH
+#undef FIRMWARE_VERSION
+#undef VALUE
+#undef FIND_VALUE
+#undef FIND_END
+ if (!retval) {
+ if (command_flags & COMMAND_SETCOLOURPARAMS) {
+ /* Adjust cam->vp to reflect these changes */
+ cam->vp.brightness =
+ new_params.colourParams.brightness*65535/100;
+ cam->vp.contrast =
+ new_params.colourParams.contrast*65535/100;
+ cam->vp.colour =
+ new_params.colourParams.saturation*65535/100;
+ }
+
+ memcpy(&cam->params, &new_params, sizeof(struct cam_params));
+ cam->mainsFreq = new_mains;
+ cam->cmd_queue |= command_flags;
+ retval = size;
+ } else
+ DBG("error: %d\n", retval);
+
+ up(&cam->param_lock);
+
+ return retval;
+}
+
+static void create_proc_cpia_cam(struct cam_data *cam)
+{
+ char name[7];
+ struct proc_dir_entry *ent;
+
+ if (!cpia_proc_root || !cam)
+ return;
+
+ sprintf(name, "video%d", cam->vdev.minor);
+
+ ent = create_proc_entry(name, S_IFREG|S_IRUGO|S_IWUSR, cpia_proc_root);
+ if (!ent)
+ return;
+
+ ent->data = cam;
+ ent->read_proc = cpia_read_proc;
+ ent->write_proc = cpia_write_proc;
+ ent->size = 3626;
+ cam->proc_entry = ent;
+}
+
+static void destroy_proc_cpia_cam(struct cam_data *cam)
+{
+ char name[7];
+
+ if (!cam || !cam->proc_entry)
+ return;
+
+ sprintf(name, "video%d", cam->vdev.minor);
+ remove_proc_entry(name, cpia_proc_root);
+ cam->proc_entry = NULL;
+}
+
+static void proc_cpia_create(void)
+{
+ cpia_proc_root = create_proc_entry("cpia", S_IFDIR, 0);
+
+ if (cpia_proc_root)
+ cpia_proc_root->owner = THIS_MODULE;
+ else
+ LOG("Unable to initialise /proc/cpia\n");
+}
+
+static void proc_cpia_destroy(void)
+{
+ remove_proc_entry("cpia", 0);
+}
+#endif /* CONFIG_PROC_FS */
+
+/* ----------------------- debug functions ---------------------- */
+
+#define printstatus(cam) \
+ DBG("%02x %02x %02x %02x %02x %02x %02x %02x\n",\
+ cam->params.status.systemState, cam->params.status.grabState, \
+ cam->params.status.streamState, cam->params.status.fatalError, \
+ cam->params.status.cmdError, cam->params.status.debugFlags, \
+ cam->params.status.vpStatus, cam->params.status.errorCode);
+
+/* ----------------------- v4l helpers -------------------------- */
+
+/* supported frame palettes and depths */
+static inline int valid_mode(u16 palette, u16 depth)
+{
+ return (palette == VIDEO_PALETTE_GREY && depth == 8) ||
+ (palette == VIDEO_PALETTE_RGB555 && depth == 16) ||
+ (palette == VIDEO_PALETTE_RGB565 && depth == 16) ||
+ (palette == VIDEO_PALETTE_RGB24 && depth == 24) ||
+ (palette == VIDEO_PALETTE_RGB32 && depth == 32) ||
+ (palette == VIDEO_PALETTE_YUV422 && depth == 16) ||
+ (palette == VIDEO_PALETTE_YUYV && depth == 16) ||
+ (palette == VIDEO_PALETTE_UYVY && depth == 16);
+}
+
+static int match_videosize( int width, int height )
+{
+ /* return the best match, where 'best' is as always
+ * the largest that is not bigger than what is requested. */
+ if (width>=352 && height>=288)
+ return VIDEOSIZE_352_288; /* CIF */
+
+ if (width>=320 && height>=240)
+ return VIDEOSIZE_320_240; /* SIF */
+
+ if (width>=288 && height>=216)
+ return VIDEOSIZE_288_216;
+
+ if (width>=256 && height>=192)
+ return VIDEOSIZE_256_192;
+
+ if (width>=224 && height>=168)
+ return VIDEOSIZE_224_168;
+
+ if (width>=192 && height>=144)
+ return VIDEOSIZE_192_144;
+
+ if (width>=176 && height>=144)
+ return VIDEOSIZE_176_144; /* QCIF */
+
+ if (width>=160 && height>=120)
+ return VIDEOSIZE_160_120; /* QSIF */
+
+ if (width>=128 && height>=96)
+ return VIDEOSIZE_128_96;
+
+ if (width>=88 && height>=72)
+ return VIDEOSIZE_88_72;
+
+ if (width>=64 && height>=48)
+ return VIDEOSIZE_64_48;
+
+ if (width>=48 && height>=48)
+ return VIDEOSIZE_48_48;
+
+ return -1;
+}
+
+/* these are the capture sizes we support */
+static void set_vw_size(struct cam_data *cam)
+{
+ /* the col/row/start/end values are the result of simple math */
+ /* study the SetROI-command in cpia developers guide p 2-22 */
+ /* streamStartLine is set to the recommended value in the cpia */
+ /* developers guide p 3-37 */
+ switch(cam->video_size) {
+ case VIDEOSIZE_CIF:
+ cam->vw.width = 352;
+ cam->vw.height = 288;
+ cam->params.format.videoSize=VIDEOSIZE_CIF;
+ cam->params.roi.colStart=0;
+ cam->params.roi.colEnd=44;
+ cam->params.roi.rowStart=0;
+ cam->params.roi.rowEnd=72;
+ cam->params.streamStartLine = 120;
+ break;
+ case VIDEOSIZE_SIF:
+ cam->vw.width = 320;
+ cam->vw.height = 240;
+ cam->params.format.videoSize=VIDEOSIZE_CIF;
+ cam->params.roi.colStart=2;
+ cam->params.roi.colEnd=42;
+ cam->params.roi.rowStart=6;
+ cam->params.roi.rowEnd=66;
+ cam->params.streamStartLine = 120;
+ break;
+ case VIDEOSIZE_288_216:
+ cam->vw.width = 288;
+ cam->vw.height = 216;
+ cam->params.format.videoSize=VIDEOSIZE_CIF;
+ cam->params.roi.colStart=4;
+ cam->params.roi.colEnd=40;
+ cam->params.roi.rowStart=9;
+ cam->params.roi.rowEnd=63;
+ cam->params.streamStartLine = 120;
+ break;
+ case VIDEOSIZE_256_192:
+ cam->vw.width = 256;
+ cam->vw.height = 192;
+ cam->params.format.videoSize=VIDEOSIZE_CIF;
+ cam->params.roi.colStart=6;
+ cam->params.roi.colEnd=38;
+ cam->params.roi.rowStart=12;
+ cam->params.roi.rowEnd=60;
+ cam->params.streamStartLine = 120;
+ break;
+ case VIDEOSIZE_224_168:
+ cam->vw.width = 224;
+ cam->vw.height = 168;
+ cam->params.format.videoSize=VIDEOSIZE_CIF;
+ cam->params.roi.colStart=8;
+ cam->params.roi.colEnd=36;
+ cam->params.roi.rowStart=15;
+ cam->params.roi.rowEnd=57;
+ cam->params.streamStartLine = 120;
+ break;
+ case VIDEOSIZE_192_144:
+ cam->vw.width = 192;
+ cam->vw.height = 144;
+ cam->params.format.videoSize=VIDEOSIZE_CIF;
+ cam->params.roi.colStart=10;
+ cam->params.roi.colEnd=34;
+ cam->params.roi.rowStart=18;
+ cam->params.roi.rowEnd=54;
+ cam->params.streamStartLine = 120;
+ break;
+ case VIDEOSIZE_QCIF:
+ cam->vw.width = 176;
+ cam->vw.height = 144;
+ cam->params.format.videoSize=VIDEOSIZE_QCIF;
+ cam->params.roi.colStart=0;
+ cam->params.roi.colEnd=22;
+ cam->params.roi.rowStart=0;
+ cam->params.roi.rowEnd=36;
+ cam->params.streamStartLine = 60;
+ break;
+ case VIDEOSIZE_QSIF:
+ cam->vw.width = 160;
+ cam->vw.height = 120;
+ cam->params.format.videoSize=VIDEOSIZE_QCIF;
+ cam->params.roi.colStart=1;
+ cam->params.roi.colEnd=21;
+ cam->params.roi.rowStart=3;
+ cam->params.roi.rowEnd=33;
+ cam->params.streamStartLine = 60;
+ break;
+ case VIDEOSIZE_128_96:
+ cam->vw.width = 128;
+ cam->vw.height = 96;
+ cam->params.format.videoSize=VIDEOSIZE_QCIF;
+ cam->params.roi.colStart=3;
+ cam->params.roi.colEnd=19;
+ cam->params.roi.rowStart=6;
+ cam->params.roi.rowEnd=30;
+ cam->params.streamStartLine = 60;
+ break;
+ case VIDEOSIZE_88_72:
+ cam->vw.width = 88;
+ cam->vw.height = 72;
+ cam->params.format.videoSize=VIDEOSIZE_QCIF;
+ cam->params.roi.colStart=5;
+ cam->params.roi.colEnd=16;
+ cam->params.roi.rowStart=9;
+ cam->params.roi.rowEnd=27;
+ cam->params.streamStartLine = 60;
+ break;
+ case VIDEOSIZE_64_48:
+ cam->vw.width = 64;
+ cam->vw.height = 48;
+ cam->params.format.videoSize=VIDEOSIZE_QCIF;
+ cam->params.roi.colStart=7;
+ cam->params.roi.colEnd=15;
+ cam->params.roi.rowStart=12;
+ cam->params.roi.rowEnd=24;
+ cam->params.streamStartLine = 60;
+ break;
+ case VIDEOSIZE_48_48:
+ cam->vw.width = 48;
+ cam->vw.height = 48;
+ cam->params.format.videoSize=VIDEOSIZE_QCIF;
+ cam->params.roi.colStart=8;
+ cam->params.roi.colEnd=14;
+ cam->params.roi.rowStart=6;
+ cam->params.roi.rowEnd=30;
+ cam->params.streamStartLine = 60;
+ break;
+ default:
+ LOG("bad videosize value: %d\n", cam->video_size);
+ }
+
+ return;
+}
+
+static int allocate_frame_buf(struct cam_data *cam)
+{
+ int i;
+
+ cam->frame_buf = rvmalloc(FRAME_NUM * CPIA_MAX_FRAME_SIZE);
+ if (!cam->frame_buf)
+ return -ENOBUFS;
+
+ for (i = 0; i < FRAME_NUM; i++)
+ cam->frame[i].data = cam->frame_buf + i * CPIA_MAX_FRAME_SIZE;
+
+ return 0;
+}
+
+static int free_frame_buf(struct cam_data *cam)
+{
+ int i;
+
+ rvfree(cam->frame_buf, FRAME_NUM*CPIA_MAX_FRAME_SIZE);
+ cam->frame_buf = 0;
+ for (i=0; i < FRAME_NUM; i++)
+ cam->frame[i].data = NULL;
+
+ return 0;
+}
+
+
+static void inline free_frames(struct cpia_frame frame[FRAME_NUM])
+{
+ int i;
+
+ for (i=0; i < FRAME_NUM; i++)
+ frame[i].state = FRAME_UNUSED;
+ return;
+}
+
+/**********************************************************************
+ *
+ * General functions
+ *
+ **********************************************************************/
+/* send an arbitrary command to the camera */
+static int do_command(struct cam_data *cam, u16 command, u8 a, u8 b, u8 c, u8 d)
+{
+ int retval, datasize;
+ u8 cmd[8], data[8];
+
+ switch(command) {
+ case CPIA_COMMAND_GetCPIAVersion:
+ case CPIA_COMMAND_GetPnPID:
+ case CPIA_COMMAND_GetCameraStatus:
+ case CPIA_COMMAND_GetVPVersion:
+ datasize=8;
+ break;
+ case CPIA_COMMAND_GetColourParams:
+ case CPIA_COMMAND_GetColourBalance:
+ case CPIA_COMMAND_GetExposure:
+ down(&cam->param_lock);
+ datasize=8;
+ break;
+ default:
+ datasize=0;
+ break;
+ }
+
+ cmd[0] = command>>8;
+ cmd[1] = command&0xff;
+ cmd[2] = a;
+ cmd[3] = b;
+ cmd[4] = c;
+ cmd[5] = d;
+ cmd[6] = datasize;
+ cmd[7] = 0;
+
+ retval = cam->ops->transferCmd(cam->lowlevel_data, cmd, data);
+ if (retval) {
+ DBG("%x - failed, retval=%d\n", command, retval);
+ if (command == CPIA_COMMAND_GetColourParams ||
+ command == CPIA_COMMAND_GetColourBalance ||
+ command == CPIA_COMMAND_GetExposure)
+ up(&cam->param_lock);
+ } else {
+ switch(command) {
+ case CPIA_COMMAND_GetCPIAVersion:
+ cam->params.version.firmwareVersion = data[0];
+ cam->params.version.firmwareRevision = data[1];
+ cam->params.version.vcVersion = data[2];
+ cam->params.version.vcRevision = data[3];
+ break;
+ case CPIA_COMMAND_GetPnPID:
+ cam->params.pnpID.vendor = data[0]+(((u16)data[1])<<8);
+ cam->params.pnpID.product = data[2]+(((u16)data[3])<<8);
+ cam->params.pnpID.deviceRevision =
+ data[4]+(((u16)data[5])<<8);
+ break;
+ case CPIA_COMMAND_GetCameraStatus:
+ cam->params.status.systemState = data[0];
+ cam->params.status.grabState = data[1];
+ cam->params.status.streamState = data[2];
+ cam->params.status.fatalError = data[3];
+ cam->params.status.cmdError = data[4];
+ cam->params.status.debugFlags = data[5];
+ cam->params.status.vpStatus = data[6];
+ cam->params.status.errorCode = data[7];
+ break;
+ case CPIA_COMMAND_GetVPVersion:
+ cam->params.vpVersion.vpVersion = data[0];
+ cam->params.vpVersion.vpRevision = data[1];
+ cam->params.vpVersion.cameraHeadID =
+ data[2]+(((u16)data[3])<<8);
+ break;
+ case CPIA_COMMAND_GetColourParams:
+ cam->params.colourParams.brightness = data[0];
+ cam->params.colourParams.contrast = data[1];
+ cam->params.colourParams.saturation = data[2];
+ up(&cam->param_lock);
+ break;
+ case CPIA_COMMAND_GetColourBalance:
+ cam->params.colourBalance.redGain = data[0];
+ cam->params.colourBalance.greenGain = data[1];
+ cam->params.colourBalance.blueGain = data[2];
+ up(&cam->param_lock);
+ break;
+ case CPIA_COMMAND_GetExposure:
+ cam->params.exposure.gain = data[0];
+ cam->params.exposure.fineExp = data[1];
+ cam->params.exposure.coarseExpLo = data[2];
+ cam->params.exposure.coarseExpHi = data[3];
+ cam->params.exposure.redComp = data[4];
+ cam->params.exposure.green1Comp = data[5];
+ cam->params.exposure.green2Comp = data[6];
+ cam->params.exposure.blueComp = data[7];
+ /* If the *Comp parameters are wacko, generate
+ * a warning, and reset them back to default
+ * values. - rich@annexia.org
+ */
+ if (cam->params.exposure.redComp < 220 ||
+ cam->params.exposure.redComp > 255 ||
+ cam->params.exposure.green1Comp < 214 ||
+ cam->params.exposure.green1Comp > 255 ||
+ cam->params.exposure.green2Comp < 214 ||
+ cam->params.exposure.green2Comp > 255 ||
+ cam->params.exposure.blueComp < 230 ||
+ cam->params.exposure.blueComp > 255)
+ {
+ printk (KERN_WARNING "*_comp parameters have gone AWOL (%d/%d/%d/%d) - reseting them\n",
+ cam->params.exposure.redComp,
+ cam->params.exposure.green1Comp,
+ cam->params.exposure.green2Comp,
+ cam->params.exposure.blueComp);
+ cam->params.exposure.redComp = 220;
+ cam->params.exposure.green1Comp = 214;
+ cam->params.exposure.green2Comp = 214;
+ cam->params.exposure.blueComp = 230;
+ }
+ up(&cam->param_lock);
+ break;
+ default:
+ break;
+ }
+ }
+ return retval;
+}
+
+/* send a command to the camera with an additional data transaction */
+static int do_command_extended(struct cam_data *cam, u16 command,
+ u8 a, u8 b, u8 c, u8 d,
+ u8 e, u8 f, u8 g, u8 h,
+ u8 i, u8 j, u8 k, u8 l)
+{
+ int retval;
+ u8 cmd[8], data[8];
+
+ cmd[0] = command>>8;
+ cmd[1] = command&0xff;
+ cmd[2] = a;
+ cmd[3] = b;
+ cmd[4] = c;
+ cmd[5] = d;
+ cmd[6] = 8;
+ cmd[7] = 0;
+ data[0] = e;
+ data[1] = f;
+ data[2] = g;
+ data[3] = h;
+ data[4] = i;
+ data[5] = j;
+ data[6] = k;
+ data[7] = l;
+
+ retval = cam->ops->transferCmd(cam->lowlevel_data, cmd, data);
+ if (retval)
+ LOG("%x - failed\n", command);
+
+ return retval;
+}
+
+/**********************************************************************
+ *
+ * Colorspace conversion
+ *
+ **********************************************************************/
+#define LIMIT(x) ((((x)>0xffffff)?0xff0000:(((x)<=0xffff)?0:(x)&0xff0000))>>16)
+
+static int yuvconvert(unsigned char *yuv, unsigned char *rgb, int out_fmt,
+ int in_uyvy, int mmap_kludge)
+{
+ int y, u, v, r, g, b, y1;
+
+ switch(out_fmt) {
+ case VIDEO_PALETTE_RGB555:
+ case VIDEO_PALETTE_RGB565:
+ case VIDEO_PALETTE_RGB24:
+ case VIDEO_PALETTE_RGB32:
+ if (in_uyvy) {
+ u = *yuv++ - 128;
+ y = (*yuv++ - 16) * 76310;
+ v = *yuv++ - 128;
+ y1 = (*yuv - 16) * 76310;
+ } else {
+ y = (*yuv++ - 16) * 76310;
+ u = *yuv++ - 128;
+ y1 = (*yuv++ - 16) * 76310;
+ v = *yuv - 128;
+ }
+ r = 104635 * v;
+ g = -25690 * u + -53294 * v;
+ b = 132278 * u;
+ break;
+ default:
+ y = *yuv++;
+ u = *yuv++;
+ y1 = *yuv++;
+ v = *yuv;
+ /* Just to avoid compiler warnings */
+ r = 0;
+ g = 0;
+ b = 0;
+ break;
+ }
+ switch(out_fmt) {
+ case VIDEO_PALETTE_RGB555:
+ *rgb++ = ((LIMIT(g+y) & 0xf8) << 2) | (LIMIT(b+y) >> 3);
+ *rgb++ = ((LIMIT(r+y) & 0xf8) >> 1) | (LIMIT(g+y) >> 6);
+ *rgb++ = ((LIMIT(g+y1) & 0xf8) << 2) | (LIMIT(b+y1) >> 3);
+ *rgb = ((LIMIT(r+y1) & 0xf8) >> 1) | (LIMIT(g+y1) >> 6);
+ return 4;
+ case VIDEO_PALETTE_RGB565:
+ *rgb++ = ((LIMIT(g+y) & 0xfc) << 3) | (LIMIT(b+y) >> 3);
+ *rgb++ = (LIMIT(r+y) & 0xf8) | (LIMIT(g+y) >> 5);
+ *rgb++ = ((LIMIT(g+y1) & 0xfc) << 3) | (LIMIT(b+y1) >> 3);
+ *rgb = (LIMIT(r+y1) & 0xf8) | (LIMIT(g+y1) >> 5);
+ return 4;
+ case VIDEO_PALETTE_RGB24:
+ if (mmap_kludge) {
+ *rgb++ = LIMIT(b+y);
+ *rgb++ = LIMIT(g+y);
+ *rgb++ = LIMIT(r+y);
+ *rgb++ = LIMIT(b+y1);
+ *rgb++ = LIMIT(g+y1);
+ *rgb = LIMIT(r+y1);
+ } else {
+ *rgb++ = LIMIT(r+y);
+ *rgb++ = LIMIT(g+y);
+ *rgb++ = LIMIT(b+y);
+ *rgb++ = LIMIT(r+y1);
+ *rgb++ = LIMIT(g+y1);
+ *rgb = LIMIT(b+y1);
+ }
+ return 6;
+ case VIDEO_PALETTE_RGB32:
+ if (mmap_kludge) {
+ *rgb++ = LIMIT(b+y);
+ *rgb++ = LIMIT(g+y);
+ *rgb++ = LIMIT(r+y);
+ rgb++;
+ *rgb++ = LIMIT(b+y1);
+ *rgb++ = LIMIT(g+y1);
+ *rgb = LIMIT(r+y1);
+ } else {
+ *rgb++ = LIMIT(r+y);
+ *rgb++ = LIMIT(g+y);
+ *rgb++ = LIMIT(b+y);
+ rgb++;
+ *rgb++ = LIMIT(r+y1);
+ *rgb++ = LIMIT(g+y1);
+ *rgb = LIMIT(b+y1);
+ }
+ return 8;
+ case VIDEO_PALETTE_GREY:
+ *rgb++ = y;
+ *rgb = y1;
+ return 2;
+ case VIDEO_PALETTE_YUV422:
+ case VIDEO_PALETTE_YUYV:
+ *rgb++ = y;
+ *rgb++ = u;
+ *rgb++ = y1;
+ *rgb = v;
+ return 4;
+ case VIDEO_PALETTE_UYVY:
+ *rgb++ = u;
+ *rgb++ = y;
+ *rgb++ = v;
+ *rgb = y1;
+ return 4;
+ default:
+ DBG("Empty: %d\n", out_fmt);
+ return 0;
+ }
+}
+
+static int skipcount(int count, int fmt)
+{
+ switch(fmt) {
+ case VIDEO_PALETTE_GREY:
+ case VIDEO_PALETTE_RGB555:
+ case VIDEO_PALETTE_RGB565:
+ case VIDEO_PALETTE_YUV422:
+ case VIDEO_PALETTE_YUYV:
+ case VIDEO_PALETTE_UYVY:
+ return 2*count;
+ case VIDEO_PALETTE_RGB24:
+ return 3*count;
+ case VIDEO_PALETTE_RGB32:
+ return 4*count;
+ default:
+ return 0;
+ }
+}
+
+static int parse_picture(struct cam_data *cam, int size)
+{
+ u8 *obuf, *ibuf, *end_obuf;
+ int ll, in_uyvy, compressed, origsize, out_fmt;
+
+ /* make sure params don't change while we are decoding */
+ down(&cam->param_lock);
+
+ obuf = cam->decompressed_frame.data;
+ end_obuf = obuf+CPIA_MAX_FRAME_SIZE;
+ ibuf = cam->raw_image;
+ origsize = size;
+ out_fmt = cam->vp.palette;
+
+ if ((ibuf[0] != MAGIC_0) || (ibuf[1] != MAGIC_1)) {
+ LOG("header not found\n");
+ up(&cam->param_lock);
+ return -1;
+ }
+
+ if ((ibuf[16] != VIDEOSIZE_QCIF) && (ibuf[16] != VIDEOSIZE_CIF)) {
+ LOG("wrong video size\n");
+ up(&cam->param_lock);
+ return -1;
+ }
+
+ if (ibuf[17] != SUBSAMPLE_422) {
+ LOG("illegal subtype %d\n",ibuf[17]);
+ up(&cam->param_lock);
+ return -1;
+ }
+
+ if (ibuf[18] != YUVORDER_YUYV && ibuf[18] != YUVORDER_UYVY) {
+ LOG("illegal yuvorder %d\n",ibuf[18]);
+ up(&cam->param_lock);
+ return -1;
+ }
+ in_uyvy = ibuf[18] == YUVORDER_UYVY;
+
+#if 0
+ /* FIXME: ROI mismatch occurs when switching capture sizes */
+ if ((ibuf[24] != cam->params.roi.colStart) ||
+ (ibuf[25] != cam->params.roi.colEnd) ||
+ (ibuf[26] != cam->params.roi.rowStart) ||
+ (ibuf[27] != cam->params.roi.rowEnd)) {
+ LOG("ROI mismatch\n");
+ up(&cam->param_lock);
+ return -1;
+ }
+#endif
+
+ if ((ibuf[28] != NOT_COMPRESSED) && (ibuf[28] != COMPRESSED)) {
+ LOG("illegal compression %d\n",ibuf[28]);
+ up(&cam->param_lock);
+ return -1;
+ }
+ compressed = (ibuf[28] == COMPRESSED);
+
+ if (ibuf[29] != NO_DECIMATION) {
+ LOG("decimation not supported\n");
+ up(&cam->param_lock);
+ return -1;
+ }
+
+ cam->params.yuvThreshold.yThreshold = ibuf[30];
+ cam->params.yuvThreshold.uvThreshold = ibuf[31];
+ cam->params.status.systemState = ibuf[32];
+ cam->params.status.grabState = ibuf[33];
+ cam->params.status.streamState = ibuf[34];
+ cam->params.status.fatalError = ibuf[35];
+ cam->params.status.cmdError = ibuf[36];
+ cam->params.status.debugFlags = ibuf[37];
+ cam->params.status.vpStatus = ibuf[38];
+ cam->params.status.errorCode = ibuf[39];
+ cam->fps = ibuf[41];
+ up(&cam->param_lock);
+
+ ibuf += FRAME_HEADER_SIZE;
+ size -= FRAME_HEADER_SIZE;
+ ll = ibuf[0] | (ibuf[1] << 8);
+ ibuf += 2;
+
+ while (size > 0) {
+ size -= (ll+2);
+ if (size < 0) {
+ LOG("Insufficient data in buffer\n");
+ return -1;
+ }
+
+ while (ll > 1) {
+ if (!compressed || (compressed && !(*ibuf & 1))) {
+ obuf += yuvconvert(ibuf, obuf, out_fmt,
+ in_uyvy, cam->mmap_kludge);
+ ibuf += 4;
+ ll -= 4;
+ } else {
+ /*skip compressed interval from previous frame*/
+ int skipsize = skipcount(*ibuf >> 1, out_fmt);
+ obuf += skipsize;
+ if (obuf > end_obuf) {
+ LOG("Insufficient data in buffer\n");
+ return -1;
+ }
+ ++ibuf;
+ ll--;
+ }
+ }
+ if (ll == 1) {
+ if (*ibuf != EOL) {
+ LOG("EOL not found giving up after %d/%d"
+ " bytes\n", origsize-size, origsize);
+ return -1;
+ }
+
+ ibuf++; /* skip over EOL */
+
+ if ((size > 3) && (ibuf[0] == EOI) && (ibuf[1] == EOI) &&
+ (ibuf[2] == EOI) && (ibuf[3] == EOI)) {
+ size -= 4;
+ break;
+ }
+
+ if (size > 1) {
+ ll = ibuf[0] | (ibuf[1] << 8);
+ ibuf += 2; /* skip over line length */
+ }
+ } else {
+ LOG("line length was not 1 but %d after %d/%d bytes\n",
+ ll, origsize-size, origsize);
+ return -1;
+ }
+ }
+
+ cam->decompressed_frame.count = obuf-cam->decompressed_frame.data;
+
+ return cam->decompressed_frame.count;
+}
+
+/* InitStreamCap wrapper to select correct start line */
+static inline int init_stream_cap(struct cam_data *cam)
+{
+ return do_command(cam, CPIA_COMMAND_InitStreamCap,
+ 0, cam->params.streamStartLine, 0, 0);
+}
+
+/* update various camera modes and settings */
+static void dispatch_commands(struct cam_data *cam)
+{
+ down(&cam->param_lock);
+ if (cam->cmd_queue==COMMAND_NONE) {
+ up(&cam->param_lock);
+ return;
+ }
+ DEB_BYTE(cam->cmd_queue);
+ DEB_BYTE(cam->cmd_queue>>8);
+ if (cam->cmd_queue & COMMAND_SETCOLOURPARAMS)
+ do_command(cam, CPIA_COMMAND_SetColourParams,
+ cam->params.colourParams.brightness,
+ cam->params.colourParams.contrast,
+ cam->params.colourParams.saturation, 0);
+
+ if (cam->cmd_queue & COMMAND_SETCOMPRESSION)
+ do_command(cam, CPIA_COMMAND_SetCompression,
+ cam->params.compression.mode,
+ cam->params.compression.decimation, 0, 0);
+
+ if (cam->cmd_queue & COMMAND_SETFORMAT) {
+ do_command(cam, CPIA_COMMAND_SetFormat,
+ cam->params.format.videoSize,
+ cam->params.format.subSample,
+ cam->params.format.yuvOrder, 0);
+ do_command(cam, CPIA_COMMAND_SetROI,
+ cam->params.roi.colStart, cam->params.roi.colEnd,
+ cam->params.roi.rowStart, cam->params.roi.rowEnd);
+ cam->first_frame = 1;
+ }
+
+ if (cam->cmd_queue & COMMAND_SETCOMPRESSIONTARGET)
+ do_command(cam, CPIA_COMMAND_SetCompressionTarget,
+ cam->params.compressionTarget.frTargeting,
+ cam->params.compressionTarget.targetFR,
+ cam->params.compressionTarget.targetQ, 0);
+
+ if (cam->cmd_queue & COMMAND_SETYUVTHRESH)
+ do_command(cam, CPIA_COMMAND_SetYUVThresh,
+ cam->params.yuvThreshold.yThreshold,
+ cam->params.yuvThreshold.uvThreshold, 0, 0);
+
+ if (cam->cmd_queue & COMMAND_SETECPTIMING)
+ do_command(cam, CPIA_COMMAND_SetECPTiming,
+ cam->params.ecpTiming, 0, 0, 0);
+
+ if (cam->cmd_queue & COMMAND_SETCOMPRESSIONPARAMS)
+ do_command_extended(cam, CPIA_COMMAND_SetCompressionParams,
+ 0, 0, 0, 0,
+ cam->params.compressionParams.hysteresis,
+ cam->params.compressionParams.threshMax,
+ cam->params.compressionParams.smallStep,
+ cam->params.compressionParams.largeStep,
+ cam->params.compressionParams.decimationHysteresis,
+ cam->params.compressionParams.frDiffStepThresh,
+ cam->params.compressionParams.qDiffStepThresh,
+ cam->params.compressionParams.decimationThreshMod);
+
+ if (cam->cmd_queue & COMMAND_SETEXPOSURE)
+ do_command_extended(cam, CPIA_COMMAND_SetExposure,
+ cam->params.exposure.gainMode,
+ cam->params.exposure.expMode,
+ cam->params.exposure.compMode,
+ cam->params.exposure.centreWeight,
+ cam->params.exposure.gain,
+ cam->params.exposure.fineExp,
+ cam->params.exposure.coarseExpLo,
+ cam->params.exposure.coarseExpHi,
+ cam->params.exposure.redComp,
+ cam->params.exposure.green1Comp,
+ cam->params.exposure.green2Comp,
+ cam->params.exposure.blueComp);
+
+ if (cam->cmd_queue & COMMAND_SETCOLOURBALANCE) {
+ if (cam->params.colourBalance.balanceModeIsAuto) {
+ do_command(cam, CPIA_COMMAND_SetColourBalance,
+ 2, 0, 0, 0);
+ } else {
+ do_command(cam, CPIA_COMMAND_SetColourBalance,
+ 1,
+ cam->params.colourBalance.redGain,
+ cam->params.colourBalance.greenGain,
+ cam->params.colourBalance.blueGain);
+ do_command(cam, CPIA_COMMAND_SetColourBalance,
+ 3, 0, 0, 0);
+ }
+ }
+
+ if (cam->cmd_queue & COMMAND_SETSENSORFPS)
+ do_command(cam, CPIA_COMMAND_SetSensorFPS,
+ cam->params.sensorFps.divisor,
+ cam->params.sensorFps.baserate, 0, 0);
+
+ if (cam->cmd_queue & COMMAND_SETAPCOR)
+ do_command(cam, CPIA_COMMAND_SetApcor,
+ cam->params.apcor.gain1,
+ cam->params.apcor.gain2,
+ cam->params.apcor.gain4,
+ cam->params.apcor.gain8);
+
+ if (cam->cmd_queue & COMMAND_SETFLICKERCTRL)
+ do_command(cam, CPIA_COMMAND_SetFlickerCtrl,
+ cam->params.flickerControl.flickerMode,
+ cam->params.flickerControl.coarseJump,
+ cam->params.flickerControl.allowableOverExposure, 0);
+
+ if (cam->cmd_queue & COMMAND_SETVLOFFSET)
+ do_command(cam, CPIA_COMMAND_SetVLOffset,
+ cam->params.vlOffset.gain1,
+ cam->params.vlOffset.gain2,
+ cam->params.vlOffset.gain4,
+ cam->params.vlOffset.gain8);
+
+ if (cam->cmd_queue & COMMAND_PAUSE)
+ do_command(cam, CPIA_COMMAND_EndStreamCap, 0, 0, 0, 0);
+
+ if (cam->cmd_queue & COMMAND_RESUME)
+ init_stream_cap(cam);
+
+ up(&cam->param_lock);
+ cam->cmd_queue = COMMAND_NONE;
+ return;
+}
+
+/* kernel thread function to read image from camera */
+static void fetch_frame(void *data)
+{
+ int image_size, retry;
+ struct cam_data *cam = (struct cam_data *)data;
+ unsigned long oldjif, rate, diff;
+
+ /* Allow up to two bad images in a row to be read and
+ * ignored before an error is reported */
+ for (retry = 0; retry < 3; ++retry) {
+ if (retry)
+ DBG("retry=%d\n", retry);
+
+ if (!cam->ops)
+ continue;
+
+ /* load first frame always uncompressed */
+ if (cam->first_frame &&
+ cam->params.compression.mode != CPIA_COMPRESSION_NONE)
+ do_command(cam, CPIA_COMMAND_SetCompression,
+ CPIA_COMPRESSION_NONE,
+ NO_DECIMATION, 0, 0);
+
+ /* init camera upload */
+ if (do_command(cam, CPIA_COMMAND_SetGrabMode,
+ CPIA_GRAB_CONTINUOUS, 0, 0, 0))
+ continue;
+
+ if (do_command(cam, CPIA_COMMAND_GrabFrame, 0,
+ cam->params.streamStartLine, 0, 0))
+ continue;
+
+ if (cam->ops->wait_for_stream_ready) {
+ /* loop until image ready */
+ do_command(cam, CPIA_COMMAND_GetCameraStatus,0,0,0,0);
+ while (cam->params.status.streamState != STREAM_READY) {
+ if (current->need_resched)
+ schedule();
+
+ current->state = TASK_INTERRUPTIBLE;
+
+ /* sleep for 10 ms, hopefully ;) */
+ schedule_timeout(10*HZ/1000);
+ if (signal_pending(current))
+ return;
+
+ do_command(cam, CPIA_COMMAND_GetCameraStatus,
+ 0, 0, 0, 0);
+ }
+ }
+
+ /* grab image from camera */
+ if (current->need_resched)
+ schedule();
+
+ oldjif = jiffies;
+ image_size = cam->ops->streamRead(cam->lowlevel_data,
+ cam->raw_image, 0);
+ if (image_size <= 0) {
+ DBG("streamRead failed: %d\n", image_size);
+ continue;
+ }
+
+ rate = image_size * HZ / 1024;
+ diff = jiffies-oldjif;
+ cam->transfer_rate = diff==0 ? rate : rate/diff;
+ /* diff==0 ? unlikely but possible */
+
+ /* camera idle now so dispatch queued commands */
+ dispatch_commands(cam);
+
+ /* Update our knowledge of the camera state - FIXME: necessary? */
+ do_command(cam, CPIA_COMMAND_GetColourBalance, 0, 0, 0, 0);
+ do_command(cam, CPIA_COMMAND_GetExposure, 0, 0, 0, 0);
+
+ /* decompress and convert image to by copying it from
+ * raw_image to decompressed_frame
+ */
+ if (current->need_resched)
+ schedule();
+
+ cam->image_size = parse_picture(cam, image_size);
+ if (cam->image_size <= 0)
+ DBG("parse_picture failed %d\n", cam->image_size);
+ else
+ break;
+ }
+
+ if (retry < 3) {
+ /* FIXME: this only works for double buffering */
+ if (cam->frame[cam->curframe].state == FRAME_READY) {
+ memcpy(cam->frame[cam->curframe].data,
+ cam->decompressed_frame.data,
+ cam->decompressed_frame.count);
+ cam->frame[cam->curframe].state = FRAME_DONE;
+ } else
+ cam->decompressed_frame.state = FRAME_DONE;
+
+#if 0
+ if (cam->first_frame &&
+ cam->params.compression.mode != CPIA_COMPRESSION_NONE) {
+ cam->first_frame = 0;
+ cam->cmd_queue |= COMMAND_SETCOMPRESSION;
+ }
+#else
+ if (cam->first_frame) {
+ cam->first_frame = 0;
+ cam->cmd_queue |= COMMAND_SETCOMPRESSION;
+ cam->cmd_queue |= COMMAND_SETEXPOSURE;
+ }
+#endif
+ }
+}
+
+static int capture_frame(struct cam_data *cam, struct video_mmap *vm)
+{
+ int retval = 0;
+
+ if (!cam->frame_buf) {
+ /* we do lazy allocation */
+ if ((retval = allocate_frame_buf(cam)))
+ return retval;
+ }
+
+ /* FIXME: the first frame seems to be captured by the camera
+ without regards to any initial settings, so we throw away
+ that one, the next one is generated with our settings
+ (exposure, color balance, ...)
+ */
+ if (cam->first_frame) {
+ cam->curframe = vm->frame;
+ cam->frame[cam->curframe].state = FRAME_READY;
+ fetch_frame(cam);
+ if (cam->frame[cam->curframe].state != FRAME_DONE)
+ retval = -EIO;
+ }
+ cam->curframe = vm->frame;
+ cam->frame[cam->curframe].state = FRAME_READY;
+ fetch_frame(cam);
+ if (cam->frame[cam->curframe].state != FRAME_DONE)
+ retval=-EIO;
+
+ return retval;
+}
+
+static int goto_high_power(struct cam_data *cam)
+{
+ if (do_command(cam, CPIA_COMMAND_GotoHiPower, 0, 0, 0, 0))
+ return -1;
+ mdelay(100); /* windows driver does it too */
+ if (do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0))
+ return -1;
+ if (cam->params.status.systemState == HI_POWER_STATE) {
+ DBG("camera now in HIGH power state\n");
+ return 0;
+ }
+ printstatus(cam);
+ return -1;
+}
+
+static int goto_low_power(struct cam_data *cam)
+{
+ if (do_command(cam, CPIA_COMMAND_GotoLoPower, 0, 0, 0, 0))
+ return -1;
+ if (do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0))
+ return -1;
+ if (cam->params.status.systemState == LO_POWER_STATE) {
+ DBG("camera now in LOW power state\n");
+ return 0;
+ }
+ printstatus(cam);
+ return -1;
+}
+
+static void save_camera_state(struct cam_data *cam)
+{
+ do_command(cam, CPIA_COMMAND_GetColourBalance, 0, 0, 0, 0);
+ do_command(cam, CPIA_COMMAND_GetExposure, 0, 0, 0, 0);
+
+ DBG("%d/%d/%d/%d/%d/%d/%d/%d\n",
+ cam->params.exposure.gain,
+ cam->params.exposure.fineExp,
+ cam->params.exposure.coarseExpLo,
+ cam->params.exposure.coarseExpHi,
+ cam->params.exposure.redComp,
+ cam->params.exposure.green1Comp,
+ cam->params.exposure.green2Comp,
+ cam->params.exposure.blueComp);
+ DBG("%d/%d/%d\n",
+ cam->params.colourBalance.redGain,
+ cam->params.colourBalance.greenGain,
+ cam->params.colourBalance.blueGain);
+}
+
+static void set_camera_state(struct cam_data *cam)
+{
+ if(cam->params.colourBalance.balanceModeIsAuto) {
+ do_command(cam, CPIA_COMMAND_SetColourBalance,
+ 2, 0, 0, 0);
+ } else {
+ do_command(cam, CPIA_COMMAND_SetColourBalance,
+ 1,
+ cam->params.colourBalance.redGain,
+ cam->params.colourBalance.greenGain,
+ cam->params.colourBalance.blueGain);
+ do_command(cam, CPIA_COMMAND_SetColourBalance,
+ 3, 0, 0, 0);
+ }
+
+
+ do_command_extended(cam, CPIA_COMMAND_SetExposure,
+ cam->params.exposure.gainMode, 1, 1,
+ cam->params.exposure.centreWeight,
+ cam->params.exposure.gain,
+ cam->params.exposure.fineExp,
+ cam->params.exposure.coarseExpLo,
+ cam->params.exposure.coarseExpHi,
+ cam->params.exposure.redComp,
+ cam->params.exposure.green1Comp,
+ cam->params.exposure.green2Comp,
+ cam->params.exposure.blueComp);
+ do_command_extended(cam, CPIA_COMMAND_SetExposure,
+ 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0);
+
+ if (!cam->params.exposure.gainMode)
+ cam->params.exposure.gainMode = 2;
+ if (!cam->params.exposure.expMode)
+ cam->params.exposure.expMode = 2;
+ if (!cam->params.exposure.centreWeight)
+ cam->params.exposure.centreWeight = 1;
+
+ cam->cmd_queue = COMMAND_SETCOMPRESSION |
+ COMMAND_SETCOMPRESSIONTARGET |
+ COMMAND_SETCOLOURPARAMS |
+ COMMAND_SETFORMAT |
+ COMMAND_SETYUVTHRESH |
+ COMMAND_SETECPTIMING |
+ COMMAND_SETCOMPRESSIONPARAMS |
+#if 0
+ COMMAND_SETEXPOSURE |
+#endif
+ COMMAND_SETCOLOURBALANCE |
+ COMMAND_SETSENSORFPS |
+ COMMAND_SETAPCOR |
+ COMMAND_SETFLICKERCTRL |
+ COMMAND_SETVLOFFSET;
+ dispatch_commands(cam);
+ save_camera_state(cam);
+
+ return;
+}
+
+static void get_version_information(struct cam_data *cam)
+{
+ /* GetCPIAVersion */
+ do_command(cam, CPIA_COMMAND_GetCPIAVersion, 0, 0, 0, 0);
+
+ /* GetPnPID */
+ do_command(cam, CPIA_COMMAND_GetPnPID, 0, 0, 0, 0);
+}
+
+/* initialize camera */
+static int reset_camera(struct cam_data *cam)
+{
+ /* Start the camera in low power mode */
+ if (goto_low_power(cam)) {
+ if (cam->params.status.systemState != WARM_BOOT_STATE)
+ return -ENODEV;
+
+ /* FIXME: this is just dirty trial and error */
+ reset_camera_struct(cam);
+ goto_high_power(cam);
+ do_command(cam, CPIA_COMMAND_DiscardFrame, 0, 0, 0, 0);
+ if (goto_low_power(cam))
+ return -NODEV;
+ }
+
+ /* procedure described in developer's guide p3-28 */
+
+ /* Check the firmware version FIXME: should we check PNPID? */
+ cam->params.version.firmwareVersion = 0;
+ get_version_information(cam);
+ if (cam->params.version.firmwareVersion != 1)
+ return -ENODEV;
+
+ /* The fatal error checking should be done after
+ * the camera powers up (developer's guide p 3-38) */
+
+ /* Set streamState before transition to high power to avoid bug
+ * in firmware 1-02 */
+ do_command(cam, CPIA_COMMAND_ModifyCameraStatus, STREAMSTATE, 0,
+ STREAM_NOT_READY, 0);
+
+ /* GotoHiPower */
+ if (goto_high_power(cam))
+ return -ENODEV;
+
+ /* Check the camera status */
+ if (do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0))
+ return -EIO;
+
+ if (cam->params.status.fatalError) {
+ DBG("fatal_error: %#04x\n",
+ cam->params.status.fatalError);
+ DBG("vp_status: %#04x\n",
+ cam->params.status.vpStatus);
+ if (cam->params.status.fatalError & ~(COM_FLAG|CPIA_FLAG)) {
+ /* Fatal error in camera */
+ return -EIO;
+ } else if (cam->params.status.fatalError & (COM_FLAG|CPIA_FLAG)) {
+ /* Firmware 1-02 may do this for parallel port cameras,
+ * just clear the flags (developer's guide p 3-38) */
+ do_command(cam, CPIA_COMMAND_ModifyCameraStatus,
+ FATALERROR, ~(COM_FLAG|CPIA_FLAG), 0, 0);
+ }
+ }
+
+ /* Check the camera status again */
+ if (cam->params.status.fatalError) {
+ if (cam->params.status.fatalError)
+ return -EIO;
+ }
+
+ /* VPVersion can't be retrieved before the camera is in HiPower,
+ * so get it here instead of in get_version_information. */
+ do_command(cam, CPIA_COMMAND_GetVPVersion, 0, 0, 0, 0);
+
+ /* set camera to a known state */
+ set_camera_state(cam);
+
+ return 0;
+}
+
+/* ------------------------- V4L interface --------------------- */
+static int cpia_open(struct video_device *dev, int flags)
+{
+ int i;
+ struct cam_data *cam = dev->priv;
+
+ if (!cam) {
+ DBG("Internal error, cam_data not found!\n");
+ return -EBUSY;
+ }
+
+ if (cam->open_count > 0) {
+ DBG("Camera already open\n");
+ return -EBUSY;
+ }
+
+ if (!cam->raw_image) {
+ cam->raw_image = rvmalloc(CPIA_MAX_IMAGE_SIZE);
+ if (!cam->raw_image)
+ return -ENOMEM;
+ }
+
+ if (!cam->decompressed_frame.data) {
+ cam->decompressed_frame.data = rvmalloc(CPIA_MAX_FRAME_SIZE);
+ if (!cam->decompressed_frame.data) {
+ rvfree(cam->raw_image, CPIA_MAX_IMAGE_SIZE);
+ cam->raw_image = NULL;
+ return -ENOMEM;
+ }
+ }
+
+ /* open cpia */
+ if (cam->ops->open(cam->lowlevel_data)) {
+ rvfree(cam->decompressed_frame.data, CPIA_MAX_FRAME_SIZE);
+ cam->decompressed_frame.data = NULL;
+ rvfree(cam->raw_image, CPIA_MAX_IMAGE_SIZE);
+ cam->raw_image = NULL;
+ return -ENODEV;
+ }
+
+ /* reset the camera */
+ if ((i = reset_camera(cam)) != 0) {
+ cam->ops->close(cam->lowlevel_data);
+ rvfree(cam->decompressed_frame.data, CPIA_MAX_FRAME_SIZE);
+ cam->decompressed_frame.data = NULL;
+ rvfree(cam->raw_image, CPIA_MAX_IMAGE_SIZE);
+ cam->raw_image = NULL;
+ return i;
+ }
+
+ /* Set ownership of /proc/cpia/videoX to current user */
+ if(cam->proc_entry)
+ cam->proc_entry->uid = current->uid;
+
+ /* set mark for loading first frame uncompressed */
+ cam->first_frame = 1;
+
+ /* init it to something */
+ cam->mmap_kludge = 0;
+
+ ++cam->open_count;
+#ifdef MODULE
+ MOD_INC_USE_COUNT;
+#endif
+ return 0;
+}
+
+static void cpia_close(struct video_device *dev)
+{
+ struct cam_data *cam;
+
+ cam = dev->priv;
+
+ if (cam->ops) {
+ /* Return ownership of /proc/cpia/videoX to root */
+ if(cam->proc_entry)
+ cam->proc_entry->uid = 0;
+
+ /* save camera state for later open (developers guide ch 3.5.3) */
+ save_camera_state(cam);
+
+ /* GotoLoPower */
+ goto_low_power(cam);
+
+ /* Update the camera ststus */
+ do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0);
+
+ /* cleanup internal state stuff */
+ free_frames(cam->frame);
+
+ /* close cpia */
+ cam->ops->close(cam->lowlevel_data);
+ }
+
+ if (--cam->open_count == 0) {
+ /* clean up capture-buffers */
+ if (cam->raw_image) {
+ rvfree(cam->raw_image, CPIA_MAX_IMAGE_SIZE);
+ cam->raw_image = NULL;
+ }
+
+ if (cam->decompressed_frame.data) {
+ rvfree(cam->decompressed_frame.data, CPIA_MAX_FRAME_SIZE);
+ cam->decompressed_frame.data = NULL;
+ }
+
+ if (cam->frame_buf)
+ free_frame_buf(cam);
+
+ if (!cam->ops) {
+ video_unregister_device(dev);
+ kfree(cam);
+ }
+ }
+
+
+#ifdef MODULE
+ MOD_DEC_USE_COUNT;
+#endif
+ return;
+}
+
+static long cpia_read(struct video_device *dev, char *buf,
+ unsigned long count, int noblock)
+{
+ struct cam_data *cam = dev->priv;
+
+ /* make this _really_ smp and multithredi-safe */
+ if (down_interruptible(&cam->busy_lock))
+ return -EINTR;
+
+ if (!buf) {
+ DBG("buf NULL\n");
+ up(&cam->busy_lock);
+ return -EINVAL;
+ }
+
+ if (!count) {
+ DBG("count 0\n");
+ up(&cam->busy_lock);
+ return 0;
+ }
+
+ if (!cam->ops) {
+ DBG("ops NULL\n");
+ up(&cam->busy_lock);
+ return -ENODEV;
+ }
+
+ /* upload frame */
+ cam->decompressed_frame.state = FRAME_READY;
+ cam->mmap_kludge=0;
+ fetch_frame(cam);
+ if (cam->decompressed_frame.state != FRAME_DONE) {
+ DBG("upload failed %d/%d\n", cam->decompressed_frame.count,
+ cam->decompressed_frame.state);
+ up(&cam->busy_lock);
+ return -EIO;
+ }
+ cam->decompressed_frame.state = FRAME_UNUSED;
+
+ /* copy data to user space */
+ if (cam->decompressed_frame.count > count) {
+ DBG("count wrong: %d, %lu\n", cam->decompressed_frame.count,
+ count);
+ up(&cam->busy_lock);
+ return -EFAULT;
+ }
+ if (copy_to_user(buf, cam->decompressed_frame.data,
+ cam->decompressed_frame.count)) {
+ DBG("copy_to_user failed\n");
+ up(&cam->busy_lock);
+ return -EFAULT;
+ }
+
+ up(&cam->busy_lock);
+ return cam->decompressed_frame.count;
+}
+
+static int cpia_ioctl(struct video_device *dev, unsigned int ioctlnr, void *arg)
+{
+ struct cam_data *cam = dev->priv;
+ int retval = 0;
+
+ if (!cam || !cam->ops)
+ return -ENODEV;
+
+ /* make this _really_ smp-safe */
+ if (down_interruptible(&cam->busy_lock))
+ return -EINTR;
+
+ //DBG("cpia_ioctl: %u\n", ioctlnr);
+
+ switch (ioctlnr) {
+ /* query capabilites */
+ case VIDIOCGCAP:
+ {
+ struct video_capability b;
+
+ DBG("VIDIOCGCAP\n");
+ strcpy(b.name, "CPiA Camera");
+ b.type = VID_TYPE_CAPTURE;
+ b.channels = 1;
+ b.audios = 0;
+ b.maxwidth = 352; /* VIDEOSIZE_CIF */
+ b.maxheight = 288;
+ b.minwidth = 48; /* VIDEOSIZE_48_48 */
+ b.minheight = 48;
+
+ if (copy_to_user(arg, &b, sizeof(b)))
+ retval = -EFAULT;
+
+ break;
+ }
+
+ /* get/set video source - we are a camera and nothing else */
+ case VIDIOCGCHAN:
+ {
+ struct video_channel v;
+
+ DBG("VIDIOCGCHAN\n");
+ if (copy_from_user(&v, arg, sizeof(v))) {
+ retval = -EFAULT;
+ break;
+ }
+ if (v.channel != 0) {
+ retval = -EINVAL;
+ break;
+ }
+
+ v.channel = 0;
+ strcpy(v.name, "Camera");
+ v.tuners = 0;
+ v.flags = 0;
+ v.type = VIDEO_TYPE_CAMERA;
+ v.norm = 0;
+
+ if (copy_to_user(arg, &v, sizeof(v)))
+ retval = -EFAULT;
+ break;
+ }
+
+ case VIDIOCSCHAN:
+ {
+ int v;
+
+ DBG("VIDIOCSCHAN\n");
+ if (copy_from_user(&v, arg, sizeof(v)))
+ retval = -EFAULT;
+
+ if (retval == 0 && v != 0)
+ retval = -EINVAL;
+
+ break;
+ }
+
+ /* image properties */
+ case VIDIOCGPICT:
+ DBG("VIDIOCGPICT\n");
+ if (copy_to_user(arg, &cam->vp, sizeof(struct video_picture)))
+ retval = -EFAULT;
+ break;
+
+ case VIDIOCSPICT:
+ {
+ struct video_picture vp;
+
+ DBG("VIDIOCSPICT\n");
+
+ /* copy_from_user */
+ if (copy_from_user(&vp, arg, sizeof(vp))) {
+ retval = -EFAULT;
+ break;
+ }
+
+ /* check validity */
+ DBG("palette: %d\n", vp.palette);
+ DBG("depth: %d\n", vp.depth);
+ if (!valid_mode(vp.palette, vp.depth)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ down(&cam->param_lock);
+ /* brightness, colour, contrast need no check 0-65535 */
+ memcpy( &cam->vp, &vp, sizeof(vp) );
+ /* update cam->params.colourParams */
+ cam->params.colourParams.brightness = vp.brightness*100/65535;
+ cam->params.colourParams.contrast = vp.contrast*100/65535;
+ cam->params.colourParams.saturation = vp.colour*100/65535;
+ /* contrast is in steps of 8, so round */
+ cam->params.colourParams.contrast =
+ ((cam->params.colourParams.contrast + 3) / 8) * 8;
+ if (cam->params.version.firmwareVersion == 1 &&
+ cam->params.version.firmwareRevision == 2 &&
+ cam->params.colourParams.contrast > 80) {
+ /* 1-02 firmware limits contrast to 80 */
+ cam->params.colourParams.contrast = 80;
+ }
+
+ /* queue command to update camera */
+ cam->cmd_queue |= COMMAND_SETCOLOURPARAMS;
+ up(&cam->param_lock);
+ DBG("VIDIOCSPICT: %d / %d // %d / %d / %d / %d\n",
+ vp.depth, vp.palette, vp.brightness, vp.hue, vp.colour,
+ vp.contrast);
+ break;
+ }
+
+ /* get/set capture window */
+ case VIDIOCGWIN:
+ DBG("VIDIOCGWIN\n");
+
+ if (copy_to_user(arg, &cam->vw, sizeof(struct video_window)))
+ retval = -EFAULT;
+ break;
+
+ case VIDIOCSWIN:
+ {
+ /* copy_from_user, check validity, copy to internal structure */
+ struct video_window vw;
+ DBG("VIDIOCSWIN\n");
+ if (copy_from_user(&vw, arg, sizeof(vw))) {
+ retval = -EFAULT;
+ break;
+ }
+
+ if (vw.clipcount != 0) { /* clipping not supported */
+ retval = -EINVAL;
+ break;
+ }
+ if (vw.clips != NULL) { /* clipping not supported */
+ retval = -EINVAL;
+ break;
+ }
+
+ /* we set the video window to something smaller or equal to what
+ * is requested by the user???
+ */
+ down(&cam->param_lock);
+ if (vw.width != cam->vw.width || vw.height != cam->vw.height) {
+ int video_size = match_videosize(vw.width, vw.height);
+
+ if (video_size < 0) {
+ retval = -EINVAL;
+ up(&cam->param_lock);
+ break;
+ }
+ cam->video_size = video_size;
+ set_vw_size(cam);
+ DBG("%d / %d\n", cam->vw.width, cam->vw.height);
+ cam->cmd_queue |= COMMAND_SETFORMAT;
+ }
+
+ // FIXME needed??? memcpy(&cam->vw, &vw, sizeof(vw));
+ up(&cam->param_lock);
+
+ /* setformat ignored by camera during streaming,
+ * so stop/dispatch/start */
+ if (cam->cmd_queue & COMMAND_SETFORMAT) {
+ DBG("\n");
+ dispatch_commands(cam);
+ }
+ DBG("%d/%d:%d\n", cam->video_size,
+ cam->vw.width, cam->vw.height);
+ break;
+ }
+
+ /* mmap interface */
+ case VIDIOCGMBUF:
+ {
+ struct video_mbuf vm;
+ int i;
+
+ DBG("VIDIOCGMBUF\n");
+ memset(&vm, 0, sizeof(vm));
+ vm.size = CPIA_MAX_FRAME_SIZE*FRAME_NUM;
+ vm.frames = FRAME_NUM;
+ for (i = 0; i < FRAME_NUM; i++)
+ vm.offsets[i] = CPIA_MAX_FRAME_SIZE * i;
+
+ if (copy_to_user((void *)arg, (void *)&vm, sizeof(vm)))
+ retval = -EFAULT;
+
+ break;
+ }
+
+ case VIDIOCMCAPTURE:
+ {
+ struct video_mmap vm;
+ int video_size;
+
+ if (copy_from_user((void *)&vm, (void *)arg, sizeof(vm))) {
+ retval = -EFAULT;
+ break;
+ }
+#if 1
+ DBG("VIDIOCMCAPTURE: %d / %d / %dx%d\n", vm.format, vm.frame,
+ vm.width, vm.height);
+#endif
+ if (vm.frame<0||vm.frame>FRAME_NUM) {
+ retval = -EINVAL;
+ break;
+ }
+
+ /* set video format */
+ cam->vp.palette = vm.format;
+ switch(vm.format) {
+ case VIDEO_PALETTE_GREY:
+ case VIDEO_PALETTE_RGB555:
+ case VIDEO_PALETTE_RGB565:
+ case VIDEO_PALETTE_YUV422:
+ case VIDEO_PALETTE_YUYV:
+ case VIDEO_PALETTE_UYVY:
+ cam->vp.depth = 16;
+ break;
+ case VIDEO_PALETTE_RGB24:
+ cam->vp.depth = 24;
+ break;
+ case VIDEO_PALETTE_RGB32:
+ cam->vp.depth = 32;
+ break;
+ default:
+ retval = -EINVAL;
+ break;
+ }
+ if (retval)
+ break;
+
+ /* set video size */
+ video_size = match_videosize(vm.width, vm.height);
+ if (cam->video_size < 0) {
+ retval = -EINVAL;
+ break;
+ }
+ if (video_size != cam->video_size) {
+ cam->video_size = video_size;
+ set_vw_size(cam);
+ cam->cmd_queue |= COMMAND_SETFORMAT;
+ dispatch_commands(cam);
+ }
+#if 0
+ DBG("VIDIOCMCAPTURE: %d / %d/%d\n", cam->video_size,
+ cam->vw.width, cam->vw.height);
+#endif
+ /* according to v4l-spec we must start streaming here */
+ cam->mmap_kludge = 1;
+ retval = capture_frame(cam, &vm);
+
+ break;
+ }
+
+ case VIDIOCSYNC:
+ {
+ int frame;
+
+ if (copy_from_user((void *)&frame, arg, sizeof(int))) {
+ retval = -EFAULT;
+ break;
+ }
+ //DBG("VIDIOCSYNC: %d\n", frame);
+
+ if (frame<0 || frame >= FRAME_NUM) {
+ retval = -EINVAL;
+ break;
+ }
+
+ switch (cam->frame[frame].state) {
+ case FRAME_UNUSED:
+ case FRAME_READY:
+ case FRAME_GRABBING:
+ DBG("sync to unused frame %d\n", frame);
+ retval = -EINVAL;
+ break;
+
+ case FRAME_DONE:
+ cam->frame[frame].state = FRAME_UNUSED;
+ //DBG("VIDIOCSYNC: %d synced\n", frame);
+ break;
+ }
+ if (retval == -EINTR) {
+ /* FIXME - xawtv does not handle this nice */
+ retval = 0;
+ }
+ break;
+ }
+
+ /* pointless to implement overlay with this camera */
+ case VIDIOCCAPTURE:
+ retval = -EINVAL;
+ break;
+ case VIDIOCGFBUF:
+ retval = -EINVAL;
+ break;
+ case VIDIOCSFBUF:
+ retval = -EINVAL;
+ break;
+ case VIDIOCKEY:
+ retval = -EINVAL;
+ break;
+
+ /* tuner interface - we have none */
+ case VIDIOCGTUNER:
+ retval = -EINVAL;
+ break;
+ case VIDIOCSTUNER:
+ retval = -EINVAL;
+ break;
+ case VIDIOCGFREQ:
+ retval = -EINVAL;
+ break;
+ case VIDIOCSFREQ:
+ retval = -EINVAL;
+ break;
+
+ /* audio interface - we have none */
+ case VIDIOCGAUDIO:
+ retval = -EINVAL;
+ break;
+ case VIDIOCSAUDIO:
+ retval = -EINVAL;
+ break;
+ default:
+ retval = -ENOIOCTLCMD;
+ break;
+ }
+
+ up(&cam->param_lock);
+ up(&cam->busy_lock);
+ return retval;
+}
+
+/* FIXME */
+static int cpia_mmap(struct video_device *dev, const char *adr,
+ unsigned long size)
+{
+ unsigned long start = (unsigned long)adr;
+ unsigned long page, pos;
+ struct cam_data *cam = dev->priv;
+ int retval;
+
+ if (!cam || !cam->ops)
+ return -ENODEV;
+
+ DBG("cpia_mmap: %ld\n", size);
+
+ if (size > FRAME_NUM*CPIA_MAX_FRAME_SIZE)
+ return -EINVAL;
+
+ if (!cam || !cam->ops)
+ return -ENODEV;
+
+ /* make this _really_ smp-safe */
+ if (down_interruptible(&cam->busy_lock))
+ return -EINTR;
+
+ if (!cam->frame_buf) { /* we do lazy allocation */
+ if ((retval = allocate_frame_buf(cam))) {
+ up(&cam->busy_lock);
+ return retval;
+ }
+ }
+
+ pos = (unsigned long)(cam->frame_buf);
+ while (size > 0) {
+ page = kvirt_to_pa(pos);
+ if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED)) {
+ up(&cam->busy_lock);
+ return -EAGAIN;
+ }
+ start += PAGE_SIZE;
+ pos += PAGE_SIZE;
+ if (size > PAGE_SIZE)
+ size -= PAGE_SIZE;
+ else
+ size = 0;
+ }
+
+ DBG("cpia_mmap: %ld\n", size);
+ up(&cam->busy_lock);
+
+ return 0;
+}
+
+int cpia_video_init(struct video_device *vdev)
+{
+#ifdef CONFIG_PROC_FS
+ create_proc_cpia_cam(vdev->priv);
+#endif
+ return 0;
+}
+
+static struct video_device cpia_template = {
+ "CPiA Camera",
+ VID_TYPE_CAPTURE,
+ VID_HARDWARE_CPIA, /* FIXME */
+ cpia_open, /* open */
+ cpia_close, /* close */
+ cpia_read, /* read */
+ NULL, /* no write */
+ NULL, /* no poll */
+ cpia_ioctl, /* ioctl */
+ cpia_mmap, /* mmap */
+ cpia_video_init, /* initialize */
+ NULL, /* priv */
+ 0, /* busy */
+ -1 /* minor - unset */
+};
+
+/* initialise cam_data structure */
+static void reset_camera_struct(struct cam_data *cam)
+{
+ /* The following parameter values are the defaults from
+ * "Software Developer's Guide for CPiA Cameras". Any changes
+ * to the defaults are noted in comments. */
+ cam->params.colourParams.brightness = 50;
+ cam->params.colourParams.contrast = 48;
+ cam->params.colourParams.saturation = 50;
+ cam->params.exposure.gainMode = 2;
+ cam->params.exposure.expMode = 2; /* AEC */
+ cam->params.exposure.compMode = 1;
+ cam->params.exposure.centreWeight = 1;
+ cam->params.exposure.gain = 0;
+ cam->params.exposure.fineExp = 0;
+ cam->params.exposure.coarseExpLo = 185;
+ cam->params.exposure.coarseExpHi = 0;
+ cam->params.exposure.redComp = 220;
+ cam->params.exposure.green1Comp = 214;
+ cam->params.exposure.green2Comp = 214;
+ cam->params.exposure.blueComp = 230;
+ cam->params.colourBalance.balanceModeIsAuto = 1;
+ cam->params.colourBalance.redGain = 32;
+ cam->params.colourBalance.greenGain = 6;
+ cam->params.colourBalance.blueGain = 92;
+ cam->params.apcor.gain1 = 0x1c;
+ cam->params.apcor.gain2 = 0x1a;
+ cam->params.apcor.gain4 = 0x2d;
+ cam->params.apcor.gain8 = 0x2a;
+ cam->params.flickerControl.flickerMode = 0;
+ cam->params.flickerControl.coarseJump =
+ flicker_jumps[cam->mainsFreq]
+ [cam->params.sensorFps.baserate]
+ [cam->params.sensorFps.divisor];
+ cam->params.vlOffset.gain1 = 24;
+ cam->params.vlOffset.gain2 = 28;
+ cam->params.vlOffset.gain4 = 30;
+ cam->params.vlOffset.gain8 = 30;
+ cam->params.compressionParams.hysteresis = 3;
+ cam->params.compressionParams.threshMax = 11;
+ cam->params.compressionParams.smallStep = 1;
+ cam->params.compressionParams.largeStep = 3;
+ cam->params.compressionParams.decimationHysteresis = 2;
+ cam->params.compressionParams.frDiffStepThresh = 5;
+ cam->params.compressionParams.qDiffStepThresh = 3;
+ cam->params.compressionParams.decimationThreshMod = 2;
+ /* End of default values from Software Developer's Guide */
+
+ cam->transfer_rate = 0;
+
+ /* Set Sensor FPS to 15fps. This seems better than 30fps
+ * for indoor lighting. */
+ cam->params.sensorFps.divisor = 1;
+ cam->params.sensorFps.baserate = 1;
+
+ cam->params.yuvThreshold.yThreshold = 15; /* FIXME? */
+ cam->params.yuvThreshold.uvThreshold = 15; /* FIXME? */
+
+ cam->params.format.subSample = SUBSAMPLE_422;
+ cam->params.format.yuvOrder = YUVORDER_YUYV;
+
+ cam->params.compression.mode = CPIA_COMPRESSION_AUTO;
+ cam->params.compressionTarget.frTargeting =
+ CPIA_COMPRESSION_TARGET_QUALITY;
+ cam->params.compressionTarget.targetFR = 7; /* FIXME? */
+ cam->params.compressionTarget.targetQ = 10; /* FIXME? */
+
+ cam->video_size = VIDEOSIZE_CIF;
+
+ cam->vp.colour = 32768; /* 50% */
+ cam->vp.hue = 32768; /* 50% */
+ cam->vp.brightness = 32768; /* 50% */
+ cam->vp.contrast = 32768; /* 50% */
+ cam->vp.whiteness = 0; /* not used -> grayscale only */
+ cam->vp.depth = 0; /* FIXME: to be set by user? */
+ cam->vp.palette = VIDEO_PALETTE_RGB24; /* FIXME: to be set by user? */
+
+ cam->vw.x = 0;
+ cam->vw.y = 0;
+ set_vw_size(cam);
+ cam->vw.chromakey = 0;
+ /* PP NOTE: my extension to use vw.flags for this, bear it! */
+ cam->vw.flags = 0;
+ cam->vw.clipcount = 0;
+ cam->vw.clips = NULL;
+
+ cam->cmd_queue = COMMAND_NONE;
+ cam->first_frame = 0;
+
+ return;
+}
+
+/* initialize cam_data structure */
+static void init_camera_struct(struct cam_data *cam,
+ struct cpia_camera_ops *ops )
+{
+ int i;
+
+ /* Default everything to 0 */
+ memset(cam, 0, sizeof(struct cam_data));
+
+ cam->ops = ops;
+ init_MUTEX(&cam->param_lock);
+ init_MUTEX(&cam->busy_lock);
+
+ reset_camera_struct(cam);
+
+ cam->proc_entry = NULL;
+
+ memcpy(&cam->vdev, &cpia_template, sizeof(cpia_template));
+ cam->vdev.priv = cam;
+
+ cam->curframe = 0;
+ for (i = 0; i < FRAME_NUM; i++) {
+ cam->frame[i].width = 0;
+ cam->frame[i].height = 0;
+ cam->frame[i].state = FRAME_UNUSED;
+ cam->frame[i].data = NULL;
+ }
+ cam->decompressed_frame.width = 0;
+ cam->decompressed_frame.height = 0;
+ cam->decompressed_frame.state = FRAME_UNUSED;
+ cam->decompressed_frame.data = NULL;
+}
+
+struct cam_data *cpia_register_camera(struct cpia_camera_ops *ops, void *lowlevel)
+{
+ struct cam_data *camera;
+
+ /* Need a lock when adding/removing cameras. This doesn't happen
+ * often and doesn't take very long, so grabbing the kernel lock
+ * should be OK. */
+
+ if ((camera = kmalloc(sizeof(struct cam_data), GFP_KERNEL)) == NULL) {
+ unlock_kernel();
+ return NULL;
+ }
+
+ init_camera_struct( camera, ops );
+ camera->lowlevel_data = lowlevel;
+
+ /* register v4l device */
+ if (video_register_device(&camera->vdev, VFL_TYPE_GRABBER) == -1) {
+ kfree(camera);
+ unlock_kernel();
+ printk(KERN_DEBUG "video_register_device failed\n");
+ return NULL;
+ }
+
+ /* get version information from camera: open/reset/close */
+
+ /* open cpia */
+ if (camera->ops->open(camera->lowlevel_data))
+ return camera;
+
+ /* reset the camera */
+ if (reset_camera(camera) != 0) {
+ camera->ops->close(camera->lowlevel_data);
+ return camera;
+ }
+
+ /* close cpia */
+ camera->ops->close(camera->lowlevel_data);
+
+/* Eh? Feeling happy? - jerdfelt */
+/*
+ camera->ops->open(camera->lowlevel_data);
+ camera->ops->close(camera->lowlevel_data);
+*/
+
+ printk(KERN_INFO " CPiA Version: %d.%02d (%d.%d)\n",
+ camera->params.version.firmwareVersion,
+ camera->params.version.firmwareRevision,
+ camera->params.version.vcVersion,
+ camera->params.version.vcRevision);
+ printk(KERN_INFO " CPiA PnP-ID: %04x:%04x:%04x\n",
+ camera->params.pnpID.vendor,
+ camera->params.pnpID.product,
+ camera->params.pnpID.deviceRevision);
+ printk(KERN_INFO " VP-Version: %d.%d %04x\n",
+ camera->params.vpVersion.vpVersion,
+ camera->params.vpVersion.vpRevision,
+ camera->params.vpVersion.cameraHeadID);
+
+ return camera;
+}
+
+void cpia_unregister_camera(struct cam_data *cam)
+{
+ if (!cam->open_count) {
+ DBG("unregistering video\n");
+ video_unregister_device(&cam->vdev);
+ } else {
+ LOG("/dev/video%d removed while open, "
+ "deferring video_unregister_device\n", cam->vdev.minor);
+ DBG("camera open -- setting ops to NULL\n");
+ cam->ops = NULL;
+ }
+
+#ifdef CONFIG_PROC_FS
+ DBG("destroying /proc/cpia/video%d\n", cam->vdev.minor);
+ destroy_proc_cpia_cam(cam);
+#endif
+ if (!cam->open_count) {
+ DBG("freeing camera\n");
+ kfree(cam);
+ }
+}
+
+/****************************************************************************
+ *
+ * Module routines
+ *
+ ***************************************************************************/
+
+#ifdef MODULE
+int init_module(void)
+{
+ printk(KERN_INFO "%s v%d.%d.%d\n", ABOUT,
+ CPIA_MAJ_VER, CPIA_MIN_VER, CPIA_PATCH_VER);
+#ifdef CONFIG_PROC_FS
+ proc_cpia_create();
+#endif
+#ifdef CONFIG_KMOD
+#ifdef CONFIG_VIDEO_CPIA_PP_MODULE
+ request_module("cpia_pp");
+#endif
+#ifdef CONFIG_VIDEO_CPIA_USB_MODULE
+ request_module("cpia_usb");
+#endif
+#endif
+return 0;
+}
+
+void cleanup_module(void)
+{
+#ifdef CONFIG_PROC_FS
+ proc_cpia_destroy();
+#endif
+}
+
+#else
+
+int cpia_init(struct video_init *unused)
+{
+ printk(KERN_INFO "%s v%d.%d.%d\n", ABOUT,
+ CPIA_MAJ_VER, CPIA_MIN_VER, CPIA_PATCH_VER);
+#ifdef CONFIG_PROC_FS
+ proc_cpia_create();
+#endif
+
+#ifdef CONFIG_VIDEO_CPIA_PP
+ cpia_pp_init();
+#endif
+#ifdef CONFIG_KMOD
+#ifdef CONFIG_VIDEO_CPIA_PP_MODULE
+ request_module("cpia_pp");
+#endif
+
+#ifdef CONFIG_VIDEO_CPIA_USB_MODULE
+ request_module("cpia_usb");
+#endif
+#endif /* CONFIG_KMOD */
+#ifdef CONFIG_VIDEO_CPIA_USB
+ cpia_usb_init();
+#endif
+ return 0;
+}
+
+/* Exported symbols for modules. */
+
+EXPORT_SYMBOL(cpia_register_camera);
+EXPORT_SYMBOL(cpia_unregister_camera);
+
+#endif
diff --git a/drivers/media/video/cpia.h b/drivers/media/video/cpia.h
new file mode 100644
index 000000000..579b5e153
--- /dev/null
+++ b/drivers/media/video/cpia.h
@@ -0,0 +1,421 @@
+#ifndef cpia_h
+#define cpia_h
+
+/*
+ * CPiA Parallel Port Video4Linux driver
+ *
+ * Supports CPiA based parallel port Video Camera's.
+ *
+ * (C) Copyright 1999 Bas Huisman,
+ * Peter Pregler,
+ * Scott J. Bertin,
+ * VLSI Vision Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define CPIA_MAJ_VER 0
+#define CPIA_MIN_VER 7
+#define CPIA_PATCH_VER 4
+
+#define CPIA_PP_MAJ_VER 0
+#define CPIA_PP_MIN_VER 7
+#define CPIA_PP_PATCH_VER 4
+
+#define CPIA_MAX_FRAME_SIZE_UNALIGNED (352 * 288 * 4) /* CIF at RGB32 */
+#define CPIA_MAX_FRAME_SIZE ((CPIA_MAX_FRAME_SIZE_UNALIGNED + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)) /* align above to PAGE_SIZE */
+
+#ifdef __KERNEL__
+
+#include <asm/uaccess.h>
+#include <linux/videodev.h>
+#include <linux/smp_lock.h>
+
+struct cpia_camera_ops
+{
+ /* open sets privdata to point to structure for this camera.
+ * Returns negative value on error, otherwise 0.
+ */
+ int (*open)(void *privdata);
+
+ /* Registers callback function cb to be called with cbdata
+ * when an image is ready. If cb is NULL, only single image grabs
+ * should be used. cb should immediately call streamRead to read
+ * the data or data may be lost. Returns negative value on error,
+ * otherwise 0.
+ */
+ int (*registerCallback)(void *privdata, void (*cb)(void *cbdata),
+ void *cbdata);
+
+ /* transferCmd sends commands to the camera. command MUST point to
+ * an 8 byte buffer in kernel space. data can be NULL if no extra
+ * data is needed. The size of the data is given by the last 2
+ * bytes of comand. data must also point to memory in kernel space.
+ * Returns negative value on error, otherwise 0.
+ */
+ int (*transferCmd)(void *privdata, u8 *command, u8 *data);
+
+ /* streamStart initiates stream capture mode.
+ * Returns negative value on error, otherwise 0.
+ */
+ int (*streamStart)(void *privdata);
+
+ /* streamStop terminates stream capture mode.
+ * Returns negative value on error, otherwise 0.
+ */
+ int (*streamStop)(void *privdata);
+
+ /* streamRead reads a frame from the camera. buffer points to a
+ * buffer large enough to hold a complete frame in kernel space.
+ * noblock indicates if this should be a non blocking read.
+ * Returns the number of bytes read, or negative value on error.
+ */
+ int (*streamRead)(void *privdata, u8 *buffer, int noblock);
+
+ /* close disables the device until open() is called again.
+ * Returns negative value on error, otherwise 0.
+ */
+ int (*close)(void *privdata);
+
+ /* If wait_for_stream_ready is non-zero, wait until the streamState
+ * is STREAM_READY before calling streamRead.
+ */
+ int wait_for_stream_ready;
+};
+
+struct cpia_frame {
+ u8 *data;
+ int count;
+ int width;
+ int height;
+ volatile int state;
+};
+
+struct cam_params {
+ struct {
+ u8 firmwareVersion;
+ u8 firmwareRevision;
+ u8 vcVersion;
+ u8 vcRevision;
+ } version;
+ struct {
+ u16 vendor;
+ u16 product;
+ u16 deviceRevision;
+ } pnpID;
+ struct {
+ u8 vpVersion;
+ u8 vpRevision;
+ u16 cameraHeadID;
+ } vpVersion;
+ struct {
+ u8 systemState;
+ u8 grabState;
+ u8 streamState;
+ u8 fatalError;
+ u8 cmdError;
+ u8 debugFlags;
+ u8 vpStatus;
+ u8 errorCode;
+ } status;
+ struct {
+ u8 brightness;
+ u8 contrast;
+ u8 saturation;
+ } colourParams;
+ struct {
+ u8 gainMode;
+ u8 expMode;
+ u8 compMode;
+ u8 centreWeight;
+ u8 gain;
+ u8 fineExp;
+ u8 coarseExpLo;
+ u8 coarseExpHi;
+ u8 redComp;
+ u8 green1Comp;
+ u8 green2Comp;
+ u8 blueComp;
+ } exposure;
+ struct {
+ u8 balanceModeIsAuto;
+ u8 redGain;
+ u8 greenGain;
+ u8 blueGain;
+ } colourBalance;
+ struct {
+ u8 divisor;
+ u8 baserate;
+ } sensorFps;
+ struct {
+ u8 gain1;
+ u8 gain2;
+ u8 gain4;
+ u8 gain8;
+ } apcor;
+ struct {
+ u8 flickerMode;
+ u8 coarseJump;
+ u8 allowableOverExposure;
+ } flickerControl;
+ struct {
+ u8 gain1;
+ u8 gain2;
+ u8 gain4;
+ u8 gain8;
+ } vlOffset;
+ struct {
+ u8 mode;
+ u8 decimation;
+ } compression;
+ struct {
+ u8 frTargeting;
+ u8 targetFR;
+ u8 targetQ;
+ } compressionTarget;
+ struct {
+ u8 yThreshold;
+ u8 uvThreshold;
+ } yuvThreshold;
+ struct {
+ u8 hysteresis;
+ u8 threshMax;
+ u8 smallStep;
+ u8 largeStep;
+ u8 decimationHysteresis;
+ u8 frDiffStepThresh;
+ u8 qDiffStepThresh;
+ u8 decimationThreshMod;
+ } compressionParams;
+ struct {
+ u8 videoSize; /* CIF/QCIF */
+ u8 subSample;
+ u8 yuvOrder;
+ } format;
+ struct {
+ u8 colStart; /* skip first 8*colStart pixels */
+ u8 colEnd; /* finish at 8*colEnd pixels */
+ u8 rowStart; /* skip first 4*rowStart lines */
+ u8 rowEnd; /* finish at 4*rowEnd lines */
+ } roi;
+ u8 ecpTiming;
+ u8 streamStartLine;
+};
+
+enum v4l_camstates {
+ CPIA_V4L_IDLE = 0,
+ CPIA_V4L_ERROR,
+ CPIA_V4L_COMMAND,
+ CPIA_V4L_GRABBING,
+ CPIA_V4L_STREAMING,
+ CPIA_V4L_STREAMING_PAUSED,
+};
+
+#define FRAME_NUM 2 /* double buffering for now */
+
+struct cam_data {
+ struct cam_data **previous;
+ struct cam_data *next;
+
+ struct semaphore busy_lock; /* guard against SMP multithreading */
+ struct cpia_camera_ops *ops; /* lowlevel driver operations */
+ void *lowlevel_data; /* private data for lowlevel driver */
+ u8 *raw_image; /* buffer for raw image data */
+ struct cpia_frame decompressed_frame;
+ /* buffer to hold decompressed frame */
+ int image_size; /* sizeof last decompressed image */
+ int open_count; /* # of process that have camera open */
+
+ /* camera status */
+ int fps; /* actual fps reported by the camera */
+ int transfer_rate; /* transfer rate from camera in kB/s */
+ u8 mainsFreq; /* for flicker control */
+
+ /* proc interface */
+ struct semaphore param_lock; /* params lock for this camera */
+ struct cam_params params; /* camera settings */
+ struct proc_dir_entry *proc_entry; /* /proc/cpia/videoX */
+
+ /* v4l */
+ int video_size; /* VIDEO_SIZE_ */
+ volatile enum v4l_camstates camstate; /* v4l layer status */
+ struct video_device vdev; /* v4l videodev */
+ struct video_picture vp; /* v4l camera settings */
+ struct video_window vw; /* v4l capture area */
+
+ /* mmap interface */
+ int curframe; /* the current frame to grab into */
+ u8 *frame_buf; /* frame buffer data */
+ struct cpia_frame frame[FRAME_NUM];
+ /* FRAME_NUM-buffering, so we need a array */
+
+ int first_frame;
+ int mmap_kludge; /* 'wrong' byte order for mmap */
+ volatile u32 cmd_queue; /* queued commands */
+};
+
+/* cpia_register_camera is called by low level driver for each camera.
+ * A unique camera number is returned, or a negative value on error */
+struct cam_data *cpia_register_camera(struct cpia_camera_ops *ops, void *lowlevel);
+
+/* cpia_unregister_camera is called by low level driver when a camera
+ * is removed. This must not fail. */
+void cpia_unregister_camera(struct cam_data *cam);
+
+/* raw CIF + 64 byte header + (2 bytes line_length + EOL) per line + 4*EOI +
+ * one byte 16bit DMA alignment
+ */
+#define CPIA_MAX_IMAGE_SIZE ((352*288*2)+64+(288*3)+5)
+
+/* constant value's */
+#define MAGIC_0 0x19
+#define MAGIC_1 0x68
+#define DATA_IN 0xC0
+#define DATA_OUT 0x40
+#define VIDEOSIZE_QCIF 0 /* 176x144 */
+#define VIDEOSIZE_CIF 1 /* 352x288 */
+#define VIDEOSIZE_SIF 2 /* 320x240 */
+#define VIDEOSIZE_QSIF 3 /* 160x120 */
+#define VIDEOSIZE_48_48 4 /* where no one has gone before, iconsize! */
+#define VIDEOSIZE_64_48 5
+#define VIDEOSIZE_128_96 6
+#define VIDEOSIZE_160_120 VIDEOSIZE_QSIF
+#define VIDEOSIZE_176_144 VIDEOSIZE_QCIF
+#define VIDEOSIZE_192_144 7
+#define VIDEOSIZE_224_168 8
+#define VIDEOSIZE_256_192 9
+#define VIDEOSIZE_288_216 10
+#define VIDEOSIZE_320_240 VIDEOSIZE_SIF
+#define VIDEOSIZE_352_288 VIDEOSIZE_CIF
+#define VIDEOSIZE_88_72 11 /* quarter CIF */
+#define SUBSAMPLE_420 0
+#define SUBSAMPLE_422 1
+#define YUVORDER_YUYV 0
+#define YUVORDER_UYVY 1
+#define NOT_COMPRESSED 0
+#define COMPRESSED 1
+#define NO_DECIMATION 0
+#define DECIMATION_ENAB 1
+#define EOI 0xff /* End Of Image */
+#define EOL 0xfd /* End Of Line */
+#define FRAME_HEADER_SIZE 64
+
+/* Image grab modes */
+#define CPIA_GRAB_SINGLE 0
+#define CPIA_GRAB_CONTINUOUS 1
+
+/* Compression parameters */
+#define CPIA_COMPRESSION_NONE 0
+#define CPIA_COMPRESSION_AUTO 1
+#define CPIA_COMPRESSION_MANUAL 2
+#define CPIA_COMPRESSION_TARGET_QUALITY 0
+#define CPIA_COMPRESSION_TARGET_FRAMERATE 1
+
+/* Return offsets for GetCameraState */
+#define SYSTEMSTATE 0
+#define GRABSTATE 1
+#define STREAMSTATE 2
+#define FATALERROR 3
+#define CMDERROR 4
+#define DEBUGFLAGS 5
+#define VPSTATUS 6
+#define ERRORCODE 7
+
+/* SystemState */
+#define UNINITIALISED_STATE 0
+#define PASS_THROUGH_STATE 1
+#define LO_POWER_STATE 2
+#define HI_POWER_STATE 3
+#define WARM_BOOT_STATE 4
+
+/* GrabState */
+#define GRAB_IDLE 0
+#define GRAB_ACTIVE 1
+#define GRAB_DONE 2
+
+/* StreamState */
+#define STREAM_NOT_READY 0
+#define STREAM_READY 1
+#define STREAM_OPEN 2
+#define STREAM_PAUSED 3
+#define STREAM_FINISHED 4
+
+/* Fatal Error, CmdError, and DebugFlags */
+#define CPIA_FLAG 1
+#define SYSTEM_FLAG 2
+#define INT_CTRL_FLAG 4
+#define PROCESS_FLAG 8
+#define COM_FLAG 16
+#define VP_CTRL_FLAG 32
+#define CAPTURE_FLAG 64
+#define DEBUG_FLAG 128
+
+/* VPStatus */
+#define VP_STATE_OK 0x00
+
+#define VP_STATE_FAILED_VIDEOINIT 0x01
+#define VP_STATE_FAILED_AECACBINIT 0x02
+#define VP_STATE_AEC_MAX 0x04
+#define VP_STATE_ACB_BMAX 0x08
+
+#define VP_STATE_ACB_RMIN 0x10
+#define VP_STATE_ACB_GMIN 0x20
+#define VP_STATE_ACB_RMAX 0x40
+#define VP_STATE_ACB_GMAX 0x80
+
+/* ErrorCode */
+#define ERROR_FLICKER_BELOW_MIN_EXP 0x01 /*flicker exposure got below minimum exposure */
+
+#define ALOG(lineno,fmt,args...) printk(fmt,lineno,##args)
+#define LOG(fmt,args...) ALOG((__LINE__),KERN_INFO __FILE__":"__FUNCTION__"(%d):"fmt,##args)
+
+#ifdef _CPIA_DEBUG_
+#define ADBG(lineno,fmt,args...) printk(fmt, jiffies, lineno, ##args)
+#define DBG(fmt,args...) ADBG((__LINE__),KERN_DEBUG __FILE__"(%ld):"__FUNCTION__"(%d):"fmt,##args)
+#else
+#define DBG(fmn,args...) do {} while(0)
+#endif
+
+#define DEB_BYTE(p)\
+ DBG("%1d %1d %1d %1d %1d %1d %1d %1d \n",\
+ (p)&0x80?1:0, (p)&0x40?1:0, (p)&0x20?1:0, (p)&0x10?1:0,\
+ (p)&0x08?1:0, (p)&0x04?1:0, (p)&0x02?1:0, (p)&0x01?1:0);
+
+#define ADD_TO_LIST(l, drv) \
+ {\
+ lock_kernel();\
+ (drv)->next = l;\
+ (drv)->previous = &(l);\
+ (l) = drv;\
+ unlock_kernel();\
+ } while(0)
+
+#define REMOVE_FROM_LIST(drv) \
+ {\
+ if ((drv)->previous != NULL) {\
+ lock_kernel();\
+ if ((drv)->next != NULL)\
+ (drv)->next->previous = (drv)->previous;\
+ *((drv)->previous) = (drv)->next;\
+ (drv)->previous = NULL;\
+ (drv)->next = NULL;\
+ unlock_kernel();\
+ }\
+ } while (0)
+
+
+#endif /* __KERNEL__ */
+
+#endif /* cpia_h */
diff --git a/drivers/media/video/cpia_pp.c b/drivers/media/video/cpia_pp.c
new file mode 100644
index 000000000..7d4be2744
--- /dev/null
+++ b/drivers/media/video/cpia_pp.c
@@ -0,0 +1,745 @@
+/*
+ * cpia_pp CPiA Parallel Port driver
+ *
+ * Supports CPiA based parallel port Video Camera's.
+ *
+ * (C) Copyright 1999 Bas Huisman <bhuism@cs.utwente.nl>
+ * (C) Copyright 1999-2000 Scott J. Bertin <sbertin@mindspring.com>,
+ * (C) Copyright 1999-2000 Peter Pregler <Peter_Pregler@email.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/config.h>
+#include <linux/version.h>
+
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/kernel.h>
+#include <linux/parport.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/smp_lock.h>
+
+#ifdef CONFIG_KMOD
+#include <linux/kmod.h>
+#endif
+
+/* #define _CPIA_DEBUG_ define for verbose debug output */
+#include "cpia.h"
+
+static int cpia_pp_open(void *privdata);
+static int cpia_pp_registerCallback(void *privdata, void (*cb) (void *cbdata),
+ void *cbdata);
+static int cpia_pp_transferCmd(void *privdata, u8 *command, u8 *data);
+static int cpia_pp_streamStart(void *privdata);
+static int cpia_pp_streamStop(void *privdata);
+static int cpia_pp_streamRead(void *privdata, u8 *buffer, int noblock);
+static int cpia_pp_close(void *privdata);
+
+#define ABOUT "Parallel port driver for Vision CPiA based cameras"
+
+/* IEEE 1284 Compatiblity Mode signal names */
+#define nStrobe PARPORT_CONTROL_STROBE /* inverted */
+#define nAutoFd PARPORT_CONTROL_AUTOFD /* inverted */
+#define nInit PARPORT_CONTROL_INIT
+#define nSelectIn PARPORT_CONTROL_SELECT
+#define IntrEnable PARPORT_CONTROL_INTEN /* normally zero for no IRQ */
+#define DirBit PARPORT_CONTROL_DIRECTION /* 0 = Forward, 1 = Reverse */
+
+#define nFault PARPORT_STATUS_ERROR
+#define Select PARPORT_STATUS_SELECT
+#define PError PARPORT_STATUS_PAPEROUT
+#define nAck PARPORT_STATUS_ACK
+#define Busy PARPORT_STATUS_BUSY /* inverted */
+
+/* some more */
+#define HostClk nStrobe
+#define HostAck nAutoFd
+#define nReverseRequest nInit
+#define Active_1284 nSelectIn
+#define nPeriphRequest nFault
+#define XFlag Select
+#define nAckReverse PError
+#define PeriphClk nAck
+#define PeriphAck Busy
+
+/* these can be used to correct for the inversion on some bits */
+#define STATUS_INVERSION_MASK (Busy)
+#define CONTROL_INVERSION_MASK (nStrobe|nAutoFd|nSelectIn)
+
+#define ECR_empty 0x01
+#define ECR_full 0x02
+#define ECR_serviceIntr 0x04
+#define ECR_dmaEn 0x08
+#define ECR_nErrIntrEn 0x10
+
+#define ECR_mode_mask 0xE0
+#define ECR_SPP_mode 0x00
+#define ECR_PS2_mode 0x20
+#define ECR_FIFO_mode 0x40
+#define ECR_ECP_mode 0x60
+
+#define ECP_FIFO_SIZE 16
+#define DMA_BUFFER_SIZE PAGE_SIZE
+ /* for 16bit DMA make sure DMA_BUFFER_SIZE is 16 bit aligned */
+#define PARPORT_CHUNK_SIZE PAGE_SIZE/* >=2.3.x */
+ /* we read this many bytes at once */
+
+#define GetECRMasked(port,mask) (parport_read_econtrol(port) & (mask))
+#define GetStatus(port) ((parport_read_status(port)^STATUS_INVERSION_MASK)&(0xf8))
+#define SetStatus(port,val) parport_write_status(port,(val)^STATUS_INVERSION_MASK)
+#define GetControl(port) ((parport_read_control(port)^CONTROL_INVERSION_MASK)&(0x3f))
+#define SetControl(port,val) parport_write_control(port,(val)^CONTROL_INVERSION_MASK)
+
+#define GetStatusMasked(port,mask) (GetStatus(port) & (mask))
+#define GetControlMasked(port,mask) (GetControl(port) & (mask))
+#define SetControlMasked(port,mask) SetControl(port,GetControl(port) | (mask));
+#define ClearControlMasked(port,mask) SetControl(port,GetControl(port)&~(mask));
+#define FrobControlBit(port,mask,value) SetControl(port,(GetControl(port)&~(mask))|((value)&(mask)));
+
+#define PACKET_LENGTH 8
+
+/* Magic numbers for defining port-device mappings */
+#define PPCPIA_PARPORT_UNSPEC -4
+#define PPCPIA_PARPORT_AUTO -3
+#define PPCPIA_PARPORT_OFF -2
+#define PPCPIA_PARPORT_NONE -1
+
+#ifdef MODULE
+static int parport_nr[PARPORT_MAX] = {[0 ... PARPORT_MAX - 1] = PPCPIA_PARPORT_UNSPEC};
+static char *parport[PARPORT_MAX] = {NULL,};
+
+MODULE_AUTHOR("B. Huisman <bhuism@cs.utwente.nl> & Peter Pregler <Peter_Pregler@email.com>");
+MODULE_DESCRIPTION("Parallel port driver for Vision CPiA based cameras");
+MODULE_PARM(parport, "1-" __MODULE_STRING(PARPORT_MAX) "s");
+MODULE_PARM_DESC(parport, "'auto' or a list of parallel port numbers. Just like lp.");
+#else
+static int parport_nr[PARPORT_MAX] __initdata =
+ {[0 ... PARPORT_MAX - 1] = PPCPIA_PARPORT_UNSPEC};
+static int parport_ptr = 0;
+#endif
+
+struct pp_cam_entry {
+ struct pardevice *pdev;
+ struct parport *port;
+ struct tq_struct cb_task;
+ int open_count;
+ wait_queue_head_t wq_stream;
+ /* image state flags */
+ int image_ready; /* we got an interrupt */
+ int image_complete; /* we have seen 4 EOI */
+
+ int streaming; /* we are in streaming mode */
+ int stream_irq;
+};
+
+static struct cpia_camera_ops cpia_pp_ops =
+{
+ cpia_pp_open,
+ cpia_pp_registerCallback,
+ cpia_pp_transferCmd,
+ cpia_pp_streamStart,
+ cpia_pp_streamStop,
+ cpia_pp_streamRead,
+ cpia_pp_close,
+ 1
+};
+
+static struct cam_data *cam_list;
+
+#ifdef _CPIA_DEBUG_
+#define DEB_PORT(port) { \
+u8 controll = GetControl(port); \
+u8 statusss = GetStatus(port); \
+DBG("nsel %c per %c naut %c nstrob %c nak %c busy %c nfaul %c sel %c init %c dir %c\n",\
+((controll & nSelectIn) ? 'U' : 'D'), \
+((statusss & PError) ? 'U' : 'D'), \
+((controll & nAutoFd) ? 'U' : 'D'), \
+((controll & nStrobe) ? 'U' : 'D'), \
+((statusss & nAck) ? 'U' : 'D'), \
+((statusss & Busy) ? 'U' : 'D'), \
+((statusss & nFault) ? 'U' : 'D'), \
+((statusss & Select) ? 'U' : 'D'), \
+((controll & nInit) ? 'U' : 'D'), \
+((controll & DirBit) ? 'R' : 'F') \
+); }
+#else
+#define DEB_PORT(port) {}
+#endif
+
+#define WHILE_OUT_TIMEOUT (HZ/10)
+#define DMA_TIMEOUT 10*HZ
+
+/* FIXME */
+static void cpia_parport_enable_irq( struct parport *port ) {
+ parport_enable_irq(port);
+ mdelay(10);
+ return;
+}
+
+static void cpia_parport_disable_irq( struct parport *port ) {
+ parport_disable_irq(port);
+ mdelay(10);
+ return;
+}
+
+/****************************************************************************
+ *
+ * EndTransferMode
+ *
+ ***************************************************************************/
+static void EndTransferMode(struct pp_cam_entry *cam)
+{
+ parport_negotiate(cam->port, IEEE1284_MODE_COMPAT);
+}
+
+/****************************************************************************
+ *
+ * ForwardSetup
+ *
+ ***************************************************************************/
+static int ForwardSetup(struct pp_cam_entry *cam)
+{
+ int retry;
+
+ /* After some commands the camera needs extra time before
+ * it will respond again, so we try up to 3 times */
+ for(retry=0; retry<3; ++retry) {
+ if(!parport_negotiate(cam->port, IEEE1284_MODE_ECP)) {
+ break;
+ }
+ }
+ if(retry == 3) {
+ DBG("Unable to negotiate ECP mode\n");
+ return -1;
+ }
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * ReverseSetup
+ *
+ ***************************************************************************/
+static int ReverseSetup(struct pp_cam_entry *cam, int extensibility)
+{
+ int retry;
+ int mode = IEEE1284_MODE_ECP;
+ if(extensibility) mode = 8|3|IEEE1284_EXT_LINK;
+
+ /* After some commands the camera needs extra time before
+ * it will respond again, so we try up to 3 times */
+ for(retry=0; retry<3; ++retry) {
+ if(!parport_negotiate(cam->port, mode)) {
+ break;
+ }
+ }
+ if(retry == 3) {
+ if(extensibility)
+ DBG("Unable to negotiate extensibility mode\n");
+ else
+ DBG("Unable to negotiate ECP mode\n");
+ return -1;
+ }
+ if(extensibility) cam->port->ieee1284.mode = IEEE1284_MODE_ECP;
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * WritePacket
+ *
+ ***************************************************************************/
+static int WritePacket(struct pp_cam_entry *cam, const u8 *packet, size_t size)
+{
+ int retval=0;
+ int size_written;
+
+ if (packet == NULL) {
+ return -EINVAL;
+ }
+ if (ForwardSetup(cam)) {
+ DBG("Write failed in setup\n");
+ return -EIO;
+ }
+ size_written = parport_write(cam->port, packet, size);
+ if(size_written != size) {
+ DBG("Write failed, wrote %d/%d\n", size_written, size);
+ retval = -EIO;
+ }
+ EndTransferMode(cam);
+ return retval;
+}
+
+/****************************************************************************
+ *
+ * ReadPacket
+ *
+ ***************************************************************************/
+static int ReadPacket(struct pp_cam_entry *cam, u8 *packet, size_t size)
+{
+ int retval=0;
+ if (packet == NULL) {
+ return -EINVAL;
+ }
+ if (ReverseSetup(cam, 0)) {
+ return -EIO;
+ }
+ if(parport_read(cam->port, packet, size) != size) {
+ retval = -EIO;
+ }
+ EndTransferMode(cam);
+ return retval;
+}
+
+/****************************************************************************
+ *
+ * cpia_pp_streamStart
+ *
+ ***************************************************************************/
+static int cpia_pp_streamStart(void *privdata)
+{
+ struct pp_cam_entry *cam = privdata;
+ DBG("\n");
+ cam->streaming=1;
+ cam->image_ready=0;
+ //if (ReverseSetup(cam,1)) return -EIO;
+ if(cam->stream_irq) cpia_parport_enable_irq(cam->port);
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * cpia_pp_streamStop
+ *
+ ***************************************************************************/
+static int cpia_pp_streamStop(void *privdata)
+{
+ struct pp_cam_entry *cam = privdata;
+
+ DBG("\n");
+ cam->streaming=0;
+ cpia_parport_disable_irq(cam->port);
+ //EndTransferMode(cam);
+
+ return 0;
+}
+
+static int cpia_pp_read(struct parport *port, u8 *buffer, int len)
+{
+ int bytes_read, new_bytes;
+ for(bytes_read=0; bytes_read<len; bytes_read += new_bytes) {
+ new_bytes = parport_read(port, buffer+bytes_read,
+ len-bytes_read);
+ if(new_bytes < 0) break;
+ }
+ return bytes_read;
+}
+
+/****************************************************************************
+ *
+ * cpia_pp_streamRead
+ *
+ ***************************************************************************/
+static int cpia_pp_streamRead(void *privdata, u8 *buffer, int noblock)
+{
+ struct pp_cam_entry *cam = privdata;
+ int read_bytes = 0;
+ int i, endseen, block_size, new_bytes;
+
+ if(cam == NULL) {
+ DBG("Internal driver error: cam is NULL\n");
+ return -EINVAL;
+ }
+ if(buffer == NULL) {
+ DBG("Internal driver error: buffer is NULL\n");
+ return -EINVAL;
+ }
+ //if(cam->streaming) DBG("%d / %d\n", cam->image_ready, noblock);
+ if( cam->stream_irq ) {
+ DBG("%d\n", cam->image_ready);
+ cam->image_ready--;
+ }
+ cam->image_complete=0;
+ if (0/*cam->streaming*/) {
+ if(!cam->image_ready) {
+ if(noblock) return -EWOULDBLOCK;
+ interruptible_sleep_on(&cam->wq_stream);
+ if( signal_pending(current) ) return -EINTR;
+ DBG("%d\n", cam->image_ready);
+ }
+ } else {
+ if (ReverseSetup(cam, 1)) {
+ DBG("unable to ReverseSetup\n");
+ return -EIO;
+ }
+ }
+ endseen = 0;
+ block_size = PARPORT_CHUNK_SIZE;
+ while( !cam->image_complete ) {
+ if(current->need_resched) schedule();
+
+ new_bytes = cpia_pp_read(cam->port, buffer, block_size );
+ if( new_bytes <= 0 ) {
+ break;
+ }
+ i=-1;
+ while(++i<new_bytes && endseen<4) {
+ if(*buffer==EOI) {
+ endseen++;
+ } else {
+ endseen=0;
+ }
+ buffer++;
+ }
+ read_bytes += i;
+ if( endseen==4 ) {
+ cam->image_complete=1;
+ break;
+ }
+ if( CPIA_MAX_IMAGE_SIZE-read_bytes <= PARPORT_CHUNK_SIZE ) {
+ block_size=CPIA_MAX_IMAGE_SIZE-read_bytes;
+ }
+ }
+ EndTransferMode(cam);
+ return cam->image_complete ? read_bytes : -EIO;
+}
+
+/****************************************************************************
+ *
+ * cpia_pp_transferCmd
+ *
+ ***************************************************************************/
+static int cpia_pp_transferCmd(void *privdata, u8 *command, u8 *data)
+{
+ int err;
+ int retval=0;
+ int databytes;
+ struct pp_cam_entry *cam = privdata;
+
+ if(cam == NULL) {
+ DBG("Internal driver error: cam is NULL\n");
+ return -EINVAL;
+ }
+ if(command == NULL) {
+ DBG("Internal driver error: command is NULL\n");
+ return -EINVAL;
+ }
+ databytes = (((int)command[7])<<8) | command[6];
+ if ((err = WritePacket(cam, command, PACKET_LENGTH)) < 0) {
+ DBG("Error writing command\n");
+ return err;
+ }
+ if(command[0] == DATA_IN) {
+ u8 buffer[8];
+ if(data == NULL) {
+ DBG("Internal driver error: data is NULL\n");
+ return -EINVAL;
+ }
+ if((err = ReadPacket(cam, buffer, 8)) < 0) {
+ return err;
+ DBG("Error reading command result\n");
+ }
+ memcpy(data, buffer, databytes);
+ } else if(command[0] == DATA_OUT) {
+ if(databytes > 0) {
+ if(data == NULL) {
+ DBG("Internal driver error: data is NULL\n");
+ retval = -EINVAL;
+ } else {
+ if((err=WritePacket(cam, data, databytes)) < 0){
+ DBG("Error writing command data\n");
+ return err;
+ }
+ }
+ }
+ } else {
+ DBG("Unexpected first byte of command: %x\n", command[0]);
+ retval = -EINVAL;
+ }
+ return retval;
+}
+
+/****************************************************************************
+ *
+ * cpia_pp_open
+ *
+ ***************************************************************************/
+static int cpia_pp_open(void *privdata)
+{
+ struct pp_cam_entry *cam = (struct pp_cam_entry *)privdata;
+
+ if (cam == NULL)
+ return -EINVAL;
+
+ if(cam->open_count == 0) {
+ if (parport_claim(cam->pdev)) {
+ DBG("failed to claim the port\n");
+ return -EBUSY;
+ }
+ parport_negotiate(cam->port, IEEE1284_MODE_COMPAT);
+ parport_data_forward(cam->port);
+ parport_write_control(cam->port, PARPORT_CONTROL_SELECT);
+ udelay(50);
+ parport_write_control(cam->port,
+ PARPORT_CONTROL_SELECT
+ | PARPORT_CONTROL_INIT);
+ }
+
+ ++cam->open_count;
+
+#ifdef MODULE
+ MOD_INC_USE_COUNT;
+#endif
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * cpia_pp_registerCallback
+ *
+ ***************************************************************************/
+static int cpia_pp_registerCallback(void *privdata, void (*cb)(void *cbdata), void *cbdata)
+{
+ struct pp_cam_entry *cam = privdata;
+ int retval = 0;
+
+ if(cam->port->irq != PARPORT_IRQ_NONE) {
+ cam->cb_task.routine = cb;
+ cam->cb_task.data = cbdata;
+ } else {
+ retval = -1;
+ }
+ return retval;
+}
+
+/****************************************************************************
+ *
+ * cpia_pp_close
+ *
+ ***************************************************************************/
+static int cpia_pp_close(void *privdata)
+{
+ struct pp_cam_entry *cam = privdata;
+#ifdef MODULE
+ MOD_DEC_USE_COUNT;
+#endif
+ if (--cam->open_count == 0) {
+ parport_release(cam->pdev);
+ }
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * cpia_pp_register
+ *
+ ***************************************************************************/
+static int cpia_pp_register(struct parport *port)
+{
+ struct pardevice *pdev = NULL;
+ struct pp_cam_entry *cam;
+ struct cam_data *cpia;
+
+ if (!(port->modes & PARPORT_MODE_ECP) &&
+ !(port->modes & PARPORT_MODE_TRISTATE)) {
+ LOG("port is not ECP capable\n");
+ return -ENXIO;
+ }
+
+ cam = kmalloc(sizeof(struct pp_cam_entry), GFP_KERNEL);
+ if (cam == NULL) {
+ LOG("failed to allocate camera structure\n");
+ return -ENOMEM;
+ }
+ memset(cam,0,sizeof(struct pp_cam_entry));
+
+ pdev = parport_register_device(port, "cpia_pp", NULL, NULL,
+ NULL, 0, cam);
+
+ if (!pdev) {
+ LOG("failed to parport_register_device\n");
+ kfree(cam);
+ return -ENXIO;
+ }
+
+ cam->pdev = pdev;
+ cam->port = port;
+ init_waitqueue_head(&cam->wq_stream);
+
+ cam->streaming = 0;
+ cam->stream_irq = 0;
+
+ if((cpia = cpia_register_camera(&cpia_pp_ops, cam)) == NULL) {
+ LOG("failed to cpia_register_camera\n");
+ parport_unregister_device(pdev);
+ kfree(cam);
+ return -ENXIO;
+ }
+ ADD_TO_LIST(cam_list, cpia);
+
+ return 0;
+}
+
+static void cpia_pp_detach (struct parport *port)
+{
+ struct cam_data *cpia;
+
+ for(cpia = cam_list; cpia != NULL; cpia = cpia->next) {
+ struct pp_cam_entry *cam = cpia->lowlevel_data;
+ if (cam && cam->port->number == port->number) {
+ REMOVE_FROM_LIST(cpia);
+
+ cpia_unregister_camera(cpia);
+
+ if(cam->open_count > 0) {
+ cpia_pp_close(cam);
+ }
+
+ parport_unregister_device(cam->pdev);
+
+ kfree(cam);
+ cpia->lowlevel_data = NULL;
+ break;
+ }
+ }
+}
+
+static void cpia_pp_attach (struct parport *port)
+{
+ unsigned int i;
+
+ switch (parport_nr[0])
+ {
+ case PPCPIA_PARPORT_UNSPEC:
+ case PPCPIA_PARPORT_AUTO:
+ if (port->probe_info[0].class != PARPORT_CLASS_MEDIA ||
+ port->probe_info[0].cmdset == NULL ||
+ strncmp(port->probe_info[0].cmdset, "CPIA_1", 6) != 0)
+ return;
+
+ cpia_pp_register(port);
+
+ break;
+
+ default:
+ for (i = 0; i < PARPORT_MAX; ++i) {
+ if (port->number == parport_nr[i]) {
+ cpia_pp_register(port);
+ break;
+ }
+ }
+ break;
+ }
+}
+
+static struct parport_driver cpia_pp_driver = {
+ "cpia_pp",
+ cpia_pp_attach,
+ cpia_pp_detach,
+ NULL
+};
+
+int cpia_pp_init(void)
+{
+ printk(KERN_INFO "%s v%d.%d.%d\n",ABOUT,
+ CPIA_PP_MAJ_VER,CPIA_PP_MIN_VER,CPIA_PP_PATCH_VER);
+
+ if(parport_nr[0] == PPCPIA_PARPORT_OFF) {
+ printk(" disabled\n");
+ return 0;
+ }
+
+ if (parport_register_driver (&cpia_pp_driver)) {
+ LOG ("unable to register with parport\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+ if (parport[0]) {
+ /* The user gave some parameters. Let's see what they were. */
+ if (!strncmp(parport[0], "auto", 4)) {
+ parport_nr[0] = PPCPIA_PARPORT_AUTO;
+ } else {
+ int n;
+ for (n = 0; n < PARPORT_MAX && parport[n]; n++) {
+ if (!strncmp(parport[n], "none", 4)) {
+ parport_nr[n] = PPCPIA_PARPORT_NONE;
+ } else {
+ char *ep;
+ unsigned long r = simple_strtoul(parport[n], &ep, 0);
+ if (ep != parport[n]) {
+ parport_nr[n] = r;
+ } else {
+ LOG("bad port specifier `%s'\n", parport[n]);
+ return -ENODEV;
+ }
+ }
+ }
+ }
+ }
+#if defined(CONFIG_KMOD) && defined(CONFIG_PNP_PARPORT_MODULE)
+ if(parport_enumerate() && !parport_enumerate()->probe_info.model) {
+ request_module("parport_probe");
+ }
+#endif
+ return cpia_pp_init();
+}
+
+void cleanup_module(void)
+{
+ parport_unregister_driver (&cpia_pp_driver);
+ return;
+}
+
+#else /* !MODULE */
+
+static int __init cpia_pp_setup(char *str)
+{
+#if 0
+ /* Is this only a 2.2ism? -jerdfelt */
+ if (!str) {
+ if (ints[0] == 0 || ints[1] == 0) {
+ /* disable driver on "cpia_pp=" or "cpia_pp=0" */
+ parport_nr[0] = PPCPIA_PARPORT_OFF;
+ }
+ } else
+#endif
+ if (!strncmp(str, "parport", 7)) {
+ int n = simple_strtoul(str + 7, NULL, 10);
+ if (parport_ptr < PARPORT_MAX) {
+ parport_nr[parport_ptr++] = n;
+ } else {
+ LOG("too many ports, %s ignored.\n", str);
+ }
+ } else if (!strcmp(str, "auto")) {
+ parport_nr[0] = PPCPIA_PARPORT_AUTO;
+ } else if (!strcmp(str, "none")) {
+ parport_nr[parport_ptr++] = PPCPIA_PARPORT_NONE;
+ }
+
+ return 0;
+}
+
+__setup("cpia_pp=", cpia_pp_setup);
+
+#endif /* !MODULE */
diff --git a/drivers/media/video/cpia_usb.c b/drivers/media/video/cpia_usb.c
new file mode 100644
index 000000000..6b67fbc81
--- /dev/null
+++ b/drivers/media/video/cpia_usb.c
@@ -0,0 +1,626 @@
+/*
+ * cpia_usb CPiA USB driver
+ *
+ * Supports CPiA based parallel port Video Camera's.
+ *
+ * Copyright (C) 1999 Jochen Scharrlach <Jochen.Scharrlach@schwaben.de>
+ * Copyright (C) 1999, 2000 Johannes Erdfelt <jerdfelt@valinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/usb.h>
+
+#include "cpia.h"
+
+#define USB_REQ_CPIA_GRAB_FRAME 0xC1
+#define USB_REQ_CPIA_UPLOAD_FRAME 0xC2
+#define WAIT_FOR_NEXT_FRAME 0
+#define FORCE_FRAME_UPLOAD 1
+
+#define FRAMES_PER_DESC 10
+#define FRAME_SIZE_PER_DESC 960 /* Shouldn't be hardcoded */
+#define CPIA_NUMSBUF 2
+#define STREAM_BUF_SIZE (PAGE_SIZE * 4)
+#define SCRATCH_BUF_SIZE (STREAM_BUF_SIZE * 2)
+
+struct cpia_sbuf {
+ char *data;
+ urb_t *urb;
+};
+
+#define FRAMEBUF_LEN (CPIA_MAX_FRAME_SIZE+100)
+enum framebuf_status {
+ FRAME_EMPTY,
+ FRAME_READING,
+ FRAME_READY,
+ FRAME_ERROR,
+};
+
+struct framebuf {
+ int length;
+ enum framebuf_status status;
+ u8 data[FRAMEBUF_LEN];
+ struct framebuf *next;
+};
+
+struct usb_cpia {
+ /* Device structure */
+ struct usb_device *dev;
+
+ unsigned char iface;
+ wait_queue_head_t wq_stream;
+
+ int cursbuf; /* Current receiving sbuf */
+ struct cpia_sbuf sbuf[CPIA_NUMSBUF]; /* Double buffering */
+
+ int streaming;
+ int open;
+ int present;
+ struct framebuf *buffers[3];
+ struct framebuf *curbuff, *workbuff;
+};
+
+static int cpia_usb_open(void *privdata);
+static int cpia_usb_registerCallback(void *privdata, void (*cb) (void *cbdata),
+ void *cbdata);
+static int cpia_usb_transferCmd(void *privdata, u8 *command, u8 *data);
+static int cpia_usb_streamStart(void *privdata);
+static int cpia_usb_streamStop(void *privdata);
+static int cpia_usb_streamRead(void *privdata, u8 *frame, int noblock);
+static int cpia_usb_close(void *privdata);
+
+#define ABOUT "USB driver for Vision CPiA based cameras"
+
+static struct cpia_camera_ops cpia_usb_ops = {
+ cpia_usb_open,
+ cpia_usb_registerCallback,
+ cpia_usb_transferCmd,
+ cpia_usb_streamStart,
+ cpia_usb_streamStop,
+ cpia_usb_streamRead,
+ cpia_usb_close,
+ 0
+};
+
+static struct cam_data *cam_list;
+
+static void cpia_usb_complete(struct urb *urb)
+{
+ int i;
+ char *cdata;
+ struct usb_cpia *ucpia;
+
+ if (!urb || !urb->context)
+ return;
+
+ ucpia = (struct usb_cpia *) urb->context;
+
+ if (!ucpia->dev || !ucpia->streaming || !ucpia->present || !ucpia->open)
+ return;
+
+ if (ucpia->workbuff->status == FRAME_EMPTY) {
+ ucpia->workbuff->status = FRAME_READING;
+ ucpia->workbuff->length = 0;
+ }
+
+ for (i = 0; i < urb->number_of_packets; i++) {
+ int n = urb->iso_frame_desc[i].actual_length;
+ int st = urb->iso_frame_desc[i].status;
+
+ cdata = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+ if (st)
+ printk(KERN_DEBUG "cpia data error: [%d] len=%d, status=%X\n", i, n, st);
+
+ if (FRAMEBUF_LEN < ucpia->workbuff->length + n) {
+ printk(KERN_DEBUG "cpia: scratch buf overflow!scr_len: %d, n: %d\n", ucpia->workbuff->length, n);
+ return;
+ }
+
+ if (n) {
+ if ((ucpia->workbuff->length > 0) ||
+ (0x19 == cdata[0] && 0x68 == cdata[1])) {
+ memcpy(ucpia->workbuff->data + ucpia->workbuff->length, cdata, n);
+ ucpia->workbuff->length += n;
+ } else
+ DBG("Ignoring packet!\n");
+ } else {
+ if (ucpia->workbuff->length > 4 &&
+ 0xff == ucpia->workbuff->data[ucpia->workbuff->length-1] &&
+ 0xff == ucpia->workbuff->data[ucpia->workbuff->length-2] &&
+ 0xff == ucpia->workbuff->data[ucpia->workbuff->length-3] &&
+ 0xff == ucpia->workbuff->data[ucpia->workbuff->length-4]) {
+ ucpia->workbuff->status = FRAME_READY;
+ ucpia->curbuff = ucpia->workbuff;
+ ucpia->workbuff = ucpia->workbuff->next;
+ ucpia->workbuff->status = FRAME_EMPTY;
+ ucpia->workbuff->length = 0;
+
+ if (waitqueue_active(&ucpia->wq_stream))
+ wake_up_interruptible(&ucpia->wq_stream);
+ }
+ }
+ }
+}
+
+static int cpia_usb_open(void *privdata)
+{
+ struct usb_cpia *ucpia = (struct usb_cpia *) privdata;
+ urb_t *urb;
+ int ret, retval = 0, fx, err;
+
+ if (!ucpia)
+ return -EINVAL;
+
+ ucpia->sbuf[0].data = kmalloc(FRAMES_PER_DESC * FRAME_SIZE_PER_DESC, GFP_KERNEL);
+ if (!ucpia->sbuf[0].data)
+ return -EINVAL;
+
+ ucpia->sbuf[1].data = kmalloc(FRAMES_PER_DESC * FRAME_SIZE_PER_DESC, GFP_KERNEL);
+ if (!ucpia->sbuf[1].data) {
+ retval = -EINVAL;
+ goto error_0;
+ }
+
+ ret = usb_set_interface(ucpia->dev, ucpia->iface, 3);
+ if (ret < 0) {
+ printk(KERN_ERR "cpia_usb_open: usb_set_interface error (ret = %d)\n", ret);
+ retval = -EBUSY;
+ goto error_all;
+ }
+
+ ucpia->buffers[0]->status = FRAME_EMPTY;
+ ucpia->buffers[0]->length = 0;
+ ucpia->buffers[1]->status = FRAME_EMPTY;
+ ucpia->buffers[1]->length = 0;
+ ucpia->buffers[2]->status = FRAME_EMPTY;
+ ucpia->buffers[2]->length = 0;
+ ucpia->curbuff = ucpia->buffers[0];
+ ucpia->workbuff = ucpia->buffers[1];
+
+ /* We double buffer the Iso lists */
+ urb = usb_alloc_urb(FRAMES_PER_DESC);
+ if (!urb) {
+ printk(KERN_ERR "cpia_init_isoc: usb_alloc_urb 0\n");
+ retval = -ENOMEM;
+ goto error_all;
+ }
+
+ ucpia->sbuf[0].urb = urb;
+ urb->dev = ucpia->dev;
+ urb->context = ucpia;
+ urb->pipe = usb_rcvisocpipe(ucpia->dev, 1);
+ urb->transfer_flags = USB_ISO_ASAP;
+ urb->transfer_buffer = ucpia->sbuf[0].data;
+ urb->complete = cpia_usb_complete;
+ urb->number_of_packets = FRAMES_PER_DESC;
+ urb->transfer_buffer_length = FRAME_SIZE_PER_DESC * FRAMES_PER_DESC;
+ for (fx = 0; fx < FRAMES_PER_DESC; fx++) {
+ urb->iso_frame_desc[fx].offset = FRAME_SIZE_PER_DESC * fx;
+ urb->iso_frame_desc[fx].length = FRAME_SIZE_PER_DESC;
+ }
+
+ urb = usb_alloc_urb(FRAMES_PER_DESC);
+ if (!urb) {
+ printk(KERN_ERR "cpia_init_isoc: usb_alloc_urb 0\n");
+ retval = -ENOMEM;
+ goto error_all;
+ }
+
+ ucpia->sbuf[1].urb = urb;
+ urb->dev = ucpia->dev;
+ urb->context = ucpia;
+ urb->pipe = usb_rcvisocpipe(ucpia->dev, 1);
+ urb->transfer_flags = USB_ISO_ASAP;
+ urb->transfer_buffer = ucpia->sbuf[1].data;
+ urb->complete = cpia_usb_complete;
+ urb->number_of_packets = FRAMES_PER_DESC;
+ urb->transfer_buffer_length = FRAME_SIZE_PER_DESC * FRAMES_PER_DESC;
+ for (fx = 0; fx < FRAMES_PER_DESC; fx++) {
+ urb->iso_frame_desc[fx].offset = FRAME_SIZE_PER_DESC * fx;
+ urb->iso_frame_desc[fx].length = FRAME_SIZE_PER_DESC;
+ }
+
+ ucpia->sbuf[1].urb->next = ucpia->sbuf[0].urb;
+ ucpia->sbuf[0].urb->next = ucpia->sbuf[1].urb;
+
+ err = usb_submit_urb(ucpia->sbuf[0].urb);
+ if (err)
+ printk(KERN_ERR "cpia_init_isoc: usb_submit_urb 0 ret %d\n",
+ err);
+ err = usb_submit_urb(ucpia->sbuf[1].urb);
+ if (err)
+ printk(KERN_ERR "cpia_init_isoc: usb_submit_urb 1 ret %d\n",
+ err);
+
+ ucpia->streaming = 1;
+ ucpia->open = 1;
+
+ return 0;
+
+error_all:
+ kfree (ucpia->sbuf[1].data);
+error_0:
+ kfree (ucpia->sbuf[0].data);
+
+ return retval;
+}
+
+//
+// convenience functions
+//
+
+/****************************************************************************
+ *
+ * WritePacket
+ *
+ ***************************************************************************/
+static int WritePacket(struct usb_device *udev, const u8 *packet, u8 *buf, size_t size)
+{
+ if (!packet)
+ return -EINVAL;
+
+ return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ packet[1] + (packet[0] << 8),
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ packet[2] + (packet[3] << 8),
+ packet[4] + (packet[5] << 8), buf, size, HZ);
+}
+
+/****************************************************************************
+ *
+ * ReadPacket
+ *
+ ***************************************************************************/
+static int ReadPacket(struct usb_device *udev, u8 *packet, u8 *buf, size_t size)
+{
+ if (!packet || size <= 0)
+ return -EINVAL;
+
+ return usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ packet[1] + (packet[0] << 8),
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ packet[2] + (packet[3] << 8),
+ packet[4] + (packet[5] << 8), buf, size, HZ);
+}
+
+static int cpia_usb_transferCmd(void *privdata, u8 *command, u8 *data)
+{
+ int err = 0;
+ int databytes;
+ struct usb_cpia *ucpia = (struct usb_cpia *)privdata;
+ struct usb_device *udev = ucpia->dev;
+
+ if (!udev) {
+ DBG("Internal driver error: udev is NULL\n");
+ return -EINVAL;
+ }
+
+ if (!command) {
+ DBG("Internal driver error: command is NULL\n");
+ return -EINVAL;
+ }
+
+ databytes = (((int)command[7])<<8) | command[6];
+
+ if (command[0] == DATA_IN) {
+ u8 buffer[8];
+
+ if (!data) {
+ DBG("Internal driver error: data is NULL\n");
+ return -EINVAL;
+ }
+
+ err = ReadPacket(udev, command, buffer, 8);
+ if (err < 0)
+ return err;
+
+ memcpy(data, buffer, databytes);
+ } else if(command[0] == DATA_OUT)
+ WritePacket(udev, command, data, databytes);
+ else {
+ DBG("Unexpected first byte of command: %x\n", command[0]);
+ err = -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cpia_usb_registerCallback(void *privdata, void (*cb) (void *cbdata),
+ void *cbdata)
+{
+ return -ENODEV;
+}
+
+static int cpia_usb_streamStart(void *privdata)
+{
+ return -ENODEV;
+}
+
+static int cpia_usb_streamStop(void *privdata)
+{
+ return -ENODEV;
+}
+
+static int cpia_usb_streamRead(void *privdata, u8 *frame, int noblock)
+{
+ struct usb_cpia *ucpia = (struct usb_cpia *) privdata;
+ struct framebuf *mybuff;
+
+ if (!ucpia || !ucpia->present)
+ return -1;
+
+ if (ucpia->curbuff->status != FRAME_READY)
+ interruptible_sleep_on(&ucpia->wq_stream);
+ else
+ DBG("Frame already waiting!\n");
+
+ mybuff = ucpia->curbuff;
+
+ if (!mybuff)
+ return -1;
+
+ if (mybuff->status != FRAME_READY || mybuff->length < 4) {
+ DBG("Something went wrong!\n");
+ return -1;
+ }
+
+ memcpy(frame, mybuff->data, mybuff->length);
+ mybuff->status = FRAME_EMPTY;
+
+/* DBG("read done, %d bytes, Header: %x/%x, Footer: %x%x%x%x\n", */
+/* mybuff->length, frame[0], frame[1], */
+/* frame[mybuff->length-4], frame[mybuff->length-3], */
+/* frame[mybuff->length-2], frame[mybuff->length-1]); */
+
+ return mybuff->length;
+}
+
+static void cpia_usb_free_resources(struct usb_cpia *ucpia, int try)
+{
+ if (!ucpia->streaming)
+ return;
+
+ ucpia->streaming = 0;
+
+ /* Set packet size to 0 */
+ if (try) {
+ int ret;
+
+ ret = usb_set_interface(ucpia->dev, ucpia->iface, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "usb_set_interface error (ret = %d)\n", ret);
+ return;
+ }
+ }
+
+ /* Unschedule all of the iso td's */
+ if (ucpia->sbuf[1].urb) {
+ usb_unlink_urb(ucpia->sbuf[1].urb);
+ usb_free_urb(ucpia->sbuf[1].urb);
+ ucpia->sbuf[1].urb = NULL;
+ }
+
+ if (ucpia->sbuf[0].urb) {
+ usb_unlink_urb(ucpia->sbuf[0].urb);
+ usb_free_urb(ucpia->sbuf[0].urb);
+ ucpia->sbuf[0].urb = NULL;
+ }
+}
+
+static int cpia_usb_close(void *privdata)
+{
+ struct usb_cpia *ucpia = (struct usb_cpia *) privdata;
+
+ ucpia->open = 0;
+
+ cpia_usb_free_resources(ucpia, 1);
+
+ if (!ucpia->present)
+ kfree(ucpia);
+
+ return 0;
+}
+
+int cpia_usb_init(void)
+{
+ /* return -ENODEV; */
+ return 0;
+}
+
+/* Probing and initializing */
+
+static void *cpia_probe(struct usb_device *udev, unsigned int ifnum)
+{
+ struct usb_interface_descriptor *interface;
+ struct usb_cpia *ucpia;
+ struct cam_data *cam;
+ int ret;
+
+ /* A multi-config CPiA camera? */
+ if (udev->descriptor.bNumConfigurations != 1)
+ return NULL;
+
+ interface = &udev->actconfig->interface[ifnum].altsetting[0];
+
+ /* Is it a CPiA? */
+ if (udev->descriptor.idVendor != 0x0553)
+ return NULL;
+ if (udev->descriptor.idProduct != 0x0002)
+ return NULL;
+
+ /* We found a CPiA */
+ printk(KERN_INFO "USB CPiA camera found\n");
+
+ ucpia = kmalloc(sizeof(*ucpia), GFP_KERNEL);
+ if (!ucpia) {
+ printk(KERN_ERR "couldn't kmalloc cpia struct\n");
+ return NULL;
+ }
+
+ memset(ucpia, 0, sizeof(*ucpia));
+
+ ucpia->dev = udev;
+ ucpia->iface = interface->bInterfaceNumber;
+ init_waitqueue_head(&ucpia->wq_stream);
+
+ ucpia->buffers[0] = vmalloc(sizeof(*ucpia->buffers[0]));
+ if (!ucpia->buffers[0]) {
+ printk(KERN_ERR "couldn't vmalloc frame buffer 0\n");
+ goto fail_alloc_0;
+ }
+
+ ucpia->buffers[1] = vmalloc(sizeof(*ucpia->buffers[1]));
+ if (!ucpia->buffers[1]) {
+ printk(KERN_ERR "couldn't vmalloc frame buffer 1\n");
+ goto fail_alloc_1;
+ }
+
+ ucpia->buffers[2] = vmalloc(sizeof(*ucpia->buffers[2]));
+ if (!ucpia->buffers[2]) {
+ printk(KERN_ERR "couldn't vmalloc frame buffer 2\n");
+ goto fail_alloc_2;
+ }
+
+ ucpia->buffers[0]->next = ucpia->buffers[1];
+ ucpia->buffers[1]->next = ucpia->buffers[2];
+ ucpia->buffers[2]->next = ucpia->buffers[0];
+
+ ret = usb_set_interface(udev, ucpia->iface, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "cpia_probe: usb_set_interface error (ret = %d)\n", ret);
+ /* goto fail_all; */
+ }
+
+ /* Before register_camera, important */
+ ucpia->present = 1;
+
+ cam = cpia_register_camera(&cpia_usb_ops, ucpia);
+ if (!cam) {
+ LOG("failed to cpia_register_camera\n");
+ goto fail_all;
+ }
+
+ ADD_TO_LIST(cam_list, cam);
+
+ return cam;
+
+fail_all:
+ vfree(ucpia->buffers[2]);
+ ucpia->buffers[2] = NULL;
+fail_alloc_2:
+ vfree(ucpia->buffers[1]);
+ ucpia->buffers[1] = NULL;
+fail_alloc_1:
+ vfree(ucpia->buffers[0]);
+ ucpia->buffers[0] = NULL;
+fail_alloc_0:
+
+ return NULL;
+}
+
+static void cpia_disconnect(struct usb_device *dev, void *ptr);
+
+static struct usb_driver cpia_driver = {
+ "cpia",
+ cpia_probe,
+ cpia_disconnect,
+ { NULL, NULL }
+};
+
+/* don't use dev, it may be NULL! (see usb_cpia_cleanup) */
+/* _disconnect from usb_cpia_cleanup is not necessary since usb_deregister */
+/* will do it for us as well as passing a udev structure - jerdfelt */
+static void cpia_disconnect(struct usb_device *udev, void *ptr)
+{
+ struct cam_data *cam = (struct cam_data *) ptr;
+ struct usb_cpia *ucpia = (struct usb_cpia *) cam->lowlevel_data;
+
+ REMOVE_FROM_LIST(cam);
+
+ /* Don't even try to reset the altsetting if we're disconnected */
+ cpia_usb_free_resources(ucpia, 0);
+
+ ucpia->present = 0;
+
+ cpia_unregister_camera(cam);
+
+ ucpia->curbuff->status = FRAME_ERROR;
+
+ if (waitqueue_active(&ucpia->wq_stream))
+ wake_up_interruptible(&ucpia->wq_stream);
+
+ usb_driver_release_interface(&cpia_driver,
+ &udev->actconfig->interface[0]);
+
+ ucpia->curbuff = ucpia->workbuff = NULL;
+
+ if (ucpia->buffers[2]) {
+ vfree(ucpia->buffers[2]);
+ ucpia->buffers[2] = NULL;
+ }
+
+ if (ucpia->buffers[1]) {
+ vfree(ucpia->buffers[1]);
+ ucpia->buffers[1] = NULL;
+ }
+
+ if (ucpia->buffers[0]) {
+ vfree(ucpia->buffers[0]);
+ ucpia->buffers[0] = NULL;
+ }
+
+ if (!ucpia->open)
+ kfree(ucpia);
+}
+
+int usb_cpia_init(void)
+{
+ cam_list = NULL;
+
+ return usb_register(&cpia_driver);
+}
+
+void usb_cpia_cleanup(void)
+{
+/*
+ struct cam_data *cam;
+
+ while ((cam = cam_list) != NULL)
+ cpia_disconnect(NULL, cam);
+*/
+
+ usb_deregister(&cpia_driver);
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+ return usb_cpia_init();
+}
+
+void cleanup_module(void)
+{
+ usb_cpia_cleanup();
+}
+#endif /* !MODULE */
diff --git a/drivers/media/video/cs8420.h b/drivers/media/video/cs8420.h
new file mode 100644
index 000000000..2b22f3a38
--- /dev/null
+++ b/drivers/media/video/cs8420.h
@@ -0,0 +1,50 @@
+/* cs8420.h - cs8420 initializations
+ Copyright (C) 1999 Nathan Laredo (laredo@gnu.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+#ifndef __CS8420_H__
+#define __CS8420_H__
+
+/* Initialization Sequence */
+
+static __u8 init8420[] = {
+ 1, 0x01, 2, 0x02, 3, 0x00, 4, 0x46,
+ 5, 0x24, 6, 0x84, 18, 0x18, 19, 0x13,
+};
+
+#define INIT8420LEN (sizeof(init8420)/2)
+
+static __u8 mode8420pro[] = { /* professional output mode */
+ 32, 0xa1, 33, 0x00, 34, 0x00, 35, 0x00,
+ 36, 0x00, 37, 0x00, 38, 0x00, 39, 0x00,
+ 40, 0x00, 41, 0x00, 42, 0x00, 43, 0x00,
+ 44, 0x00, 45, 0x00, 46, 0x00, 47, 0x00,
+ 48, 0x00, 49, 0x00, 50, 0x00, 51, 0x00,
+ 52, 0x00, 53, 0x00, 54, 0x00, 55, 0x00,
+};
+#define MODE8420LEN (sizeof(mode8420pro)/2)
+
+static __u8 mode8420con[] = { /* consumer output mode */
+ 32, 0x20, 33, 0x00, 34, 0x00, 35, 0x48,
+ 36, 0x00, 37, 0x00, 38, 0x00, 39, 0x00,
+ 40, 0x00, 41, 0x00, 42, 0x00, 43, 0x00,
+ 44, 0x00, 45, 0x00, 46, 0x00, 47, 0x00,
+ 48, 0x00, 49, 0x00, 50, 0x00, 51, 0x00,
+ 52, 0x00, 53, 0x00, 54, 0x00, 55, 0x00,
+};
+
+#endif
diff --git a/drivers/media/video/i2c-old.c b/drivers/media/video/i2c-old.c
new file mode 100644
index 000000000..c896057cd
--- /dev/null
+++ b/drivers/media/video/i2c-old.c
@@ -0,0 +1,450 @@
+/*
+ * Generic i2c interface for linux
+ *
+ * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de>
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/locks.h>
+#include <linux/sched.h>
+#include <linux/malloc.h>
+#include <linux/i2c-old.h>
+
+#define REGPRINT(x) if (verbose) (x)
+#define I2C_DEBUG(x) if (i2c_debug) (x)
+
+static int scan = 0;
+static int verbose = 0;
+static int i2c_debug = 0;
+
+#if LINUX_VERSION_CODE >= 0x020117
+MODULE_PARM(scan,"i");
+MODULE_PARM(verbose,"i");
+MODULE_PARM(i2c_debug,"i");
+#endif
+
+/* ----------------------------------------------------------------------- */
+
+static struct i2c_bus *busses[I2C_BUS_MAX];
+static struct i2c_driver *drivers[I2C_DRIVER_MAX];
+static int bus_count = 0, driver_count = 0;
+
+#ifdef CONFIG_VIDEO_BUZ
+extern int saa7111_init(void);
+extern int saa7185_init(void);
+#endif
+#ifdef CONFIG_VIDEO_LML33
+extern int bt819_init(void);
+extern int bt856_init(void);
+#endif
+
+int i2c_init(void)
+{
+ printk(KERN_INFO "i2c: initialized%s\n",
+ scan ? " (i2c bus scan enabled)" : "");
+ /* anything to do here ? */
+#ifdef CONFIG_VIDEO_BUZ
+ saa7111_init();
+ saa7185_init();
+#endif
+#ifdef CONFIG_VIDEO_LML33
+ bt819_init();
+ bt856_init();
+#endif
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static void i2c_attach_device(struct i2c_bus *bus, struct i2c_driver *driver)
+{
+ struct i2c_device *device;
+ int i,j,ack=1;
+ unsigned char addr;
+ LOCK_FLAGS;
+
+ /* probe for device */
+ LOCK_I2C_BUS(bus);
+ for (addr = driver->addr_l; addr <= driver->addr_h; addr += 2)
+ {
+ i2c_start(bus);
+ ack = i2c_sendbyte(bus,addr,0);
+ i2c_stop(bus);
+ if (!ack)
+ break;
+ }
+ UNLOCK_I2C_BUS(bus);
+ if (ack)
+ return;
+
+ /* got answer */
+ for (i = 0; i < I2C_DEVICE_MAX; i++)
+ if (NULL == driver->devices[i])
+ break;
+ if (I2C_DEVICE_MAX == i)
+ return;
+
+ for (j = 0; j < I2C_DEVICE_MAX; j++)
+ if (NULL == bus->devices[j])
+ break;
+ if (I2C_DEVICE_MAX == j)
+ return;
+
+ if (NULL == (device = kmalloc(sizeof(struct i2c_device),GFP_KERNEL)))
+ return;
+ device->bus = bus;
+ device->driver = driver;
+ device->addr = addr;
+
+ /* Attach */
+
+ if (driver->attach(device)!=0)
+ {
+ kfree(device);
+ return;
+ }
+ driver->devices[i] = device;
+ driver->devcount++;
+ bus->devices[j] = device;
+ bus->devcount++;
+
+ if (bus->attach_inform)
+ bus->attach_inform(bus,driver->id);
+ REGPRINT(printk("i2c: device attached: %s (addr=0x%02x, bus=%s, driver=%s)\n",device->name,addr,bus->name,driver->name));
+}
+
+static void i2c_detach_device(struct i2c_device *device)
+{
+ int i;
+
+ if (device->bus->detach_inform)
+ device->bus->detach_inform(device->bus,device->driver->id);
+ device->driver->detach(device);
+
+ for (i = 0; i < I2C_DEVICE_MAX; i++)
+ if (device == device->driver->devices[i])
+ break;
+ if (I2C_DEVICE_MAX == i)
+ {
+ printk(KERN_WARNING "i2c: detach_device #1: device not found: %s\n",
+ device->name);
+ return;
+ }
+ device->driver->devices[i] = NULL;
+ device->driver->devcount--;
+
+ for (i = 0; i < I2C_DEVICE_MAX; i++)
+ if (device == device->bus->devices[i])
+ break;
+ if (I2C_DEVICE_MAX == i)
+ {
+ printk(KERN_WARNING "i2c: detach_device #2: device not found: %s\n",
+ device->name);
+ return;
+ }
+ device->bus->devices[i] = NULL;
+ device->bus->devcount--;
+
+ REGPRINT(printk("i2c: device detached: %s (addr=0x%02x, bus=%s, driver=%s)\n",device->name,device->addr,device->bus->name,device->driver->name));
+ kfree(device);
+}
+
+/* ----------------------------------------------------------------------- */
+
+int i2c_register_bus(struct i2c_bus *bus)
+{
+ int i,ack;
+ LOCK_FLAGS;
+
+ memset(bus->devices,0,sizeof(bus->devices));
+ bus->devcount = 0;
+
+ for (i = 0; i < I2C_BUS_MAX; i++)
+ if (NULL == busses[i])
+ break;
+ if (I2C_BUS_MAX == i)
+ return -ENOMEM;
+
+ busses[i] = bus;
+ bus_count++;
+ REGPRINT(printk("i2c: bus registered: %s\n",bus->name));
+
+ MOD_INC_USE_COUNT;
+
+ if (scan)
+ {
+ /* scan whole i2c bus */
+ LOCK_I2C_BUS(bus);
+ for (i = 0; i < 256; i+=2)
+ {
+ i2c_start(bus);
+ ack = i2c_sendbyte(bus,i,0);
+ i2c_stop(bus);
+ if (!ack)
+ {
+ printk(KERN_INFO "i2c: scanning bus %s: found device at addr=0x%02x\n",
+ bus->name,i);
+ }
+ }
+ UNLOCK_I2C_BUS(bus);
+ }
+
+ /* probe available drivers */
+ for (i = 0; i < I2C_DRIVER_MAX; i++)
+ if (drivers[i])
+ i2c_attach_device(bus,drivers[i]);
+ return 0;
+}
+
+int i2c_unregister_bus(struct i2c_bus *bus)
+{
+ int i;
+
+ /* detach devices */
+ for (i = 0; i < I2C_DEVICE_MAX; i++)
+ if (bus->devices[i])
+ i2c_detach_device(bus->devices[i]);
+
+ for (i = 0; i < I2C_BUS_MAX; i++)
+ if (bus == busses[i])
+ break;
+ if (I2C_BUS_MAX == i)
+ {
+ printk(KERN_WARNING "i2c: unregister_bus #1: bus not found: %s\n",
+ bus->name);
+ return -ENODEV;
+ }
+
+ MOD_DEC_USE_COUNT;
+
+ busses[i] = NULL;
+ bus_count--;
+ REGPRINT(printk("i2c: bus unregistered: %s\n",bus->name));
+
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+int i2c_register_driver(struct i2c_driver *driver)
+{
+ int i;
+
+ memset(driver->devices,0,sizeof(driver->devices));
+ driver->devcount = 0;
+
+ for (i = 0; i < I2C_DRIVER_MAX; i++)
+ if (NULL == drivers[i])
+ break;
+ if (I2C_DRIVER_MAX == i)
+ return -ENOMEM;
+
+ drivers[i] = driver;
+ driver_count++;
+
+ MOD_INC_USE_COUNT;
+
+ REGPRINT(printk("i2c: driver registered: %s\n",driver->name));
+
+ /* Probe available busses */
+ for (i = 0; i < I2C_BUS_MAX; i++)
+ if (busses[i])
+ i2c_attach_device(busses[i],driver);
+
+ return 0;
+}
+
+int i2c_unregister_driver(struct i2c_driver *driver)
+{
+ int i;
+
+ /* detach devices */
+ for (i = 0; i < I2C_DEVICE_MAX; i++)
+ if (driver->devices[i])
+ i2c_detach_device(driver->devices[i]);
+
+ for (i = 0; i < I2C_DRIVER_MAX; i++)
+ if (driver == drivers[i])
+ break;
+ if (I2C_DRIVER_MAX == i)
+ {
+ printk(KERN_WARNING "i2c: unregister_driver: driver not found: %s\n",
+ driver->name);
+ return -ENODEV;
+ }
+
+ MOD_DEC_USE_COUNT;
+
+ drivers[i] = NULL;
+ driver_count--;
+ REGPRINT(printk("i2c: driver unregistered: %s\n",driver->name));
+
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+int i2c_control_device(struct i2c_bus *bus, int id,
+ unsigned int cmd, void *arg)
+{
+ int i;
+
+ for (i = 0; i < I2C_DEVICE_MAX; i++)
+ if (bus->devices[i] && bus->devices[i]->driver->id == id)
+ break;
+ if (i == I2C_DEVICE_MAX)
+ return -ENODEV;
+ if (NULL == bus->devices[i]->driver->command)
+ return -ENODEV;
+ return bus->devices[i]->driver->command(bus->devices[i],cmd,arg);
+}
+
+/* ----------------------------------------------------------------------- */
+
+#define I2C_SET(bus,ctrl,data) (bus->i2c_setlines(bus,ctrl,data))
+#define I2C_GET(bus) (bus->i2c_getdataline(bus))
+
+void i2c_start(struct i2c_bus *bus)
+{
+ I2C_SET(bus,0,1);
+ I2C_SET(bus,1,1);
+ I2C_SET(bus,1,0);
+ I2C_SET(bus,0,0);
+ I2C_DEBUG(printk("%s: < ",bus->name));
+}
+
+void i2c_stop(struct i2c_bus *bus)
+{
+ I2C_SET(bus,0,0);
+ I2C_SET(bus,1,0);
+ I2C_SET(bus,1,1);
+ I2C_DEBUG(printk(">\n"));
+}
+
+void i2c_one(struct i2c_bus *bus)
+{
+ I2C_SET(bus,0,1);
+ I2C_SET(bus,1,1);
+ I2C_SET(bus,0,1);
+}
+
+void i2c_zero(struct i2c_bus *bus)
+{
+ I2C_SET(bus,0,0);
+ I2C_SET(bus,1,0);
+ I2C_SET(bus,0,0);
+}
+
+int i2c_ack(struct i2c_bus *bus)
+{
+ int ack;
+
+ I2C_SET(bus,0,1);
+ I2C_SET(bus,1,1);
+ ack = I2C_GET(bus);
+ I2C_SET(bus,0,1);
+ return ack;
+}
+
+int i2c_sendbyte(struct i2c_bus *bus,unsigned char data,int wait_for_ack)
+{
+ int i, ack;
+
+ I2C_SET(bus,0,0);
+ for (i=7; i>=0; i--)
+ (data&(1<<i)) ? i2c_one(bus) : i2c_zero(bus);
+ if (wait_for_ack)
+ udelay(wait_for_ack);
+ ack=i2c_ack(bus);
+ I2C_DEBUG(printk("%02x%c ",(int)data,ack?'-':'+'));
+ return ack;
+}
+
+unsigned char i2c_readbyte(struct i2c_bus *bus,int last)
+{
+ int i;
+ unsigned char data=0;
+
+ I2C_SET(bus,0,1);
+ for (i=7; i>=0; i--)
+ {
+ I2C_SET(bus,1,1);
+ if (I2C_GET(bus))
+ data |= (1<<i);
+ I2C_SET(bus,0,1);
+ }
+ last ? i2c_one(bus) : i2c_zero(bus);
+ I2C_DEBUG(printk("=%02x%c ",(int)data,last?'-':'+'));
+ return data;
+}
+
+/* ----------------------------------------------------------------------- */
+
+int i2c_read(struct i2c_bus *bus, unsigned char addr)
+{
+ int ret;
+
+ if (bus->i2c_read)
+ return bus->i2c_read(bus, addr);
+
+ i2c_start(bus);
+ i2c_sendbyte(bus,addr,0);
+ ret = i2c_readbyte(bus,1);
+ i2c_stop(bus);
+ return ret;
+}
+
+int i2c_write(struct i2c_bus *bus, unsigned char addr,
+ unsigned char data1, unsigned char data2, int both)
+{
+ int ack;
+
+ if (bus->i2c_write)
+ return bus->i2c_write(bus, addr, data1, data2, both);
+
+ i2c_start(bus);
+ i2c_sendbyte(bus,addr,0);
+ ack = i2c_sendbyte(bus,data1,0);
+ if (both)
+ ack = i2c_sendbyte(bus,data2,0);
+ i2c_stop(bus);
+ return ack ? -1 : 0 ;
+}
+
+/* ----------------------------------------------------------------------- */
+
+#ifdef MODULE
+
+#if LINUX_VERSION_CODE >= 0x020100
+EXPORT_SYMBOL(i2c_register_bus);
+EXPORT_SYMBOL(i2c_unregister_bus);
+EXPORT_SYMBOL(i2c_register_driver);
+EXPORT_SYMBOL(i2c_unregister_driver);
+EXPORT_SYMBOL(i2c_control_device);
+EXPORT_SYMBOL(i2c_start);
+EXPORT_SYMBOL(i2c_stop);
+EXPORT_SYMBOL(i2c_one);
+EXPORT_SYMBOL(i2c_zero);
+EXPORT_SYMBOL(i2c_ack);
+EXPORT_SYMBOL(i2c_sendbyte);
+EXPORT_SYMBOL(i2c_readbyte);
+EXPORT_SYMBOL(i2c_read);
+EXPORT_SYMBOL(i2c_write);
+#endif
+
+int init_module(void)
+{
+ return i2c_init();
+}
+
+void cleanup_module(void)
+{
+}
+#endif
diff --git a/drivers/media/video/i2c-parport.c b/drivers/media/video/i2c-parport.c
new file mode 100644
index 000000000..00b574f60
--- /dev/null
+++ b/drivers/media/video/i2c-parport.c
@@ -0,0 +1,147 @@
+/*
+ * I2C driver for parallel port
+ *
+ * Author: Phil Blundell <philb@gnu.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * This driver implements a simple I2C protocol by bit-twiddling some
+ * signals on the parallel port. Since the outputs on the parallel port
+ * aren't open collector, three lines rather than two are used:
+ *
+ * D0 clock out
+ * D1 data out
+ * BUSY data in
+ */
+
+#include <linux/parport.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/i2c-old.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+
+#define I2C_DELAY 10
+
+static int debug = 0;
+
+struct parport_i2c_bus
+{
+ struct i2c_bus i2c;
+ struct parport_i2c_bus *next;
+};
+
+static struct parport_i2c_bus *bus_list;
+
+static spinlock_t bus_list_lock = SPIN_LOCK_UNLOCKED;
+
+/* software I2C functions */
+
+static void i2c_setlines(struct i2c_bus *bus, int clk, int data)
+{
+ struct parport *p = bus->data;
+ parport_write_data(p, (clk?1:0) | (data?2:0));
+ udelay(I2C_DELAY);
+}
+
+static int i2c_getdataline(struct i2c_bus *bus)
+{
+ struct parport *p = bus->data;
+ return (parport_read_status(p) & PARPORT_STATUS_BUSY) ? 0 : 1;
+}
+
+static struct i2c_bus parport_i2c_bus_template =
+{
+ "...",
+ I2C_BUSID_PARPORT,
+ NULL,
+
+ SPIN_LOCK_UNLOCKED,
+
+ NULL,
+ NULL,
+
+ i2c_setlines,
+ i2c_getdataline,
+ NULL,
+ NULL,
+};
+
+static void i2c_parport_attach(struct parport *port)
+{
+ struct parport_i2c_bus *b = kmalloc(sizeof(struct parport_i2c_bus),
+ GFP_KERNEL);
+ b->i2c = parport_i2c_bus_template;
+ b->i2c.data = parport_get_port (port);
+ strncpy(b->i2c.name, port->name, 32);
+ spin_lock(&bus_list_lock);
+ b->next = bus_list;
+ bus_list = b;
+ spin_unlock(&bus_list_lock);
+ i2c_register_bus(&b->i2c);
+ if (debug)
+ printk(KERN_DEBUG "i2c: attached to %s\n", port->name);
+}
+
+static void i2c_parport_detach(struct parport *port)
+{
+ struct parport_i2c_bus *b, *old_b = NULL;
+ spin_lock(&bus_list_lock);
+ b = bus_list;
+ while (b)
+ {
+ if (b->i2c.data == port)
+ {
+ if (old_b)
+ old_b->next = b->next;
+ else
+ bus_list = b->next;
+ i2c_unregister_bus(&b->i2c);
+ kfree(b);
+ break;
+ }
+ old_b = b;
+ b = b->next;
+ }
+ spin_unlock(&bus_list_lock);
+ if (debug)
+ printk(KERN_DEBUG "i2c: detached from %s\n", port->name);
+}
+
+static struct parport_driver parport_i2c_driver =
+{
+ "i2c",
+ i2c_parport_attach,
+ i2c_parport_detach
+};
+
+#ifdef MODULE
+int init_module(void)
+#else
+int __init i2c_parport_init(void)
+#endif
+{
+ printk("I2C: driver for parallel port v0.1 philb@gnu.org\n");
+ parport_register_driver(&parport_i2c_driver);
+ return 0;
+}
+
+#ifdef MODULE
+MODULE_PARM(debug, "i");
+
+void cleanup_module(void)
+{
+ struct parport_i2c_bus *b = bus_list;
+ while (b)
+ {
+ struct parport_i2c_bus *next = b->next;
+ i2c_unregister_bus(&b->i2c);
+ kfree(b);
+ b = next;
+ }
+ parport_unregister_driver(&parport_i2c_driver);
+}
+#endif
diff --git a/drivers/media/video/ibmmpeg2.h b/drivers/media/video/ibmmpeg2.h
new file mode 100644
index 000000000..68e10387c
--- /dev/null
+++ b/drivers/media/video/ibmmpeg2.h
@@ -0,0 +1,94 @@
+/* ibmmpeg2.h - IBM MPEGCD21 definitions */
+
+#ifndef __IBM_MPEG2__
+#define __IBM_MPEG2__
+
+/* Define all MPEG Decoder registers */
+/* Chip Control and Status */
+#define IBM_MP2_CHIP_CONTROL 0x200*2
+#define IBM_MP2_CHIP_MODE 0x201*2
+/* Timer Control and Status */
+#define IBM_MP2_SYNC_STC2 0x202*2
+#define IBM_MP2_SYNC_STC1 0x203*2
+#define IBM_MP2_SYNC_STC0 0x204*2
+#define IBM_MP2_SYNC_PTS2 0x205*2
+#define IBM_MP2_SYNC_PTS1 0x206*2
+#define IBM_MP2_SYNC_PTS0 0x207*2
+/* Video FIFO Control */
+#define IBM_MP2_FIFO 0x208*2
+#define IBM_MP2_FIFOW 0x100*2
+#define IBM_MP2_FIFO_STAT 0x209*2
+#define IBM_MP2_RB_THRESHOLD 0x22b*2
+/* Command buffer */
+#define IBM_MP2_COMMAND 0x20a*2
+#define IBM_MP2_CMD_DATA 0x20b*2
+#define IBM_MP2_CMD_STAT 0x20c*2
+#define IBM_MP2_CMD_ADDR 0x20d*2
+/* Internal Processor Control and Status */
+#define IBM_MP2_PROC_IADDR 0x20e*2
+#define IBM_MP2_PROC_IDATA 0x20f*2
+#define IBM_MP2_WR_PROT 0x235*2
+/* DRAM Access */
+#define IBM_MP2_DRAM_ADDR 0x210*2
+#define IBM_MP2_DRAM_DATA 0x212*2
+#define IBM_MP2_DRAM_CMD_STAT 0x213*2
+#define IBM_MP2_BLOCK_SIZE 0x23b*2
+#define IBM_MP2_SRC_ADDR 0x23c*2
+/* Onscreen Display */
+#define IBM_MP2_OSD_ADDR 0x214*2
+#define IBM_MP2_OSD_DATA 0x215*2
+#define IBM_MP2_OSD_MODE 0x217*2
+#define IBM_MP2_OSD_LINK_ADDR 0x229*2
+#define IBM_MP2_OSD_SIZE 0x22a*2
+/* Interrupt Control */
+#define IBM_MP2_HOST_INT 0x218*2
+#define IBM_MP2_MASK0 0x219*2
+#define IBM_MP2_HOST_INT1 0x23e*2
+#define IBM_MP2_MASK1 0x23f*2
+/* Audio Control */
+#define IBM_MP2_AUD_IADDR 0x21a*2
+#define IBM_MP2_AUD_IDATA 0x21b*2
+#define IBM_MP2_AUD_FIFO 0x21c*2
+#define IBM_MP2_AUD_FIFOW 0x101*2
+#define IBM_MP2_AUD_CTL 0x21d*2
+#define IBM_MP2_BEEP_CTL 0x21e*2
+#define IBM_MP2_FRNT_ATTEN 0x22d*2
+/* Display Control */
+#define IBM_MP2_DISP_MODE 0x220*2
+#define IBM_MP2_DISP_DLY 0x221*2
+#define IBM_MP2_VBI_CTL 0x222*2
+#define IBM_MP2_DISP_LBOR 0x223*2
+#define IBM_MP2_DISP_TBOR 0x224*2
+/* Polarity Control */
+#define IBM_MP2_INFC_CTL 0x22c*2
+
+/* control commands */
+#define IBM_MP2_PLAY 0
+#define IBM_MP2_PAUSE 1
+#define IBM_MP2_SINGLE_FRAME 2
+#define IBM_MP2_FAST_FORWARD 3
+#define IBM_MP2_SLOW_MOTION 4
+#define IBM_MP2_IMED_NORM_PLAY 5
+#define IBM_MP2_RESET_WINDOW 6
+#define IBM_MP2_FREEZE_FRAME 7
+#define IBM_MP2_RESET_VID_RATE 8
+#define IBM_MP2_CONFIG_DECODER 9
+#define IBM_MP2_CHANNEL_SWITCH 10
+#define IBM_MP2_RESET_AUD_RATE 11
+#define IBM_MP2_PRE_OP_CHN_SW 12
+#define IBM_MP2_SET_STILL_MODE 14
+
+/* Define Xilinx FPGA Internal Registers */
+
+/* general control register 0 */
+#define XILINX_CTL0 0x600
+/* genlock delay resister 1 */
+#define XILINX_GLDELAY 0x602
+/* send 16 bits to CS3310 port */
+#define XILINX_CS3310 0x604
+/* send 16 bits to CS3310 and complete */
+#define XILINX_CS3310_CMPLT 0x60c
+/* pulse width modulator control */
+#define XILINX_PWM 0x606
+
+#endif
diff --git a/drivers/media/video/msp3400.c b/drivers/media/video/msp3400.c
new file mode 100644
index 000000000..2d2b1e89c
--- /dev/null
+++ b/drivers/media/video/msp3400.c
@@ -0,0 +1,1454 @@
+/*
+ * programming the msp34* sound processor family
+ *
+ * (c) 1997-2000 Gerd Knorr <kraxel@goldbach.in-berlin.de>
+ *
+ * what works and what doesn't:
+ *
+ * AM-Mono
+ * Support for Hauppauge cards added (decoding handled by tuner) added by
+ * Frederic Crozat <fcrozat@mail.dotcom.fr>
+ *
+ * FM-Mono
+ * should work. The stereo modes are backward compatible to FM-mono,
+ * therefore FM-Mono should be allways available.
+ *
+ * FM-Stereo (B/G, used in germany)
+ * should work, with autodetect
+ *
+ * FM-Stereo (satellite)
+ * should work, no autodetect (i.e. default is mono, but you can
+ * switch to stereo -- untested)
+ *
+ * NICAM (B/G, L , used in UK, Scandinavia, Spain and France)
+ * should work, with autodetect. Support for NICAM was added by
+ * Pekka Pietikainen <pp@netppl.fi>
+ *
+ *
+ * TODO:
+ * - better SAT support
+ *
+ *
+ * 980623 Thomas Sailer (sailer@ife.ee.ethz.ch)
+ * using soundcore instead of OSS
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/i2c.h>
+#include <linux/videodev.h>
+#include <asm/semaphore.h>
+#include <linux/init.h>
+
+#ifdef CONFIG_SMP
+#include <asm/pgtable.h>
+#include <linux/smp_lock.h>
+#endif
+/* kernel_thread */
+#define __KERNEL_SYSCALLS__
+#include <linux/unistd.h>
+
+#include "audiochip.h"
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = {I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {0x40,0x40,I2C_CLIENT_END};
+static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static struct i2c_client_address_data addr_data = {
+ normal_i2c, normal_i2c_range,
+ probe, probe_range,
+ ignore, ignore_range,
+ force
+};
+
+/* insmod parameters */
+static int debug = 0; /* debug output */
+static int once = 0; /* no continous stereo monitoring */
+static int amsound = 0; /* hard-wire AM sound at 6.5 Hz (france),
+ the autoscan seems work well only with FM... */
+static int simple = -1; /* use short programming (>= msp3410 only) */
+static int dolby = 0;
+
+struct msp3400c {
+ int simple;
+ int nicam;
+ int mode;
+ int norm;
+ int stereo;
+ int nicam_on;
+ int main, second; /* sound carrier */
+
+ int left, right; /* volume */
+ int bass, treble;
+
+ /* thread */
+ struct task_struct *thread;
+ wait_queue_head_t wq;
+
+ struct semaphore *notify;
+ int active,restart,rmmod;
+
+ int watch_stereo;
+ struct timer_list wake_stereo;
+};
+
+#define MSP3400_MAX 4
+static struct i2c_client *msps[MSP3400_MAX];
+
+#define VIDEO_MODE_RADIO 16 /* norm magic for radio mode */
+
+/* ---------------------------------------------------------------------- */
+
+#define dprintk if (debug) printk
+
+MODULE_PARM(once,"i");
+MODULE_PARM(debug,"i");
+MODULE_PARM(simple,"i");
+MODULE_PARM(amsound,"i");
+MODULE_PARM(dolby,"i");
+
+/* ---------------------------------------------------------------------- */
+
+#define I2C_MSP3400C 0x80
+#define I2C_MSP3400C_DEM 0x10
+#define I2C_MSP3400C_DFP 0x12
+
+/* ----------------------------------------------------------------------- */
+/* functions for talking to the MSP3400C Sound processor */
+
+static int msp3400c_reset(struct i2c_client *client)
+{
+ static char reset_off[3] = { 0x00, 0x80, 0x00 };
+ static char reset_on[3] = { 0x00, 0x00, 0x00 };
+
+ i2c_master_send(client,reset_off,3); /* XXX ignore errors here */
+ if (3 != i2c_master_send(client,reset_on, 3)) {
+ printk(KERN_ERR "msp3400: chip reset failed, penguin on i2c bus?\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+msp3400c_read(struct i2c_client *client, int dev, int addr)
+{
+ int err;
+
+ unsigned char write[3];
+ unsigned char read[2];
+ struct i2c_msg msgs[2] = {
+ { client->addr, 0, 3, write },
+ { client->addr, I2C_M_RD, 2, read }
+ };
+ write[0] = dev+1;
+ write[1] = addr >> 8;
+ write[2] = addr & 0xff;
+
+ for (err = 0; err < 3;) {
+ if (2 == i2c_transfer(client->adapter,msgs,2))
+ break;
+ err++;
+ printk(KERN_WARNING "msp34xx: I/O error #%d (read 0x%02x/0x%02x)\n",
+ err, dev, addr);
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/10);
+ }
+ if (3 == err) {
+ printk(KERN_WARNING "msp34xx: giving up, reseting chip. Sound will go off, sorry folks :-|\n");
+ msp3400c_reset(client);
+ return -1;
+ }
+ return read[0] << 8 | read[1];
+}
+
+static int
+msp3400c_write(struct i2c_client *client, int dev, int addr, int val)
+{
+ int err;
+ unsigned char buffer[5];
+
+ buffer[0] = dev;
+ buffer[1] = addr >> 8;
+ buffer[2] = addr & 0xff;
+ buffer[3] = val >> 8;
+ buffer[4] = val & 0xff;
+
+ for (err = 0; err < 3;) {
+ if (5 == i2c_master_send(client, buffer, 5))
+ break;
+ err++;
+ printk(KERN_WARNING "msp34xx: I/O error #%d (write 0x%02x/0x%02x)\n",
+ err, dev, addr);
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/10);
+ }
+ if (3 == err) {
+ printk(KERN_WARNING "msp34xx: giving up, reseting chip. Sound will go off, sorry folks :-|\n");
+ msp3400c_reset(client);
+ return -1;
+ }
+ return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* This macro is allowed for *constants* only, gcc must calculate it
+ at compile time. Remember -- no floats in kernel mode */
+#define MSP_CARRIER(freq) ((int)((float)(freq/18.432)*(1<<24)))
+
+#define MSP_MODE_AM_DETECT 0
+#define MSP_MODE_FM_RADIO 2
+#define MSP_MODE_FM_TERRA 3
+#define MSP_MODE_FM_SAT 4
+#define MSP_MODE_FM_NICAM1 5
+#define MSP_MODE_FM_NICAM2 6
+#define MSP_MODE_AM_NICAM 7
+#define MSP_MODE_BTSC 8
+
+static struct MSP_INIT_DATA_DEM {
+ int fir1[6];
+ int fir2[6];
+ int cdo1;
+ int cdo2;
+ int ad_cv;
+ int mode_reg;
+ int dfp_src;
+ int dfp_matrix;
+} msp_init_data[] = {
+ /* AM (for carrier detect / msp3400) */
+ { { 75, 19, 36, 35, 39, 40 }, { 75, 19, 36, 35, 39, 40 },
+ MSP_CARRIER(5.5), MSP_CARRIER(5.5),
+ 0x00d0, 0x0500, 0x0020, 0x3000},
+
+ /* AM (for carrier detect / msp3410) */
+ { { -1, -1, -8, 2, 59, 126 }, { -1, -1, -8, 2, 59, 126 },
+ MSP_CARRIER(5.5), MSP_CARRIER(5.5),
+ 0x00d0, 0x0100, 0x0020, 0x3000},
+
+ /* FM Radio */
+ { { -8, -8, 4, 6, 78, 107 }, { -8, -8, 4, 6, 78, 107 },
+ MSP_CARRIER(10.7), MSP_CARRIER(10.7),
+ 0x00d0, 0x0480, 0x0020, 0x3000 },
+
+ /* Terrestial FM-mono + FM-stereo */
+ { { 3, 18, 27, 48, 66, 72 }, { 3, 18, 27, 48, 66, 72 },
+ MSP_CARRIER(5.5), MSP_CARRIER(5.5),
+ 0x00d0, 0x0480, 0x0030, 0x3000},
+
+ /* Sat FM-mono */
+ { { 1, 9, 14, 24, 33, 37 }, { 3, 18, 27, 48, 66, 72 },
+ MSP_CARRIER(6.5), MSP_CARRIER(6.5),
+ 0x00c6, 0x0480, 0x0000, 0x3000},
+
+ /* NICAM/FM -- B/G (5.5/5.85), D/K (6.5/5.85) */
+ { { -2, -8, -10, 10, 50, 86 }, { 3, 18, 27, 48, 66, 72 },
+ MSP_CARRIER(5.5), MSP_CARRIER(5.5),
+ 0x00d0, 0x0040, 0x0120, 0x3000},
+
+ /* NICAM/FM -- I (6.0/6.552) */
+ { { 2, 4, -6, -4, 40, 94 }, { 3, 18, 27, 48, 66, 72 },
+ MSP_CARRIER(6.0), MSP_CARRIER(6.0),
+ 0x00d0, 0x0040, 0x0120, 0x3000},
+
+ /* NICAM/AM -- L (6.5/5.85) */
+ { { -2, -8, -10, 10, 50, 86 }, { -4, -12, -9, 23, 79, 126 },
+ MSP_CARRIER(6.5), MSP_CARRIER(6.5),
+ 0x00c6, 0x0140, 0x0120, 0x7c03},
+};
+
+struct CARRIER_DETECT {
+ int cdo;
+ char *name;
+};
+
+static struct CARRIER_DETECT carrier_detect_main[] = {
+ /* main carrier */
+ { MSP_CARRIER(4.5), "4.5 NTSC" },
+ { MSP_CARRIER(5.5), "5.5 PAL B/G" },
+ { MSP_CARRIER(6.0), "6.0 PAL I" },
+ { MSP_CARRIER(6.5), "6.5 PAL D/K + SAT + SECAM" }
+};
+
+static struct CARRIER_DETECT carrier_detect_55[] = {
+ /* PAL B/G */
+ { MSP_CARRIER(5.7421875), "5.742 PAL B/G FM-stereo" },
+ { MSP_CARRIER(5.85), "5.85 PAL B/G NICAM" }
+};
+
+static struct CARRIER_DETECT carrier_detect_65[] = {
+ /* PAL SAT / SECAM */
+ { MSP_CARRIER(5.85), "5.85 PAL D/K + SECAM NICAM" },
+ { MSP_CARRIER(6.2578125), "6.25 PAL D/K1 FM-stereo" },
+ { MSP_CARRIER(6.7421875), "6.74 PAL D/K2 FM-stereo" },
+ { MSP_CARRIER(7.02), "7.02 PAL SAT FM-stereo s/b" },
+ { MSP_CARRIER(7.20), "7.20 PAL SAT FM-stereo s" },
+ { MSP_CARRIER(7.38), "7.38 PAL SAT FM-stereo b" },
+};
+
+#define CARRIER_COUNT(x) (sizeof(x)/sizeof(struct CARRIER_DETECT))
+
+/* ------------------------------------------------------------------------ */
+
+static void msp3400c_setcarrier(struct i2c_client *client, int cdo1, int cdo2)
+{
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x0093, cdo1 & 0xfff);
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x009b, cdo1 >> 12);
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x00a3, cdo2 & 0xfff);
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x00ab, cdo2 >> 12);
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/
+}
+
+static void msp3400c_setvolume(struct i2c_client *client, int left, int right)
+{
+ int vol,val,balance;
+
+ vol = (left > right) ? left : right;
+ val = (vol * 0x73 / 65535) << 8;
+ balance = 0;
+ if (vol > 0)
+ balance = ((right-left) * 127) / vol;
+
+ dprintk("msp34xx: setvolume: %d:%d 0x%02x 0x%02x\n",
+ left,right,val>>8,balance);
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0000, val); /* loudspeaker */
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0006, val); /* headphones */
+ /* scart - on/off only */
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0007, val ? 0x4000 : 0);
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0001, balance << 8);
+}
+
+static void msp3400c_setbass(struct i2c_client *client, int bass)
+{
+ int val = ((bass-32768) * 0x60 / 65535) << 8;
+
+ dprintk("msp34xx: setbass: %d 0x%02x\n",bass, val>>8);
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0002, val); /* loudspeaker */
+}
+
+static void msp3400c_settreble(struct i2c_client *client, int treble)
+{
+ int val = ((treble-32768) * 0x60 / 65535) << 8;
+
+ dprintk("msp34xx: settreble: %d 0x%02x\n",treble, val>>8);
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0003, val); /* loudspeaker */
+}
+
+static void msp3400c_setmode(struct i2c_client *client, int type)
+{
+ struct msp3400c *msp = client->data;
+ int i;
+
+ dprintk("msp3400: setmode: %d\n",type);
+ msp->mode = type;
+ msp->stereo = VIDEO_SOUND_MONO;
+
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x00bb, /* ad_cv */
+ msp_init_data[type].ad_cv);
+
+ for (i = 5; i >= 0; i--) /* fir 1 */
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x0001,
+ msp_init_data[type].fir1[i]);
+
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0004); /* fir 2 */
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0040);
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0000);
+ for (i = 5; i >= 0; i--)
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005,
+ msp_init_data[type].fir2[i]);
+
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x0083, /* MODE_REG */
+ msp_init_data[type].mode_reg);
+
+ msp3400c_setcarrier(client, msp_init_data[type].cdo1,
+ msp_init_data[type].cdo2);
+
+ msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/
+
+ if (dolby) {
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,
+ 0x0520); /* I2S1 */
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,
+ 0x0620); /* I2S2 */
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,
+ msp_init_data[type].dfp_src);
+ } else {
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,
+ msp_init_data[type].dfp_src);
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,
+ msp_init_data[type].dfp_src);
+ }
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,
+ msp_init_data[type].dfp_src);
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e,
+ msp_init_data[type].dfp_matrix);
+
+ if (msp->nicam) {
+ /* nicam prescale */
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0010, 0x5a00); /* was: 0x3000 */
+ }
+}
+
+/* turn on/off nicam + stereo */
+static void msp3400c_setstereo(struct i2c_client *client, int mode)
+{
+ struct msp3400c *msp = client->data;
+ int nicam=0; /* channel source: FM/AM or nicam */
+ int src=0;
+
+ /* switch demodulator */
+ switch (msp->mode) {
+ case MSP_MODE_FM_TERRA:
+ dprintk("msp3400: FM setstereo: %d\n",mode);
+ msp3400c_setcarrier(client,msp->second,msp->main);
+ switch (mode) {
+ case VIDEO_SOUND_STEREO:
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3001);
+ break;
+ case VIDEO_SOUND_MONO:
+ case VIDEO_SOUND_LANG1:
+ case VIDEO_SOUND_LANG2:
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3000);
+ break;
+ }
+ break;
+ case MSP_MODE_FM_SAT:
+ dprintk("msp3400: SAT setstereo: %d\n",mode);
+ switch (mode) {
+ case VIDEO_SOUND_MONO:
+ msp3400c_setcarrier(client, MSP_CARRIER(6.5), MSP_CARRIER(6.5));
+ break;
+ case VIDEO_SOUND_STEREO:
+ msp3400c_setcarrier(client, MSP_CARRIER(7.2), MSP_CARRIER(7.02));
+ break;
+ case VIDEO_SOUND_LANG1:
+ msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02));
+ break;
+ case VIDEO_SOUND_LANG2:
+ msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02));
+ break;
+ }
+ break;
+ case MSP_MODE_FM_NICAM1:
+ case MSP_MODE_FM_NICAM2:
+ case MSP_MODE_AM_NICAM:
+ dprintk("msp3400: NICAM setstereo: %d\n",mode);
+ msp3400c_setcarrier(client,msp->second,msp->main);
+ if (msp->nicam_on)
+ nicam=0x0100;
+ break;
+ case MSP_MODE_BTSC:
+ dprintk("msp3400: BTSC setstereo: %d\n",mode);
+ nicam=0x0300;
+ break;
+ default:
+ dprintk("msp3400: mono setstereo\n");
+ return;
+ }
+
+ /* switch audio */
+ switch (mode) {
+ case VIDEO_SOUND_STEREO:
+ src = 0x0020 | nicam;
+#if 0
+ /* spatial effect */
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0005,0x4000);
+#endif
+ break;
+ case VIDEO_SOUND_MONO:
+ if (msp->mode == MSP_MODE_AM_NICAM) {
+ dprintk("msp3400: switching to AM mono\n");
+ /* AM mono decoding is handled by tuner, not MSP chip */
+ /* so let's redirect sound from tuner via SCART */
+ /* volume prescale for SCART */
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x000d, 0x1900);
+ /* SCART switching control register*/
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0013, 0xe900);
+ src = 0x0200;
+ break;
+ }
+ case VIDEO_SOUND_LANG1:
+ src = 0x0000 | nicam;
+ break;
+ case VIDEO_SOUND_LANG2:
+ src = 0x0010 | nicam;
+ break;
+ }
+ if (dolby) {
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,0x0520);
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,0x0620);
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src);
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,src);
+ } else {
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,src);
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,src);
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src);
+ }
+}
+
+static void
+msp3400c_print_mode(struct msp3400c *msp)
+{
+ if (msp->main == msp->second) {
+ printk("msp3400: mono sound carrier: %d.%03d MHz\n",
+ msp->main/910000,(msp->main/910)%1000);
+ } else {
+ printk("msp3400: main sound carrier: %d.%03d MHz\n",
+ msp->main/910000,(msp->main/910)%1000);
+ }
+ if (msp->mode == MSP_MODE_FM_NICAM1 ||
+ msp->mode == MSP_MODE_FM_NICAM2)
+ printk("msp3400: NICAM/FM carrier : %d.%03d MHz\n",
+ msp->second/910000,(msp->second/910)%1000);
+ if (msp->mode == MSP_MODE_AM_NICAM)
+ printk("msp3400: NICAM/AM carrier : %d.%03d MHz\n",
+ msp->second/910000,(msp->second/910)%1000);
+ if (msp->mode == MSP_MODE_FM_TERRA &&
+ msp->main != msp->second) {
+ printk("msp3400: FM-stereo carrier : %d.%03d MHz\n",
+ msp->second/910000,(msp->second/910)%1000);
+ }
+}
+
+/* ----------------------------------------------------------------------- */
+
+struct REGISTER_DUMP {
+ int addr;
+ char *name;
+};
+
+struct REGISTER_DUMP d1[] = {
+ { 0x007e, "autodetect" },
+ { 0x0023, "C_AD_BITS " },
+ { 0x0038, "ADD_BITS " },
+ { 0x003e, "CIB_BITS " },
+ { 0x0057, "ERROR_RATE" },
+};
+
+static int
+autodetect_stereo(struct i2c_client *client)
+{
+ struct msp3400c *msp = client->data;
+ int val;
+ int newstereo = msp->stereo;
+ int newnicam = msp->nicam_on;
+ int update = 0;
+
+ switch (msp->mode) {
+ case MSP_MODE_FM_TERRA:
+ val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x18);
+ if (val > 32768)
+ val -= 65536;
+ dprintk("msp34xx: stereo detect register: %d\n",val);
+
+ if (val > 4096) {
+ newstereo = VIDEO_SOUND_STEREO | VIDEO_SOUND_MONO;
+ } else if (val < -4096) {
+ newstereo = VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2;
+ } else {
+ newstereo = VIDEO_SOUND_MONO;
+ }
+ newnicam = 0;
+ break;
+ case MSP_MODE_FM_NICAM1:
+ case MSP_MODE_FM_NICAM2:
+ case MSP_MODE_AM_NICAM:
+ val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x23);
+ dprintk("msp34xx: nicam sync=%d, mode=%d\n",val & 1, (val & 0x1e) >> 1);
+
+ if (val & 1) {
+ /* nicam synced */
+ switch ((val & 0x1e) >> 1) {
+ case 0:
+ case 8:
+ newstereo = VIDEO_SOUND_STEREO;
+ break;
+ case 1:
+ case 9:
+ newstereo = VIDEO_SOUND_MONO
+ | VIDEO_SOUND_LANG1;
+ break;
+ case 2:
+ case 10:
+ newstereo = VIDEO_SOUND_MONO
+ | VIDEO_SOUND_LANG1
+ | VIDEO_SOUND_LANG2;
+ break;
+ default:
+ newstereo = VIDEO_SOUND_MONO;
+ break;
+ }
+ newnicam=1;
+ } else {
+ newnicam = 0;
+ newstereo = VIDEO_SOUND_MONO;
+ }
+ break;
+ case MSP_MODE_BTSC:
+ val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x200);
+ dprintk("msp3410: status=0x%x (pri=%s, sec=%s, %s%s%s)\n",
+ val,
+ (val & 0x0002) ? "no" : "yes",
+ (val & 0x0004) ? "no" : "yes",
+ (val & 0x0040) ? "stereo" : "mono",
+ (val & 0x0080) ? ", nicam 2nd mono" : "",
+ (val & 0x0100) ? ", bilingual/SAP" : "");
+ newstereo = VIDEO_SOUND_MONO;
+ if (val & 0x0040) newstereo |= VIDEO_SOUND_STEREO;
+ if (val & 0x0100) newstereo |= VIDEO_SOUND_LANG1;
+ break;
+ }
+ if (newstereo != msp->stereo) {
+ update = 1;
+ dprintk("msp34xx: watch: stereo %d => %d\n",
+ msp->stereo,newstereo);
+ msp->stereo = newstereo;
+ }
+ if (newnicam != msp->nicam_on) {
+ update = 1;
+ dprintk("msp34xx: watch: nicam %d => %d\n",
+ msp->nicam_on,newnicam);
+ msp->nicam_on = newnicam;
+ }
+ return update;
+}
+
+/*
+ * A kernel thread for msp3400 control -- we don't want to block the
+ * in the ioctl while doing the sound carrier & stereo detect
+ */
+
+static void msp3400c_stereo_wake(unsigned long data)
+{
+ struct msp3400c *msp = (struct msp3400c*)data; /* XXX alpha ??? */
+
+ wake_up_interruptible(&msp->wq);
+}
+
+/* stereo/multilang monitoring */
+static void watch_stereo(struct i2c_client *client)
+{
+ struct msp3400c *msp = client->data;
+
+ if (autodetect_stereo(client)) {
+ if (msp->stereo & VIDEO_SOUND_STEREO)
+ msp3400c_setstereo(client,VIDEO_SOUND_STEREO);
+ else if (msp->stereo & VIDEO_SOUND_LANG1)
+ msp3400c_setstereo(client,VIDEO_SOUND_LANG1);
+ else
+ msp3400c_setstereo(client,VIDEO_SOUND_MONO);
+ }
+ if (once)
+ msp->watch_stereo = 0;
+ if (msp->watch_stereo)
+ mod_timer(&msp->wake_stereo, jiffies+5*HZ);
+}
+
+static int msp3400c_thread(void *data)
+{
+ struct i2c_client *client = data;
+ struct msp3400c *msp = client->data;
+
+ struct CARRIER_DETECT *cd;
+ int count, max1,max2,val1,val2, val,this;
+
+#ifdef CONFIG_SMP
+ lock_kernel();
+#endif
+
+ daemonize();
+ sigfillset(&current->blocked);
+ strcpy(current->comm,"msp3400");
+
+ msp->thread = current;
+
+#ifdef CONFIG_SMP
+ unlock_kernel();
+#endif
+
+ printk("msp3400: daemon started\n");
+ if(msp->notify != NULL)
+ up(msp->notify);
+
+ for (;;) {
+ if (msp->rmmod)
+ goto done;
+ if (debug > 1)
+ printk("msp3400: thread: sleep\n");
+ interruptible_sleep_on(&msp->wq);
+ if (debug > 1)
+ printk("msp3400: thread: wakeup\n");
+ if (msp->rmmod || signal_pending(current))
+ goto done;
+
+ if (VIDEO_MODE_RADIO == msp->norm)
+ continue; /* nothing to do */
+
+ msp->active = 1;
+
+ if (msp->watch_stereo) {
+ watch_stereo(client);
+ msp->active = 0;
+ continue;
+ }
+
+ /* some time for the tuner to sync */
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/5);
+ if (signal_pending(current))
+ goto done;
+
+ restart:
+ msp->restart = 0;
+ msp3400c_setvolume(client, 0, 0);
+ msp3400c_setmode(client, MSP_MODE_AM_DETECT /* +1 */ );
+ val1 = val2 = 0;
+ max1 = max2 = -1;
+ del_timer(&msp->wake_stereo);
+ msp->watch_stereo = 0;
+
+ /* carrier detect pass #1 -- main carrier */
+ cd = carrier_detect_main; count = CARRIER_COUNT(carrier_detect_main);
+
+ if (amsound && (msp->norm == VIDEO_MODE_SECAM)) {
+ /* autodetect doesn't work well with AM ... */
+ max1 = 3;
+ count = 0;
+ dprintk("msp3400: AM sound override\n");
+ }
+
+ for (this = 0; this < count; this++) {
+ msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo);
+
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/10);
+ if (signal_pending(current))
+ goto done;
+ if (msp->restart)
+ msp->restart = 0;
+
+ val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b);
+ if (val > 32768)
+ val -= 65536;
+ if (val1 < val)
+ val1 = val, max1 = this;
+ dprintk("msp3400: carrier1 val: %5d / %s\n", val,cd[this].name);
+ }
+
+ /* carrier detect pass #2 -- second (stereo) carrier */
+ switch (max1) {
+ case 1: /* 5.5 */
+ cd = carrier_detect_55; count = CARRIER_COUNT(carrier_detect_55);
+ break;
+ case 3: /* 6.5 */
+ cd = carrier_detect_65; count = CARRIER_COUNT(carrier_detect_65);
+ break;
+ case 0: /* 4.5 */
+ case 2: /* 6.0 */
+ default:
+ cd = NULL; count = 0;
+ break;
+ }
+
+ if (amsound && (msp->norm == VIDEO_MODE_SECAM)) {
+ /* autodetect doesn't work well with AM ... */
+ cd = NULL; count = 0; max2 = 0;
+ }
+ for (this = 0; this < count; this++) {
+ msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo);
+
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/10);
+ if (signal_pending(current))
+ goto done;
+ if (msp->restart)
+ goto restart;
+
+ val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b);
+ if (val > 32768)
+ val -= 65536;
+ if (val2 < val)
+ val2 = val, max2 = this;
+ dprintk("msp3400: carrier2 val: %5d / %s\n", val,cd[this].name);
+ }
+
+ /* programm the msp3400 according to the results */
+ msp->main = carrier_detect_main[max1].cdo;
+ switch (max1) {
+ case 1: /* 5.5 */
+ if (max2 == 0) {
+ /* B/G FM-stereo */
+ msp->second = carrier_detect_55[max2].cdo;
+ msp3400c_setmode(client, MSP_MODE_FM_TERRA);
+ msp->nicam_on = 0;
+ msp3400c_setstereo(client, VIDEO_SOUND_MONO);
+ msp->watch_stereo = 1;
+ } else if (max2 == 1 && msp->nicam) {
+ /* B/G NICAM */
+ msp->second = carrier_detect_55[max2].cdo;
+ msp3400c_setmode(client, MSP_MODE_FM_NICAM1);
+ msp->nicam_on = 1;
+ msp3400c_setcarrier(client, msp->second, msp->main);
+ msp->watch_stereo = 1;
+ } else {
+ goto no_second;
+ }
+ break;
+ case 2: /* 6.0 */
+ /* PAL I NICAM */
+ msp->second = MSP_CARRIER(6.552);
+ msp3400c_setmode(client, MSP_MODE_FM_NICAM2);
+ msp->nicam_on = 1;
+ msp3400c_setcarrier(client, msp->second, msp->main);
+ msp->watch_stereo = 1;
+ break;
+ case 3: /* 6.5 */
+ if (max2 == 1 || max2 == 2) {
+ /* D/K FM-stereo */
+ msp->second = carrier_detect_65[max2].cdo;
+ msp3400c_setmode(client, MSP_MODE_FM_TERRA);
+ msp->nicam_on = 0;
+ msp3400c_setstereo(client, VIDEO_SOUND_MONO);
+ msp->watch_stereo = 1;
+ } else if (max2 == 0 &&
+ msp->norm == VIDEO_MODE_SECAM) {
+ /* L NICAM or AM-mono */
+ msp->second = carrier_detect_65[max2].cdo;
+ msp3400c_setmode(client, MSP_MODE_AM_NICAM);
+ msp->nicam_on = 0;
+ msp3400c_setstereo(client, VIDEO_SOUND_MONO);
+ msp3400c_setcarrier(client, msp->second, msp->main);
+ msp->watch_stereo = 1;
+ } else if (max2 == 0 && msp->nicam) {
+ /* D/K NICAM */
+ msp->second = carrier_detect_65[max2].cdo;
+ msp3400c_setmode(client, MSP_MODE_FM_NICAM1);
+ msp->nicam_on = 1;
+ msp3400c_setcarrier(client, msp->second, msp->main);
+ msp->watch_stereo = 1;
+ } else {
+ goto no_second;
+ }
+ break;
+ case 0: /* 4.5 */
+ default:
+ no_second:
+ msp->second = carrier_detect_main[max1].cdo;
+ msp3400c_setmode(client, MSP_MODE_FM_TERRA);
+ msp->nicam_on = 0;
+ msp3400c_setcarrier(client, msp->second, msp->main);
+ msp->stereo = VIDEO_SOUND_MONO;
+ msp3400c_setstereo(client, VIDEO_SOUND_MONO);
+ break;
+ }
+
+ /* unmute */
+ msp3400c_setvolume(client, msp->left, msp->right);
+
+ if (msp->watch_stereo)
+ mod_timer(&msp->wake_stereo, jiffies+5*HZ);
+
+ if (debug)
+ msp3400c_print_mode(msp);
+
+ msp->active = 0;
+ }
+
+done:
+ dprintk("msp3400: thread: exit\n");
+ msp->active = 0;
+ msp->thread = NULL;
+
+ if(msp->notify != NULL)
+ up(msp->notify);
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+/* this one uses the automatic sound standard detection of newer */
+/* msp34xx chip versions */
+
+static struct MODES {
+ int retval;
+ int main, second;
+ char *name;
+} modelist[] = {
+ { 0x0000, 0, 0, "ERROR" },
+ { 0x0001, 0, 0, "autodetect start" },
+ { 0x0002, MSP_CARRIER(4.5), MSP_CARRIER(4.72), "4.5/4.72 M Dual FM-Stereo" },
+ { 0x0003, MSP_CARRIER(5.5), MSP_CARRIER(5.7421875), "5.5/5.74 B/G Dual FM-Stereo" },
+ { 0x0004, MSP_CARRIER(6.5), MSP_CARRIER(6.2578125), "6.5/6.25 D/K1 Dual FM-Stereo" },
+ { 0x0005, MSP_CARRIER(6.5), MSP_CARRIER(6.7421875), "6.5/6.74 D/K2 Dual FM-Stereo" },
+ { 0x0006, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5 D/K FM-Mono (HDEV3)" },
+ { 0x0008, MSP_CARRIER(5.5), MSP_CARRIER(5.85), "5.5/5.85 B/G NICAM FM" },
+ { 0x0009, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 L NICAM AM" },
+ { 0x000a, MSP_CARRIER(6.0), MSP_CARRIER(6.55), "6.0/6.55 I NICAM FM" },
+ { 0x000b, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 D/K NICAM FM" },
+ { 0x000c, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 D/K NICAM FM (HDEV2)" },
+ { 0x0020, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M BTSC-Stereo" },
+ { 0x0021, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M BTSC-Mono + SAP" },
+ { 0x0030, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M EIA-J Japan Stereo" },
+ { 0x0040, MSP_CARRIER(10.7), MSP_CARRIER(10.7), "10.7 FM-Stereo Radio" },
+ { 0x0050, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5 SAT-Mono" },
+ { 0x0051, MSP_CARRIER(7.02), MSP_CARRIER(7.20), "7.02/7.20 SAT-Stereo" },
+ { 0x0060, MSP_CARRIER(7.2), MSP_CARRIER(7.2), "7.2 SAT ADR" },
+ { -1, 0, 0, NULL }, /* EOF */
+};
+
+static int msp3410d_thread(void *data)
+{
+ struct i2c_client *client = data;
+ struct msp3400c *msp = client->data;
+ int mode,val,i,std;
+
+#ifdef CONFIG_SMP
+ lock_kernel();
+#endif
+
+ daemonize();
+ sigfillset(&current->blocked);
+ strcpy(current->comm,"msp3410 [auto]");
+
+ msp->thread = current;
+
+#ifdef CONFIG_SMP
+ unlock_kernel();
+#endif
+
+ printk("msp3410: daemon started\n");
+ if(msp->notify != NULL)
+ up(msp->notify);
+
+ for (;;) {
+ if (msp->rmmod)
+ goto done;
+ if (debug > 1)
+ printk("msp3410: thread: sleep\n");
+ interruptible_sleep_on(&msp->wq);
+ if (debug > 1)
+ printk("msp3410: thread: wakeup\n");
+ if (msp->rmmod || signal_pending(current))
+ goto done;
+
+ if (VIDEO_MODE_RADIO == msp->norm)
+ continue; /* nothing to do */
+
+ msp->active = 1;
+
+ if (msp->watch_stereo) {
+ watch_stereo(client);
+ msp->active = 0;
+ continue;
+ }
+
+ /* some time for the tuner to sync */
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/5);
+ if (signal_pending(current))
+ goto done;
+
+ restart:
+ msp->restart = 0;
+ del_timer(&msp->wake_stereo);
+ msp->watch_stereo = 0;
+
+ /* put into sane state (and mute) */
+ msp3400c_reset(client);
+
+ /* start autodetect */
+ switch (msp->norm) {
+ case VIDEO_MODE_PAL:
+ mode = 0x1003;
+ std = 1;
+ break;
+ case VIDEO_MODE_NTSC: /* BTSC */
+ mode = 0x2003;
+ std = 0x0020;
+ break;
+ case VIDEO_MODE_SECAM:
+ mode = 0x0003;
+ std = 1;
+ break;
+ default:
+ mode = 0x0003;
+ std = 1;
+ break;
+ }
+ msp3400c_write(client, I2C_MSP3400C_DEM, 0x30, mode);
+ msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, std);
+ msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403);
+ msp3400c_write(client, I2C_MSP3400C_DFP, 0x10, 0x5a00);
+ if (debug) {
+ int i;
+ for (i = 0; modelist[i].name != NULL; i++)
+ if (modelist[i].retval == std)
+ break;
+ printk("msp3410: setting mode: %s (0x%04x)\n",
+ modelist[i].name ? modelist[i].name : "unknown",std);
+ }
+
+ if (std != 1) {
+ /* programmed some specific mode */
+ val = std;
+ } else {
+ /* triggered autodetect */
+ for (;;) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/10);
+ if (signal_pending(current))
+ goto done;
+ if (msp->restart)
+ goto restart;
+
+ /* check results */
+ val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x7e);
+ if (val < 0x07ff)
+ break;
+ dprintk("msp3410: detection still in progress\n");
+ }
+ }
+ for (i = 0; modelist[i].name != NULL; i++)
+ if (modelist[i].retval == val)
+ break;
+ dprintk("msp3410: current mode: %s (0x%04x)\n",
+ modelist[i].name ? modelist[i].name : "unknown",
+ val);
+ msp->main = modelist[i].main;
+ msp->second = modelist[i].second;
+
+ if (amsound && (msp->norm == VIDEO_MODE_SECAM) && (val != 0x0009)) {
+ /* autodetection has failed, let backup */
+ dprintk("msp3410: autodetection failed, switching to backup mode: %s (0x%04x)\n",
+ modelist[8].name ? modelist[8].name : "unknown",val);
+ val = 0x0009;
+ msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, val);
+ }
+
+ /* set prescale / stereo */
+ switch (val) {
+ case 0x0009:
+ msp->mode = MSP_MODE_AM_NICAM;
+ msp->stereo = VIDEO_SOUND_MONO;
+ msp3400c_setstereo(client,VIDEO_SOUND_MONO);
+ msp->watch_stereo = 1;
+ break;
+ case 0x0020: /* BTSC */
+ /* just turn on stereo */
+ msp->mode = MSP_MODE_BTSC;
+ msp->stereo = VIDEO_SOUND_STEREO;
+ msp->watch_stereo = 1;
+ msp3400c_setstereo(client,VIDEO_SOUND_STEREO);
+ /* set prescale */
+ msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403); /* FM */
+ break;
+ case 0x0003:
+ msp->mode = MSP_MODE_FM_TERRA;
+ msp->stereo = VIDEO_SOUND_MONO;
+ msp->watch_stereo = 1;
+ /* fall */
+ default:
+ msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403); /* FM */
+ msp3400c_write(client, I2C_MSP3400C_DFP, 0x10, 0x5a00); /* NICAM */
+ break;
+ }
+
+ /* unmute */
+ msp3400c_setbass(client, msp->bass);
+ msp3400c_settreble(client, msp->treble);
+ msp3400c_setvolume(client, msp->left, msp->right);
+
+ if (msp->watch_stereo)
+ mod_timer(&msp->wake_stereo, jiffies+HZ);
+
+ msp->active = 0;
+ }
+
+done:
+ dprintk("msp3410: thread: exit\n");
+ msp->active = 0;
+ msp->thread = NULL;
+
+ if(msp->notify != NULL)
+ up(msp->notify);
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static int msp_attach(struct i2c_adapter *adap, int addr,
+ unsigned short flags, int kind);
+static int msp_detach(struct i2c_client *client);
+static int msp_probe(struct i2c_adapter *adap);
+static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg);
+
+static struct i2c_driver driver = {
+ "i2c msp3400 driver",
+ I2C_DRIVERID_MSP3400,
+ I2C_DF_NOTIFY,
+ msp_probe,
+ msp_detach,
+ msp_command,
+};
+
+static struct i2c_client client_template =
+{
+ "unset",
+ -1,
+ 0,
+ 0,
+ NULL,
+ &driver
+};
+
+static int msp_attach(struct i2c_adapter *adap, int addr,
+ unsigned short flags, int kind)
+{
+ DECLARE_MUTEX_LOCKED(sem);
+ struct msp3400c *msp;
+ struct i2c_client *c;
+ int rev1,rev2,i;
+
+ client_template.adapter = adap;
+ client_template.addr = addr;
+
+ if (-1 == msp3400c_reset(&client_template)) {
+ dprintk("msp3400: no chip found\n");
+ return -1;
+ }
+
+ if (NULL == (c = kmalloc(sizeof(struct i2c_client),GFP_KERNEL)))
+ return -ENOMEM;
+ memcpy(c,&client_template,sizeof(struct i2c_client));
+ if (NULL == (msp = kmalloc(sizeof(struct msp3400c),GFP_KERNEL))) {
+ kfree(c);
+ return -ENOMEM;
+ }
+
+ memset(msp,0,sizeof(struct msp3400c));
+ msp->left = 65535;
+ msp->right = 65535;
+ msp->bass = 32768;
+ msp->treble = 32768;
+ c->data = msp;
+ init_waitqueue_head(&msp->wq);
+
+ if (-1 == msp3400c_reset(c)) {
+ kfree(msp);
+ dprintk("msp3400: no chip found\n");
+ return -1;
+ }
+
+ rev1 = msp3400c_read(c, I2C_MSP3400C_DFP, 0x1e);
+ rev2 = msp3400c_read(c, I2C_MSP3400C_DFP, 0x1f);
+ if (0 == rev1 && 0 == rev2) {
+ kfree(msp);
+ printk("msp3400: error while reading chip version\n");
+ return -1;
+ }
+
+#if 0
+ /* this will turn on a 1kHz beep - might be useful for debugging... */
+ msp3400c_write(client,I2C_MSP3400C_DFP, 0x0014, 0x1040);
+#endif
+
+ sprintf(c->name,"MSP34%02d%c-%c%d",
+ (rev2>>8)&0xff, (rev1&0xff)+'@', ((rev1>>8)&0xff)+'@', rev2&0x1f);
+ msp->nicam = (((rev2>>8)&0xff) != 00) ? 1 : 0;
+
+ if (simple == -1) {
+ /* default mode */
+ /* msp->simple = (((rev2>>8)&0xff) == 0) ? 0 : 1; */
+ msp->simple = ((rev1&0xff)+'@' > 'C');
+ } else {
+ /* use insmod option */
+ msp->simple = simple;
+ }
+
+ /* timer for stereo checking */
+ msp->wake_stereo.function = msp3400c_stereo_wake;
+ msp->wake_stereo.data = (unsigned long)msp;
+
+ /* hello world :-) */
+ printk(KERN_INFO "msp3400: init: chip=%s",c->name);
+ if (msp->nicam)
+ printk(", has NICAM support");
+ printk("\n");
+
+ /* startup control thread */
+ MOD_INC_USE_COUNT;
+ msp->notify = &sem;
+ kernel_thread(msp->simple ? msp3410d_thread : msp3400c_thread,
+ (void *)c, 0);
+ down(&sem);
+ msp->notify = NULL;
+ wake_up_interruptible(&msp->wq);
+
+#ifdef REGISTER_MIXER
+ if ((msp->mixer_num = register_sound_mixer(&msp3400c_mixer_fops,mixer)) < 0)
+ printk(KERN_ERR "msp3400c: cannot allocate mixer device\n");
+#endif
+
+ /* update our own array */
+ for (i = 0; i < MSP3400_MAX; i++) {
+ if (NULL == msps[i]) {
+ msps[i] = c;
+ break;
+ }
+ }
+
+ /* done */
+ i2c_attach_client(c);
+ return 0;
+}
+
+static int msp_detach(struct i2c_client *client)
+{
+ DECLARE_MUTEX_LOCKED(sem);
+ struct msp3400c *msp = (struct msp3400c*)client->data;
+ int i;
+
+#ifdef REGISTER_MIXER
+ if (msp->mixer_num >= 0)
+ unregister_sound_mixer(msp->mixer_num);
+#endif
+
+ /* shutdown control thread */
+ del_timer(&msp->wake_stereo);
+ if (msp->thread)
+ {
+ msp->notify = &sem;
+ msp->rmmod = 1;
+ wake_up_interruptible(&msp->wq);
+ down(&sem);
+ msp->notify = NULL;
+ }
+ msp3400c_reset(client);
+
+ /* update our own array */
+ for (i = 0; i < MSP3400_MAX; i++) {
+ if (client == msps[i]) {
+ msps[i] = NULL;
+ break;
+ }
+ }
+
+ i2c_detach_client(client);
+ kfree(msp);
+ kfree(client);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int msp_probe(struct i2c_adapter *adap)
+{
+ if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848))
+ return i2c_probe(adap, &addr_data, msp_attach);
+ return 0;
+}
+
+static int msp_command(struct i2c_client *client,unsigned int cmd, void *arg)
+{
+ struct msp3400c *msp = (struct msp3400c*)client->data;
+#if 0
+ int *iarg = (int*)arg;
+ __u16 *sarg = arg;
+#endif
+
+ switch (cmd) {
+
+ case AUDC_SET_RADIO:
+ msp->norm = VIDEO_MODE_RADIO;
+ msp->watch_stereo=0;
+ del_timer(&msp->wake_stereo);
+ if (msp->simple) {
+ msp3400c_reset(client);
+ msp3400c_write(client, I2C_MSP3400C_DEM, 0x30, 0x0003); /* automatic */
+ msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, 0x0040); /* FM Radio */
+ msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403); /* FM prescale */
+ msp3400c_setbass(client, msp->bass);
+ msp3400c_settreble(client, msp->treble);
+ msp3400c_setvolume(client, msp->left, msp->right);
+ } else {
+ msp3400c_setmode(client,MSP_MODE_FM_RADIO);
+ msp3400c_setcarrier(client, MSP_CARRIER(10.7),MSP_CARRIER(10.7));
+ msp3400c_setvolume(client,msp->left, msp->right);
+ }
+ break;
+
+ /* --- v4l ioctls --- */
+ /* take care: bttv does userspace copying, we'll get a
+ kernel pointer here... */
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *va = arg;
+
+ va->flags |= VIDEO_AUDIO_VOLUME |
+ VIDEO_AUDIO_BASS |
+ VIDEO_AUDIO_TREBLE;
+ va->volume=MAX(msp->left,msp->right);
+ va->balance=(32768*MIN(msp->left,msp->right))/
+ (va->volume ? va->volume : 1);
+ va->balance=(msp->left<msp->right)?
+ (65535-va->balance) : va->balance;
+ va->bass = msp->bass;
+ va->treble = msp->treble;
+
+ autodetect_stereo(client);
+ va->mode = msp->stereo;
+ break;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *va = arg;
+
+ msp->left = (MIN(65536 - va->balance,32768) *
+ va->volume) / 32768;
+ msp->right = (MIN(va->balance,32768) *
+ va->volume) / 32768;
+ msp->bass = va->bass;
+ msp->treble = va->treble;
+ msp3400c_setvolume(client,msp->left, msp->right);
+ msp3400c_setbass(client,msp->bass);
+ msp3400c_settreble(client,msp->treble);
+
+ if (va->mode != 0) {
+ msp->watch_stereo=0;
+ del_timer(&msp->wake_stereo);
+ msp->stereo = va->mode;
+ msp3400c_setstereo(client,va->mode);
+ }
+ break;
+ }
+ case VIDIOCSCHAN:
+ {
+ struct video_channel *vc = arg;
+
+ msp->norm = vc->norm;
+ break;
+ }
+ case VIDIOCSFREQ:
+ {
+ /* new channel -- kick audio carrier scan */
+ msp3400c_setvolume(client,0,0);
+ msp->watch_stereo=0;
+ del_timer(&msp->wake_stereo);
+ if (msp->active)
+ msp->restart = 1;
+ wake_up_interruptible(&msp->wq);
+ break;
+ }
+
+ /* --- v4l2 ioctls --- */
+ /* NOT YET */
+
+#if 0
+ /* --- old, obsolete interface --- */
+ case AUDC_SET_TVNORM:
+ msp->norm = *iarg;
+ break;
+ case AUDC_SWITCH_MUTE:
+ /* channels switching step one -- mute */
+ msp->watch_stereo=0;
+ del_timer(&msp->wake_stereo);
+ msp3400c_setvolume(client,0,0);
+ break;
+ case AUDC_NEWCHANNEL:
+ /* channels switching step two -- trigger sound carrier scan */
+ msp->watch_stereo=0;
+ del_timer(&msp->wake_stereo);
+ if (msp->active)
+ msp->restart = 1;
+ wake_up_interruptible(&msp->wq);
+ break;
+
+ case AUDC_GET_VOLUME_LEFT:
+ *sarg = msp->left;
+ break;
+ case AUDC_GET_VOLUME_RIGHT:
+ *sarg = msp->right;
+ break;
+ case AUDC_SET_VOLUME_LEFT:
+ msp->left = *sarg;
+ msp3400c_setvolume(client,msp->left, msp->right);
+ break;
+ case AUDC_SET_VOLUME_RIGHT:
+ msp->right = *sarg;
+ msp3400c_setvolume(client,msp->left, msp->right);
+ break;
+
+ case AUDC_GET_BASS:
+ *sarg = msp->bass;
+ break;
+ case AUDC_SET_BASS:
+ msp->bass = *sarg;
+ msp3400c_setbass(client,msp->bass);
+ break;
+
+ case AUDC_GET_TREBLE:
+ *sarg = msp->treble;
+ break;
+ case AUDC_SET_TREBLE:
+ msp->treble = *sarg;
+ msp3400c_settreble(client,msp->treble);
+ break;
+
+ case AUDC_GET_STEREO:
+ autodetect_stereo(client);
+ *sarg = msp->stereo;
+ break;
+ case AUDC_SET_STEREO:
+ if (*sarg) {
+ msp->watch_stereo=0;
+ del_timer(&msp->wake_stereo);
+ msp->stereo = *sarg;
+ msp3400c_setstereo(client,*sarg);
+ }
+ break;
+
+ case AUDC_GET_DC:
+ if (msp->simple)
+ break; /* fixme */
+ *sarg = ((int)msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b) +
+ (int)msp3400c_read(client, I2C_MSP3400C_DFP, 0x1c));
+ break;
+#endif
+ default:
+ /* nothing */
+ }
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+int msp3400_init_module(void)
+{
+ i2c_add_driver(&driver);
+ return 0;
+}
+
+void msp3400_cleanup_module(void)
+{
+ i2c_del_driver(&driver);
+}
+
+module_init(msp3400_init_module);
+module_exit(msp3400_cleanup_module);
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/planb.c b/drivers/media/video/planb.c
new file mode 100644
index 000000000..94707619d
--- /dev/null
+++ b/drivers/media/video/planb.c
@@ -0,0 +1,2341 @@
+/*
+ planb - PlanB frame grabber driver
+
+ PlanB is used in the 7x00/8x00 series of PowerMacintosh
+ Computers as video input DMA controller.
+
+ Copyright (C) 1998 Michel Lanners (mlan@cpu.lu)
+
+ Based largely on the bttv driver by Ralph Metzler (rjkm@thp.uni-koeln.de)
+
+ Additional debugging and coding by Takashi Oe (toe@unlserve.unl.edu)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/* $Id: planb.c,v 1.18 1999/05/02 17:36:34 mlan Exp $ */
+
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/malloc.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/vmalloc.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/wrapper.h>
+#include <linux/tqueue.h>
+#include <linux/videodev.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/prom.h>
+#include <asm/dbdma.h>
+#include <asm/pgtable.h>
+#include <asm/page.h>
+#include <asm/irq.h>
+
+#include "planb.h"
+#include "saa7196.h"
+
+
+/* Would you mind for some ugly debugging? */
+//#define DEBUG(x...) printk(KERN_DEBUG ## x) /* Debug driver */
+#define DEBUG(x...) /* Don't debug driver */
+//#define IDEBUG(x...) printk(KERN_DEBUG ## x) /* Debug interrupt part */
+#define IDEBUG(x...) /* Don't debug interrupt part */
+
+/* Ever seen a Mac with more than 1 of these? */
+#define PLANB_MAX 1
+
+static int planb_num;
+static struct planb planbs[PLANB_MAX];
+static volatile struct planb_registers *planb_regs;
+
+static int def_norm = PLANB_DEF_NORM; /* default norm */
+
+MODULE_PARM(def_norm, "i");
+MODULE_PARM_DESC(def_norm, "Default startup norm (0=PAL, 1=NTSC, 2=SECAM)");
+
+/* ------------------ PlanB Exported Functions ------------------ */
+static long planb_write(struct video_device *, const char *, unsigned long, int);
+static long planb_read(struct video_device *, char *, unsigned long, int);
+static int planb_open(struct video_device *, int);
+static void planb_close(struct video_device *);
+static int planb_ioctl(struct video_device *, unsigned int, void *);
+static int planb_init_done(struct video_device *);
+static int planb_mmap(struct video_device *, const char *, unsigned long);
+static void planb_irq(int, void *, struct pt_regs *);
+static void release_planb(void);
+int init_planbs(struct video_init *);
+
+/* ------------------ PlanB Internal Functions ------------------ */
+static int planb_prepare_open(struct planb *);
+static void planb_prepare_close(struct planb *);
+static void saa_write_reg(unsigned char, unsigned char);
+static unsigned char saa_status(int, struct planb *);
+static void saa_set(unsigned char, unsigned char, struct planb *);
+static void saa_init_regs(struct planb *);
+static int grabbuf_alloc(struct planb *);
+static int vgrab(struct planb *, struct video_mmap *);
+static void add_clip(struct planb *, struct video_clip *);
+static void fill_cmd_buff(struct planb *);
+static void cmd_buff(struct planb *);
+static volatile struct dbdma_cmd *setup_grab_cmd(int, struct planb *);
+static void overlay_start(struct planb *);
+static void overlay_stop(struct planb *);
+static inline void tab_cmd_dbdma(volatile struct dbdma_cmd *, unsigned short,
+ unsigned int);
+static inline void tab_cmd_store(volatile struct dbdma_cmd *, unsigned int,
+ unsigned int);
+static inline void tab_cmd_gen(volatile struct dbdma_cmd *, unsigned short,
+ unsigned short, unsigned int, unsigned int);
+static int init_planb(struct planb *);
+static int find_planb(void);
+static void planb_pre_capture(int, int, struct planb *);
+static volatile struct dbdma_cmd *cmd_geo_setup(volatile struct dbdma_cmd *,
+ int, int, int, int, int, struct planb *);
+static inline void planb_dbdma_stop(volatile struct dbdma_regs *);
+static unsigned int saa_geo_setup(int, int, int, int, struct planb *);
+static inline int overlay_is_active(struct planb *);
+
+/*******************************/
+/* Memory management functions */
+/*******************************/
+
+static int grabbuf_alloc(struct planb *pb)
+{
+ int i, npage;
+
+ npage = MAX_GBUFFERS * ((PLANB_MAX_FBUF / PAGE_SIZE + 1)
+#ifndef PLANB_GSCANLINE
+ + MAX_LNUM
+#endif /* PLANB_GSCANLINE */
+ );
+ if ((pb->rawbuf = (unsigned char**) kmalloc (npage
+ * sizeof(unsigned long), GFP_KERNEL)) == 0)
+ return -ENOMEM;
+ for (i = 0; i < npage; i++) {
+ pb->rawbuf[i] = (unsigned char *)__get_free_pages(GFP_KERNEL
+ |GFP_DMA, 0);
+ if (!pb->rawbuf[i])
+ break;
+ mem_map_reserve(virt_to_page(pb->rawbuf[i]));
+ }
+ if (i-- < npage) {
+ printk(KERN_DEBUG "PlanB: init_grab: grab buffer not allocated\n");
+ for (; i > 0; i--) {
+ mem_map_unreserve(virt_to_page(pb->rawbuf[i]));
+ free_pages((unsigned long)pb->rawbuf[i], 0);
+ }
+ kfree(pb->rawbuf);
+ return -ENOBUFS;
+ }
+ pb->rawbuf_size = npage;
+ return 0;
+}
+
+/*****************************/
+/* Hardware access functions */
+/*****************************/
+
+static void saa_write_reg(unsigned char addr, unsigned char val)
+{
+ planb_regs->saa_addr = addr; eieio();
+ planb_regs->saa_regval = val; eieio();
+ return;
+}
+
+/* return status byte 0 or 1: */
+static unsigned char saa_status(int byte, struct planb *pb)
+{
+ saa_regs[pb->win.norm][SAA7196_STDC] =
+ (saa_regs[pb->win.norm][SAA7196_STDC] & ~2) | ((byte & 1) << 1);
+ saa_write_reg (SAA7196_STDC, saa_regs[pb->win.norm][SAA7196_STDC]);
+
+ /* Let's wait 30msec for this one */
+ current->state = TASK_INTERRUPTIBLE;
+#if LINUX_VERSION_CODE >= 0x02017F
+ schedule_timeout(30 * HZ / 1000);
+#else
+ current->timeout = jiffies + 30 * HZ / 1000; /* 30 ms */;
+ schedule();
+#endif
+
+ return (unsigned char)in_8 (&planb_regs->saa_status);
+}
+
+static void saa_set(unsigned char addr, unsigned char val, struct planb *pb)
+{
+ if(saa_regs[pb->win.norm][addr] != val) {
+ saa_regs[pb->win.norm][addr] = val;
+ saa_write_reg (addr, val);
+ }
+ return;
+}
+
+static void saa_init_regs(struct planb *pb)
+{
+ int i;
+
+ for (i = 0; i < SAA7196_NUMREGS; i++)
+ saa_write_reg (i, saa_regs[pb->win.norm][i]);
+}
+
+static unsigned int saa_geo_setup(int width, int height, int interlace, int bpp,
+ struct planb *pb)
+{
+ int ht, norm = pb->win.norm;
+
+ switch(bpp) {
+ case 2:
+ /* RGB555+a 1x16-bit + 16-bit transparent */
+ saa_regs[norm][SAA7196_FMTS] &= ~0x3;
+ break;
+ case 1:
+ case 4:
+ /* RGB888 1x24-bit + 8-bit transparent */
+ saa_regs[norm][SAA7196_FMTS] &= ~0x1;
+ saa_regs[norm][SAA7196_FMTS] |= 0x2;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ht = (interlace ? height / 2 : height);
+ saa_regs[norm][SAA7196_OUTPIX] = (unsigned char) (width & 0x00ff);
+ saa_regs[norm][SAA7196_HFILT] = (saa_regs[norm][SAA7196_HFILT] & ~0x3)
+ | (width >> 8 & 0x3);
+ saa_regs[norm][SAA7196_OUTLINE] = (unsigned char) (ht & 0xff);
+ saa_regs[norm][SAA7196_VYP] = (saa_regs[norm][SAA7196_VYP] & ~0x3)
+ | (ht >> 8 & 0x3);
+ /* feed both fields if interlaced, or else feed only even fields */
+ saa_regs[norm][SAA7196_FMTS] = (interlace) ?
+ (saa_regs[norm][SAA7196_FMTS] & ~0x60)
+ : (saa_regs[norm][SAA7196_FMTS] | 0x60);
+ /* transparent mode; extended format enabled */
+ saa_regs[norm][SAA7196_DPATH] |= 0x3;
+
+ return 0;
+}
+
+/***************************/
+/* DBDMA support functions */
+/***************************/
+
+static inline void planb_dbdma_restart(volatile struct dbdma_regs *ch)
+{
+ out_le32(&ch->control, PLANB_CLR(RUN));
+ out_le32(&ch->control, PLANB_SET(RUN|WAKE) | PLANB_CLR(PAUSE));
+}
+
+static inline void planb_dbdma_stop(volatile struct dbdma_regs *ch)
+{
+ int i = 0;
+
+ out_le32(&ch->control, PLANB_CLR(RUN) | PLANB_SET(FLUSH));
+ while((in_le32(&ch->status) == (ACTIVE | FLUSH)) && (i < 999)) {
+ IDEBUG("PlanB: waiting for DMA to stop\n");
+ i++;
+ }
+}
+
+static inline void tab_cmd_dbdma(volatile struct dbdma_cmd *ch,
+ unsigned short command, unsigned int cmd_dep)
+{
+ st_le16(&ch->command, command);
+ st_le32(&ch->cmd_dep, cmd_dep);
+}
+
+static inline void tab_cmd_store(volatile struct dbdma_cmd *ch,
+ unsigned int phy_addr, unsigned int cmd_dep)
+{
+ st_le16(&ch->command, STORE_WORD | KEY_SYSTEM);
+ st_le16(&ch->req_count, 4);
+ st_le32(&ch->phy_addr, phy_addr);
+ st_le32(&ch->cmd_dep, cmd_dep);
+}
+
+static inline void tab_cmd_gen(volatile struct dbdma_cmd *ch,
+ unsigned short command, unsigned short req_count,
+ unsigned int phy_addr, unsigned int cmd_dep)
+{
+ st_le16(&ch->command, command);
+ st_le16(&ch->req_count, req_count);
+ st_le32(&ch->phy_addr, phy_addr);
+ st_le32(&ch->cmd_dep, cmd_dep);
+}
+
+static volatile struct dbdma_cmd *cmd_geo_setup(
+ volatile struct dbdma_cmd *c1, int width, int height, int interlace,
+ int bpp, int clip, struct planb *pb)
+{
+ int norm = pb->win.norm;
+
+ if((saa_geo_setup(width, height, interlace, bpp, pb)) != 0)
+ return (volatile struct dbdma_cmd *)NULL;
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr),
+ SAA7196_FMTS);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval),
+ saa_regs[norm][SAA7196_FMTS]);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr),
+ SAA7196_DPATH);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval),
+ saa_regs[norm][SAA7196_DPATH]);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->even),
+ bpp | ((clip)? PLANB_CLIPMASK: 0));
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->odd),
+ bpp | ((clip)? PLANB_CLIPMASK: 0));
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr),
+ SAA7196_OUTPIX);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval),
+ saa_regs[norm][SAA7196_OUTPIX]);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr),
+ SAA7196_HFILT);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval),
+ saa_regs[norm][SAA7196_HFILT]);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr),
+ SAA7196_OUTLINE);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval),
+ saa_regs[norm][SAA7196_OUTLINE]);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr),
+ SAA7196_VYP);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval),
+ saa_regs[norm][SAA7196_VYP]);
+ return c1;
+}
+
+/******************************/
+/* misc. supporting functions */
+/******************************/
+
+static void __planb_wait(struct planb *pb)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ add_wait_queue(&pb->lockq, &wait);
+repeat:
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ if (pb->lock) {
+ schedule();
+ goto repeat;
+ }
+ remove_wait_queue(&pb->lockq, &wait);
+ current->state = TASK_RUNNING;
+}
+
+static inline void planb_wait(struct planb *pb)
+{
+ DEBUG("PlanB: planb_wait\n");
+ if(pb->lock)
+ __planb_wait(pb);
+}
+
+static inline void planb_lock(struct planb *pb)
+{
+ DEBUG("PlanB: planb_lock\n");
+ if(pb->lock)
+ __planb_wait(pb);
+ pb->lock = 1;
+}
+
+static inline void planb_unlock(struct planb *pb)
+{
+ DEBUG("PlanB: planb_unlock\n");
+ pb->lock = 0;
+ wake_up(&pb->lockq);
+}
+
+/***************/
+/* Driver Core */
+/***************/
+
+static int planb_prepare_open(struct planb *pb)
+{
+ int i, size;
+
+ /* allocate memory for two plus alpha command buffers (size: max lines,
+ plus 40 commands handling, plus 1 alignment), plus dummy command buf,
+ plus clipmask buffer, plus frame grabbing status */
+ size = (pb->tab_size*(2+MAX_GBUFFERS*TAB_FACTOR)+1+MAX_GBUFFERS
+ * PLANB_DUMMY)*sizeof(struct dbdma_cmd)
+ +(PLANB_MAXLINES*((PLANB_MAXPIXELS+7)& ~7))/8
+ +MAX_GBUFFERS*sizeof(unsigned int);
+ if ((pb->priv_space = kmalloc (size, GFP_KERNEL)) == 0)
+ return -ENOMEM;
+ memset ((void *) pb->priv_space, 0, size);
+ pb->overlay_last1 = pb->ch1_cmd = (volatile struct dbdma_cmd *)
+ DBDMA_ALIGN (pb->priv_space);
+ pb->overlay_last2 = pb->ch2_cmd = pb->ch1_cmd + pb->tab_size;
+ pb->ch1_cmd_phys = virt_to_bus(pb->ch1_cmd);
+ pb->cap_cmd[0] = pb->ch2_cmd + pb->tab_size;
+ pb->pre_cmd[0] = pb->cap_cmd[0] + pb->tab_size * TAB_FACTOR;
+ for (i = 1; i < MAX_GBUFFERS; i++) {
+ pb->cap_cmd[i] = pb->pre_cmd[i-1] + PLANB_DUMMY;
+ pb->pre_cmd[i] = pb->cap_cmd[i] + pb->tab_size * TAB_FACTOR;
+ }
+ pb->frame_stat=(volatile unsigned int *)(pb->pre_cmd[MAX_GBUFFERS-1]
+ + PLANB_DUMMY);
+ pb->mask = (unsigned char *)(pb->frame_stat+MAX_GBUFFERS);
+
+ pb->rawbuf = NULL;
+ pb->rawbuf_size = 0;
+ pb->grabbing = 0;
+ for (i = 0; i < MAX_GBUFFERS; i++) {
+ pb->frame_stat[i] = GBUFFER_UNUSED;
+ pb->gwidth[i] = 0;
+ pb->gheight[i] = 0;
+ pb->gfmt[i] = 0;
+ pb->gnorm_switch[i] = 0;
+#ifndef PLANB_GSCANLINE
+ pb->lsize[i] = 0;
+ pb->lnum[i] = 0;
+#endif /* PLANB_GSCANLINE */
+ }
+ pb->gcount = 0;
+ pb->suspend = 0;
+ pb->last_fr = -999;
+ pb->prev_last_fr = -999;
+
+ /* Reset DMA controllers */
+ planb_dbdma_stop(&pb->planb_base->ch2);
+ planb_dbdma_stop(&pb->planb_base->ch1);
+
+ return 0;
+}
+
+static void planb_prepare_close(struct planb *pb)
+{
+ int i;
+
+ /* make sure the dma's are idle */
+ planb_dbdma_stop(&pb->planb_base->ch2);
+ planb_dbdma_stop(&pb->planb_base->ch1);
+ /* free kernel memory of command buffers */
+ if(pb->priv_space != 0) {
+ kfree (pb->priv_space);
+ pb->priv_space = 0;
+ pb->cmd_buff_inited = 0;
+ }
+ if(pb->rawbuf) {
+ for (i = 0; i < pb->rawbuf_size; i++) {
+ mem_map_unreserve(virt_to_page(pb->rawbuf[i]));
+ free_pages((unsigned long)pb->rawbuf[i], 0);
+ }
+ kfree(pb->rawbuf);
+ }
+ pb->rawbuf = NULL;
+}
+
+/*****************************/
+/* overlay support functions */
+/*****************************/
+
+static void overlay_start(struct planb *pb)
+{
+
+ DEBUG("PlanB: overlay_start()\n");
+
+ if(ACTIVE & in_le32(&pb->planb_base->ch1.status)) {
+
+ DEBUG("PlanB: presumably, grabbing is in progress...\n");
+
+ planb_dbdma_stop(&pb->planb_base->ch2);
+ out_le32 (&pb->planb_base->ch2.cmdptr,
+ virt_to_bus(pb->ch2_cmd));
+ planb_dbdma_restart(&pb->planb_base->ch2);
+ st_le16 (&pb->ch1_cmd->command, DBDMA_NOP);
+ tab_cmd_dbdma(pb->last_cmd[pb->last_fr],
+ DBDMA_NOP | BR_ALWAYS,
+ virt_to_bus(pb->ch1_cmd));
+ eieio();
+ pb->prev_last_fr = pb->last_fr;
+ pb->last_fr = -2;
+ if(!(ACTIVE & in_le32(&pb->planb_base->ch1.status))) {
+ IDEBUG("PlanB: became inactive "
+ "in the mean time... reactivating\n");
+ planb_dbdma_stop(&pb->planb_base->ch1);
+ out_le32 (&pb->planb_base->ch1.cmdptr,
+ virt_to_bus(pb->ch1_cmd));
+ planb_dbdma_restart(&pb->planb_base->ch1);
+ }
+ } else {
+
+ DEBUG("PlanB: currently idle, so can do whatever\n");
+
+ planb_dbdma_stop(&pb->planb_base->ch2);
+ planb_dbdma_stop(&pb->planb_base->ch1);
+ st_le32 (&pb->planb_base->ch2.cmdptr,
+ virt_to_bus(pb->ch2_cmd));
+ st_le32 (&pb->planb_base->ch1.cmdptr,
+ virt_to_bus(pb->ch1_cmd));
+ out_le16 (&pb->ch1_cmd->command, DBDMA_NOP);
+ planb_dbdma_restart(&pb->planb_base->ch2);
+ planb_dbdma_restart(&pb->planb_base->ch1);
+ pb->last_fr = -1;
+ }
+ return;
+}
+
+static void overlay_stop(struct planb *pb)
+{
+ DEBUG("PlanB: overlay_stop()\n");
+
+ if(pb->last_fr == -1) {
+
+ DEBUG("PlanB: no grabbing, it seems...\n");
+
+ planb_dbdma_stop(&pb->planb_base->ch2);
+ planb_dbdma_stop(&pb->planb_base->ch1);
+ pb->last_fr = -999;
+ } else if(pb->last_fr == -2) {
+ unsigned int cmd_dep;
+ tab_cmd_dbdma(pb->cap_cmd[pb->prev_last_fr], DBDMA_STOP, 0);
+ eieio();
+ cmd_dep = (unsigned int)in_le32(&pb->overlay_last1->cmd_dep);
+ if(overlay_is_active(pb)) {
+
+ DEBUG("PlanB: overlay is currently active\n");
+
+ planb_dbdma_stop(&pb->planb_base->ch2);
+ planb_dbdma_stop(&pb->planb_base->ch1);
+ if(cmd_dep != pb->ch1_cmd_phys) {
+ out_le32(&pb->planb_base->ch1.cmdptr,
+ virt_to_bus(pb->overlay_last1));
+ planb_dbdma_restart(&pb->planb_base->ch1);
+ }
+ }
+ pb->last_fr = pb->prev_last_fr;
+ pb->prev_last_fr = -999;
+ }
+ return;
+}
+
+static void suspend_overlay(struct planb *pb)
+{
+ int fr = -1;
+ struct dbdma_cmd last;
+
+ DEBUG("PlanB: suspend_overlay: %d\n", pb->suspend);
+
+ if(pb->suspend++)
+ return;
+ if(ACTIVE & in_le32(&pb->planb_base->ch1.status)) {
+ if(pb->last_fr == -2) {
+ fr = pb->prev_last_fr;
+ memcpy(&last, (void*)pb->last_cmd[fr], sizeof(last));
+ tab_cmd_dbdma(pb->last_cmd[fr], DBDMA_STOP, 0);
+ }
+ if(overlay_is_active(pb)) {
+ planb_dbdma_stop(&pb->planb_base->ch2);
+ planb_dbdma_stop(&pb->planb_base->ch1);
+ pb->suspended.overlay = 1;
+ pb->suspended.frame = fr;
+ memcpy(&pb->suspended.cmd, &last, sizeof(last));
+ return;
+ }
+ }
+ pb->suspended.overlay = 0;
+ pb->suspended.frame = fr;
+ memcpy(&pb->suspended.cmd, &last, sizeof(last));
+ return;
+}
+
+static void resume_overlay(struct planb *pb)
+{
+
+ DEBUG("PlanB: resume_overlay: %d\n", pb->suspend);
+
+ if(pb->suspend > 1)
+ return;
+ if(pb->suspended.frame != -1) {
+ memcpy((void*)pb->last_cmd[pb->suspended.frame],
+ &pb->suspended.cmd, sizeof(pb->suspended.cmd));
+ }
+ if(ACTIVE & in_le32(&pb->planb_base->ch1.status)) {
+ goto finish;
+ }
+ if(pb->suspended.overlay) {
+
+ DEBUG("PlanB: overlay being resumed\n");
+
+ st_le16 (&pb->ch1_cmd->command, DBDMA_NOP);
+ st_le16 (&pb->ch2_cmd->command, DBDMA_NOP);
+ /* Set command buffer addresses */
+ st_le32(&pb->planb_base->ch1.cmdptr,
+ virt_to_bus(pb->overlay_last1));
+ out_le32(&pb->planb_base->ch2.cmdptr,
+ virt_to_bus(pb->overlay_last2));
+ /* Start the DMA controller */
+ out_le32 (&pb->planb_base->ch2.control,
+ PLANB_CLR(PAUSE) | PLANB_SET(RUN|WAKE));
+ out_le32 (&pb->planb_base->ch1.control,
+ PLANB_CLR(PAUSE) | PLANB_SET(RUN|WAKE));
+ } else if(pb->suspended.frame != -1) {
+ out_le32(&pb->planb_base->ch1.cmdptr,
+ virt_to_bus(pb->last_cmd[pb->suspended.frame]));
+ out_le32 (&pb->planb_base->ch1.control,
+ PLANB_CLR(PAUSE) | PLANB_SET(RUN|WAKE));
+ }
+
+finish:
+ pb->suspend--;
+ wake_up_interruptible(&pb->suspendq);
+}
+
+static void add_clip(struct planb *pb, struct video_clip *clip)
+{
+ volatile unsigned char *base;
+ int xc = clip->x, yc = clip->y;
+ int wc = clip->width, hc = clip->height;
+ int ww = pb->win.width, hw = pb->win.height;
+ int x, y, xtmp1, xtmp2;
+
+ DEBUG("PlanB: clip %dx%d+%d+%d\n", wc, hc, xc, yc);
+
+ if(xc < 0) {
+ wc += xc;
+ xc = 0;
+ }
+ if(yc < 0) {
+ hc += yc;
+ yc = 0;
+ }
+ if(xc + wc > ww)
+ wc = ww - xc;
+ if(wc <= 0) /* Nothing to do */
+ return;
+ if(yc + hc > hw)
+ hc = hw - yc;
+
+ for (y = yc; y < yc+hc; y++) {
+ xtmp1=xc>>3;
+ xtmp2=(xc+wc)>>3;
+ base = pb->mask + y*96;
+ if(xc != 0 || wc >= 8)
+ *(base + xtmp1) &= (unsigned char)(0x00ff &
+ (0xff00 >> (xc&7)));
+ for (x = xtmp1 + 1; x < xtmp2; x++) {
+ *(base + x) = 0;
+ }
+ if(xc < (ww & ~0x7))
+ *(base + xtmp2) &= (unsigned char)(0x00ff >>
+ ((xc+wc) & 7));
+ }
+
+ return;
+}
+
+static void fill_cmd_buff(struct planb *pb)
+{
+ int restore = 0;
+ volatile struct dbdma_cmd last;
+
+ DEBUG("PlanB: fill_cmd_buff()\n");
+
+ if(pb->overlay_last1 != pb->ch1_cmd) {
+ restore = 1;
+ last = *(pb->overlay_last1);
+ }
+ memset ((void *) pb->ch1_cmd, 0, 2 * pb->tab_size
+ * sizeof(struct dbdma_cmd));
+ cmd_buff (pb);
+ if(restore)
+ *(pb->overlay_last1) = last;
+ if(pb->suspended.overlay) {
+ unsigned long jump_addr = in_le32(&pb->overlay_last1->cmd_dep);
+ if(jump_addr != pb->ch1_cmd_phys) {
+ int i;
+
+ DEBUG("PlanB: adjusting ch1's jump address\n");
+
+ for(i = 0; i < MAX_GBUFFERS; i++) {
+ if(pb->need_pre_capture[i]) {
+ if(jump_addr == virt_to_bus(pb->pre_cmd[i]))
+ goto found;
+ } else {
+ if(jump_addr == virt_to_bus(pb->cap_cmd[i]))
+ goto found;
+ }
+ }
+
+ DEBUG("PlanB: not found...\n");
+
+ goto out;
+found:
+ if(pb->need_pre_capture[i])
+ out_le32(&pb->pre_cmd[i]->phy_addr,
+ virt_to_bus(pb->overlay_last1));
+ else
+ out_le32(&pb->cap_cmd[i]->phy_addr,
+ virt_to_bus(pb->overlay_last1));
+ }
+ }
+out:
+ pb->cmd_buff_inited = 1;
+
+ return;
+}
+
+static void cmd_buff(struct planb *pb)
+{
+ int i, bpp, count, nlines, stepsize, interlace;
+ unsigned long base, jump, addr_com, addr_dep;
+ volatile struct dbdma_cmd *c1 = pb->ch1_cmd;
+ volatile struct dbdma_cmd *c2 = pb->ch2_cmd;
+
+ interlace = pb->win.interlace;
+ bpp = pb->win.bpp;
+ count = (bpp * ((pb->win.x + pb->win.width > pb->win.swidth) ?
+ (pb->win.swidth - pb->win.x) : pb->win.width));
+ nlines = ((pb->win.y + pb->win.height > pb->win.sheight) ?
+ (pb->win.sheight - pb->win.y) : pb->win.height);
+
+ /* Do video in: */
+
+ /* Preamble commands: */
+ addr_com = virt_to_bus(c1);
+ addr_dep = virt_to_bus(&c1->cmd_dep);
+ tab_cmd_dbdma(c1++, DBDMA_NOP, 0);
+ jump = virt_to_bus(c1+16); /* 14 by cmd_geo_setup() and 2 for padding */
+ if((c1 = cmd_geo_setup(c1, pb->win.width, pb->win.height, interlace,
+ bpp, 1, pb)) == NULL) {
+ printk(KERN_WARNING "PlanB: encountered serious problems\n");
+ tab_cmd_dbdma(pb->ch1_cmd + 1, DBDMA_STOP, 0);
+ tab_cmd_dbdma(pb->ch2_cmd + 1, DBDMA_STOP, 0);
+ return;
+ }
+ tab_cmd_store(c1++, addr_com, (unsigned)(DBDMA_NOP | BR_ALWAYS) << 16);
+ tab_cmd_store(c1++, addr_dep, jump);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.wait_sel),
+ PLANB_SET(FIELD_SYNC));
+ /* (1) wait for field sync to be set */
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(ODD_FIELD));
+ /* wait for field sync to be cleared */
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0);
+ /* if not odd field, wait until field sync is set again */
+ tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFSET, virt_to_bus(c1-3)); c1++;
+ /* assert ch_sync to ch2 */
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch2.control),
+ PLANB_SET(CH_SYNC));
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(DMA_ABORT));
+
+ base = (pb->frame_buffer_phys + pb->offset + pb->win.y * (pb->win.bpl
+ + pb->win.pad) + pb->win.x * bpp);
+
+ if (interlace) {
+ stepsize = 2;
+ jump = virt_to_bus(c1 + (nlines + 1) / 2);
+ } else {
+ stepsize = 1;
+ jump = virt_to_bus(c1 + nlines);
+ }
+
+ /* even field data: */
+ for (i=0; i < nlines; i += stepsize, c1++)
+ tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET,
+ count, base + i * (pb->win.bpl + pb->win.pad), jump);
+
+ /* For non-interlaced, we use even fields only */
+ if (!interlace)
+ goto cmd_tab_data_end;
+
+ /* Resync to odd field */
+ /* (2) wait for field sync to be set */
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(ODD_FIELD));
+ /* wait for field sync to be cleared */
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0);
+ /* if not odd field, wait until field sync is set again */
+ tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFCLR, virt_to_bus(c1-3)); c1++;
+ /* assert ch_sync to ch2 */
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch2.control),
+ PLANB_SET(CH_SYNC));
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(DMA_ABORT));
+
+ /* odd field data: */
+ jump = virt_to_bus(c1 + nlines / 2);
+ for (i=1; i < nlines; i += stepsize, c1++)
+ tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET, count,
+ base + i * (pb->win.bpl + pb->win.pad), jump);
+
+ /* And jump back to the start */
+cmd_tab_data_end:
+ pb->overlay_last1 = c1; /* keep a pointer to the last command */
+ tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, virt_to_bus(pb->ch1_cmd));
+
+ /* Clipmask command buffer */
+
+ /* Preamble commands: */
+ tab_cmd_dbdma(c2++, DBDMA_NOP, 0);
+ tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.wait_sel),
+ PLANB_SET(CH_SYNC));
+ /* wait until ch1 asserts ch_sync */
+ tab_cmd_dbdma(c2++, DBDMA_NOP | WAIT_IFCLR, 0);
+ /* clear ch_sync asserted by ch1 */
+ tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.control),
+ PLANB_CLR(CH_SYNC));
+ tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.wait_sel),
+ PLANB_SET(FIELD_SYNC));
+ tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.br_sel),
+ PLANB_SET(ODD_FIELD));
+
+ /* jump to end of even field if appropriate */
+ /* this points to (interlace)? pos. C: pos. B */
+ jump = (interlace) ? virt_to_bus(c2 + (nlines + 1) / 2 + 2):
+ virt_to_bus(c2 + nlines + 2);
+ /* if odd field, skip over to odd field clipmasking */
+ tab_cmd_dbdma(c2++, DBDMA_NOP | BR_IFSET, jump);
+
+ /* even field mask: */
+ tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.br_sel),
+ PLANB_SET(DMA_ABORT));
+ /* this points to pos. B */
+ jump = (interlace) ? virt_to_bus(c2 + nlines + 1):
+ virt_to_bus(c2 + nlines);
+ base = virt_to_bus(pb->mask);
+ for (i=0; i < nlines; i += stepsize, c2++)
+ tab_cmd_gen(c2, OUTPUT_MORE | KEY_STREAM0 | BR_IFSET, 96,
+ base + i * 96, jump);
+
+ /* For non-interlaced, we use only even fields */
+ if(!interlace)
+ goto cmd_tab_mask_end;
+
+ /* odd field mask: */
+/* C */ tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.br_sel),
+ PLANB_SET(DMA_ABORT));
+ /* this points to pos. B */
+ jump = virt_to_bus(c2 + nlines / 2);
+ base = virt_to_bus(pb->mask);
+ for (i=1; i < nlines; i += 2, c2++) /* abort if set */
+ tab_cmd_gen(c2, OUTPUT_MORE | KEY_STREAM0 | BR_IFSET, 96,
+ base + i * 96, jump);
+
+ /* Inform channel 1 and jump back to start */
+cmd_tab_mask_end:
+ /* ok, I just realized this is kind of flawed. */
+ /* this part is reached only after odd field clipmasking. */
+ /* wanna clean up? */
+ /* wait for field sync to be set */
+ /* corresponds to fsync (1) of ch1 */
+/* B */ tab_cmd_dbdma(c2++, DBDMA_NOP | WAIT_IFCLR, 0);
+ /* restart ch1, meant to clear any dead bit or something */
+ tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch1.control),
+ PLANB_CLR(RUN));
+ tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch1.control),
+ PLANB_SET(RUN));
+ pb->overlay_last2 = c2; /* keep a pointer to the last command */
+ /* start over even field clipmasking */
+ tab_cmd_dbdma(c2, DBDMA_NOP | BR_ALWAYS, virt_to_bus(pb->ch2_cmd));
+
+ eieio();
+ return;
+}
+
+/*********************************/
+/* grabdisplay support functions */
+/*********************************/
+
+static int palette2fmt[] = {
+ 0,
+ PLANB_GRAY,
+ 0,
+ 0,
+ 0,
+ PLANB_COLOUR32,
+ PLANB_COLOUR15,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+};
+
+#define PLANB_PALETTE_MAX 15
+
+static inline int overlay_is_active(struct planb *pb)
+{
+ unsigned int size = pb->tab_size * sizeof(struct dbdma_cmd);
+ unsigned int caddr = (unsigned)in_le32(&pb->planb_base->ch1.cmdptr);
+
+ return (in_le32(&pb->overlay_last1->cmd_dep) == pb->ch1_cmd_phys)
+ && (caddr < (pb->ch1_cmd_phys + size))
+ && (caddr >= (unsigned)pb->ch1_cmd_phys);
+}
+
+static int vgrab(struct planb *pb, struct video_mmap *mp)
+{
+ unsigned int fr = mp->frame;
+ unsigned int format;
+
+ if(pb->rawbuf==NULL) {
+ int err;
+ if((err=grabbuf_alloc(pb)))
+ return err;
+ }
+
+ IDEBUG("PlanB: grab %d: %dx%d(%u)\n", pb->grabbing,
+ mp->width, mp->height, fr);
+
+ if(pb->grabbing >= MAX_GBUFFERS)
+ return -ENOBUFS;
+ if(fr > (MAX_GBUFFERS - 1) || fr < 0)
+ return -EINVAL;
+ if(mp->height <= 0 || mp->width <= 0)
+ return -EINVAL;
+ if(mp->format < 0 || mp->format >= PLANB_PALETTE_MAX)
+ return -EINVAL;
+ if((format = palette2fmt[mp->format]) == 0)
+ return -EINVAL;
+ if (mp->height * mp->width * format > PLANB_MAX_FBUF) /* format = bpp */
+ return -EINVAL;
+
+ planb_lock(pb);
+ if(mp->width != pb->gwidth[fr] || mp->height != pb->gheight[fr] ||
+ format != pb->gfmt[fr] || (pb->gnorm_switch[fr])) {
+ int i;
+#ifndef PLANB_GSCANLINE
+ unsigned int osize = pb->gwidth[fr] * pb->gheight[fr]
+ * pb->gfmt[fr];
+ unsigned int nsize = mp->width * mp->height * format;
+#endif
+
+ IDEBUG("PlanB: gwidth = %d, gheight = %d, mp->format = %u\n",
+ mp->width, mp->height, mp->format);
+
+#ifndef PLANB_GSCANLINE
+ if(pb->gnorm_switch[fr])
+ nsize = 0;
+ if (nsize < osize) {
+ for(i = pb->gbuf_idx[fr]; osize > 0; i++) {
+ memset((void *)pb->rawbuf[i], 0, PAGE_SIZE);
+ osize -= PAGE_SIZE;
+ }
+ }
+ for(i = pb->l_fr_addr_idx[fr]; i < pb->l_fr_addr_idx[fr]
+ + pb->lnum[fr]; i++)
+ memset((void *)pb->rawbuf[i], 0, PAGE_SIZE);
+#else
+/* XXX TODO */
+/*
+ if(pb->gnorm_switch[fr])
+ memset((void *)pb->gbuffer[fr], 0,
+ pb->gbytes_per_line * pb->gheight[fr]);
+ else {
+ if(mp->
+ for(i = 0; i < pb->gheight[fr]; i++) {
+ memset((void *)(pb->gbuffer[fr]
+ + pb->gbytes_per_line * i
+ }
+ }
+*/
+#endif
+ pb->gwidth[fr] = mp->width;
+ pb->gheight[fr] = mp->height;
+ pb->gfmt[fr] = format;
+ pb->last_cmd[fr] = setup_grab_cmd(fr, pb);
+ planb_pre_capture(fr, pb->gfmt[fr], pb); /* gfmt = bpp */
+ pb->need_pre_capture[fr] = 1;
+ pb->gnorm_switch[fr] = 0;
+ } else
+ pb->need_pre_capture[fr] = 0;
+ pb->frame_stat[fr] = GBUFFER_GRABBING;
+ if(!(ACTIVE & in_le32(&pb->planb_base->ch1.status))) {
+
+ IDEBUG("PlanB: ch1 inactive, initiating grabbing\n");
+
+ planb_dbdma_stop(&pb->planb_base->ch1);
+ if(pb->need_pre_capture[fr]) {
+
+ IDEBUG("PlanB: padding pre-capture sequence\n");
+
+ out_le32 (&pb->planb_base->ch1.cmdptr,
+ virt_to_bus(pb->pre_cmd[fr]));
+ } else {
+ tab_cmd_dbdma(pb->last_cmd[fr], DBDMA_STOP, 0);
+ tab_cmd_dbdma(pb->cap_cmd[fr], DBDMA_NOP, 0);
+ /* let's be on the safe side. here is not timing critical. */
+ tab_cmd_dbdma((pb->cap_cmd[fr] + 1), DBDMA_NOP, 0);
+ out_le32 (&pb->planb_base->ch1.cmdptr,
+ virt_to_bus(pb->cap_cmd[fr]));
+ }
+ planb_dbdma_restart(&pb->planb_base->ch1);
+ pb->last_fr = fr;
+ } else {
+ int i;
+
+ IDEBUG("PlanB: ch1 active, grabbing being queued\n");
+
+ if((pb->last_fr == -1) || ((pb->last_fr == -2) &&
+ overlay_is_active(pb))) {
+
+ IDEBUG("PlanB: overlay is active, grabbing defered\n");
+
+ tab_cmd_dbdma(pb->last_cmd[fr],
+ DBDMA_NOP | BR_ALWAYS,
+ virt_to_bus(pb->ch1_cmd));
+ if(pb->need_pre_capture[fr]) {
+
+ IDEBUG("PlanB: padding pre-capture sequence\n");
+
+ tab_cmd_store(pb->pre_cmd[fr],
+ virt_to_bus(&pb->overlay_last1->cmd_dep),
+ virt_to_bus(pb->ch1_cmd));
+ eieio();
+ out_le32 (&pb->overlay_last1->cmd_dep,
+ virt_to_bus(pb->pre_cmd[fr]));
+ } else {
+ tab_cmd_store(pb->cap_cmd[fr],
+ virt_to_bus(&pb->overlay_last1->cmd_dep),
+ virt_to_bus(pb->ch1_cmd));
+ tab_cmd_dbdma((pb->cap_cmd[fr] + 1),
+ DBDMA_NOP, 0);
+ eieio();
+ out_le32 (&pb->overlay_last1->cmd_dep,
+ virt_to_bus(pb->cap_cmd[fr]));
+ }
+ for(i = 0; overlay_is_active(pb) && i < 999; i++)
+ IDEBUG("PlanB: waiting for overlay done\n");
+ tab_cmd_dbdma(pb->ch1_cmd, DBDMA_NOP, 0);
+ pb->prev_last_fr = fr;
+ pb->last_fr = -2;
+ } else if(pb->last_fr == -2) {
+
+ IDEBUG("PlanB: mixed mode detected, grabbing"
+ " will be done before activating overlay\n");
+
+ tab_cmd_dbdma(pb->ch1_cmd, DBDMA_NOP, 0);
+ if(pb->need_pre_capture[fr]) {
+
+ IDEBUG("PlanB: padding pre-capture sequence\n");
+
+ tab_cmd_dbdma(pb->last_cmd[pb->prev_last_fr],
+ DBDMA_NOP | BR_ALWAYS,
+ virt_to_bus(pb->pre_cmd[fr]));
+ eieio();
+ } else {
+ tab_cmd_dbdma(pb->cap_cmd[fr], DBDMA_NOP, 0);
+ if(pb->gwidth[pb->prev_last_fr] !=
+ pb->gwidth[fr]
+ || pb->gheight[pb->prev_last_fr] !=
+ pb->gheight[fr]
+ || pb->gfmt[pb->prev_last_fr] !=
+ pb->gfmt[fr])
+ tab_cmd_dbdma((pb->cap_cmd[fr] + 1),
+ DBDMA_NOP, 0);
+ else
+ tab_cmd_dbdma((pb->cap_cmd[fr] + 1),
+ DBDMA_NOP | BR_ALWAYS,
+ virt_to_bus(pb->cap_cmd[fr] + 16));
+ tab_cmd_dbdma(pb->last_cmd[pb->prev_last_fr],
+ DBDMA_NOP | BR_ALWAYS,
+ virt_to_bus(pb->cap_cmd[fr]));
+ eieio();
+ }
+ tab_cmd_dbdma(pb->last_cmd[fr],
+ DBDMA_NOP | BR_ALWAYS,
+ virt_to_bus(pb->ch1_cmd));
+ eieio();
+ pb->prev_last_fr = fr;
+ pb->last_fr = -2;
+ } else {
+
+ IDEBUG("PlanB: active grabbing session detected\n");
+
+ if(pb->need_pre_capture[fr]) {
+
+ IDEBUG("PlanB: padding pre-capture sequence\n");
+
+ tab_cmd_dbdma(pb->last_cmd[pb->last_fr],
+ DBDMA_NOP | BR_ALWAYS,
+ virt_to_bus(pb->pre_cmd[fr]));
+ eieio();
+ } else {
+ tab_cmd_dbdma(pb->last_cmd[fr], DBDMA_STOP, 0);
+ tab_cmd_dbdma(pb->cap_cmd[fr], DBDMA_NOP, 0);
+ if(pb->gwidth[pb->last_fr] != pb->gwidth[fr]
+ || pb->gheight[pb->last_fr] !=
+ pb->gheight[fr]
+ || pb->gfmt[pb->last_fr] !=
+ pb->gfmt[fr])
+ tab_cmd_dbdma((pb->cap_cmd[fr] + 1),
+ DBDMA_NOP, 0);
+ else
+ tab_cmd_dbdma((pb->cap_cmd[fr] + 1),
+ DBDMA_NOP | BR_ALWAYS,
+ virt_to_bus(pb->cap_cmd[fr] + 16));
+ tab_cmd_dbdma(pb->last_cmd[pb->last_fr],
+ DBDMA_NOP | BR_ALWAYS,
+ virt_to_bus(pb->cap_cmd[fr]));
+ eieio();
+ }
+ pb->last_fr = fr;
+ }
+ if(!(ACTIVE & in_le32(&pb->planb_base->ch1.status))) {
+
+ IDEBUG("PlanB: became inactive in the mean time..."
+ "reactivating\n");
+
+ planb_dbdma_stop(&pb->planb_base->ch1);
+ out_le32 (&pb->planb_base->ch1.cmdptr,
+ virt_to_bus(pb->cap_cmd[fr]));
+ planb_dbdma_restart(&pb->planb_base->ch1);
+ }
+ }
+ pb->grabbing++;
+ planb_unlock(pb);
+
+ return 0;
+}
+
+static void planb_pre_capture(int fr, int bpp, struct planb *pb)
+{
+ volatile struct dbdma_cmd *c1 = pb->pre_cmd[fr];
+ int interlace = (pb->gheight[fr] > pb->maxlines/2)? 1: 0;
+
+ tab_cmd_dbdma(c1++, DBDMA_NOP, 0);
+ if((c1 = cmd_geo_setup(c1, pb->gwidth[fr], pb->gheight[fr], interlace,
+ bpp, 0, pb)) == NULL) {
+ printk(KERN_WARNING "PlanB: encountered some problems\n");
+ tab_cmd_dbdma(pb->pre_cmd[fr] + 1, DBDMA_STOP, 0);
+ return;
+ }
+ /* Sync to even field */
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.wait_sel),
+ PLANB_SET(FIELD_SYNC));
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(ODD_FIELD));
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0);
+ tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFSET, virt_to_bus(c1-3)); c1++;
+ tab_cmd_dbdma(c1++, DBDMA_NOP | INTR_ALWAYS, 0);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(DMA_ABORT));
+ /* For non-interlaced, we use even fields only */
+ if (pb->gheight[fr] <= pb->maxlines/2)
+ goto cmd_tab_data_end;
+ /* Sync to odd field */
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(ODD_FIELD));
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0);
+ tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFCLR, virt_to_bus(c1-3)); c1++;
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(DMA_ABORT));
+cmd_tab_data_end:
+ tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, virt_to_bus(pb->cap_cmd[fr]));
+
+ eieio();
+}
+
+static volatile struct dbdma_cmd *setup_grab_cmd(int fr, struct planb *pb)
+{
+ int i, bpp, count, nlines, stepsize, interlace;
+#ifdef PLANB_GSCANLINE
+ int scanline;
+#else
+ int nlpp, leftover1;
+ unsigned long base;
+#endif
+ unsigned long jump;
+ int pagei;
+ volatile struct dbdma_cmd *c1;
+ volatile struct dbdma_cmd *jump_addr;
+
+ c1 = pb->cap_cmd[fr];
+ interlace = (pb->gheight[fr] > pb->maxlines/2)? 1: 0;
+ bpp = pb->gfmt[fr]; /* gfmt = bpp */
+ count = bpp * pb->gwidth[fr];
+ nlines = pb->gheight[fr];
+#ifdef PLANB_GSCANLINE
+ scanline = pb->gbytes_per_line;
+#else
+ pb->lsize[fr] = count;
+ pb->lnum[fr] = 0;
+#endif
+
+ /* Do video in: */
+
+ /* Preamble commands: */
+ tab_cmd_dbdma(c1++, DBDMA_NOP, 0);
+ tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, virt_to_bus(c1 + 16)); c1++;
+ if((c1 = cmd_geo_setup(c1, pb->gwidth[fr], pb->gheight[fr], interlace,
+ bpp, 0, pb)) == NULL) {
+ printk(KERN_WARNING "PlanB: encountered serious problems\n");
+ tab_cmd_dbdma(pb->cap_cmd[fr] + 1, DBDMA_STOP, 0);
+ return (pb->cap_cmd[fr] + 2);
+ }
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.wait_sel),
+ PLANB_SET(FIELD_SYNC));
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(ODD_FIELD));
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0);
+ tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFSET, virt_to_bus(c1-3)); c1++;
+ tab_cmd_dbdma(c1++, DBDMA_NOP | INTR_ALWAYS, 0);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(DMA_ABORT));
+
+ if (interlace) {
+ stepsize = 2;
+ jump_addr = c1 + TAB_FACTOR * (nlines + 1) / 2;
+ } else {
+ stepsize = 1;
+ jump_addr = c1 + TAB_FACTOR * nlines;
+ }
+ jump = virt_to_bus(jump_addr);
+
+ /* even field data: */
+
+ pagei = pb->gbuf_idx[fr];
+#ifdef PLANB_GSCANLINE
+ for (i = 0; i < nlines; i += stepsize) {
+ tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count,
+ virt_to_bus(pb->rawbuf[pagei
+ + i * scanline / PAGE_SIZE]), jump);
+ }
+#else
+ i = 0;
+ leftover1 = 0;
+ do {
+ int j;
+
+ base = virt_to_bus(pb->rawbuf[pagei]);
+ nlpp = (PAGE_SIZE - leftover1) / count / stepsize;
+ for(j = 0; j < nlpp && i < nlines; j++, i += stepsize, c1++)
+ tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET,
+ count, base + count * j * stepsize + leftover1, jump);
+ if(i < nlines) {
+ int lov0 = PAGE_SIZE - count * nlpp * stepsize - leftover1;
+
+ if(lov0 == 0)
+ leftover1 = 0;
+ else {
+ if(lov0 >= count) {
+ tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count, base
+ + count * nlpp * stepsize + leftover1, jump);
+ } else {
+ pb->l_to_addr[fr][pb->lnum[fr]] = pb->rawbuf[pagei]
+ + count * nlpp * stepsize + leftover1;
+ pb->l_to_next_idx[fr][pb->lnum[fr]] = pagei + 1;
+ pb->l_to_next_size[fr][pb->lnum[fr]] = count - lov0;
+ tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count,
+ virt_to_bus(pb->rawbuf[pb->l_fr_addr_idx[fr]
+ + pb->lnum[fr]]), jump);
+ if(++pb->lnum[fr] > MAX_LNUM)
+ pb->lnum[fr]--;
+ }
+ leftover1 = count * stepsize - lov0;
+ i += stepsize;
+ }
+ }
+ pagei++;
+ } while(i < nlines);
+ tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, jump);
+ c1 = jump_addr;
+#endif /* PLANB_GSCANLINE */
+
+ /* For non-interlaced, we use even fields only */
+ if (!interlace)
+ goto cmd_tab_data_end;
+
+ /* Sync to odd field */
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0);
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(ODD_FIELD));
+ tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0);
+ tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFCLR, virt_to_bus(c1-3)); c1++;
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel),
+ PLANB_SET(DMA_ABORT));
+
+ /* odd field data: */
+ jump_addr = c1 + TAB_FACTOR * nlines / 2;
+ jump = virt_to_bus(jump_addr);
+#ifdef PLANB_GSCANLINE
+ for (i = 1; i < nlines; i += stepsize) {
+ tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count,
+ virt_to_bus(pb->rawbuf[pagei
+ + i * scanline / PAGE_SIZE]), jump);
+ }
+#else
+ i = 1;
+ leftover1 = 0;
+ pagei = pb->gbuf_idx[fr];
+ if(nlines <= 1)
+ goto skip;
+ do {
+ int j;
+
+ base = virt_to_bus(pb->rawbuf[pagei]);
+ nlpp = (PAGE_SIZE - leftover1) / count / stepsize;
+ if(leftover1 >= count) {
+ tab_cmd_gen(c1++, INPUT_MORE | KEY_STREAM0 | BR_IFSET, count,
+ base + leftover1 - count, jump);
+ i += stepsize;
+ }
+ for(j = 0; j < nlpp && i < nlines; j++, i += stepsize, c1++)
+ tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET, count,
+ base + count * (j * stepsize + 1) + leftover1, jump);
+ if(i < nlines) {
+ int lov0 = PAGE_SIZE - count * nlpp * stepsize - leftover1;
+
+ if(lov0 == 0)
+ leftover1 = 0;
+ else {
+ if(lov0 > count) {
+ pb->l_to_addr[fr][pb->lnum[fr]] = pb->rawbuf[pagei]
+ + count * (nlpp * stepsize + 1) + leftover1;
+ pb->l_to_next_idx[fr][pb->lnum[fr]] = pagei + 1;
+ pb->l_to_next_size[fr][pb->lnum[fr]] = count * stepsize
+ - lov0;
+ tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count,
+ virt_to_bus(pb->rawbuf[pb->l_fr_addr_idx[fr]
+ + pb->lnum[fr]]), jump);
+ if(++pb->lnum[fr] > MAX_LNUM)
+ pb->lnum[fr]--;
+ i += stepsize;
+ }
+ leftover1 = count * stepsize - lov0;
+ }
+ }
+ pagei++;
+ } while(i < nlines);
+skip:
+ tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, jump);
+ c1 = jump_addr;
+#endif /* PLANB_GSCANLINE */
+
+cmd_tab_data_end:
+ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->intr_stat),
+ (fr << 9) | PLANB_FRM_IRQ | PLANB_GEN_IRQ);
+ /* stop it */
+ tab_cmd_dbdma(c1, DBDMA_STOP, 0);
+
+ eieio();
+ return c1;
+}
+
+static void planb_irq(int irq, void *dev_id, struct pt_regs * regs)
+{
+ unsigned int stat, astat;
+ struct planb *pb = (struct planb *)dev_id;
+
+ IDEBUG("PlanB: planb_irq()\n");
+
+ /* get/clear interrupt status bits */
+ eieio();
+ stat = in_le32(&pb->planb_base->intr_stat);
+ astat = stat & pb->intr_mask;
+ out_le32(&pb->planb_base->intr_stat, PLANB_FRM_IRQ
+ & ~astat & stat & ~PLANB_GEN_IRQ);
+ IDEBUG("PlanB: stat = %X, astat = %X\n", stat, astat);
+
+ if(astat & PLANB_FRM_IRQ) {
+ unsigned int fr = stat >> 9;
+#ifndef PLANB_GSCANLINE
+ int i;
+#endif
+ IDEBUG("PlanB: PLANB_FRM_IRQ\n");
+
+ pb->gcount++;
+
+ IDEBUG("PlanB: grab %d: fr = %d, gcount = %d\n",
+ pb->grabbing, fr, pb->gcount);
+#ifndef PLANB_GSCANLINE
+ IDEBUG("PlanB: %d * %d bytes are being copied over\n",
+ pb->lnum[fr], pb->lsize[fr]);
+ for(i = 0; i < pb->lnum[fr]; i++) {
+ int first = pb->lsize[fr] - pb->l_to_next_size[fr][i];
+
+ memcpy(pb->l_to_addr[fr][i],
+ pb->rawbuf[pb->l_fr_addr_idx[fr] + i],
+ first);
+ memcpy(pb->rawbuf[pb->l_to_next_idx[fr][i]],
+ pb->rawbuf[pb->l_fr_addr_idx[fr] + i] + first,
+ pb->l_to_next_size[fr][i]);
+ }
+#endif
+ pb->frame_stat[fr] = GBUFFER_DONE;
+ pb->grabbing--;
+ wake_up_interruptible(&pb->capq);
+ return;
+ }
+ /* incorrect interrupts? */
+ pb->intr_mask = PLANB_CLR_IRQ;
+ out_le32(&pb->planb_base->intr_stat, PLANB_CLR_IRQ);
+ printk(KERN_ERR "PlanB: IRQ lockup, cleared intrrupts"
+ " unconditionally\n");
+}
+
+/*******************************
+ * Device Operations functions *
+ *******************************/
+
+static int planb_open(struct video_device *dev, int mode)
+{
+ struct planb *pb = (struct planb *)dev;
+
+ if (pb->user == 0) {
+ int err;
+ if((err = planb_prepare_open(pb)) != 0)
+ return err;
+ }
+ pb->user++;
+
+ DEBUG("PlanB: device opened\n");
+
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void planb_close(struct video_device *dev)
+{
+ struct planb *pb = (struct planb *)dev;
+
+ if(pb->user < 1) /* ??? */
+ return;
+ planb_lock(pb);
+ if (pb->user == 1) {
+ if (pb->overlay) {
+ planb_dbdma_stop(&pb->planb_base->ch2);
+ planb_dbdma_stop(&pb->planb_base->ch1);
+ pb->overlay = 0;
+ }
+ planb_prepare_close(pb);
+ }
+ pb->user--;
+ planb_unlock(pb);
+
+ DEBUG("PlanB: device closed\n");
+
+ MOD_DEC_USE_COUNT;
+}
+
+static long planb_read(struct video_device *v, char *buf, unsigned long count,
+ int nonblock)
+{
+ DEBUG("planb: read request\n");
+ return -EINVAL;
+}
+
+static long planb_write(struct video_device *v, const char *buf,
+ unsigned long count, int nonblock)
+{
+ DEBUG("planb: write request\n");
+ return -EINVAL;
+}
+
+static int planb_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct planb *pb=(struct planb *)dev;
+
+ switch (cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability b;
+
+ DEBUG("PlanB: IOCTL VIDIOCGCAP\n");
+
+ strcpy (b.name, pb->video_dev.name);
+ b.type = VID_TYPE_OVERLAY | VID_TYPE_CLIPPING |
+ VID_TYPE_FRAMERAM | VID_TYPE_SCALES |
+ VID_TYPE_CAPTURE;
+ b.channels = 2; /* composite & svhs */
+ b.audios = 0;
+ b.maxwidth = PLANB_MAXPIXELS;
+ b.maxheight = PLANB_MAXLINES;
+ b.minwidth = 32; /* wild guess */
+ b.minheight = 32;
+ if (copy_to_user(arg,&b,sizeof(b)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSFBUF:
+ {
+ struct video_buffer v;
+ unsigned short bpp;
+ unsigned int fmt;
+
+ DEBUG("PlanB: IOCTL VIDIOCSFBUF\n");
+
+ if (!capable(CAP_SYS_ADMIN)
+ || !capable(CAP_SYS_RAWIO))
+ return -EPERM;
+ if (copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ planb_lock(pb);
+ switch(v.depth) {
+ case 8:
+ bpp = 1;
+ fmt = PLANB_GRAY;
+ break;
+ case 15:
+ case 16:
+ bpp = 2;
+ fmt = PLANB_COLOUR15;
+ break;
+ case 24:
+ case 32:
+ bpp = 4;
+ fmt = PLANB_COLOUR32;
+ break;
+ default:
+ planb_unlock(pb);
+ return -EINVAL;
+ }
+ if (bpp * v.width > v.bytesperline) {
+ planb_unlock(pb);
+ return -EINVAL;
+ }
+ pb->win.bpp = bpp;
+ pb->win.color_fmt = fmt;
+ pb->frame_buffer_phys = (unsigned long) v.base;
+ pb->win.sheight = v.height;
+ pb->win.swidth = v.width;
+ pb->picture.depth = pb->win.depth = v.depth;
+ pb->win.bpl = pb->win.bpp * pb->win.swidth;
+ pb->win.pad = v.bytesperline - pb->win.bpl;
+
+ DEBUG("PlanB: Display at %p is %d by %d, bytedepth %d,"
+ " bpl %d (+ %d)\n", v.base, v.width,v.height,
+ pb->win.bpp, pb->win.bpl, pb->win.pad);
+
+ pb->cmd_buff_inited = 0;
+ if(pb->overlay) {
+ suspend_overlay(pb);
+ fill_cmd_buff(pb);
+ resume_overlay(pb);
+ }
+ planb_unlock(pb);
+ return 0;
+ }
+ case VIDIOCGFBUF:
+ {
+ struct video_buffer v;
+
+ DEBUG("PlanB: IOCTL VIDIOCGFBUF\n");
+
+ v.base = (void *)pb->frame_buffer_phys;
+ v.height = pb->win.sheight;
+ v.width = pb->win.swidth;
+ v.depth = pb->win.depth;
+ v.bytesperline = pb->win.bpl + pb->win.pad;
+ if (copy_to_user(arg, &v, sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCCAPTURE:
+ {
+ int i;
+
+ if(copy_from_user(&i, arg, sizeof(i)))
+ return -EFAULT;
+ if(i==0) {
+ DEBUG("PlanB: IOCTL VIDIOCCAPTURE Stop\n");
+
+ if (!(pb->overlay))
+ return 0;
+ planb_lock(pb);
+ pb->overlay = 0;
+ overlay_stop(pb);
+ planb_unlock(pb);
+ } else {
+ DEBUG("PlanB: IOCTL VIDIOCCAPTURE Start\n");
+
+ if (pb->frame_buffer_phys == 0 ||
+ pb->win.width == 0 ||
+ pb->win.height == 0)
+ return -EINVAL;
+ if (pb->overlay)
+ return 0;
+ planb_lock(pb);
+ pb->overlay = 1;
+ if(!(pb->cmd_buff_inited))
+ fill_cmd_buff(pb);
+ overlay_start(pb);
+ planb_unlock(pb);
+ }
+ return 0;
+ }
+ case VIDIOCGCHAN:
+ {
+ struct video_channel v;
+
+ DEBUG("PlanB: IOCTL VIDIOCGCHAN\n");
+
+ if(copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ v.flags = 0;
+ v.tuners = 0;
+ v.type = VIDEO_TYPE_CAMERA;
+ v.norm = pb->win.norm;
+ switch(v.channel)
+ {
+ case 0:
+ strcpy(v.name,"Composite");
+ break;
+ case 1:
+ strcpy(v.name,"SVHS");
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+ if(copy_to_user(arg,&v,sizeof(v)))
+ return -EFAULT;
+
+ return 0;
+ }
+ case VIDIOCSCHAN:
+ {
+ struct video_channel v;
+
+ DEBUG("PlanB: IOCTL VIDIOCSCHAN\n");
+
+ if(copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+
+ if (v.norm != pb->win.norm) {
+ int i, maxlines;
+
+ switch (v.norm)
+ {
+ case VIDEO_MODE_PAL:
+ case VIDEO_MODE_SECAM:
+ maxlines = PLANB_MAXLINES;
+ break;
+ case VIDEO_MODE_NTSC:
+ maxlines = PLANB_NTSC_MAXLINES;
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+ planb_lock(pb);
+ /* empty the grabbing queue */
+ while(pb->grabbing)
+ interruptible_sleep_on(&pb->capq);
+ pb->maxlines = maxlines;
+ pb->win.norm = v.norm;
+ /* Stop overlay if running */
+ suspend_overlay(pb);
+ for(i = 0; i < MAX_GBUFFERS; i++)
+ pb->gnorm_switch[i] = 1;
+ /* I know it's an overkill, but.... */
+ fill_cmd_buff(pb);
+ /* ok, now init it accordingly */
+ saa_init_regs (pb);
+ /* restart overlay if it was running */
+ resume_overlay(pb);
+ planb_unlock(pb);
+ }
+
+ switch(v.channel)
+ {
+ case 0: /* Composite */
+ saa_set (SAA7196_IOCC,
+ ((saa_regs[pb->win.norm][SAA7196_IOCC] &
+ ~7) | 3), pb);
+ break;
+ case 1: /* SVHS */
+ saa_set (SAA7196_IOCC,
+ ((saa_regs[pb->win.norm][SAA7196_IOCC] &
+ ~7) | 4), pb);
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+
+ return 0;
+ }
+ case VIDIOCGPICT:
+ {
+ struct video_picture vp = pb->picture;
+
+ DEBUG("PlanB: IOCTL VIDIOCGPICT\n");
+
+ switch(pb->win.color_fmt) {
+ case PLANB_GRAY:
+ vp.palette = VIDEO_PALETTE_GREY;
+ case PLANB_COLOUR15:
+ vp.palette = VIDEO_PALETTE_RGB555;
+ break;
+ case PLANB_COLOUR32:
+ vp.palette = VIDEO_PALETTE_RGB32;
+ break;
+ default:
+ vp.palette = 0;
+ break;
+ }
+
+ if(copy_to_user(arg,&vp,sizeof(vp)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSPICT:
+ {
+ struct video_picture vp;
+
+ DEBUG("PlanB: IOCTL VIDIOCSPICT\n");
+
+ if(copy_from_user(&vp,arg,sizeof(vp)))
+ return -EFAULT;
+ pb->picture = vp;
+ /* Should we do sanity checks here? */
+ saa_set (SAA7196_BRIG, (unsigned char)
+ ((pb->picture.brightness) >> 8), pb);
+ saa_set (SAA7196_HUEC, (unsigned char)
+ ((pb->picture.hue) >> 8) ^ 0x80, pb);
+ saa_set (SAA7196_CSAT, (unsigned char)
+ ((pb->picture.colour) >> 9), pb);
+ saa_set (SAA7196_CONT, (unsigned char)
+ ((pb->picture.contrast) >> 9), pb);
+
+ return 0;
+ }
+ case VIDIOCSWIN:
+ {
+ struct video_window vw;
+ struct video_clip clip;
+ int i;
+
+ DEBUG("PlanB: IOCTL VIDIOCSWIN\n");
+
+ if(copy_from_user(&vw,arg,sizeof(vw)))
+ return -EFAULT;
+
+ planb_lock(pb);
+ /* Stop overlay if running */
+ suspend_overlay(pb);
+ pb->win.interlace = (vw.height > pb->maxlines/2)? 1: 0;
+ if (pb->win.x != vw.x ||
+ pb->win.y != vw.y ||
+ pb->win.width != vw.width ||
+ pb->win.height != vw.height ||
+ !pb->cmd_buff_inited) {
+ pb->win.x = vw.x;
+ pb->win.y = vw.y;
+ pb->win.width = vw.width;
+ pb->win.height = vw.height;
+ fill_cmd_buff(pb);
+ }
+ /* Reset clip mask */
+ memset ((void *) pb->mask, 0xff, (pb->maxlines
+ * ((PLANB_MAXPIXELS + 7) & ~7)) / 8);
+ /* Add any clip rects */
+ for (i = 0; i < vw.clipcount; i++) {
+ if (copy_from_user(&clip, vw.clips + i,
+ sizeof(struct video_clip)))
+ return -EFAULT;
+ add_clip(pb, &clip);
+ }
+ /* restart overlay if it was running */
+ resume_overlay(pb);
+ planb_unlock(pb);
+ return 0;
+ }
+ case VIDIOCGWIN:
+ {
+ struct video_window vw;
+
+ DEBUG("PlanB: IOCTL VIDIOCGWIN\n");
+
+ vw.x=pb->win.x;
+ vw.y=pb->win.y;
+ vw.width=pb->win.width;
+ vw.height=pb->win.height;
+ vw.chromakey=0;
+ vw.flags=0;
+ if(pb->win.interlace)
+ vw.flags|=VIDEO_WINDOW_INTERLACE;
+ if(copy_to_user(arg,&vw,sizeof(vw)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSYNC: {
+ int i;
+
+ IDEBUG("PlanB: IOCTL VIDIOCSYNC\n");
+
+ if(copy_from_user((void *)&i,arg,sizeof(int)))
+ return -EFAULT;
+
+ IDEBUG("PlanB: sync to frame %d\n", i);
+
+ if(i > (MAX_GBUFFERS - 1) || i < 0)
+ return -EINVAL;
+chk_grab:
+ switch (pb->frame_stat[i]) {
+ case GBUFFER_UNUSED:
+ return -EINVAL;
+ case GBUFFER_GRABBING:
+ IDEBUG("PlanB: waiting for grab"
+ " done (%d)\n", i);
+ interruptible_sleep_on(&pb->capq);
+ if(signal_pending(current))
+ return -EINTR;
+ goto chk_grab;
+ case GBUFFER_DONE:
+ pb->frame_stat[i] = GBUFFER_UNUSED;
+ break;
+ }
+ return 0;
+ }
+
+ case VIDIOCMCAPTURE:
+ {
+ struct video_mmap vm;
+ volatile unsigned int status;
+
+ IDEBUG("PlanB: IOCTL VIDIOCMCAPTURE\n");
+
+ if(copy_from_user((void *) &vm,(void *)arg,sizeof(vm)))
+ return -EFAULT;
+ status = pb->frame_stat[vm.frame];
+ if (status != GBUFFER_UNUSED)
+ return -EBUSY;
+
+ return vgrab(pb, &vm);
+ }
+
+ case VIDIOCGMBUF:
+ {
+ int i;
+ struct video_mbuf vm;
+
+ DEBUG("PlanB: IOCTL VIDIOCGMBUF\n");
+
+ memset(&vm, 0 , sizeof(vm));
+ vm.size = PLANB_MAX_FBUF * MAX_GBUFFERS;
+ vm.frames = MAX_GBUFFERS;
+ for(i = 0; i<MAX_GBUFFERS; i++)
+ vm.offsets[i] = PLANB_MAX_FBUF * i;
+ if(copy_to_user((void *)arg, (void *)&vm, sizeof(vm)))
+ return -EFAULT;
+ return 0;
+ }
+
+ case PLANBIOCGSAAREGS:
+ {
+ struct planb_saa_regs preg;
+
+ DEBUG("PlanB: IOCTL PLANBIOCGSAAREGS\n");
+
+ if(copy_from_user(&preg, arg, sizeof(preg)))
+ return -EFAULT;
+ if(preg.addr >= SAA7196_NUMREGS)
+ return -EINVAL;
+ preg.val = saa_regs[pb->win.norm][preg.addr];
+ if(copy_to_user((void *)arg, (void *)&preg,
+ sizeof(preg)))
+ return -EFAULT;
+ return 0;
+ }
+
+ case PLANBIOCSSAAREGS:
+ {
+ struct planb_saa_regs preg;
+
+ DEBUG("PlanB: IOCTL PLANBIOCSSAAREGS\n");
+
+ if(copy_from_user(&preg, arg, sizeof(preg)))
+ return -EFAULT;
+ if(preg.addr >= SAA7196_NUMREGS)
+ return -EINVAL;
+ saa_set (preg.addr, preg.val, pb);
+ return 0;
+ }
+
+ case PLANBIOCGSTAT:
+ {
+ struct planb_stat_regs pstat;
+
+ DEBUG("PlanB: IOCTL PLANBIOCGSTAT\n");
+
+ pstat.ch1_stat = in_le32(&pb->planb_base->ch1.status);
+ pstat.ch2_stat = in_le32(&pb->planb_base->ch2.status);
+ pstat.saa_stat0 = saa_status(0, pb);
+ pstat.saa_stat1 = saa_status(1, pb);
+
+ if(copy_to_user((void *)arg, (void *)&pstat,
+ sizeof(pstat)))
+ return -EFAULT;
+ return 0;
+ }
+
+ case PLANBIOCSMODE: {
+ int v;
+
+ DEBUG("PlanB: IOCTL PLANBIOCSMODE\n");
+
+ if(copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+
+ switch(v)
+ {
+ case PLANB_TV_MODE:
+ saa_set (SAA7196_STDC,
+ (saa_regs[pb->win.norm][SAA7196_STDC] &
+ 0x7f), pb);
+ break;
+ case PLANB_VTR_MODE:
+ saa_set (SAA7196_STDC,
+ (saa_regs[pb->win.norm][SAA7196_STDC] |
+ 0x80), pb);
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+ pb->win.mode = v;
+ return 0;
+ }
+ case PLANBIOCGMODE: {
+ int v=pb->win.mode;
+
+ DEBUG("PlanB: IOCTL PLANBIOCGMODE\n");
+
+ if(copy_to_user(arg,&v,sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+#ifdef PLANB_GSCANLINE
+ case PLANBG_GRAB_BPL: {
+ int v=pb->gbytes_per_line;
+
+ DEBUG("PlanB: IOCTL PLANBG_GRAB_BPL\n");
+
+ if(copy_to_user(arg,&v,sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+#endif /* PLANB_GSCANLINE */
+ case PLANB_INTR_DEBUG: {
+ int i;
+
+ DEBUG("PlanB: IOCTL PLANB_INTR_DEBUG\n");
+
+ if(copy_from_user(&i, arg, sizeof(i)))
+ return -EFAULT;
+
+ /* avoid hang ups all together */
+ for (i = 0; i < MAX_GBUFFERS; i++) {
+ if(pb->frame_stat[i] == GBUFFER_GRABBING) {
+ pb->frame_stat[i] = GBUFFER_DONE;
+ }
+ }
+ if(pb->grabbing)
+ pb->grabbing--;
+ wake_up_interruptible(&pb->capq);
+ return 0;
+ }
+ case PLANB_INV_REGS: {
+ int i;
+ struct planb_any_regs any;
+
+ DEBUG("PlanB: IOCTL PLANB_INV_REGS\n");
+
+ if(copy_from_user(&any, arg, sizeof(any)))
+ return -EFAULT;
+ if(any.offset < 0 || any.offset + any.bytes > 0x400)
+ return -EINVAL;
+ if(any.bytes > 128)
+ return -EINVAL;
+ for (i = 0; i < any.bytes; i++) {
+ any.data[i] =
+ in_8((unsigned char *)pb->planb_base
+ + any.offset + i);
+ }
+ if(copy_to_user(arg,&any,sizeof(any)))
+ return -EFAULT;
+ return 0;
+ }
+ default:
+ {
+ DEBUG("PlanB: Unimplemented IOCTL\n");
+ return -ENOIOCTLCMD;
+ }
+ /* Some IOCTLs are currently unsupported on PlanB */
+ case VIDIOCGTUNER: {
+ DEBUG("PlanB: IOCTL VIDIOCGTUNER\n");
+ goto unimplemented; }
+ case VIDIOCSTUNER: {
+ DEBUG("PlanB: IOCTL VIDIOCSTUNER\n");
+ goto unimplemented; }
+ case VIDIOCSFREQ: {
+ DEBUG("PlanB: IOCTL VIDIOCSFREQ\n");
+ goto unimplemented; }
+ case VIDIOCGFREQ: {
+ DEBUG("PlanB: IOCTL VIDIOCGFREQ\n");
+ goto unimplemented; }
+ case VIDIOCKEY: {
+ DEBUG("PlanB: IOCTL VIDIOCKEY\n");
+ goto unimplemented; }
+ case VIDIOCSAUDIO: {
+ DEBUG("PlanB: IOCTL VIDIOCSAUDIO\n");
+ goto unimplemented; }
+ case VIDIOCGAUDIO: {
+ DEBUG("PlanB: IOCTL VIDIOCGAUDIO\n");
+ goto unimplemented; }
+unimplemented:
+ DEBUG(" Unimplemented\n");
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static int planb_mmap(struct video_device *dev, const char *adr, unsigned long size)
+{
+ int i;
+ struct planb *pb = (struct planb *)dev;
+ unsigned long start = (unsigned long)adr;
+
+ if (size > MAX_GBUFFERS * PLANB_MAX_FBUF)
+ return -EINVAL;
+ if (!pb->rawbuf) {
+ int err;
+ if((err=grabbuf_alloc(pb)))
+ return err;
+ }
+ for (i = 0; i < pb->rawbuf_size; i++) {
+ if (remap_page_range(start, virt_to_phys((void *)pb->rawbuf[i]),
+ PAGE_SIZE, PAGE_SHARED))
+ return -EAGAIN;
+ start += PAGE_SIZE;
+ if (size <= PAGE_SIZE)
+ break;
+ size -= PAGE_SIZE;
+ }
+ return 0;
+}
+
+/* This gets called upon device registration */
+/* we could do some init here */
+static int planb_init_done(struct video_device *dev)
+{
+ return 0;
+}
+
+static struct video_device planb_template=
+{
+ PLANB_DEVICE_NAME,
+ VID_TYPE_OVERLAY,
+ VID_HARDWARE_PLANB,
+ planb_open,
+ planb_close,
+ planb_read,
+ planb_write,
+#if LINUX_VERSION_CODE >= 0x020100
+ NULL, /* poll */
+#endif
+ planb_ioctl,
+ planb_mmap, /* mmap? */
+ planb_init_done,
+ NULL, /* pointer to private data */
+ 0,
+ 0
+};
+
+static int init_planb(struct planb *pb)
+{
+ unsigned char saa_rev;
+ int i, result;
+ unsigned long flags;
+
+ memset ((void *) &pb->win, 0, sizeof (struct planb_window));
+ /* Simple sanity check */
+ if(def_norm >= NUM_SUPPORTED_NORM || def_norm < 0) {
+ printk(KERN_ERR "PlanB: Option(s) invalid\n");
+ return -2;
+ }
+ pb->win.norm = def_norm;
+ pb->win.mode = PLANB_TV_MODE; /* TV mode */
+ pb->win.interlace=1;
+ pb->win.x=0;
+ pb->win.y=0;
+ pb->win.width=768; /* 640 */
+ pb->win.height=576; /* 480 */
+ pb->maxlines=576;
+#if 0
+ btv->win.cropwidth=768; /* 640 */
+ btv->win.cropheight=576; /* 480 */
+ btv->win.cropx=0;
+ btv->win.cropy=0;
+#endif
+ pb->win.pad=0;
+ pb->win.bpp=4;
+ pb->win.depth=32;
+ pb->win.color_fmt=PLANB_COLOUR32;
+ pb->win.bpl=1024*pb->win.bpp;
+ pb->win.swidth=1024;
+ pb->win.sheight=768;
+#ifdef PLANB_GSCANLINE
+ if((pb->gbytes_per_line = PLANB_MAXPIXELS * 4) > PAGE_SIZE
+ || (pb->gbytes_per_line <= 0))
+ return -3;
+ else {
+ /* page align pb->gbytes_per_line for DMA purpose */
+ for(i = PAGE_SIZE; pb->gbytes_per_line < (i>>1);)
+ i>>=1;
+ pb->gbytes_per_line = i;
+ }
+#endif
+ pb->tab_size = PLANB_MAXLINES + 40;
+ pb->suspend = 0;
+ pb->lock = 0;
+ init_waitqueue_head(&pb->lockq);
+ pb->ch1_cmd = 0;
+ pb->ch2_cmd = 0;
+ pb->mask = 0;
+ pb->priv_space = 0;
+ pb->offset = 0;
+ pb->user = 0;
+ pb->overlay = 0;
+ init_waitqueue_head(&pb->suspendq);
+ pb->cmd_buff_inited = 0;
+ pb->frame_buffer_phys = 0;
+
+ /* Reset DMA controllers */
+ planb_dbdma_stop(&pb->planb_base->ch2);
+ planb_dbdma_stop(&pb->planb_base->ch1);
+
+ saa_rev = (saa_status(0, pb) & 0xf0) >> 4;
+ printk(KERN_INFO "PlanB: SAA7196 video processor rev. %d\n", saa_rev);
+ /* Initialize the SAA registers in memory and on chip */
+ saa_init_regs (pb);
+
+ /* clear interrupt mask */
+ pb->intr_mask = PLANB_CLR_IRQ;
+
+ save_flags(flags); cli();
+ result = request_irq(pb->irq, planb_irq, 0, "PlanB", (void *)pb);
+ if (result < 0) {
+ if (result==-EINVAL)
+ printk(KERN_ERR "PlanB: Bad irq number (%d) "
+ "or handler\n", (int)pb->irq);
+ else if (result==-EBUSY)
+ printk(KERN_ERR "PlanB: I don't know why, "
+ "but IRQ %d is busy\n", (int)pb->irq);
+ restore_flags(flags);
+ return result;
+ }
+ disable_irq(pb->irq);
+ restore_flags(flags);
+
+ /* Now add the template and register the device unit. */
+ memcpy(&pb->video_dev,&planb_template,sizeof(planb_template));
+
+ pb->picture.brightness=0x90<<8;
+ pb->picture.contrast = 0x70 << 8;
+ pb->picture.colour = 0x70<<8;
+ pb->picture.hue = 0x8000;
+ pb->picture.whiteness = 0;
+ pb->picture.depth = pb->win.depth;
+
+ pb->frame_stat=NULL;
+ init_waitqueue_head(&pb->capq);
+ for(i=0; i<MAX_GBUFFERS; i++) {
+ pb->gbuf_idx[i] = PLANB_MAX_FBUF * i / PAGE_SIZE;
+ pb->gwidth[i]=0;
+ pb->gheight[i]=0;
+ pb->gfmt[i]=0;
+ pb->cap_cmd[i]=NULL;
+#ifndef PLANB_GSCANLINE
+ pb->l_fr_addr_idx[i] = MAX_GBUFFERS * (PLANB_MAX_FBUF
+ / PAGE_SIZE + 1) + MAX_LNUM * i;
+ pb->lsize[i] = 0;
+ pb->lnum[i] = 0;
+#endif
+ }
+ pb->rawbuf=NULL;
+ pb->grabbing=0;
+
+ /* enable interrupts */
+ out_le32(&pb->planb_base->intr_stat, PLANB_CLR_IRQ);
+ pb->intr_mask = PLANB_FRM_IRQ;
+ enable_irq(pb->irq);
+
+ if(video_register_device(&pb->video_dev, VFL_TYPE_GRABBER)<0)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * Scan for a PlanB controller, request the irq and map the io memory
+ */
+
+static int find_planb(void)
+{
+ struct planb *pb;
+ struct device_node *planb_devices;
+ unsigned char dev_fn, confreg, bus;
+ unsigned int old_base, new_base;
+ unsigned int irq;
+ struct pci_dev *pdev;
+
+ if (_machine != _MACH_Pmac)
+ return 0;
+
+ planb_devices = find_devices("planb");
+ if (planb_devices == 0) {
+ planb_num=0;
+ printk(KERN_WARNING "PlanB: no device found!\n");
+ return planb_num;
+ }
+
+ if (planb_devices->next != NULL)
+ printk(KERN_ERR "Warning: only using first PlanB device!\n");
+ pb = &planbs[0];
+ planb_num = 1;
+
+ if (planb_devices->n_addrs != 1) {
+ printk (KERN_WARNING "PlanB: expecting 1 address for planb "
+ "(got %d)", planb_devices->n_addrs);
+ return 0;
+ }
+
+ if (planb_devices->n_intrs == 0) {
+ printk(KERN_WARNING "PlanB: no intrs for device %s\n",
+ planb_devices->full_name);
+ return 0;
+ } else {
+ irq = planb_devices->intrs[0].line;
+ }
+
+ /* Initialize PlanB's PCI registers */
+
+ /* There is a bug with the way OF assigns addresses
+ to the devices behind the chaos bridge.
+ control needs only 0x1000 of space, but decodes only
+ the upper 16 bits. It therefore occupies a full 64K.
+ OF assigns the planb controller memory within this space;
+ so we need to change that here in order to access planb. */
+
+ /* We remap to 0xf1000000 in hope that nobody uses it ! */
+
+ bus = (planb_devices->addrs[0].space >> 16) & 0xff;
+ dev_fn = (planb_devices->addrs[0].space >> 8) & 0xff;
+ confreg = planb_devices->addrs[0].space & 0xff;
+ old_base = planb_devices->addrs[0].address;
+ new_base = 0xf1000000;
+
+ DEBUG("PlanB: Found on bus %d, dev %d, func %d, "
+ "membase 0x%x (base reg. 0x%x)\n",
+ bus, PCI_SLOT(dev_fn), PCI_FUNC(dev_fn), old_base, confreg);
+
+ pdev = pci_find_slot (bus, dev_fn);
+ if (!pdev) {
+ printk(KERN_ERR "cannot find slot\n");
+ /* XXX handle error */
+ }
+
+ /* Enable response in memory space, bus mastering,
+ use memory write and invalidate */
+ pci_write_config_word (pdev, PCI_COMMAND,
+ PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER |
+ PCI_COMMAND_INVALIDATE);
+ /* Set PCI Cache line size & latency timer */
+ pci_write_config_byte (pdev, PCI_CACHE_LINE_SIZE, 0x8);
+ pci_write_config_byte (pdev, PCI_LATENCY_TIMER, 0x40);
+
+ /* Set the new base address */
+ pci_write_config_dword (pdev, confreg, new_base);
+
+ planb_regs = (volatile struct planb_registers *)
+ ioremap (new_base, 0x400);
+ pb->planb_base = planb_regs;
+ pb->planb_base_phys = (struct planb_registers *)new_base;
+ pb->irq = irq;
+
+ return planb_num;
+}
+
+static void release_planb(void)
+{
+ int i;
+ struct planb *pb;
+
+ for (i=0;i<planb_num; i++)
+ {
+ pb=&planbs[i];
+
+ /* stop and flash DMAs unconditionally */
+ planb_dbdma_stop(&pb->planb_base->ch2);
+ planb_dbdma_stop(&pb->planb_base->ch1);
+
+ /* clear and free interrupts */
+ pb->intr_mask = PLANB_CLR_IRQ;
+ out_le32 (&pb->planb_base->intr_stat, PLANB_CLR_IRQ);
+ free_irq(pb->irq, pb);
+
+ /* make sure all allocated memory are freed */
+ planb_prepare_close(pb);
+
+ printk(KERN_INFO "PlanB: unregistering with v4l\n");
+ video_unregister_device(&pb->video_dev);
+
+ /* note that iounmap() does nothing on the PPC right now */
+ iounmap ((void *)pb->planb_base);
+ }
+}
+
+#ifdef MODULE
+
+int init_module(void)
+{
+#else
+int __init init_planbs(struct video_init *unused)
+{
+#endif
+ int i;
+
+ if (find_planb()<=0)
+ return -EIO;
+
+ for (i=0; i<planb_num; i++) {
+ if (init_planb(&planbs[i])<0) {
+ printk(KERN_ERR "PlanB: error registering device %d"
+ " with v4l\n", i);
+ release_planb();
+ return -EIO;
+ }
+ printk(KERN_INFO "PlanB: registered device %d with v4l\n", i);
+ }
+ return 0;
+}
+
+#ifdef MODULE
+
+void cleanup_module(void)
+{
+ release_planb();
+}
+
+#endif
diff --git a/drivers/media/video/planb.h b/drivers/media/video/planb.h
new file mode 100644
index 000000000..98d5697c9
--- /dev/null
+++ b/drivers/media/video/planb.h
@@ -0,0 +1,232 @@
+/*
+ planb - PlanB frame grabber driver
+
+ PlanB is used in the 7x00/8x00 series of PowerMacintosh
+ Computers as video input DMA controller.
+
+ Copyright (C) 1998 Michel Lanners (mlan@cpu.lu)
+
+ Based largely on the bttv driver by Ralph Metzler (rjkm@thp.uni-koeln.de)
+
+ Additional debugging and coding by Takashi Oe (toe@unlserve.unl.edu)
+
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/* $Id: planb.h,v 1.13 1999/05/03 19:28:56 mlan Exp $ */
+
+#ifndef _PLANB_H_
+#define _PLANB_H_
+
+#ifdef __KERNEL__
+#include <asm/dbdma.h>
+#include "saa7196.h"
+#endif /* __KERNEL__ */
+
+#define PLANB_DEVICE_NAME "Apple PlanB Video-In"
+#define PLANB_REV "1.0"
+
+#ifdef __KERNEL__
+//#define PLANB_GSCANLINE /* use this if apps have the notion of */
+ /* grab buffer scanline */
+/* This should be safe for both PAL and NTSC */
+#define PLANB_MAXPIXELS 768
+#define PLANB_MAXLINES 576
+#define PLANB_NTSC_MAXLINES 480
+
+/* Uncomment your preferred norm ;-) */
+#define PLANB_DEF_NORM VIDEO_MODE_PAL
+//#define PLANB_DEF_NORM VIDEO_MODE_NTSC
+//#define PLANB_DEF_NORM VIDEO_MODE_SECAM
+
+/* fields settings */
+#define PLANB_GRAY 0x1 /* 8-bit mono? */
+#define PLANB_COLOUR15 0x2 /* 16-bit mode */
+#define PLANB_COLOUR32 0x4 /* 32-bit mode */
+#define PLANB_CLIPMASK 0x8 /* hardware clipmasking */
+
+/* misc. flags for PlanB DMA operation */
+#define CH_SYNC 0x1 /* synchronize channels (set by ch1;
+ cleared by ch2) */
+#define FIELD_SYNC 0x2 /* used for the start of each field
+ (0 -> 1 -> 0 for ch1; 0 -> 1 for ch2) */
+#define EVEN_FIELD 0x0 /* even field is detected if unset */
+#define DMA_ABORT 0x2 /* error or just out of sync if set */
+#define ODD_FIELD 0x4 /* odd field is detected if set */
+
+/* for capture operations */
+#define MAX_GBUFFERS 2
+/* note PLANB_MAX_FBUF must be divisible by PAGE_SIZE */
+#ifdef PLANB_GSCANLINE
+#define PLANB_MAX_FBUF 0x240000 /* 576 * 1024 * 4 */
+#define TAB_FACTOR (1)
+#else
+#define PLANB_MAX_FBUF 0x1b0000 /* 576 * 768 * 4 */
+#define TAB_FACTOR (2)
+#endif
+#endif /* __KERNEL__ */
+
+struct planb_saa_regs {
+ unsigned char addr;
+ unsigned char val;
+};
+
+struct planb_stat_regs {
+ unsigned int ch1_stat;
+ unsigned int ch2_stat;
+ unsigned char saa_stat0;
+ unsigned char saa_stat1;
+};
+
+struct planb_any_regs {
+ unsigned int offset;
+ unsigned int bytes;
+ unsigned char data[128];
+};
+
+/* planb private ioctls */
+#define PLANBIOCGSAAREGS _IOWR('v', BASE_VIDIOCPRIVATE, struct planb_saa_regs) /* Read a saa7196 reg value */
+#define PLANBIOCSSAAREGS _IOW('v', BASE_VIDIOCPRIVATE + 1, struct planb_saa_regs) /* Set a saa7196 reg value */
+#define PLANBIOCGSTAT _IOR('v', BASE_VIDIOCPRIVATE + 2, struct planb_stat_regs) /* Read planb status */
+#define PLANB_TV_MODE 1
+#define PLANB_VTR_MODE 2
+#define PLANBIOCGMODE _IOR('v', BASE_VIDIOCPRIVATE + 3, int) /* Get TV/VTR mode */
+#define PLANBIOCSMODE _IOW('v', BASE_VIDIOCPRIVATE + 4, int) /* Set TV/VTR mode */
+
+#ifdef PLANB_GSCANLINE
+#define PLANBG_GRAB_BPL _IOR('v', BASE_VIDIOCPRIVATE + 5, int) /* # of bytes per scanline in grab buffer */
+#endif
+
+/* call wake_up_interruptible() with appropriate actions */
+#define PLANB_INTR_DEBUG _IOW('v', BASE_VIDIOCPRIVATE + 20, int)
+/* investigate which reg does what */
+#define PLANB_INV_REGS _IOWR('v', BASE_VIDIOCPRIVATE + 21, struct planb_any_regs)
+
+#ifdef __KERNEL__
+
+/* Potentially useful macros */
+#define PLANB_SET(x) ((x) << 16 | (x))
+#define PLANB_CLR(x) ((x) << 16)
+
+/* This represents the physical register layout */
+struct planb_registers {
+ volatile struct dbdma_regs ch1; /* 0x00: video in */
+ volatile unsigned int even; /* 0x40: even field setting */
+ volatile unsigned int odd; /* 0x44; odd field setting */
+ unsigned int pad1[14]; /* empty? */
+ volatile struct dbdma_regs ch2; /* 0x80: clipmask out */
+ unsigned int pad2[16]; /* 0xc0: empty? */
+ volatile unsigned int reg3; /* 0x100: ???? */
+ volatile unsigned int intr_stat; /* 0x104: irq status */
+#define PLANB_CLR_IRQ 0x00 /* clear Plan B interrupt */
+#define PLANB_GEN_IRQ 0x01 /* assert Plan B interrupt */
+#define PLANB_FRM_IRQ 0x0100 /* end of frame */
+ unsigned int pad3[1]; /* empty? */
+ volatile unsigned int reg5; /* 0x10c: ??? */
+ unsigned int pad4[60]; /* empty? */
+ volatile unsigned char saa_addr; /* 0x200: SAA subadr */
+ char pad5[3];
+ volatile unsigned char saa_regval; /* SAA7196 write reg. val */
+ char pad6[3];
+ volatile unsigned char saa_status; /* SAA7196 status byte */
+ /* There is more unused stuff here */
+};
+
+struct planb_window {
+ int x, y;
+ ushort width, height;
+ ushort bpp, bpl, depth, pad;
+ ushort swidth, sheight;
+ int norm;
+ int interlace;
+ u32 color_fmt;
+ int chromakey;
+ int mode; /* used to switch between TV/VTR modes */
+};
+
+struct planb_suspend {
+ int overlay;
+ int frame;
+ struct dbdma_cmd cmd;
+};
+
+struct planb {
+ struct video_device video_dev;
+ struct video_picture picture; /* Current picture params */
+ struct video_audio audio_dev; /* Current audio params */
+
+ volatile struct planb_registers *planb_base; /* virt base of planb */
+ struct planb_registers *planb_base_phys; /* phys base of planb */
+ void *priv_space; /* Org. alloc. mem for kfree */
+ int user;
+ unsigned int tab_size;
+ int maxlines;
+ int lock;
+ wait_queue_head_t lockq;
+ unsigned int irq; /* interrupt number */
+ volatile unsigned int intr_mask;
+
+ int overlay; /* overlay running? */
+ struct planb_window win;
+ unsigned long frame_buffer_phys; /* We need phys for DMA */
+ int offset; /* offset of pixel 1 */
+ volatile struct dbdma_cmd *ch1_cmd; /* Video In DMA cmd buffer */
+ volatile struct dbdma_cmd *ch2_cmd; /* Clip Out DMA cmd buffer */
+ volatile struct dbdma_cmd *overlay_last1;
+ volatile struct dbdma_cmd *overlay_last2;
+ unsigned long ch1_cmd_phys;
+ volatile unsigned char *mask; /* Clipmask buffer */
+ int suspend;
+ wait_queue_head_t suspendq;
+ struct planb_suspend suspended;
+ int cmd_buff_inited; /* cmd buffer inited? */
+
+ int grabbing;
+ unsigned int gcount;
+ wait_queue_head_t capq;
+ int last_fr;
+ int prev_last_fr;
+ unsigned char **rawbuf;
+ int rawbuf_size;
+ int gbuf_idx[MAX_GBUFFERS];
+ volatile struct dbdma_cmd *cap_cmd[MAX_GBUFFERS];
+ volatile struct dbdma_cmd *last_cmd[MAX_GBUFFERS];
+ volatile struct dbdma_cmd *pre_cmd[MAX_GBUFFERS];
+ int need_pre_capture[MAX_GBUFFERS];
+#define PLANB_DUMMY 40 /* # of command buf's allocated for pre-capture seq. */
+ int gwidth[MAX_GBUFFERS], gheight[MAX_GBUFFERS];
+ unsigned int gfmt[MAX_GBUFFERS];
+ int gnorm_switch[MAX_GBUFFERS];
+ volatile unsigned int *frame_stat;
+#define GBUFFER_UNUSED 0x00U
+#define GBUFFER_GRABBING 0x01U
+#define GBUFFER_DONE 0x02U
+#ifdef PLANB_GSCANLINE
+ int gbytes_per_line;
+#else
+#define MAX_LNUM 431 /* change this if PLANB_MAXLINES or */
+ /* PLANB_MAXPIXELS changes */
+ int l_fr_addr_idx[MAX_GBUFFERS];
+ unsigned char *l_to_addr[MAX_GBUFFERS][MAX_LNUM];
+ int l_to_next_idx[MAX_GBUFFERS][MAX_LNUM];
+ int l_to_next_size[MAX_GBUFFERS][MAX_LNUM];
+ int lsize[MAX_GBUFFERS], lnum[MAX_GBUFFERS];
+#endif
+};
+
+#endif /* __KERNEL__ */
+
+#endif /* _PLANB_H_ */
diff --git a/drivers/media/video/pms.c b/drivers/media/video/pms.c
new file mode 100644
index 000000000..1e50880a0
--- /dev/null
+++ b/drivers/media/video/pms.c
@@ -0,0 +1,1074 @@
+/*
+ * Media Vision Pro Movie Studio
+ * or
+ * "all you need is an I2C bus some RAM and a prayer"
+ *
+ * This draws heavily on code
+ *
+ * (c) Wolfgang Koehler, wolf@first.gmd.de, Dec. 1994
+ * Kiefernring 15
+ * 14478 Potsdam, Germany
+ *
+ * Most of this code is directly derived from his userspace driver.
+ * His driver works so send any reports to alan@redhat.com unless the
+ * userspace driver also doesnt work for you...
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/sched.h>
+#include <linux/videodev.h>
+#include <linux/version.h>
+#include <asm/uaccess.h>
+
+
+#define MOTOROLA 1
+#define PHILIPS2 2
+#define PHILIPS1 3
+#define MVVMEMORYWIDTH 0x40 /* 512 bytes */
+
+struct pms_device
+{
+ struct video_device v;
+ struct video_picture picture;
+ int height;
+ int width;
+};
+
+struct i2c_info
+{
+ u8 slave;
+ u8 sub;
+ u8 data;
+ u8 hits;
+};
+
+static int i2c_count = 0;
+static struct i2c_info i2cinfo[64];
+
+static int decoder = PHILIPS2;
+static int standard = 0; /* 0 - auto 1 - ntsc 2 - pal 3 - secam */
+
+/*
+ * I/O ports and Shared Memory
+ */
+
+static int io_port = 0x250;
+static int data_port = 0x251;
+static int mem_base = 0xC8000;
+
+
+
+extern __inline__ void mvv_write(u8 index, u8 value)
+{
+ outw(index|(value<<8), io_port);
+}
+
+extern __inline__ u8 mvv_read(u8 index)
+{
+ outb(index, io_port);
+ return inb(data_port);
+}
+
+static int pms_i2c_stat(u8 slave)
+{
+ int counter;
+ int i;
+
+ outb(0x28, io_port);
+
+ counter=0;
+ while((inb(data_port)&0x01)==0)
+ if(counter++==256)
+ break;
+
+ while((inb(data_port)&0x01)!=0)
+ if(counter++==256)
+ break;
+
+ outb(slave, io_port);
+
+ counter=0;
+ while((inb(data_port)&0x01)==0)
+ if(counter++==256)
+ break;
+
+ while((inb(data_port)&0x01)!=0)
+ if(counter++==256)
+ break;
+
+ for(i=0;i<12;i++)
+ {
+ char st=inb(data_port);
+ if((st&2)!=0)
+ return -1;
+ if((st&1)==0)
+ break;
+ }
+ outb(0x29, io_port);
+ return inb(data_port);
+}
+
+static int pms_i2c_write(u16 slave, u16 sub, u16 data)
+{
+ int skip=0;
+ int count;
+ int i;
+
+ for(i=0;i<i2c_count;i++)
+ {
+ if((i2cinfo[i].slave==slave) &&
+ (i2cinfo[i].sub == sub))
+ {
+ if(i2cinfo[i].data==data)
+ skip=1;
+ i2cinfo[i].data=data;
+ i=i2c_count+1;
+ }
+ }
+
+ if(i==i2c_count && i2c_count<64)
+ {
+ i2cinfo[i2c_count].slave=slave;
+ i2cinfo[i2c_count].sub=sub;
+ i2cinfo[i2c_count].data=data;
+ i2c_count++;
+ }
+
+ if(skip)
+ return 0;
+
+ mvv_write(0x29, sub);
+ mvv_write(0x2A, data);
+ mvv_write(0x28, slave);
+
+ outb(0x28, io_port);
+
+ count=0;
+ while((inb(data_port)&1)==0)
+ if(count>255)
+ break;
+ while((inb(data_port)&1)!=0)
+ if(count>255)
+ break;
+
+ count=inb(data_port);
+
+ if(count&2)
+ return -1;
+ return count;
+}
+
+static int pms_i2c_read(int slave, int sub)
+{
+ int i=0;
+ for(i=0;i<i2c_count;i++)
+ {
+ if(i2cinfo[i].slave==slave && i2cinfo[i].sub==sub)
+ return i2cinfo[i].data;
+ }
+ return 0;
+}
+
+
+static void pms_i2c_andor(int slave, int sub, int and, int or)
+{
+ u8 tmp;
+
+ tmp=pms_i2c_read(slave, sub);
+ tmp = (tmp&and)|or;
+ pms_i2c_write(slave, sub, tmp);
+}
+
+/*
+ * Control functions
+ */
+
+
+static void pms_videosource(short source)
+{
+ mvv_write(0x2E, source?0x31:0x30);
+}
+
+static void pms_hue(short hue)
+{
+ switch(decoder)
+ {
+ case MOTOROLA:
+ pms_i2c_write(0x8A, 0x00, hue);
+ break;
+ case PHILIPS2:
+ pms_i2c_write(0x8A, 0x07, hue);
+ break;
+ case PHILIPS1:
+ pms_i2c_write(0x42, 0x07, hue);
+ break;
+ }
+}
+
+static void pms_colour(short colour)
+{
+ switch(decoder)
+ {
+ case MOTOROLA:
+ pms_i2c_write(0x8A, 0x00, colour);
+ break;
+ case PHILIPS1:
+ pms_i2c_write(0x42, 0x12, colour);
+ break;
+ }
+}
+
+
+static void pms_contrast(short contrast)
+{
+ switch(decoder)
+ {
+ case MOTOROLA:
+ pms_i2c_write(0x8A, 0x00, contrast);
+ break;
+ case PHILIPS1:
+ pms_i2c_write(0x42, 0x13, contrast);
+ break;
+ }
+}
+
+static void pms_brightness(short brightness)
+{
+ switch(decoder)
+ {
+ case MOTOROLA:
+ pms_i2c_write(0x8A, 0x00, brightness);
+ pms_i2c_write(0x8A, 0x00, brightness);
+ pms_i2c_write(0x8A, 0x00, brightness);
+ break;
+ case PHILIPS1:
+ pms_i2c_write(0x42, 0x19, brightness);
+ break;
+ }
+}
+
+
+static void pms_format(short format)
+{
+ int target;
+ standard = format;
+
+ if(decoder==PHILIPS1)
+ target=0x42;
+ else if(decoder==PHILIPS2)
+ target=0x8A;
+ else
+ return;
+
+ switch(format)
+ {
+ case 0: /* Auto */
+ pms_i2c_andor(target, 0x0D, 0xFE,0x00);
+ pms_i2c_andor(target, 0x0F, 0x3F,0x80);
+ break;
+ case 1: /* NTSC */
+ pms_i2c_andor(target, 0x0D, 0xFE, 0x00);
+ pms_i2c_andor(target, 0x0F, 0x3F, 0x40);
+ break;
+ case 2: /* PAL */
+ pms_i2c_andor(target, 0x0D, 0xFE, 0x00);
+ pms_i2c_andor(target, 0x0F, 0x3F, 0x00);
+ break;
+ case 3: /* SECAM */
+ pms_i2c_andor(target, 0x0D, 0xFE, 0x01);
+ pms_i2c_andor(target, 0x0F, 0x3F, 0x00);
+ break;
+ }
+}
+
+#ifdef FOR_FUTURE_EXPANSION
+
+/*
+ * These features of the PMS card are not currently exposes. They
+ * could become a private v4l ioctl for PMSCONFIG or somesuch if
+ * people need it. We also don't yet use the PMS interrupt.
+ */
+
+static void pms_hstart(short start)
+{
+ switch(decoder)
+ {
+ case PHILIPS1:
+ pms_i2c_write(0x8A, 0x05, start);
+ pms_i2c_write(0x8A, 0x18, start);
+ break;
+ case PHILIPS2:
+ pms_i2c_write(0x42, 0x05, start);
+ pms_i2c_write(0x42, 0x18, start);
+ break;
+ }
+}
+
+/*
+ * Bandpass filters
+ */
+
+static void pms_bandpass(short pass)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0x8A, 0x06, 0xCF, (pass&0x03)<<4);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42, 0x06, 0xCF, (pass&0x03)<<4);
+}
+
+static void pms_antisnow(short snow)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0x8A, 0x06, 0xF3, (snow&0x03)<<2);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42, 0x06, 0xF3, (snow&0x03)<<2);
+}
+
+static void pms_sharpness(short sharp)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0x8A, 0x06, 0xFC, sharp&0x03);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42, 0x06, 0xFC, sharp&0x03);
+}
+
+static void pms_chromaagc(short agc)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0x8A, 0x0C, 0x9F, (agc&0x03)<<5);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42, 0x0C, 0x9F, (agc&0x03)<<5);
+}
+
+static void pms_vertnoise(short noise)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0x8A, 0x10, 0xFC, noise&3);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42, 0x10, 0xFC, noise&3);
+}
+
+static void pms_forcecolour(short colour)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0x8A, 0x0C, 0x7F, (colour&1)<<7);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42, 0x0C, 0x7, (colour&1)<<7);
+}
+
+static void pms_antigamma(short gamma)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0xB8, 0x00, 0x7F, (gamma&1)<<7);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42, 0x20, 0x7, (gamma&1)<<7);
+}
+
+static void pms_prefilter(short filter)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0x8A, 0x06, 0xBF, (filter&1)<<6);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42, 0x06, 0xBF, (filter&1)<<6);
+}
+
+static void pms_hfilter(short filter)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0xB8, 0x04, 0x1F, (filter&7)<<5);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42, 0x24, 0x1F, (filter&7)<<5);
+}
+
+static void pms_vfilter(short filter)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0xB8, 0x08, 0x9F, (filter&3)<<5);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42, 0x28, 0x9F, (filter&3)<<5);
+}
+
+static void pms_killcolour(short colour)
+{
+ if(decoder==PHILIPS2)
+ {
+ pms_i2c_andor(0x8A, 0x08, 0x07, (colour&0x1F)<<3);
+ pms_i2c_andor(0x8A, 0x09, 0x07, (colour&0x1F)<<3);
+ }
+ else if(decoder==PHILIPS1)
+ {
+ pms_i2c_andor(0x42, 0x08, 0x07, (colour&0x1F)<<3);
+ pms_i2c_andor(0x42, 0x09, 0x07, (colour&0x1F)<<3);
+ }
+}
+
+static void pms_chromagain(short chroma)
+{
+ if(decoder==PHILIPS2)
+ {
+ pms_i2c_write(0x8A, 0x11, chroma);
+ }
+ else if(decoder==PHILIPS1)
+ {
+ pms_i2c_write(0x42, 0x11, chroma);
+ }
+}
+
+
+static void pms_spacialcompl(short data)
+{
+ mvv_write(0x3B, data);
+}
+
+static void pms_spacialcomph(short data)
+{
+ mvv_write(0x3A, data);
+}
+
+static void pms_vstart(short start)
+{
+ mvv_write(0x16, start);
+ mvv_write(0x17, (start>>8)&0x01);
+}
+
+#endif
+
+static void pms_secamcross(short cross)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0x8A, 0x0F, 0xDF, (cross&1)<<5);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42, 0x0F, 0xDF, (cross&1)<<5);
+}
+
+
+static void pms_swsense(short sense)
+{
+ if(decoder==PHILIPS2)
+ {
+ pms_i2c_write(0x8A, 0x0A, sense);
+ pms_i2c_write(0x8A, 0x0B, sense);
+ }
+ else if(decoder==PHILIPS1)
+ {
+ pms_i2c_write(0x42, 0x0A, sense);
+ pms_i2c_write(0x42, 0x0B, sense);
+ }
+}
+
+
+static void pms_framerate(short frr)
+{
+ int fps=(standard==1)?30:25;
+ if(frr==0)
+ return;
+ fps=fps/frr;
+ mvv_write(0x14,0x80|fps);
+ mvv_write(0x15,1);
+}
+
+static void pms_vert(u8 deciden, u8 decinum)
+{
+ mvv_write(0x1C, deciden); /* Denominator */
+ mvv_write(0x1D, decinum); /* Numerator */
+}
+
+/*
+ * Turn 16bit ratios into best small ratio the chipset can grok
+ */
+
+static void pms_vertdeci(unsigned short decinum, unsigned short deciden)
+{
+ /* Knock it down by /5 once */
+ if(decinum%5==0)
+ {
+ deciden/=5;
+ decinum/=5;
+ }
+ /*
+ * 3's
+ */
+ while(decinum%3==0 && deciden%3==0)
+ {
+ deciden/=3;
+ decinum/=3;
+ }
+ /*
+ * 2's
+ */
+ while(decinum%2==0 && deciden%2==0)
+ {
+ decinum/=2;
+ deciden/=2;
+ }
+ /*
+ * Fudgyify
+ */
+ while(deciden>32)
+ {
+ deciden/=2;
+ decinum=(decinum+1)/2;
+ }
+ if(deciden==32)
+ deciden--;
+ pms_vert(deciden,decinum);
+}
+
+static void pms_horzdeci(short decinum, short deciden)
+{
+ if(decinum<=512)
+ {
+ if(decinum%5==0)
+ {
+ decinum/=5;
+ deciden/=5;
+ }
+ }
+ else
+ {
+ decinum=512;
+ deciden=640; /* 768 would be ideal */
+ }
+
+ while(((decinum|deciden)&1)==0)
+ {
+ decinum>>=1;
+ deciden>>=1;
+ }
+ while(deciden>32)
+ {
+ deciden>>=1;
+ decinum=(decinum+1)>>1;
+ }
+ if(deciden==32)
+ deciden--;
+
+ mvv_write(0x24, 0x80|deciden);
+ mvv_write(0x25, decinum);
+}
+
+static void pms_resolution(short width, short height)
+{
+ int fg_height;
+
+ fg_height=height;
+ if(fg_height>280)
+ fg_height=280;
+
+ mvv_write(0x18, fg_height);
+ mvv_write(0x19, fg_height>>8);
+
+ if(standard==1)
+ {
+ mvv_write(0x1A, 0xFC);
+ mvv_write(0x1B, 0x00);
+ if(height>fg_height)
+ pms_vertdeci(240,240);
+ else
+ pms_vertdeci(fg_height,240);
+ }
+ else
+ {
+ mvv_write(0x1A, 0x1A);
+ mvv_write(0x1B, 0x01);
+ if(fg_height>256)
+ pms_vertdeci(270,270);
+ else
+ pms_vertdeci(fg_height, 270);
+ }
+ mvv_write(0x12,0);
+ mvv_write(0x13, MVVMEMORYWIDTH);
+ mvv_write(0x42, 0x00);
+ mvv_write(0x43, 0x00);
+ mvv_write(0x44, MVVMEMORYWIDTH);
+
+ mvv_write(0x22, width+8);
+ mvv_write(0x23, (width+8)>> 8);
+
+ if(standard==1)
+ pms_horzdeci(width,640);
+ else
+ pms_horzdeci(width+8, 768);
+
+ mvv_write(0x30, mvv_read(0x30)&0xFE);
+ mvv_write(0x08, mvv_read(0x08)|0x01);
+ mvv_write(0x01, mvv_read(0x01)&0xFD);
+ mvv_write(0x32, 0x00);
+ mvv_write(0x33, MVVMEMORYWIDTH);
+}
+
+
+/*
+ * Set Input
+ */
+
+static void pms_vcrinput(short input)
+{
+ if(decoder==PHILIPS2)
+ pms_i2c_andor(0x8A,0x0D,0x7F,(input&1)<<7);
+ else if(decoder==PHILIPS1)
+ pms_i2c_andor(0x42,0x0D,0x7F,(input&1)<<7);
+}
+
+
+static int pms_capture(struct pms_device *dev, char *buf, int rgb555, int count)
+{
+ int y;
+ int dw = 2*dev->width;
+ u32 src = mem_base;
+
+ char tmp[dw+32]; /* using a temp buffer is faster than direct */
+ int cnt = 0;
+ int len=0;
+ unsigned char r8 = 0x5; /* value for reg8 */
+
+ if (rgb555)
+ r8 |= 0x20; /* else use untranslated rgb = 565 */
+ mvv_write(0x08,r8); /* capture rgb555/565, init DRAM, PC enable */
+
+/* printf("%d %d %d %d %d %x %x\n",width,height,voff,nom,den,mvv_buf); */
+
+ for (y = 0; y < dev->height; y++ )
+ {
+ isa_writeb(0, src); /* synchronisiert neue Zeile */
+
+ /*
+ * This is in truth a fifo, be very careful as if you
+ * forgot this odd things will occur 8)
+ */
+
+ isa_memcpy_fromio(tmp, src, dw+32); /* discard 16 word */
+ cnt -= dev->height;
+ while (cnt <= 0)
+ {
+ /*
+ * Don't copy too far
+ */
+ int dt=dw;
+ if(dt+len>count)
+ dt=count-len;
+ cnt += dev->height;
+ copy_to_user(buf, tmp+32, dt);
+ buf += dt;
+ len += dt;
+ }
+ }
+ return len;
+}
+
+
+/*
+ * Video4linux interfacing
+ */
+
+static int pms_open(struct video_device *dev, int flags)
+{
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void pms_close(struct video_device *dev)
+{
+ MOD_DEC_USE_COUNT;
+}
+
+static int pms_init_done(struct video_device *dev)
+{
+ return 0;
+}
+
+static long pms_write(struct video_device *v, const char *buf, unsigned long count, int noblock)
+{
+ return -EINVAL;
+}
+
+static int pms_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct pms_device *pd=(struct pms_device *)dev;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability b;
+ strcpy(b.name, "Mediavision PMS");
+ b.type = VID_TYPE_CAPTURE|VID_TYPE_SCALES;
+ b.channels = 4;
+ b.audios = 0;
+ b.maxwidth = 640;
+ b.maxheight = 480;
+ b.minwidth = 16;
+ b.minheight = 16;
+ if(copy_to_user(arg, &b,sizeof(b)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCGCHAN:
+ {
+ struct video_channel v;
+ if(copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ if(v.channel<0 || v.channel>3)
+ return -EINVAL;
+ v.flags=0;
+ v.tuners=1;
+ /* Good question.. its composite or SVHS so.. */
+ v.type = VIDEO_TYPE_CAMERA;
+ switch(v.channel)
+ {
+ case 0:
+ strcpy(v.name, "Composite");break;
+ case 1:
+ strcpy(v.name, "SVideo");break;
+ case 2:
+ strcpy(v.name, "Composite(VCR)");break;
+ case 3:
+ strcpy(v.name, "SVideo(VCR)");break;
+ }
+ if(copy_to_user(arg, &v, sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSCHAN:
+ {
+ int v;
+ if(copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ if(v<0 || v>3)
+ return -EINVAL;
+ pms_videosource(v&1);
+ pms_vcrinput(v>>1);
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner v;
+ if(copy_from_user(&v, arg, sizeof(v))!=0)
+ return -EFAULT;
+ if(v.tuner)
+ return -EINVAL;
+ strcpy(v.name, "Format");
+ v.rangelow=0;
+ v.rangehigh=0;
+ v.flags= VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM;
+ switch(standard)
+ {
+ case 0:
+ v.mode = VIDEO_MODE_AUTO;
+ break;
+ case 1:
+ v.mode = VIDEO_MODE_NTSC;
+ break;
+ case 2:
+ v.mode = VIDEO_MODE_PAL;
+ break;
+ case 3:
+ v.mode = VIDEO_MODE_SECAM;
+ break;
+ }
+ if(copy_to_user(arg,&v,sizeof(v))!=0)
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner v;
+ if(copy_from_user(&v, arg, sizeof(v))!=0)
+ return -EFAULT;
+ if(v.tuner)
+ return -EINVAL;
+ switch(v.mode)
+ {
+ case VIDEO_MODE_AUTO:
+ pms_framerate(25);
+ pms_secamcross(0);
+ pms_format(0);
+ break;
+ case VIDEO_MODE_NTSC:
+ pms_framerate(30);
+ pms_secamcross(0);
+ pms_format(1);
+ break;
+ case VIDEO_MODE_PAL:
+ pms_framerate(25);
+ pms_secamcross(0);
+ pms_format(2);
+ break;
+ case VIDEO_MODE_SECAM:
+ pms_framerate(25);
+ pms_secamcross(1);
+ pms_format(2);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+ }
+ case VIDIOCGPICT:
+ {
+ struct video_picture p=pd->picture;
+ if(copy_to_user(arg, &p, sizeof(p)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSPICT:
+ {
+ struct video_picture p;
+ if(copy_from_user(&p, arg, sizeof(p)))
+ return -EFAULT;
+ if(!((p.palette==VIDEO_PALETTE_RGB565 && p.depth==16)
+ ||(p.palette==VIDEO_PALETTE_RGB555 && p.depth==15)))
+ return -EINVAL;
+ pd->picture=p;
+
+ /*
+ * Now load the card.
+ */
+
+ pms_brightness(p.brightness>>8);
+ pms_hue(p.hue>>8);
+ pms_colour(p.colour>>8);
+ pms_contrast(p.contrast>>8);
+ return 0;
+ }
+ case VIDIOCSWIN:
+ {
+ struct video_window vw;
+ if(copy_from_user(&vw, arg,sizeof(vw)))
+ return -EFAULT;
+ if(vw.flags)
+ return -EINVAL;
+ if(vw.clipcount)
+ return -EINVAL;
+ if(vw.height<16||vw.height>480)
+ return -EINVAL;
+ if(vw.width<16||vw.width>640)
+ return -EINVAL;
+ pd->width=vw.width;
+ pd->height=vw.height;
+ pms_resolution(pd->width, pd->height);
+ /* Ok we figured out what to use from our wide choice */
+ return 0;
+ }
+ case VIDIOCGWIN:
+ {
+ struct video_window vw;
+ vw.x=0;
+ vw.y=0;
+ vw.width=pd->width;
+ vw.height=pd->height;
+ vw.chromakey=0;
+ vw.flags=0;
+ if(copy_to_user(arg, &vw, sizeof(vw)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCCAPTURE:
+ return -EINVAL;
+ case VIDIOCGFBUF:
+ return -EINVAL;
+ case VIDIOCSFBUF:
+ return -EINVAL;
+ case VIDIOCKEY:
+ return 0;
+ case VIDIOCGFREQ:
+ return -EINVAL;
+ case VIDIOCSFREQ:
+ return -EINVAL;
+ case VIDIOCGAUDIO:
+ return -EINVAL;
+ case VIDIOCSAUDIO:
+ return -EINVAL;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static long pms_read(struct video_device *v, char *buf, unsigned long count, int noblock)
+{
+ struct pms_device *pd=(struct pms_device *)v;
+ int len;
+
+ /* FIXME: semaphore this */
+ len=pms_capture(pd, buf, (pd->picture.depth==16)?0:1,count);
+ return len;
+}
+
+
+struct video_device pms_template=
+{
+ "Mediavision PMS",
+ VID_TYPE_CAPTURE,
+ VID_HARDWARE_PMS,
+ pms_open,
+ pms_close,
+ pms_read,
+ pms_write,
+ NULL, /* FIXME - we can use POLL on this board with the irq */
+ pms_ioctl,
+ NULL,
+ pms_init_done,
+ NULL,
+ 0,
+ 0
+};
+
+struct pms_device pms_device;
+
+
+/*
+ * Probe for and initialise the Mediavision PMS
+ */
+
+static int init_mediavision(void)
+{
+ int id;
+ int idec, decst;
+ int i;
+
+ unsigned char i2c_defs[]={
+ 0x4C,0x30,0x00,0xE8,
+ 0xB6,0xE2,0x00,0x00,
+ 0xFF,0xFF,0x00,0x00,
+ 0x00,0x00,0x78,0x98,
+ 0x00,0x00,0x00,0x00,
+ 0x34,0x0A,0xF4,0xCE,
+ 0xE4
+ };
+
+ if(check_region(0x9A01,1))
+ {
+ printk(KERN_WARNING "mediavision: unable to detect: 0x9A01 in use.\n");
+ return -EBUSY;
+ }
+ if(check_region(io_port,3))
+ {
+ printk(KERN_WARNING "mediavision: I/O port %d in use.\n", io_port);
+ return -EBUSY;
+ }
+ outb(0xB8, 0x9A01); /* Unlock */
+ outb(io_port>>4, 0x9A01); /* Set IO port */
+
+
+ id=mvv_read(3);
+ decst=pms_i2c_stat(0x43);
+
+ if(decst!=-1)
+ idec=2;
+ else if(pms_i2c_stat(0xb9)!=-1)
+ idec=3;
+ else if(pms_i2c_stat(0x8b)!=-1)
+ idec=1;
+ else
+ idec=0;
+
+ printk(KERN_INFO "PMS type is %d\n", idec);
+ if(idec==0)
+ return -ENODEV;
+
+ /*
+ * Ok we have a PMS of some sort
+ */
+
+ request_region(io_port,3, "Mediavision PMS");
+ request_region(0x9A01, 1, "Mediavision PMS config");
+
+ mvv_write(0x04, mem_base>>12); /* Set the memory area */
+
+ /* Ok now load the defaults */
+
+ for(i=0;i<0x19;i++)
+ {
+ if(i2c_defs[i]==0xFF)
+ pms_i2c_andor(0x8A, i, 0x07,0x00);
+ else
+ pms_i2c_write(0x8A, i, i2c_defs[i]);
+ }
+
+ pms_i2c_write(0xB8,0x00,0x12);
+ pms_i2c_write(0xB8,0x04,0x00);
+ pms_i2c_write(0xB8,0x07,0x00);
+ pms_i2c_write(0xB8,0x08,0x00);
+ pms_i2c_write(0xB8,0x09,0xFF);
+ pms_i2c_write(0xB8,0x0A,0x00);
+ pms_i2c_write(0xB8,0x0B,0x10);
+ pms_i2c_write(0xB8,0x10,0x03);
+
+ mvv_write(0x01, 0x00);
+ mvv_write(0x05, 0xA0);
+ mvv_write(0x08, 0x25);
+ mvv_write(0x09, 0x00);
+ mvv_write(0x0A, 0x20|MVVMEMORYWIDTH);
+
+ mvv_write(0x10, 0x02);
+ mvv_write(0x1E, 0x0C);
+ mvv_write(0x1F, 0x03);
+ mvv_write(0x26, 0x06);
+
+ mvv_write(0x2B, 0x00);
+ mvv_write(0x2C, 0x20);
+ mvv_write(0x2D, 0x00);
+ mvv_write(0x2F, 0x70);
+ mvv_write(0x32, 0x00);
+ mvv_write(0x33, MVVMEMORYWIDTH);
+ mvv_write(0x34, 0x00);
+ mvv_write(0x35, 0x00);
+ mvv_write(0x3A, 0x80);
+ mvv_write(0x3B, 0x10);
+ mvv_write(0x20, 0x00);
+ mvv_write(0x21, 0x00);
+ mvv_write(0x30, 0x22);
+ return 0;
+}
+
+/*
+ * Initialization and module stuff
+ */
+
+static int __init init_pms_cards(void)
+{
+ printk(KERN_INFO "Mediavision Pro Movie Studio driver 0.02\n");
+
+ data_port = io_port +1;
+
+ if(init_mediavision())
+ {
+ printk(KERN_INFO "Board not found.\n");
+ return -ENODEV;
+ }
+ memcpy(&pms_device, &pms_template, sizeof(pms_template));
+ pms_device.height=240;
+ pms_device.width=320;
+ pms_swsense(75);
+ pms_resolution(320,240);
+ return video_register_device((struct video_device *)&pms_device, VFL_TYPE_GRABBER);
+}
+
+MODULE_PARM(io_port,"i");
+MODULE_PARM(mem_base,"i");
+
+static void __exit shutdown_mediavision(void)
+{
+ release_region(io_port,3);
+ release_region(0x9A01, 1);
+}
+
+static void __exit cleanup_pms_module(void)
+{
+ shutdown_mediavision();
+ video_unregister_device((struct video_device *)&pms_device);
+}
+
+module_init(init_pms_cards);
+module_exit(cleanup_pms_module);
+
diff --git a/drivers/media/video/saa5249.c b/drivers/media/video/saa5249.c
new file mode 100644
index 000000000..1213e2ee5
--- /dev/null
+++ b/drivers/media/video/saa5249.c
@@ -0,0 +1,683 @@
+/*
+ * Cleaned up to use existing videodev interface and allow the idea
+ * of multiple teletext decoders on the video4linux iface. Changed i2c
+ * to cover addressing clashes on device busses. It's also rebuilt so
+ * you can add arbitary multiple teletext devices to Linux video4linux
+ * now (well 32 anyway).
+ *
+ * Alan Cox <Alan.Cox@linux.org>
+ *
+ * The original driver was heavily modified to match the i2c interface
+ * It was truncated to use the WinTV boards, too.
+ *
+ * Copyright (c) 1998 Richard Guenther <richard.guenther@student.uni-tuebingen.de>
+ *
+ * $Id: saa5249.c,v 1.1 1998/03/30 22:23:23 alan Exp $
+ *
+ * Derived From
+ *
+ * vtx.c:
+ * This is a loadable character-device-driver for videotext-interfaces
+ * (aka teletext). Please check the Makefile/README for a list of supported
+ * interfaces.
+ *
+ * Copyright (c) 1994-97 Martin Buck <martin-2.buck@student.uni-ulm.de>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <stdarg.h>
+#include <linux/i2c.h>
+#include <linux/videotext.h>
+#include <linux/videodev.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define VTX_VER_MAJ 1
+#define VTX_VER_MIN 7
+
+
+
+#define NUM_DAUS 4
+#define NUM_BUFS 8
+#define IF_NAME "SAA5249"
+
+static const int disp_modes[8][3] =
+{
+ { 0x46, 0x03, 0x03 }, /* DISPOFF */
+ { 0x46, 0xcc, 0xcc }, /* DISPNORM */
+ { 0x44, 0x0f, 0x0f }, /* DISPTRANS */
+ { 0x46, 0xcc, 0x46 }, /* DISPINS */
+ { 0x44, 0x03, 0x03 }, /* DISPOFF, interlaced */
+ { 0x44, 0xcc, 0xcc }, /* DISPNORM, interlaced */
+ { 0x44, 0x0f, 0x0f }, /* DISPTRANS, interlaced */
+ { 0x44, 0xcc, 0x46 } /* DISPINS, interlaced */
+};
+
+
+
+#define PAGE_WAIT (300*HZ/1000) /* Time between requesting page and */
+ /* checking status bits */
+#define PGBUF_EXPIRE (15*HZ) /* Time to wait before retransmitting */
+ /* page regardless of infobits */
+typedef struct {
+ u8 pgbuf[VTX_VIRTUALSIZE]; /* Page-buffer */
+ u8 laststat[10]; /* Last value of infobits for DAU */
+ u8 sregs[7]; /* Page-request registers */
+ unsigned long expire; /* Time when page will be expired */
+ unsigned clrfound : 1; /* VTXIOCCLRFOUND has been called */
+ unsigned stopped : 1; /* VTXIOCSTOPDAU has been called */
+} vdau_t;
+
+struct saa5249_device
+{
+ vdau_t vdau[NUM_DAUS]; /* Data for virtual DAUs (the 5249 only has one */
+ /* real DAU, so we have to simulate some more) */
+ int vtx_use_count;
+ int is_searching[NUM_DAUS];
+ int disp_mode;
+ int virtual_mode;
+ struct i2c_client *client;
+};
+
+
+#define CCTWR 34 /* I²C write/read-address of vtx-chip */
+#define CCTRD 35
+#define NOACK_REPEAT 10 /* Retry access this many times on failure */
+#define CLEAR_DELAY (HZ/20) /* Time required to clear a page */
+#define READY_TIMEOUT (30*HZ/1000) /* Time to wait for ready signal of I²C-bus interface */
+#define INIT_DELAY 500 /* Time in usec to wait at initialization of CEA interface */
+#define START_DELAY 10 /* Time in usec to wait before starting write-cycle (CEA) */
+
+#define VTX_DEV_MINOR 0
+
+/* General defines and debugging support */
+
+#ifndef FALSE
+#define FALSE 0
+#define TRUE 1
+#endif
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+#define RESCHED \
+ do { \
+ if (current->need_resched) \
+ schedule(); \
+ } while (0)
+
+static struct video_device saa_template; /* Declared near bottom */
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = {34>>1,I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {I2C_CLIENT_END};
+static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+
+static struct i2c_client_address_data addr_data = {
+ normal_i2c, normal_i2c_range,
+ probe, probe_range,
+ ignore, ignore_range,
+ force
+};
+
+static struct i2c_client client_template;
+
+static int saa5249_attach(struct i2c_adapter *adap, int addr, unsigned short flags, int kind)
+{
+ int pgbuf;
+ int err;
+ struct i2c_client *client;
+ struct video_device *vd;
+ struct saa5249_device *t;
+
+ printk(KERN_INFO "saa5249: teletext chip found.\n");
+ client=kmalloc(sizeof(*client), GFP_KERNEL);
+ if(client==NULL)
+ return -ENOMEM;
+ client_template.adapter = adap;
+ client_template.addr = addr;
+ memcpy(client, &client_template, sizeof(*client));
+ t = kmalloc(sizeof(*t), GFP_KERNEL);
+ if(t==NULL)
+ {
+ kfree(client);
+ return -ENOMEM;
+ }
+ memset(t, 0, sizeof(*t));
+ strcpy(client->name, IF_NAME);
+
+ /*
+ * Now create a video4linux device
+ */
+
+ client->data = vd=(struct video_device *)kmalloc(sizeof(struct video_device), GFP_KERNEL);
+ if(vd==NULL)
+ {
+ kfree(t);
+ kfree(client);
+ return -ENOMEM;
+ }
+ memcpy(vd, &saa_template, sizeof(*vd));
+
+ for (pgbuf = 0; pgbuf < NUM_DAUS; pgbuf++)
+ {
+ memset(t->vdau[pgbuf].pgbuf, ' ', sizeof(t->vdau[0].pgbuf));
+ memset(t->vdau[pgbuf].sregs, 0, sizeof(t->vdau[0].sregs));
+ memset(t->vdau[pgbuf].laststat, 0, sizeof(t->vdau[0].laststat));
+ t->vdau[pgbuf].expire = 0;
+ t->vdau[pgbuf].clrfound = TRUE;
+ t->vdau[pgbuf].stopped = TRUE;
+ t->is_searching[pgbuf] = FALSE;
+ }
+ vd->priv=t;
+
+ /*
+ * Register it
+ */
+
+ if((err=video_register_device(vd, VFL_TYPE_VTX))<0)
+ {
+ kfree(t);
+ kfree(vd);
+ kfree(client);
+ return err;
+ }
+ t->client = client;
+ i2c_attach_client(client);
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+/*
+ * We do most of the hard work when we become a device on the i2c.
+ */
+
+static int saa5249_probe(struct i2c_adapter *adap)
+{
+ /* Only attach these chips to the BT848 bus for now */
+
+ if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848))
+ {
+ return i2c_probe(adap, &addr_data, saa5249_attach);
+ }
+ return 0;
+}
+
+static int saa5249_detach(struct i2c_client *client)
+{
+ struct video_device *vd=client->data;
+ i2c_detach_client(client);
+ video_unregister_device(vd);
+ kfree(vd->priv);
+ kfree(vd);
+ kfree(client);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int saa5249_command(struct i2c_client *device,
+ unsigned int cmd, void *arg)
+{
+ return -EINVAL;
+}
+
+/* new I2C driver support */
+
+static struct i2c_driver i2c_driver_videotext =
+{
+ IF_NAME, /* name */
+ I2C_DRIVERID_SAA5249, /* in i2c.h */
+ I2C_DF_NOTIFY,
+ saa5249_probe,
+ saa5249_detach,
+ saa5249_command
+};
+
+static struct i2c_client client_template = {
+ "(unset)",
+ -1,
+ 0,
+ 0,
+ NULL,
+ &i2c_driver_videotext
+};
+
+/*
+ * Wait the given number of jiffies (10ms). This calls the scheduler, so the actual
+ * delay may be longer.
+ */
+
+static void jdelay(unsigned long delay)
+{
+ sigset_t oldblocked = current->blocked;
+
+ spin_lock_irq(&current->sigmask_lock);
+ sigfillset(&current->blocked);
+ recalc_sigpending(current);
+ spin_unlock_irq(&current->sigmask_lock);
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(delay);
+
+ spin_lock_irq(&current->sigmask_lock);
+ current->blocked = oldblocked;
+ recalc_sigpending(current);
+ spin_unlock_irq(&current->sigmask_lock);
+}
+
+
+/*
+ * I2C interfaces
+ */
+
+static int i2c_sendbuf(struct saa5249_device *t, int reg, int count, u8 *data)
+{
+ char buf[64];
+
+ buf[0] = reg;
+ memcpy(buf+1, data, count);
+
+ if(i2c_master_send(t->client, buf, count+1)==count+1)
+ return 0;
+ return -1;
+}
+
+static int i2c_senddata(struct saa5249_device *t, ...)
+{
+ unsigned char buf[64];
+ int v;
+ int ct=0;
+ va_list argp;
+ va_start(argp,t);
+
+ while((v=va_arg(argp,int))!=-1)
+ buf[ct++]=v;
+ return i2c_sendbuf(t, buf[0], ct-1, buf+1);
+}
+
+/* Get count number of bytes from I²C-device at address adr, store them in buf. Start & stop
+ * handshaking is done by this routine, ack will be sent after the last byte to inhibit further
+ * sending of data. If uaccess is TRUE, data is written to user-space with put_user.
+ * Returns -1 if I²C-device didn't send acknowledge, 0 otherwise
+ */
+
+static int i2c_getdata(struct saa5249_device *t, int count, u8 *buf)
+{
+ if(i2c_master_recv(t->client, buf, count)!=count)
+ return -1;
+ return 0;
+}
+
+
+/*
+ * Standard character-device-driver functions
+ */
+
+static int saa5249_ioctl(struct video_device *vd, unsigned int cmd, void *arg)
+{
+ struct saa5249_device *t=vd->priv;
+ static int virtual_mode = FALSE;
+
+ switch(cmd)
+ {
+ case VTXIOCGETINFO:
+ {
+ vtx_info_t info;
+ info.version_major = VTX_VER_MAJ;
+ info.version_minor = VTX_VER_MIN;
+ info.numpages = NUM_DAUS;
+ /*info.cct_type = CCT_TYPE;*/
+ if(copy_to_user((void*)arg, &info, sizeof(vtx_info_t)))
+ return -EFAULT;
+ return 0;
+ }
+
+ case VTXIOCCLRPAGE:
+ {
+ vtx_pagereq_t req;
+
+ if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t)))
+ return -EFAULT;
+ if (req.pgbuf < 0 || req.pgbuf >= NUM_DAUS)
+ return -EINVAL;
+ memset(t->vdau[req.pgbuf].pgbuf, ' ', sizeof(t->vdau[0].pgbuf));
+ t->vdau[req.pgbuf].clrfound = TRUE;
+ return 0;
+ }
+
+ case VTXIOCCLRFOUND:
+ {
+ vtx_pagereq_t req;
+
+ if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t)))
+ return -EFAULT;
+ if (req.pgbuf < 0 || req.pgbuf >= NUM_DAUS)
+ return -EINVAL;
+ t->vdau[req.pgbuf].clrfound = TRUE;
+ return 0;
+ }
+
+ case VTXIOCPAGEREQ:
+ {
+ vtx_pagereq_t req;
+ if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t)))
+ return -EFAULT;
+ if (!(req.pagemask & PGMASK_PAGE))
+ req.page = 0;
+ if (!(req.pagemask & PGMASK_HOUR))
+ req.hour = 0;
+ if (!(req.pagemask & PGMASK_MINUTE))
+ req.minute = 0;
+ if (req.page < 0 || req.page > 0x8ff) /* 7FF ?? */
+ return -EINVAL;
+ req.page &= 0x7ff;
+ if (req.hour < 0 || req.hour > 0x3f || req.minute < 0 || req.minute > 0x7f ||
+ req.pagemask < 0 || req.pagemask >= PGMASK_MAX || req.pgbuf < 0 || req.pgbuf >= NUM_DAUS)
+ return -EINVAL;
+ t->vdau[req.pgbuf].sregs[0] = (req.pagemask & PG_HUND ? 0x10 : 0) | (req.page / 0x100);
+ t->vdau[req.pgbuf].sregs[1] = (req.pagemask & PG_TEN ? 0x10 : 0) | ((req.page / 0x10) & 0xf);
+ t->vdau[req.pgbuf].sregs[2] = (req.pagemask & PG_UNIT ? 0x10 : 0) | (req.page & 0xf);
+ t->vdau[req.pgbuf].sregs[3] = (req.pagemask & HR_TEN ? 0x10 : 0) | (req.hour / 0x10);
+ t->vdau[req.pgbuf].sregs[4] = (req.pagemask & HR_UNIT ? 0x10 : 0) | (req.hour & 0xf);
+ t->vdau[req.pgbuf].sregs[5] = (req.pagemask & MIN_TEN ? 0x10 : 0) | (req.minute / 0x10);
+ t->vdau[req.pgbuf].sregs[6] = (req.pagemask & MIN_UNIT ? 0x10 : 0) | (req.minute & 0xf);
+ t->vdau[req.pgbuf].stopped = FALSE;
+ t->vdau[req.pgbuf].clrfound = TRUE;
+ t->is_searching[req.pgbuf] = TRUE;
+ return 0;
+ }
+
+ case VTXIOCGETSTAT:
+ {
+ vtx_pagereq_t req;
+ u8 infobits[10];
+ vtx_pageinfo_t info;
+ int a;
+
+ if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t)))
+ return -EFAULT;
+ if (req.pgbuf < 0 || req.pgbuf >= NUM_DAUS)
+ return -EINVAL;
+ if (!t->vdau[req.pgbuf].stopped)
+ {
+ if (i2c_senddata(t, 2, 0, -1) ||
+ i2c_sendbuf(t, 3, sizeof(t->vdau[0].sregs), t->vdau[req.pgbuf].sregs) ||
+ i2c_senddata(t, 8, 0, 25, 0, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', -1) ||
+ i2c_senddata(t, 2, 0, t->vdau[req.pgbuf].sregs[0] | 8, -1) ||
+ i2c_senddata(t, 8, 0, 25, 0, -1))
+ return -EIO;
+ jdelay(PAGE_WAIT);
+ if (i2c_getdata(t, 10, infobits))
+ return -EIO;
+
+ if (!(infobits[8] & 0x10) && !(infobits[7] & 0xf0) && /* check FOUND-bit */
+ (memcmp(infobits, t->vdau[req.pgbuf].laststat, sizeof(infobits)) ||
+ time_after_eq(jiffies, t->vdau[req.pgbuf].expire)))
+ { /* check if new page arrived */
+ if (i2c_senddata(t, 8, 0, 0, 0, -1) ||
+ i2c_getdata(t, VTX_PAGESIZE, t->vdau[req.pgbuf].pgbuf))
+ return -EIO;
+ t->vdau[req.pgbuf].expire = jiffies + PGBUF_EXPIRE;
+ memset(t->vdau[req.pgbuf].pgbuf + VTX_PAGESIZE, ' ', VTX_VIRTUALSIZE - VTX_PAGESIZE);
+ if (t->virtual_mode)
+ {
+ /* Packet X/24 */
+ if (i2c_senddata(t, 8, 0, 0x20, 0, -1) ||
+ i2c_getdata(t, 40, t->vdau[req.pgbuf].pgbuf + VTX_PAGESIZE + 20 * 40))
+ return -EIO;
+ /* Packet X/27/0 */
+ if (i2c_senddata(t, 8, 0, 0x21, 0, -1) ||
+ i2c_getdata(t, 40, t->vdau[req.pgbuf].pgbuf + VTX_PAGESIZE + 16 * 40))
+ return -EIO;
+ /* Packet 8/30/0...8/30/15
+ * FIXME: AFAIK, the 5249 does hamming-decoding for some bytes in packet 8/30,
+ * so we should undo this here.
+ */
+ if (i2c_senddata(t, 8, 0, 0x22, 0, -1) ||
+ i2c_getdata(t, 40, t->vdau[req.pgbuf].pgbuf + VTX_PAGESIZE + 23 * 40))
+ return -EIO;
+ }
+ t->vdau[req.pgbuf].clrfound = FALSE;
+ memcpy(t->vdau[req.pgbuf].laststat, infobits, sizeof(infobits));
+ }
+ else
+ {
+ memcpy(infobits, t->vdau[req.pgbuf].laststat, sizeof(infobits));
+ }
+ }
+ else
+ {
+ memcpy(infobits, t->vdau[req.pgbuf].laststat, sizeof(infobits));
+ }
+
+ info.pagenum = ((infobits[8] << 8) & 0x700) | ((infobits[1] << 4) & 0xf0) | (infobits[0] & 0x0f);
+ if (info.pagenum < 0x100)
+ info.pagenum += 0x800;
+ info.hour = ((infobits[5] << 4) & 0x30) | (infobits[4] & 0x0f);
+ info.minute = ((infobits[3] << 4) & 0x70) | (infobits[2] & 0x0f);
+ info.charset = ((infobits[7] >> 1) & 7);
+ info.delete = !!(infobits[3] & 8);
+ info.headline = !!(infobits[5] & 4);
+ info.subtitle = !!(infobits[5] & 8);
+ info.supp_header = !!(infobits[6] & 1);
+ info.update = !!(infobits[6] & 2);
+ info.inter_seq = !!(infobits[6] & 4);
+ info.dis_disp = !!(infobits[6] & 8);
+ info.serial = !!(infobits[7] & 1);
+ info.notfound = !!(infobits[8] & 0x10);
+ info.pblf = !!(infobits[9] & 0x20);
+ info.hamming = 0;
+ for (a = 0; a <= 7; a++)
+ {
+ if (infobits[a] & 0xf0)
+ {
+ info.hamming = 1;
+ break;
+ }
+ }
+ if (t->vdau[req.pgbuf].clrfound)
+ info.notfound = 1;
+ if(copy_to_user(req.buffer, &info, sizeof(vtx_pageinfo_t)))
+ return -EFAULT;
+ if (!info.hamming && !info.notfound)
+ {
+ t->is_searching[req.pgbuf] = FALSE;
+ }
+ return 0;
+ }
+
+ case VTXIOCGETPAGE:
+ {
+ vtx_pagereq_t req;
+ int start, end;
+
+ if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t)))
+ return -EFAULT;
+ if (req.pgbuf < 0 || req.pgbuf >= NUM_DAUS || req.start < 0 ||
+ req.start > req.end || req.end >= (virtual_mode ? VTX_VIRTUALSIZE : VTX_PAGESIZE))
+ return -EINVAL;
+ if(copy_to_user(req.buffer, &t->vdau[req.pgbuf].pgbuf[req.start], req.end - req.start + 1))
+ return -EFAULT;
+
+ /*
+ * Always read the time directly from SAA5249
+ */
+
+ if (req.start <= 39 && req.end >= 32)
+ {
+ int len;
+ char buf[16];
+ start = MAX(req.start, 32);
+ end = MIN(req.end, 39);
+ len=end-start+1;
+ if (i2c_senddata(t, 8, 0, 0, start, -1) ||
+ i2c_getdata(t, len, buf))
+ return -EIO;
+ if(copy_to_user(req.buffer+start-req.start, buf, len))
+ return -EFAULT;
+ }
+ /* Insert the current header if DAU is still searching for a page */
+ if (req.start <= 31 && req.end >= 7 && t->is_searching[req.pgbuf])
+ {
+ char buf[32];
+ int len;
+ start = MAX(req.start, 7);
+ end = MIN(req.end, 31);
+ len=end-start+1;
+ if (i2c_senddata(t, 8, 0, 0, start, -1) ||
+ i2c_getdata(t, len, buf))
+ return -EIO;
+ if(copy_to_user(req.buffer+start-req.start, buf, len))
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ case VTXIOCSTOPDAU:
+ {
+ vtx_pagereq_t req;
+
+ if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t)))
+ return -EFAULT;
+ if (req.pgbuf < 0 || req.pgbuf >= NUM_DAUS)
+ return -EINVAL;
+ t->vdau[req.pgbuf].stopped = TRUE;
+ t->is_searching[req.pgbuf] = FALSE;
+ return 0;
+ }
+
+ case VTXIOCPUTPAGE:
+ case VTXIOCSETDISP:
+ case VTXIOCPUTSTAT:
+ return 0;
+
+ case VTXIOCCLRCACHE:
+ {
+ if (i2c_senddata(t, 0, NUM_DAUS, 0, 8, -1) || i2c_senddata(t, 11,
+ ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
+ ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ', -1))
+ return -EIO;
+ if (i2c_senddata(t, 3, 0x20, -1))
+ return -EIO;
+ jdelay(10 * CLEAR_DELAY); /* I have no idea how long we have to wait here */
+ return 0;
+ }
+
+ case VTXIOCSETVIRT:
+ {
+ /* The SAA5249 has virtual-row reception turned on always */
+ t->virtual_mode = (int)arg;
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+
+static int saa5249_open(struct video_device *vd, int nb)
+{
+ struct saa5249_device *t=vd->priv;
+ int pgbuf;
+
+ if (t->client==NULL)
+ return -ENODEV;
+
+ if (i2c_senddata(t, 0, 0, -1) || /* Select R11 */
+ /* Turn off parity checks (we do this ourselves) */
+ i2c_senddata(t, 1, disp_modes[t->disp_mode][0], 0, -1) ||
+ /* Display TV-picture, no virtual rows */
+ i2c_senddata(t, 4, NUM_DAUS, disp_modes[t->disp_mode][1], disp_modes[t->disp_mode][2], 7, -1)) /* Set display to page 4 */
+
+ {
+ return -EIO;
+ }
+
+ for (pgbuf = 0; pgbuf < NUM_DAUS; pgbuf++)
+ {
+ memset(t->vdau[pgbuf].pgbuf, ' ', sizeof(t->vdau[0].pgbuf));
+ memset(t->vdau[pgbuf].sregs, 0, sizeof(t->vdau[0].sregs));
+ memset(t->vdau[pgbuf].laststat, 0, sizeof(t->vdau[0].laststat));
+ t->vdau[pgbuf].expire = 0;
+ t->vdau[pgbuf].clrfound = TRUE;
+ t->vdau[pgbuf].stopped = TRUE;
+ t->is_searching[pgbuf] = FALSE;
+ }
+ t->virtual_mode=FALSE;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+
+
+static void saa5249_release(struct video_device *vd)
+{
+ struct saa5249_device *t=vd->priv;
+ i2c_senddata(t, 1, 0x20, -1); /* Turn off CCT */
+ i2c_senddata(t, 5, 3, 3, -1); /* Turn off TV-display */
+ MOD_DEC_USE_COUNT;
+ return;
+}
+
+static long saa5249_write(struct video_device *v, const char *buf, unsigned long l, int nb)
+{
+ return -EINVAL;
+}
+
+static int __init init_saa_5249 (void)
+{
+ printk(KERN_INFO "SAA5249 driver (" IF_NAME " interface) for VideoText version %d.%d\n",
+ VTX_VER_MAJ, VTX_VER_MIN);
+ return i2c_add_driver(&i2c_driver_videotext);
+}
+
+static void __exit cleanup_saa_5249 (void)
+{
+ i2c_del_driver(&i2c_driver_videotext);
+}
+
+module_init(init_saa_5249);
+module_exit(cleanup_saa_5249);
+
+static struct video_device saa_template =
+{
+ IF_NAME,
+ VID_TYPE_TELETEXT, /*| VID_TYPE_TUNER ?? */
+ VID_HARDWARE_SAA5249,
+ saa5249_open,
+ saa5249_release,
+ NULL, /* read */
+ saa5249_write,
+ NULL, /* poll */
+ saa5249_ioctl,
+ /* the rest are null */
+};
+
diff --git a/drivers/media/video/saa7110.c b/drivers/media/video/saa7110.c
new file mode 100644
index 000000000..6146b84b9
--- /dev/null
+++ b/drivers/media/video/saa7110.c
@@ -0,0 +1,429 @@
+/*
+ saa7110 - Philips SAA7110(A) video decoder driver
+
+ Copyright (C) 1998 Pauline Middelink <middelin@polyware.nl>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include <linux/i2c-old.h>
+#include <linux/videodev.h>
+#include "linux/video_decoder.h"
+
+#define DEBUG(x...) /* remove when no long debugging */
+
+#define SAA7110_MAX_INPUT 9 /* 6 CVBS, 3 SVHS */
+#define SAA7110_MAX_OUTPUT 0 /* its a decoder only */
+
+#define I2C_SAA7110 0x9C /* or 0x9E */
+
+#define I2C_DELAY 10 /* 10 us or 100khz */
+
+struct saa7110 {
+ struct i2c_bus *bus;
+ int addr;
+ unsigned char reg[36];
+
+ int norm;
+ int input;
+ int enable;
+ int bright;
+ int contrast;
+ int hue;
+ int sat;
+};
+
+/* ----------------------------------------------------------------------- */
+/* I2C support functions */
+/* ----------------------------------------------------------------------- */
+static
+int saa7110_write(struct saa7110 *decoder, unsigned char subaddr, unsigned char data)
+{
+ int ack;
+
+ LOCK_I2C_BUS(decoder->bus);
+ i2c_start(decoder->bus);
+ i2c_sendbyte(decoder->bus, decoder->addr, I2C_DELAY);
+ i2c_sendbyte(decoder->bus, subaddr, I2C_DELAY);
+ ack = i2c_sendbyte(decoder->bus, data, I2C_DELAY);
+ i2c_stop(decoder->bus);
+ decoder->reg[subaddr] = data;
+ UNLOCK_I2C_BUS(decoder->bus);
+ return ack;
+}
+
+static
+int saa7110_write_block(struct saa7110* decoder, unsigned const char *data, unsigned int len)
+{
+ unsigned subaddr = *data;
+
+ LOCK_I2C_BUS(decoder->bus);
+ i2c_start(decoder->bus);
+ i2c_sendbyte(decoder->bus,decoder->addr,I2C_DELAY);
+ while (len-- > 0) {
+ if (i2c_sendbyte(decoder->bus,*data,0)) {
+ i2c_stop(decoder->bus);
+ return -EAGAIN;
+ }
+ decoder->reg[subaddr++] = *data++;
+ }
+ i2c_stop(decoder->bus);
+ UNLOCK_I2C_BUS(decoder->bus);
+
+ return 0;
+}
+
+static
+int saa7110_read(struct saa7110* decoder)
+{
+ int data;
+
+ LOCK_I2C_BUS(decoder->bus);
+ i2c_start(decoder->bus);
+ i2c_sendbyte(decoder->bus, decoder->addr, I2C_DELAY);
+ i2c_start(decoder->bus);
+ i2c_sendbyte(decoder->bus, decoder->addr | 1, I2C_DELAY);
+ data = i2c_readbyte(decoder->bus, 1);
+ i2c_stop(decoder->bus);
+ UNLOCK_I2C_BUS(decoder->bus);
+ return data;
+}
+
+/* ----------------------------------------------------------------------- */
+/* SAA7110 functions */
+/* ----------------------------------------------------------------------- */
+static
+int saa7110_selmux(struct i2c_device *device, int chan)
+{
+static const unsigned char modes[9][8] = {
+/* mode 0 */ { 0x00, 0xD9, 0x17, 0x40, 0x03, 0x44, 0x75, 0x16 },
+/* mode 1 */ { 0x00, 0xD8, 0x17, 0x40, 0x03, 0x44, 0x75, 0x16 },
+/* mode 2 */ { 0x00, 0xBA, 0x07, 0x91, 0x03, 0x60, 0xB5, 0x05 },
+/* mode 3 */ { 0x00, 0xB8, 0x07, 0x91, 0x03, 0x60, 0xB5, 0x05 },
+/* mode 4 */ { 0x00, 0x7C, 0x07, 0xD2, 0x83, 0x60, 0xB5, 0x03 },
+/* mode 5 */ { 0x00, 0x78, 0x07, 0xD2, 0x83, 0x60, 0xB5, 0x03 },
+/* mode 6 */ { 0x80, 0x59, 0x17, 0x42, 0xA3, 0x44, 0x75, 0x12 },
+/* mode 7 */ { 0x80, 0x9A, 0x17, 0xB1, 0x13, 0x60, 0xB5, 0x14 },
+/* mode 8 */ { 0x80, 0x3C, 0x27, 0xC1, 0x23, 0x44, 0x75, 0x21 } };
+ struct saa7110* decoder = device->data;
+ const unsigned char* ptr = modes[chan];
+
+ saa7110_write(decoder,0x06,ptr[0]); /* Luminance control */
+ saa7110_write(decoder,0x20,ptr[1]); /* Analog Control #1 */
+ saa7110_write(decoder,0x21,ptr[2]); /* Analog Control #2 */
+ saa7110_write(decoder,0x22,ptr[3]); /* Mixer Control #1 */
+ saa7110_write(decoder,0x2C,ptr[4]); /* Mixer Control #2 */
+ saa7110_write(decoder,0x30,ptr[5]); /* ADCs gain control */
+ saa7110_write(decoder,0x31,ptr[6]); /* Mixer Control #3 */
+ saa7110_write(decoder,0x21,ptr[7]); /* Analog Control #2 */
+
+ return 0;
+}
+
+static
+int determine_norm(struct i2c_device* dev)
+{
+ struct saa7110* decoder = dev->data;
+ int status;
+
+ /* mode changed, start automatic detection */
+ status = saa7110_read(decoder);
+ if ((status & 3) == 0) {
+ saa7110_write(decoder,0x06,0x80);
+ if (status & 0x20) {
+ DEBUG(printk(KERN_INFO "%s: norm=bw60\n",dev->name));
+ saa7110_write(decoder,0x2E,0x81);
+ return VIDEO_MODE_NTSC;
+ }
+ DEBUG(printk(KERN_INFO "%s: norm=bw50\n",dev->name));
+ saa7110_write(decoder,0x2E,0x9A);
+ return VIDEO_MODE_PAL;
+ }
+
+ saa7110_write(decoder,0x06,0x00);
+ if (status & 0x20) { /* 60Hz */
+ DEBUG(printk(KERN_INFO "%s: norm=ntsc\n",dev->name));
+ saa7110_write(decoder,0x0D,0x06);
+ saa7110_write(decoder,0x11,0x2C);
+ saa7110_write(decoder,0x2E,0x81);
+ return VIDEO_MODE_NTSC;
+ }
+
+ /* 50Hz -> PAL/SECAM */
+ saa7110_write(decoder,0x0D,0x06);
+ saa7110_write(decoder,0x11,0x59);
+ saa7110_write(decoder,0x2E,0x9A);
+
+ mdelay(150); /* pause 150 ms */
+
+ status = saa7110_read(decoder);
+ if ((status & 0x03) == 0x01) {
+ DEBUG(printk(KERN_INFO "%s: norm=secam\n",dev->name));
+ saa7110_write(decoder,0x0D,0x07);
+ return VIDEO_MODE_SECAM;
+ }
+ DEBUG(printk(KERN_INFO "%s: norm=pal\n",dev->name));
+ return VIDEO_MODE_PAL;
+}
+
+static
+int saa7110_attach(struct i2c_device *device)
+{
+static const unsigned char initseq[] = {
+ 0, 0x4C, 0x3C, 0x0D, 0xEF, 0xBD, 0xF0, 0x00, 0x00,
+ 0xF8, 0xF8, 0x60, 0x60, 0x00, 0x06, 0x18, 0x90,
+ 0x00, 0x2C, 0x40, 0x46, 0x42, 0x1A, 0xFF, 0xDA,
+ 0xF0, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xD9, 0x17, 0x40, 0x41, 0x80, 0x41, 0x80, 0x4F,
+ 0xFE, 0x01, 0xCF, 0x0F, 0x03, 0x01, 0x81, 0x03,
+ 0x40, 0x75, 0x01, 0x8C, 0x03};
+ struct saa7110* decoder;
+ int rv;
+
+ device->data = decoder = kmalloc(sizeof(struct saa7110), GFP_KERNEL);
+ if (device->data == 0)
+ return -ENOMEM;
+
+ MOD_INC_USE_COUNT;
+
+ /* clear our private data */
+ memset(decoder, 0, sizeof(struct saa7110));
+ strcpy(device->name, "saa7110");
+ decoder->bus = device->bus;
+ decoder->addr = device->addr;
+ decoder->norm = VIDEO_MODE_PAL;
+ decoder->input = 0;
+ decoder->enable = 1;
+ decoder->bright = 32768;
+ decoder->contrast = 32768;
+ decoder->hue = 32768;
+ decoder->sat = 32768;
+
+ rv = saa7110_write_block(decoder, initseq, sizeof(initseq));
+ if (rv < 0)
+ printk(KERN_ERR "%s_attach: init status %d\n", device->name, rv);
+ else {
+ saa7110_write(decoder,0x21,0x16);
+ saa7110_write(decoder,0x0D,0x04);
+ DEBUG(printk(KERN_INFO "%s_attach: chip version %x\n", device->name, saa7110_read(decoder)));
+ saa7110_write(decoder,0x0D,0x06);
+ }
+
+ /* setup and implicit mode 0 select has been performed */
+ return 0;
+}
+
+static
+int saa7110_detach(struct i2c_device *device)
+{
+ struct saa7110* decoder = device->data;
+
+ DEBUG(printk(KERN_INFO "%s_detach\n",device->name));
+
+ /* stop further output */
+ saa7110_write(decoder,0x0E,0x00);
+
+ kfree(device->data);
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static
+int saa7110_command(struct i2c_device *device, unsigned int cmd, void *arg)
+{
+ struct saa7110* decoder = device->data;
+ int v;
+
+ switch (cmd) {
+ case DECODER_GET_CAPABILITIES:
+ {
+ struct video_decoder_capability *dc = arg;
+ dc->flags = VIDEO_DECODER_PAL
+ | VIDEO_DECODER_NTSC
+ | VIDEO_DECODER_SECAM
+ | VIDEO_DECODER_AUTO
+ | VIDEO_DECODER_CCIR;
+ dc->inputs = SAA7110_MAX_INPUT;
+ dc->outputs = SAA7110_MAX_OUTPUT;
+ }
+ break;
+
+ case DECODER_GET_STATUS:
+ {
+ struct saa7110* decoder = device->data;
+ int status;
+ int res = 0;
+
+ status = i2c_read(device->bus,device->addr|1);
+ if (status & 0x40)
+ res |= DECODER_STATUS_GOOD;
+ if (status & 0x03)
+ res |= DECODER_STATUS_COLOR;
+
+ switch (decoder->norm) {
+ case VIDEO_MODE_NTSC:
+ res |= DECODER_STATUS_NTSC;
+ break;
+ case VIDEO_MODE_PAL:
+ res |= DECODER_STATUS_PAL;
+ break;
+ case VIDEO_MODE_SECAM:
+ res |= DECODER_STATUS_SECAM;
+ break;
+ }
+ *(int*)arg = res;
+ }
+ break;
+
+ case DECODER_SET_NORM:
+ v = *(int*)arg;
+ if (decoder->norm != v) {
+ decoder->norm = v;
+ saa7110_write(decoder, 0x06, 0x00);
+ switch (v) {
+ case VIDEO_MODE_NTSC:
+ saa7110_write(decoder, 0x0D, 0x06);
+ saa7110_write(decoder, 0x11, 0x2C);
+ saa7110_write(decoder, 0x30, 0x81);
+saa7110_write(decoder, 0x2A, 0xDF);
+ break;
+ case VIDEO_MODE_PAL:
+ saa7110_write(decoder, 0x0D, 0x06);
+ saa7110_write(decoder, 0x11, 0x59);
+ saa7110_write(decoder, 0x2E, 0x9A);
+ break;
+ case VIDEO_MODE_SECAM:
+ saa7110_write(decoder, 0x0D, 0x07);
+ saa7110_write(decoder, 0x11, 0x59);
+ saa7110_write(decoder, 0x2E, 0x9A);
+ break;
+ case VIDEO_MODE_AUTO:
+ *(int*)arg = determine_norm(device);
+ break;
+ default:
+ return -EPERM;
+ }
+ }
+ break;
+
+ case DECODER_SET_INPUT:
+ v = *(int*)arg;
+ if (v<0 || v>SAA7110_MAX_INPUT)
+ return -EINVAL;
+ if (decoder->input != v) {
+ decoder->input = v;
+ saa7110_selmux(device, v);
+ }
+ break;
+
+ case DECODER_SET_OUTPUT:
+ v = *(int*)arg;
+ /* not much choice of outputs */
+ if (v != 0)
+ return -EINVAL;
+ break;
+
+ case DECODER_ENABLE_OUTPUT:
+ v = *(int*)arg;
+ if (decoder->enable != v) {
+ decoder->enable = v;
+ saa7110_write(decoder,0x0E, v ? 0x18 : 0x00);
+ }
+ break;
+
+ case DECODER_SET_PICTURE:
+ {
+ struct video_picture *pic = arg;
+
+ if (decoder->bright != pic->brightness) {
+ /* We want 0 to 255 we get 0-65535 */
+ decoder->bright = pic->brightness;
+ saa7110_write(decoder, 0x19, decoder->bright >> 8);
+ }
+ if (decoder->contrast != pic->contrast) {
+ /* We want 0 to 127 we get 0-65535 */
+ decoder->contrast = pic->contrast;
+ saa7110_write(decoder, 0x13, decoder->contrast >> 9);
+ }
+ if (decoder->sat != pic->colour) {
+ /* We want 0 to 127 we get 0-65535 */
+ decoder->sat = pic->colour;
+ saa7110_write(decoder, 0x12, decoder->sat >> 9);
+ }
+ if (decoder->hue != pic->hue) {
+ /* We want -128 to 127 we get 0-65535 */
+ decoder->hue = pic->hue;
+ saa7110_write(decoder, 0x07, (decoder->hue>>8)-128);
+ }
+ }
+ break;
+
+ case DECODER_DUMP:
+ for (v=0; v<34; v+=16) {
+ int j;
+ DEBUG(printk(KERN_INFO "%s: %03x\n",device->name,v));
+ for (j=0; j<16; j++) {
+ DEBUG(printk(KERN_INFO " %02x",decoder->reg[v+j]));
+ }
+ DEBUG(printk(KERN_INFO "\n"));
+ }
+ break;
+
+ default:
+ DEBUG(printk(KERN_INFO "unknown saa7110_command??(%d)\n",cmd));
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+struct i2c_driver i2c_driver_saa7110 =
+{
+ "saa7110", /* name */
+
+ I2C_DRIVERID_VIDEODECODER, /* in i2c.h */
+ I2C_SAA7110, I2C_SAA7110+1, /* Addr range */
+
+ saa7110_attach,
+ saa7110_detach,
+ saa7110_command
+};
+
+EXPORT_NO_SYMBOLS;
+
+#ifdef MODULE
+int init_module(void)
+#else
+int saa7110_init(void)
+#endif
+{
+ return i2c_register_driver(&i2c_driver_saa7110);
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ i2c_unregister_driver(&i2c_driver_saa7110);
+}
+#endif
diff --git a/drivers/media/video/saa7111.c b/drivers/media/video/saa7111.c
new file mode 100644
index 000000000..1eeeca352
--- /dev/null
+++ b/drivers/media/video/saa7111.c
@@ -0,0 +1,418 @@
+/*
+ saa7111 - Philips SAA7111A video decoder driver version 0.0.3
+
+ Copyright (C) 1998 Dave Perks <dperks@ibm.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+#include <linux/signal.h>
+#include <asm/io.h>
+#include <asm/pgtable.h>
+#include <asm/page.h>
+#include <linux/sched.h>
+#include <asm/segment.h>
+#include <linux/types.h>
+#include <linux/wrapper.h>
+
+#include <linux/videodev.h>
+#include <linux/version.h>
+#include <asm/uaccess.h>
+
+#include <linux/i2c-old.h>
+#include <linux/video_decoder.h>
+
+#define DEBUG(x) /* Debug driver */
+
+/* ----------------------------------------------------------------------- */
+
+struct saa7111 {
+ struct i2c_bus *bus;
+ int addr;
+ unsigned char reg[32];
+
+ int norm;
+ int input;
+ int enable;
+ int bright;
+ int contrast;
+ int hue;
+ int sat;
+};
+
+#define I2C_SAA7111 0x48
+
+#define I2C_DELAY 10
+
+/* ----------------------------------------------------------------------- */
+
+static int saa7111_write(struct saa7111 *dev, unsigned char subaddr, unsigned char data)
+{
+ int ack;
+
+ LOCK_I2C_BUS(dev->bus);
+ i2c_start(dev->bus);
+ i2c_sendbyte(dev->bus, dev->addr, I2C_DELAY);
+ i2c_sendbyte(dev->bus, subaddr, I2C_DELAY);
+ ack = i2c_sendbyte(dev->bus, data, I2C_DELAY);
+ dev->reg[subaddr] = data;
+ i2c_stop(dev->bus);
+ UNLOCK_I2C_BUS(dev->bus);
+ return ack;
+}
+
+static int saa7111_write_block(struct saa7111 *dev, unsigned const char *data, unsigned int len)
+{
+ int ack = 0;
+ unsigned subaddr;
+
+ while (len > 1) {
+ LOCK_I2C_BUS(dev->bus);
+ i2c_start(dev->bus);
+ i2c_sendbyte(dev->bus, dev->addr, I2C_DELAY);
+ ack = i2c_sendbyte(dev->bus, (subaddr = *data++), I2C_DELAY);
+ ack = i2c_sendbyte(dev->bus, (dev->reg[subaddr] = *data++), I2C_DELAY);
+ len -= 2;
+ while (len > 1 && *data == ++subaddr) {
+ data++;
+ ack = i2c_sendbyte(dev->bus, (dev->reg[subaddr] = *data++), I2C_DELAY);
+ len -= 2;
+ }
+ i2c_stop(dev->bus);
+ UNLOCK_I2C_BUS(dev->bus);
+ }
+ return ack;
+}
+
+static int saa7111_read(struct saa7111 *dev, unsigned char subaddr)
+{
+ int data;
+
+ LOCK_I2C_BUS(dev->bus);
+ i2c_start(dev->bus);
+ i2c_sendbyte(dev->bus, dev->addr, I2C_DELAY);
+ i2c_sendbyte(dev->bus, subaddr, I2C_DELAY);
+ i2c_start(dev->bus);
+ i2c_sendbyte(dev->bus, dev->addr | 1, I2C_DELAY);
+ data = i2c_readbyte(dev->bus, 1);
+ i2c_stop(dev->bus);
+ UNLOCK_I2C_BUS(dev->bus);
+ return data;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static int saa7111_attach(struct i2c_device *device)
+{
+ int i;
+ struct saa7111 *decoder;
+
+ static const unsigned char init[] =
+ {
+ 0x00, 0x00, /* 00 - ID byte */
+ 0x01, 0x00, /* 01 - reserved */
+
+ /*front end */
+ 0x02, 0xd0, /* 02 - FUSE=3, GUDL=2, MODE=0 */
+ 0x03, 0x23, /* 03 - HLNRS=0, VBSL=1, WPOFF=0, HOLDG=0, GAFIX=0, GAI1=256, GAI2=256 */
+ 0x04, 0x00, /* 04 - GAI1=256 */
+ 0x05, 0x00, /* 05 - GAI2=256 */
+
+ /* decoder */
+ 0x06, 0xf6, /* 06 - HSB at 13(50Hz) / 17(60Hz) pixels after end of last line */
+ 0x07, 0xdd, /* 07 - HSS at 113(50Hz) / 117(60Hz) pixels after end of last line */
+ 0x08, 0xc8, /* 08 - AUFD=1, FSEL=1, EXFIL=0, VTRC=1, HPLL=0, VNOI=0 */
+ 0x09, 0x01, /* 09 - BYPS=0, PREF=0, BPSS=0, VBLB=0, UPTCV=0, APER=1 */
+ 0x0a, 0x80, /* 0a - BRIG=128 */
+ 0x0b, 0x47, /* 0b - CONT=1.109 */
+ 0x0c, 0x40, /* 0c - SATN=1.0 */
+ 0x0d, 0x00, /* 0d - HUE=0 */
+ 0x0e, 0x01, /* 0e - CDTO=0, CSTD=0, DCCF=0, FCTC=0, CHBW=1 */
+ 0x0f, 0x00, /* 0f - reserved */
+ 0x10, 0x48, /* 10 - OFTS=1, HDEL=0, VRLN=1, YDEL=0 */
+ 0x11, 0x1c, /* 11 - GPSW=0, CM99=0, FECO=0, COMPO=1, OEYC=1, OEHV=1, VIPB=0, COLO=0 */
+ 0x12, 0x00, /* 12 - output control 2 */
+ 0x13, 0x00, /* 13 - output control 3 */
+ 0x14, 0x00, /* 14 - reserved */
+ 0x15, 0x00, /* 15 - VBI */
+ 0x16, 0x00, /* 16 - VBI */
+ 0x17, 0x00, /* 17 - VBI */
+ };
+
+ device->data = decoder = kmalloc(sizeof(struct saa7111), GFP_KERNEL);
+ if (decoder == NULL) {
+ return -ENOMEM;
+ }
+ MOD_INC_USE_COUNT;
+
+ memset(decoder, 0, sizeof(struct saa7111));
+ strcpy(device->name, "saa7111");
+ decoder->bus = device->bus;
+ decoder->addr = device->addr;
+ decoder->norm = VIDEO_MODE_NTSC;
+ decoder->input = 0;
+ decoder->enable = 1;
+ decoder->bright = 32768;
+ decoder->contrast = 32768;
+ decoder->hue = 32768;
+ decoder->sat = 32768;
+
+ i = saa7111_write_block(decoder, init, sizeof(init));
+ if (i < 0) {
+ printk(KERN_ERR "%s_attach: init status %d\n", device->name, i);
+ } else {
+ printk(KERN_INFO "%s_attach: chip version %x\n", device->name, saa7111_read(decoder, 0x00));
+ }
+ return 0;
+}
+
+
+static int saa7111_detach(struct i2c_device *device)
+{
+ kfree(device->data);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int saa7111_command(struct i2c_device *device, unsigned int cmd, void *arg)
+{
+ struct saa7111 *decoder = device->data;
+
+ switch (cmd) {
+
+#if defined(DECODER_DUMP)
+ case DECODER_DUMP:
+ {
+ int i;
+
+ for (i = 0; i < 32; i += 16) {
+ int j;
+
+ printk("KERN_DEBUG %s: %03x", device->name, i);
+ for (j = 0; j < 16; ++j) {
+ printk(" %02x", saa7111_read(decoder, i + j));
+ }
+ printk("\n");
+ }
+ }
+ break;
+#endif /* defined(DECODER_DUMP) */
+
+ case DECODER_GET_CAPABILITIES:
+ {
+ struct video_decoder_capability *cap = arg;
+
+ cap->flags
+ = VIDEO_DECODER_PAL
+ | VIDEO_DECODER_NTSC
+ | VIDEO_DECODER_AUTO
+ | VIDEO_DECODER_CCIR;
+ cap->inputs = 8;
+ cap->outputs = 1;
+ }
+ break;
+
+ case DECODER_GET_STATUS:
+ {
+ int *iarg = arg;
+ int status;
+ int res;
+
+ status = saa7111_read(decoder, 0x1f);
+ res = 0;
+ if ((status & (1 << 6)) == 0) {
+ res |= DECODER_STATUS_GOOD;
+ }
+ switch (decoder->norm) {
+ case VIDEO_MODE_NTSC:
+ res |= DECODER_STATUS_NTSC;
+ break;
+ case VIDEO_MODE_PAL:
+ res |= DECODER_STATUS_PAL;
+ break;
+ default:
+ case VIDEO_MODE_AUTO:
+ if ((status & (1 << 5)) != 0) {
+ res |= DECODER_STATUS_NTSC;
+ } else {
+ res |= DECODER_STATUS_PAL;
+ }
+ break;
+ }
+ if ((status & (1 << 0)) != 0) {
+ res |= DECODER_STATUS_COLOR;
+ }
+ *iarg = res;
+ }
+ break;
+
+ case DECODER_SET_NORM:
+ {
+ int *iarg = arg;
+
+ switch (*iarg) {
+
+ case VIDEO_MODE_NTSC:
+ saa7111_write(decoder, 0x08, (decoder->reg[0x08] & 0x3f) | 0x40);
+ break;
+
+ case VIDEO_MODE_PAL:
+ saa7111_write(decoder, 0x08, (decoder->reg[0x08] & 0x3f) | 0x00);
+ break;
+
+ case VIDEO_MODE_AUTO:
+ saa7111_write(decoder, 0x08, (decoder->reg[0x08] & 0x3f) | 0x80);
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+ decoder->norm = *iarg;
+ }
+ break;
+
+ case DECODER_SET_INPUT:
+ {
+ int *iarg = arg;
+
+ if (*iarg < 0 || *iarg > 7) {
+ return -EINVAL;
+ }
+ if (decoder->input != *iarg) {
+ decoder->input = *iarg;
+ /* select mode */
+ saa7111_write(decoder, 0x02, (decoder->reg[0x02] & 0xf8) | decoder->input);
+ /* bypass chrominance trap for modes 4..7 */
+ saa7111_write(decoder, 0x09, (decoder->reg[0x09] & 0x7f) | ((decoder->input > 3) ? 0x80 : 0));
+ }
+ }
+ break;
+
+ case DECODER_SET_OUTPUT:
+ {
+ int *iarg = arg;
+
+ /* not much choice of outputs */
+ if (*iarg != 0) {
+ return -EINVAL;
+ }
+ }
+ break;
+
+ case DECODER_ENABLE_OUTPUT:
+ {
+ int *iarg = arg;
+ int enable = (*iarg != 0);
+
+ if (decoder->enable != enable) {
+ decoder->enable = enable;
+
+// RJ: If output should be disabled (for playing videos), we also need a open PLL.
+ // The input is set to 0 (where no input source is connected), although this
+ // is not necessary.
+ //
+ // If output should be enabled, we have to reverse the above.
+
+ if (decoder->enable) {
+ saa7111_write(decoder, 0x02, (decoder->reg[0x02] & 0xf8) | decoder->input);
+ saa7111_write(decoder, 0x08, (decoder->reg[0x08] & 0xfb));
+ saa7111_write(decoder, 0x11, (decoder->reg[0x11] & 0xf3) | 0x0c);
+ } else {
+ saa7111_write(decoder, 0x02, (decoder->reg[0x02] & 0xf8));
+ saa7111_write(decoder, 0x08, (decoder->reg[0x08] & 0xfb) | 0x04);
+ saa7111_write(decoder, 0x11, (decoder->reg[0x11] & 0xf3));
+ }
+ }
+ }
+ break;
+
+ case DECODER_SET_PICTURE:
+ {
+ struct video_picture *pic = arg;
+
+ if (decoder->bright != pic->brightness) {
+ /* We want 0 to 255 we get 0-65535 */
+ decoder->bright = pic->brightness;
+ saa7111_write(decoder, 0x0a, decoder->bright >> 8);
+ }
+ if (decoder->contrast != pic->contrast) {
+ /* We want 0 to 127 we get 0-65535 */
+ decoder->contrast = pic->contrast;
+ saa7111_write(decoder, 0x0b, decoder->contrast >> 9);
+ }
+ if (decoder->sat != pic->colour) {
+ /* We want 0 to 127 we get 0-65535 */
+ decoder->sat = pic->colour;
+ saa7111_write(decoder, 0x0c, decoder->sat >> 9);
+ }
+ if (decoder->hue != pic->hue) {
+ /* We want -128 to 127 we get 0-65535 */
+ decoder->hue = pic->hue;
+ saa7111_write(decoder, 0x0d, (decoder->hue - 32768) >> 8);
+ }
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+struct i2c_driver i2c_driver_saa7111 =
+{
+ "saa7111", /* name */
+ I2C_DRIVERID_VIDEODECODER, /* ID */
+ I2C_SAA7111, I2C_SAA7111 + 1,
+
+ saa7111_attach,
+ saa7111_detach,
+ saa7111_command
+};
+
+EXPORT_NO_SYMBOLS;
+
+#ifdef MODULE
+int init_module(void)
+#else
+int saa7111_init(void)
+#endif
+{
+ return i2c_register_driver(&i2c_driver_saa7111);
+}
+
+
+
+#ifdef MODULE
+
+void cleanup_module(void)
+{
+ i2c_unregister_driver(&i2c_driver_saa7111);
+}
+
+#endif
diff --git a/drivers/media/video/saa7121.h b/drivers/media/video/saa7121.h
new file mode 100644
index 000000000..74e37d405
--- /dev/null
+++ b/drivers/media/video/saa7121.h
@@ -0,0 +1,132 @@
+/* saa7121.h - saa7121 initializations
+ Copyright (C) 1999 Nathan Laredo (laredo@gnu.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+#ifndef __SAA7121_H__
+#define __SAA7121_H__
+
+#define NTSC_BURST_START 0x19 /* 28 */
+#define NTSC_BURST_END 0x1d /* 29 */
+#define NTSC_CHROMA_PHASE 0x67 /* 5a */
+#define NTSC_GAINU 0x76 /* 5b */
+#define NTSC_GAINV 0xa5 /* 5c */
+#define NTSC_BLACK_LEVEL 0x2a /* 5d */
+#define NTSC_BLANKING_LEVEL 0x2e /* 5e */
+#define NTSC_VBI_BLANKING 0x2e /* 5f */
+#define NTSC_DAC_CONTROL 0x11 /* 61 */
+#define NTSC_BURST_AMP 0x3f /* 62 */
+#define NTSC_SUBC3 0x1f /* 63 */
+#define NTSC_SUBC2 0x7c /* 64 */
+#define NTSC_SUBC1 0xf0 /* 65 */
+#define NTSC_SUBC0 0x21 /* 66 */
+#define NTSC_HTRIG 0x72 /* 6c */
+#define NTSC_VTRIG 0x00 /* 6c */
+#define NTSC_MULTI 0x30 /* 6e */
+#define NTSC_CCTTX 0x11 /* 6f */
+#define NTSC_FIRST_ACTIVE 0x12 /* 7a */
+#define NTSC_LAST_ACTIVE 0x02 /* 7b */
+#define NTSC_MSB_VERTICAL 0x40 /* 7c */
+
+#define PAL_BURST_START 0x21 /* 28 */
+#define PAL_BURST_END 0x1d /* 29 */
+#define PAL_CHROMA_PHASE 0x3f /* 5a */
+#define PAL_GAINU 0x7d /* 5b */
+#define PAL_GAINV 0xaf /* 5c */
+#define PAL_BLACK_LEVEL 0x23 /* 5d */
+#define PAL_BLANKING_LEVEL 0x35 /* 5e */
+#define PAL_VBI_BLANKING 0x35 /* 5f */
+#define PAL_DAC_CONTROL 0x02 /* 61 */
+#define PAL_BURST_AMP 0x2f /* 62 */
+#define PAL_SUBC3 0xcb /* 63 */
+#define PAL_SUBC2 0x8a /* 64 */
+#define PAL_SUBC1 0x09 /* 65 */
+#define PAL_SUBC0 0x2a /* 66 */
+#define PAL_HTRIG 0x86 /* 6c */
+#define PAL_VTRIG 0x04 /* 6d */
+#define PAL_MULTI 0x20 /* 6e */
+#define PAL_CCTTX 0x15 /* 6f */
+#define PAL_FIRST_ACTIVE 0x16 /* 7a */
+#define PAL_LAST_ACTIVE 0x36 /* 7b */
+#define PAL_MSB_VERTICAL 0x40 /* 7c */
+
+/* Initialization Sequence */
+
+static __u8 init7121ntsc[] = {
+ 0x26, 0x0, 0x27, 0x0,
+ 0x28, NTSC_BURST_START, 0x29, NTSC_BURST_END,
+ 0x2a, 0x0, 0x2b, 0x0, 0x2c, 0x0, 0x2d, 0x0,
+ 0x2e, 0x0, 0x2f, 0x0, 0x30, 0x0, 0x31, 0x0,
+ 0x32, 0x0, 0x33, 0x0, 0x34, 0x0, 0x35, 0x0,
+ 0x36, 0x0, 0x37, 0x0, 0x38, 0x0, 0x39, 0x0,
+ 0x3a, 0x03, 0x3b, 0x0, 0x3c, 0x0, 0x3d, 0x0,
+ 0x3e, 0x0, 0x3f, 0x0, 0x40, 0x0, 0x41, 0x0,
+ 0x42, 0x0, 0x43, 0x0, 0x44, 0x0, 0x45, 0x0,
+ 0x46, 0x0, 0x47, 0x0, 0x48, 0x0, 0x49, 0x0,
+ 0x4a, 0x0, 0x4b, 0x0, 0x4c, 0x0, 0x4d, 0x0,
+ 0x4e, 0x0, 0x4f, 0x0, 0x50, 0x0, 0x51, 0x0,
+ 0x52, 0x0, 0x53, 0x0, 0x54, 0x0, 0x55, 0x0,
+ 0x56, 0x0, 0x57, 0x0, 0x58, 0x0, 0x59, 0x0,
+ 0x5a, NTSC_CHROMA_PHASE, 0x5b, NTSC_GAINU,
+ 0x5c, NTSC_GAINV, 0x5d, NTSC_BLACK_LEVEL,
+ 0x5e, NTSC_BLANKING_LEVEL, 0x5f, NTSC_VBI_BLANKING,
+ 0x60, 0x0, 0x61, NTSC_DAC_CONTROL,
+ 0x62, NTSC_BURST_AMP, 0x63, NTSC_SUBC3,
+ 0x64, NTSC_SUBC2, 0x65, NTSC_SUBC1,
+ 0x66, NTSC_SUBC0, 0x67, 0x80, 0x68, 0x80,
+ 0x69, 0x80, 0x6a, 0x80, 0x6b, 0x29,
+ 0x6c, NTSC_HTRIG, 0x6d, NTSC_VTRIG,
+ 0x6e, NTSC_MULTI, 0x6f, NTSC_CCTTX,
+ 0x70, 0xc9, 0x71, 0x68, 0x72, 0x60, 0x73, 0x0,
+ 0x74, 0x0, 0x75, 0x0, 0x76, 0x0, 0x77, 0x0,
+ 0x78, 0x0, 0x79, 0x0, 0x7a, NTSC_FIRST_ACTIVE,
+ 0x7b, NTSC_LAST_ACTIVE, 0x7c, NTSC_MSB_VERTICAL,
+ 0x7d, 0x0, 0x7e, 0x0, 0x7f, 0x0
+};
+#define INIT7121LEN (sizeof(init7121ntsc)/2)
+
+static __u8 init7121pal[] = {
+ 0x26, 0x0, 0x27, 0x0,
+ 0x28, PAL_BURST_START, 0x29, PAL_BURST_END,
+ 0x2a, 0x0, 0x2b, 0x0, 0x2c, 0x0, 0x2d, 0x0,
+ 0x2e, 0x0, 0x2f, 0x0, 0x30, 0x0, 0x31, 0x0,
+ 0x32, 0x0, 0x33, 0x0, 0x34, 0x0, 0x35, 0x0,
+ 0x36, 0x0, 0x37, 0x0, 0x38, 0x0, 0x39, 0x0,
+ 0x3a, 0x03, 0x3b, 0x0, 0x3c, 0x0, 0x3d, 0x0,
+ 0x3e, 0x0, 0x3f, 0x0, 0x40, 0x0, 0x41, 0x0,
+ 0x42, 0x0, 0x43, 0x0, 0x44, 0x0, 0x45, 0x0,
+ 0x46, 0x0, 0x47, 0x0, 0x48, 0x0, 0x49, 0x0,
+ 0x4a, 0x0, 0x4b, 0x0, 0x4c, 0x0, 0x4d, 0x0,
+ 0x4e, 0x0, 0x4f, 0x0, 0x50, 0x0, 0x51, 0x0,
+ 0x52, 0x0, 0x53, 0x0, 0x54, 0x0, 0x55, 0x0,
+ 0x56, 0x0, 0x57, 0x0, 0x58, 0x0, 0x59, 0x0,
+ 0x5a, PAL_CHROMA_PHASE, 0x5b, PAL_GAINU,
+ 0x5c, PAL_GAINV, 0x5d, PAL_BLACK_LEVEL,
+ 0x5e, PAL_BLANKING_LEVEL, 0x5f, PAL_VBI_BLANKING,
+ 0x60, 0x0, 0x61, PAL_DAC_CONTROL,
+ 0x62, PAL_BURST_AMP, 0x63, PAL_SUBC3,
+ 0x64, PAL_SUBC2, 0x65, PAL_SUBC1,
+ 0x66, PAL_SUBC0, 0x67, 0x80, 0x68, 0x80,
+ 0x69, 0x80, 0x6a, 0x80, 0x6b, 0x29,
+ 0x6c, PAL_HTRIG, 0x6d, PAL_VTRIG,
+ 0x6e, PAL_MULTI, 0x6f, PAL_CCTTX,
+ 0x70, 0xc9, 0x71, 0x68, 0x72, 0x60, 0x73, 0x0,
+ 0x74, 0x0, 0x75, 0x0, 0x76, 0x0, 0x77, 0x0,
+ 0x78, 0x0, 0x79, 0x0, 0x7a, PAL_FIRST_ACTIVE,
+ 0x7b, PAL_LAST_ACTIVE, 0x7c, PAL_MSB_VERTICAL,
+ 0x7d, 0x0, 0x7e, 0x0, 0x7f, 0x0
+};
+#endif
diff --git a/drivers/media/video/saa7146.h b/drivers/media/video/saa7146.h
new file mode 100644
index 000000000..481308e6c
--- /dev/null
+++ b/drivers/media/video/saa7146.h
@@ -0,0 +1,117 @@
+/*
+ saa7146.h - definitions philips saa7146 based cards
+ Copyright (C) 1999 Nathan Laredo (laredo@gnu.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef __SAA7146__
+#define __SAA7146__
+
+#define SAA7146_VERSION_CODE 0x000101
+
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include <linux/i2c.h>
+#include <linux/videodev.h>
+
+#ifndef O_NONCAP
+#define O_NONCAP O_TRUNC
+#endif
+
+#define MAX_GBUFFERS 2
+#define FBUF_SIZE 0x190000
+
+#ifdef __KERNEL__
+
+struct saa7146_window
+{
+ int x, y;
+ ushort width, height;
+ ushort bpp, bpl;
+ ushort swidth, sheight;
+ short cropx, cropy;
+ ushort cropwidth, cropheight;
+ unsigned long vidadr;
+ int color_fmt;
+ ushort depth;
+};
+
+/* Per-open data for handling multiple opens on one device */
+struct device_open
+{
+ int isopen;
+ int noncapturing;
+ struct saa7146 *dev;
+};
+#define MAX_OPENS 3
+
+struct saa7146
+{
+ struct video_device video_dev;
+ struct video_picture picture;
+ struct video_audio audio_dev;
+ struct video_info vidinfo;
+ int user;
+ int cap;
+ int capuser;
+ int irqstate; /* irq routine is state driven */
+ int writemode;
+ int playmode;
+ unsigned int nr;
+ unsigned long irq; /* IRQ used by SAA7146 card */
+ unsigned short id;
+ struct i2c_bus i2c;
+ struct pci_dev *dev;
+ unsigned char revision;
+ unsigned char boardcfg[64]; /* 64 bytes of config from eeprom */
+ unsigned long saa7146_adr; /* bus address of IO mem from PCI BIOS */
+ struct saa7146_window win;
+ unsigned char *saa7146_mem; /* pointer to mapped IO memory */
+ struct device_open open_data[MAX_OPENS];
+#define MAX_MARKS 16
+ /* for a/v sync */
+ int endmark[MAX_MARKS], endmarkhead, endmarktail;
+ u32 *dmaRPS1, *pageRPS1, *dmaRPS2, *pageRPS2, *dmavid1, *dmavid2,
+ *dmavid3, *dmaa1in, *dmaa1out, *dmaa2in, *dmaa2out,
+ *pagedebi, *pagevid1, *pagevid2, *pagevid3, *pagea1in,
+ *pagea1out, *pagea2in, *pagea2out;
+ wait_queue_head_t i2cq, debiq, audq, vidq;
+ u8 *vidbuf, *audbuf, *osdbuf, *dmadebi;
+ int audhead, vidhead, osdhead, audtail, vidtail, osdtail;
+ spinlock_t lock; /* the device lock */
+};
+#endif
+
+#ifdef _ALPHA_SAA7146
+#define saawrite(dat,adr) writel((dat),(char *) (saa->saa7146_adr+(adr)))
+#define saaread(adr) readl(saa->saa7146_adr+(adr))
+#else
+#define saawrite(dat,adr) writel((dat), (char *) (saa->saa7146_mem+(adr)))
+#define saaread(adr) readl(saa->saa7146_mem+(adr))
+#endif
+
+#define saaand(dat,adr) saawrite((dat) & saaread(adr), adr)
+#define saaor(dat,adr) saawrite((dat) | saaread(adr), adr)
+#define saaaor(dat,mask,adr) saawrite((dat) | ((mask) & saaread(adr)), adr)
+
+/* bitmask of attached hardware found */
+#define SAA7146_UNKNOWN 0x00000000
+#define SAA7146_SAA7111 0x00000001
+#define SAA7146_SAA7121 0x00000002
+#define SAA7146_IBMMPEG 0x00000004
+
+#endif
diff --git a/drivers/media/video/saa7146reg.h b/drivers/media/video/saa7146reg.h
new file mode 100644
index 000000000..6cc910f50
--- /dev/null
+++ b/drivers/media/video/saa7146reg.h
@@ -0,0 +1,283 @@
+/*
+ saa7146.h - definitions philips saa7146 based cards
+ Copyright (C) 1999 Nathan Laredo (laredo@gnu.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef __SAA7146_REG__
+#define __SAA7146_REG__
+#define SAA7146_BASE_ODD1 0x00
+#define SAA7146_BASE_EVEN1 0x04
+#define SAA7146_PROT_ADDR1 0x08
+#define SAA7146_PITCH1 0x0c
+#define SAA7146_PAGE1 0x10
+#define SAA7146_NUM_LINE_BYTE1 0x14
+#define SAA7146_BASE_ODD2 0x18
+#define SAA7146_BASE_EVEN2 0x1c
+#define SAA7146_PROT_ADDR2 0x20
+#define SAA7146_PITCH2 0x24
+#define SAA7146_PAGE2 0x28
+#define SAA7146_NUM_LINE_BYTE2 0x2c
+#define SAA7146_BASE_ODD3 0x30
+#define SAA7146_BASE_EVEN3 0x34
+#define SAA7146_PROT_ADDR3 0x38
+#define SAA7146_PITCH3 0x3c
+#define SAA7146_PAGE3 0x40
+#define SAA7146_NUM_LINE_BYTE3 0x44
+#define SAA7146_PCI_BT_V1 0x48
+#define SAA7146_PCI_BT_V2 0x49
+#define SAA7146_PCI_BT_V3 0x4a
+#define SAA7146_PCI_BT_DEBI 0x4b
+#define SAA7146_PCI_BT_A 0x4c
+#define SAA7146_DD1_INIT 0x50
+#define SAA7146_DD1_STREAM_B 0x54
+#define SAA7146_DD1_STREAM_A 0x56
+#define SAA7146_BRS_CTRL 0x58
+#define SAA7146_HPS_CTRL 0x5c
+#define SAA7146_HPS_V_SCALE 0x60
+#define SAA7146_HPS_V_GAIN 0x64
+#define SAA7146_HPS_H_PRESCALE 0x68
+#define SAA7146_HPS_H_SCALE 0x6c
+#define SAA7146_BCS_CTRL 0x70
+#define SAA7146_CHROMA_KEY_RANGE 0x74
+#define SAA7146_CLIP_FORMAT_CTRL 0x78
+#define SAA7146_DEBI_CONFIG 0x7c
+#define SAA7146_DEBI_COMMAND 0x80
+#define SAA7146_DEBI_PAGE 0x84
+#define SAA7146_DEBI_AD 0x88
+#define SAA7146_I2C_TRANSFER 0x8c
+#define SAA7146_I2C_STATUS 0x90
+#define SAA7146_BASE_A1_IN 0x94
+#define SAA7146_PROT_A1_IN 0x98
+#define SAA7146_PAGE_A1_IN 0x9C
+#define SAA7146_BASE_A1_OUT 0xa0
+#define SAA7146_PROT_A1_OUT 0xa4
+#define SAA7146_PAGE_A1_OUT 0xa8
+#define SAA7146_BASE_A2_IN 0xac
+#define SAA7146_PROT_A2_IN 0xb0
+#define SAA7146_PAGE_A2_IN 0xb4
+#define SAA7146_BASE_A2_OUT 0xb8
+#define SAA7146_PROT_A2_OUT 0xbc
+#define SAA7146_PAGE_A2_OUT 0xc0
+#define SAA7146_RPS_PAGE0 0xc4
+#define SAA7146_RPS_PAGE1 0xc8
+#define SAA7146_RPS_THRESH0 0xcc
+#define SAA7146_RPS_THRESH1 0xd0
+#define SAA7146_RPS_TOV0 0xd4
+#define SAA7146_RPS_TOV1 0xd8
+#define SAA7146_IER 0xdc
+#define SAA7146_GPIO_CTRL 0xe0
+#define SAA7146_EC1SSR 0xe4
+#define SAA7146_EC2SSR 0xe8
+#define SAA7146_ECT1R 0xec
+#define SAA7146_ECT2R 0xf0
+#define SAA7146_ACON1 0xf4
+#define SAA7146_ACON2 0xf8
+#define SAA7146_MC1 0xfc
+#define SAA7146_MC2 0x100
+#define SAA7146_RPS_ADDR0 0x104
+#define SAA7146_RPS_ADDR1 0x108
+#define SAA7146_ISR 0x10c
+#define SAA7146_PSR 0x110
+#define SAA7146_SSR 0x114
+#define SAA7146_EC1R 0x118
+#define SAA7146_EC2R 0x11c
+#define SAA7146_VDP1 0x120
+#define SAA7146_VDP2 0x124
+#define SAA7146_VDP3 0x128
+#define SAA7146_ADP1 0x12c
+#define SAA7146_ADP2 0x130
+#define SAA7146_ADP3 0x134
+#define SAA7146_ADP4 0x138
+#define SAA7146_DDP 0x13c
+#define SAA7146_LEVEL_REP 0x140
+#define SAA7146_FB_BUFFER1 0x144
+#define SAA7146_FB_BUFFER2 0x148
+#define SAA7146_A_TIME_SLOT1 0x180
+#define SAA7146_A_TIME_SLOT2 0x1C0
+
+/* bitfield defines */
+#define MASK_31 0x80000000
+#define MASK_30 0x40000000
+#define MASK_29 0x20000000
+#define MASK_28 0x10000000
+#define MASK_27 0x08000000
+#define MASK_26 0x04000000
+#define MASK_25 0x02000000
+#define MASK_24 0x01000000
+#define MASK_23 0x00800000
+#define MASK_22 0x00400000
+#define MASK_21 0x00200000
+#define MASK_20 0x00100000
+#define MASK_19 0x00080000
+#define MASK_18 0x00040000
+#define MASK_17 0x00020000
+#define MASK_16 0x00010000
+#define MASK_15 0x00008000
+#define MASK_14 0x00004000
+#define MASK_13 0x00002000
+#define MASK_12 0x00001000
+#define MASK_11 0x00000800
+#define MASK_10 0x00000400
+#define MASK_09 0x00000200
+#define MASK_08 0x00000100
+#define MASK_07 0x00000080
+#define MASK_06 0x00000040
+#define MASK_05 0x00000020
+#define MASK_04 0x00000010
+#define MASK_03 0x00000008
+#define MASK_02 0x00000004
+#define MASK_01 0x00000002
+#define MASK_00 0x00000001
+#define MASK_B0 0x000000ff
+#define MASK_B1 0x0000ff00
+#define MASK_B2 0x00ff0000
+#define MASK_B3 0xff000000
+#define MASK_W0 0x0000ffff
+#define MASK_W1 0xffff0000
+#define MASK_PA 0xfffffffc
+#define MASK_PR 0xfffffffe
+#define MASK_ER 0xffffffff
+#define MASK_NONE 0x00000000
+
+#define SAA7146_PAGE_MAP_EN MASK_11
+/* main control register 1 */
+#define SAA7146_MC1_MRST_N MASK_15
+#define SAA7146_MC1_ERPS1 MASK_13
+#define SAA7146_MC1_ERPS0 MASK_12
+#define SAA7146_MC1_EDP MASK_11
+#define SAA7146_MC1_EVP MASK_10
+#define SAA7146_MC1_EAP MASK_09
+#define SAA7146_MC1_EI2C MASK_08
+#define SAA7146_MC1_TR_E_DEBI MASK_07
+#define SAA7146_MC1_TR_E_1 MASK_06
+#define SAA7146_MC1_TR_E_2 MASK_05
+#define SAA7146_MC1_TR_E_3 MASK_04
+#define SAA7146_MC1_TR_E_A2_OUT MASK_03
+#define SAA7146_MC1_TR_E_A2_IN MASK_02
+#define SAA7146_MC1_TR_E_A1_OUT MASK_01
+#define SAA7146_MC1_TR_E_A1_IN MASK_00
+/* main control register 2 */
+#define SAA7146_MC2_RPS_SIG4 MASK_15
+#define SAA7146_MC2_RPS_SIG3 MASK_14
+#define SAA7146_MC2_RPS_SIG2 MASK_13
+#define SAA7146_MC2_RPS_SIG1 MASK_12
+#define SAA7146_MC2_RPS_SIG0 MASK_11
+#define SAA7146_MC2_UPLD_D1_B MASK_10
+#define SAA7146_MC2_UPLD_D1_A MASK_09
+#define SAA7146_MC2_UPLD_BRS MASK_08
+#define SAA7146_MC2_UPLD_HPS_H MASK_06
+#define SAA7146_MC2_UPLD_HPS_V MASK_05
+#define SAA7146_MC2_UPLD_DMA3 MASK_04
+#define SAA7146_MC2_UPLD_DMA2 MASK_03
+#define SAA7146_MC2_UPLD_DMA1 MASK_02
+#define SAA7146_MC2_UPLD_DEBI MASK_01
+#define SAA7146_MC2_UPLD_I2C MASK_00
+/* Primary Status Register and Interrupt Enable/Status Registers */
+#define SAA7146_PSR_PPEF MASK_31
+#define SAA7146_PSR_PABO MASK_30
+#define SAA7146_PSR_PPED MASK_29
+#define SAA7146_PSR_RPS_I1 MASK_28
+#define SAA7146_PSR_RPS_I0 MASK_27
+#define SAA7146_PSR_RPS_LATE1 MASK_26
+#define SAA7146_PSR_RPS_LATE0 MASK_25
+#define SAA7146_PSR_RPS_E1 MASK_24
+#define SAA7146_PSR_RPS_E0 MASK_23
+#define SAA7146_PSR_RPS_TO1 MASK_22
+#define SAA7146_PSR_RPS_TO0 MASK_21
+#define SAA7146_PSR_UPLD MASK_20
+#define SAA7146_PSR_DEBI_S MASK_19
+#define SAA7146_PSR_DEBI_E MASK_18
+#define SAA7146_PSR_I2C_S MASK_17
+#define SAA7146_PSR_I2C_E MASK_16
+#define SAA7146_PSR_A2_IN MASK_15
+#define SAA7146_PSR_A2_OUT MASK_14
+#define SAA7146_PSR_A1_IN MASK_13
+#define SAA7146_PSR_A1_OUT MASK_12
+#define SAA7146_PSR_AFOU MASK_11
+#define SAA7146_PSR_V_PE MASK_10
+#define SAA7146_PSR_VFOU MASK_09
+#define SAA7146_PSR_FIDA MASK_08
+#define SAA7146_PSR_FIDB MASK_07
+#define SAA7146_PSR_PIN3 MASK_06
+#define SAA7146_PSR_PIN2 MASK_05
+#define SAA7146_PSR_PIN1 MASK_04
+#define SAA7146_PSR_PIN0 MASK_03
+#define SAA7146_PSR_ECS MASK_02
+#define SAA7146_PSR_EC3S MASK_01
+#define SAA7146_PSR_EC0S MASK_00
+/* Secondary Status Register */
+#define SAA7146_SSR_PRQ MASK_31
+#define SAA7146_SSR_PMA MASK_30
+#define SAA7146_SSR_RPS_RE1 MASK_29
+#define SAA7146_SSR_RPS_PE1 MASK_28
+#define SAA7146_SSR_RPS_A1 MASK_27
+#define SAA7146_SSR_RPS_RE0 MASK_26
+#define SAA7146_SSR_RPS_PE0 MASK_25
+#define SAA7146_SSR_RPS_A0 MASK_24
+#define SAA7146_SSR_DEBI_TO MASK_23
+#define SAA7146_SSR_DEBI_EF MASK_22
+#define SAA7146_SSR_I2C_EA MASK_21
+#define SAA7146_SSR_I2C_EW MASK_20
+#define SAA7146_SSR_I2C_ER MASK_19
+#define SAA7146_SSR_I2C_EL MASK_18
+#define SAA7146_SSR_I2C_EF MASK_17
+#define SAA7146_SSR_V3P MASK_16
+#define SAA7146_SSR_V2P MASK_15
+#define SAA7146_SSR_V1P MASK_14
+#define SAA7146_SSR_VF3 MASK_13
+#define SAA7146_SSR_VF2 MASK_12
+#define SAA7146_SSR_VF1 MASK_11
+#define SAA7146_SSR_AF2_IN MASK_10
+#define SAA7146_SSR_AF2_OUT MASK_09
+#define SAA7146_SSR_AF1_IN MASK_08
+#define SAA7146_SSR_AF1_OUT MASK_07
+#define SAA7146_SSR_VGT MASK_05
+#define SAA7146_SSR_LNQG MASK_04
+#define SAA7146_SSR_EC5S MASK_03
+#define SAA7146_SSR_EC4S MASK_02
+#define SAA7146_SSR_EC2S MASK_01
+#define SAA7146_SSR_EC1S MASK_00
+/* I2C status register */
+#define SAA7146_I2C_ABORT MASK_07
+#define SAA7146_I2C_SPERR MASK_06
+#define SAA7146_I2C_APERR MASK_05
+#define SAA7146_I2C_DTERR MASK_04
+#define SAA7146_I2C_DRERR MASK_03
+#define SAA7146_I2C_AL MASK_02
+#define SAA7146_I2C_ERR MASK_01
+#define SAA7146_I2C_BUSY MASK_00
+/* output formats */
+#define SAA7146_YUV422 0
+#define SAA7146_RGB16 0
+#define SAA7146_YUV444 1
+#define SAA7146_RGB24 1
+#define SAA7146_ARGB32 2
+#define SAA7146_YUV411 3
+#define SAA7146_ARGB15 3
+#define SAA7146_YUV2 4
+#define SAA7146_RGAB15 4
+#define SAA7146_Y8 6
+#define SAA7146_YUV8 7
+#define SAA7146_RGB8 7
+#define SAA7146_YUV444p 8
+#define SAA7146_YUV422p 9
+#define SAA7146_YUV420p 10
+#define SAA7146_YUV1620 11
+#define SAA7146_Y1 13
+#define SAA7146_Y2 14
+#define SAA7146_YUV1 15
+#endif
diff --git a/drivers/media/video/saa7185.c b/drivers/media/video/saa7185.c
new file mode 100644
index 000000000..c30e6353b
--- /dev/null
+++ b/drivers/media/video/saa7185.c
@@ -0,0 +1,377 @@
+/*
+ saa7185 - Philips SAA7185B video encoder driver version 0.0.3
+
+ Copyright (C) 1998 Dave Perks <dperks@ibm.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+#include <linux/signal.h>
+#include <asm/io.h>
+#include <asm/pgtable.h>
+#include <asm/page.h>
+#include <linux/sched.h>
+#include <asm/segment.h>
+#include <linux/types.h>
+#include <linux/wrapper.h>
+
+#include <linux/videodev.h>
+#include <linux/version.h>
+#include <asm/uaccess.h>
+
+#include <linux/i2c-old.h>
+#include <linux/video_encoder.h>
+
+#define DEBUG(x) x /* Debug driver */
+
+/* ----------------------------------------------------------------------- */
+
+struct saa7185 {
+ struct i2c_bus *bus;
+ int addr;
+ unsigned char reg[128];
+
+ int norm;
+ int enable;
+ int bright;
+ int contrast;
+ int hue;
+ int sat;
+};
+
+#define I2C_SAA7185 0x88
+
+#define I2C_DELAY 10
+
+/* ----------------------------------------------------------------------- */
+
+static int saa7185_write(struct saa7185 *dev, unsigned char subaddr, unsigned char data)
+{
+ int ack;
+
+ LOCK_I2C_BUS(dev->bus);
+
+ i2c_start(dev->bus);
+ i2c_sendbyte(dev->bus, dev->addr, I2C_DELAY);
+ i2c_sendbyte(dev->bus, subaddr, I2C_DELAY);
+ ack = i2c_sendbyte(dev->bus, data, I2C_DELAY);
+ dev->reg[subaddr] = data;
+ i2c_stop(dev->bus);
+ UNLOCK_I2C_BUS(dev->bus);
+ return ack;
+}
+
+static int saa7185_write_block(struct saa7185 *dev, unsigned const char *data, unsigned int len)
+{
+ int ack = 0;
+ unsigned subaddr;
+
+ while (len > 1) {
+ LOCK_I2C_BUS(dev->bus);
+ i2c_start(dev->bus);
+ i2c_sendbyte(dev->bus, dev->addr, I2C_DELAY);
+ ack = i2c_sendbyte(dev->bus, (subaddr = *data++), I2C_DELAY);
+ ack = i2c_sendbyte(dev->bus, (dev->reg[subaddr] = *data++), I2C_DELAY);
+ len -= 2;
+ while (len > 1 && *data == ++subaddr) {
+ data++;
+ ack = i2c_sendbyte(dev->bus, (dev->reg[subaddr] = *data++), I2C_DELAY);
+ len -= 2;
+ }
+ i2c_stop(dev->bus);
+ UNLOCK_I2C_BUS(dev->bus);
+ }
+ return ack;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static const unsigned char init_common[] =
+{
+ 0x3a, 0x0f, /* CBENB=0, V656=0, VY2C=1, YUV2C=1, MY2C=1, MUV2C=1 */
+
+ 0x42, 0x6b, /* OVLY0=107 */
+ 0x43, 0x00, /* OVLU0=0 white */
+ 0x44, 0x00, /* OVLV0=0 */
+ 0x45, 0x22, /* OVLY1=34 */
+ 0x46, 0xac, /* OVLU1=172 yellow */
+ 0x47, 0x0e, /* OVLV1=14 */
+ 0x48, 0x03, /* OVLY2=3 */
+ 0x49, 0x1d, /* OVLU2=29 cyan */
+ 0x4a, 0xac, /* OVLV2=172 */
+ 0x4b, 0xf0, /* OVLY3=240 */
+ 0x4c, 0xc8, /* OVLU3=200 green */
+ 0x4d, 0xb9, /* OVLV3=185 */
+ 0x4e, 0xd4, /* OVLY4=212 */
+ 0x4f, 0x38, /* OVLU4=56 magenta */
+ 0x50, 0x47, /* OVLV4=71 */
+ 0x51, 0xc1, /* OVLY5=193 */
+ 0x52, 0xe3, /* OVLU5=227 red */
+ 0x53, 0x54, /* OVLV5=84 */
+ 0x54, 0xa3, /* OVLY6=163 */
+ 0x55, 0x54, /* OVLU6=84 blue */
+ 0x56, 0xf2, /* OVLV6=242 */
+ 0x57, 0x90, /* OVLY7=144 */
+ 0x58, 0x00, /* OVLU7=0 black */
+ 0x59, 0x00, /* OVLV7=0 */
+
+ 0x5a, 0x00, /* CHPS=0 */
+ 0x5b, 0x76, /* GAINU=118 */
+ 0x5c, 0xa5, /* GAINV=165 */
+ 0x5d, 0x3c, /* BLCKL=60 */
+ 0x5e, 0x3a, /* BLNNL=58 */
+ 0x5f, 0x3a, /* CCRS=0, BLNVB=58 */
+ 0x60, 0x00, /* NULL */
+
+/* 0x61 - 0x66 set according to norm */
+
+ 0x67, 0x00, /* 0 : caption 1st byte odd field */
+ 0x68, 0x00, /* 0 : caption 2nd byte odd field */
+ 0x69, 0x00, /* 0 : caption 1st byte even field */
+ 0x6a, 0x00, /* 0 : caption 2nd byte even field */
+
+ 0x6b, 0x91, /* MODIN=2, PCREF=0, SCCLN=17 */
+ 0x6c, 0x20, /* SRCV1=0, TRCV2=1, ORCV1=0, PRCV1=0, CBLF=0, ORCV2=0, PRCV2=0 */
+ 0x6d, 0x00, /* SRCM1=0, CCEN=0 */
+
+ 0x6e, 0x0e, /* HTRIG=0x00e, approx. centered, at least for PAL */
+ 0x6f, 0x00, /* HTRIG upper bits */
+ 0x70, 0x20, /* PHRES=0, SBLN=1, VTRIG=0 */
+
+/* The following should not be needed */
+
+ 0x71, 0x15, /* BMRQ=0x115 */
+ 0x72, 0x90, /* EMRQ=0x690 */
+ 0x73, 0x61, /* EMRQ=0x690, BMRQ=0x115 */
+ 0x74, 0x00, /* NULL */
+ 0x75, 0x00, /* NULL */
+ 0x76, 0x00, /* NULL */
+ 0x77, 0x15, /* BRCV=0x115 */
+ 0x78, 0x90, /* ERCV=0x690 */
+ 0x79, 0x61, /* ERCV=0x690, BRCV=0x115 */
+
+/* Field length controls */
+
+ 0x7a, 0x70, /* FLC=0 */
+
+/* The following should not be needed if SBLN = 1 */
+
+ 0x7b, 0x16, /* FAL=22 */
+ 0x7c, 0x35, /* LAL=244 */
+ 0x7d, 0x20, /* LAL=244, FAL=22 */
+};
+
+static const unsigned char init_pal[] =
+{
+ 0x61, 0x1e, /* FISE=0, PAL=1, SCBW=1, RTCE=1, YGS=1, INPI=0, DOWN=0 */
+ 0x62, 0xc8, /* DECTYP=1, BSTA=72 */
+ 0x63, 0xcb, /* FSC0 */
+ 0x64, 0x8a, /* FSC1 */
+ 0x65, 0x09, /* FSC2 */
+ 0x66, 0x2a, /* FSC3 */
+};
+
+static const unsigned char init_ntsc[] =
+{
+ 0x61, 0x1d, /* FISE=1, PAL=0, SCBW=1, RTCE=1, YGS=1, INPI=0, DOWN=0 */
+ 0x62, 0xe6, /* DECTYP=1, BSTA=102 */
+ 0x63, 0x1f, /* FSC0 */
+ 0x64, 0x7c, /* FSC1 */
+ 0x65, 0xf0, /* FSC2 */
+ 0x66, 0x21, /* FSC3 */
+};
+
+static int saa7185_attach(struct i2c_device *device)
+{
+ int i;
+ struct saa7185 *encoder;
+
+ device->data = encoder = kmalloc(sizeof(struct saa7185), GFP_KERNEL);
+ if (encoder == NULL) {
+ return -ENOMEM;
+ }
+ MOD_INC_USE_COUNT;
+
+ memset(encoder, 0, sizeof(struct saa7185));
+ strcpy(device->name, "saa7185");
+ encoder->bus = device->bus;
+ encoder->addr = device->addr;
+ encoder->norm = VIDEO_MODE_NTSC;
+ encoder->enable = 1;
+
+ i = saa7185_write_block(encoder, init_common, sizeof(init_common));
+ if (i >= 0) {
+ i = saa7185_write_block(encoder, init_ntsc, sizeof(init_ntsc));
+ }
+ if (i < 0) {
+ printk(KERN_ERR "%s_attach: init error %d\n", device->name, i);
+ }
+ return 0;
+}
+
+
+static int saa7185_detach(struct i2c_device *device)
+{
+ kfree(device->data);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int saa7185_command(struct i2c_device *device, unsigned int cmd, void *arg)
+{
+ struct saa7185 *encoder = device->data;
+
+ switch (cmd) {
+
+ case ENCODER_GET_CAPABILITIES:
+ {
+ struct video_encoder_capability *cap = arg;
+
+ cap->flags
+ = VIDEO_ENCODER_PAL
+ | VIDEO_ENCODER_NTSC
+ | VIDEO_ENCODER_SECAM
+ | VIDEO_ENCODER_CCIR;
+ cap->inputs = 1;
+ cap->outputs = 1;
+ }
+ break;
+
+ case ENCODER_SET_NORM:
+ {
+ int *iarg = arg;
+
+ switch (*iarg) {
+
+ case VIDEO_MODE_NTSC:
+ saa7185_write_block(encoder, init_ntsc, sizeof(init_ntsc));
+ break;
+
+ case VIDEO_MODE_PAL:
+ saa7185_write_block(encoder, init_pal, sizeof(init_pal));
+ break;
+
+ case VIDEO_MODE_SECAM:
+ default:
+ return -EINVAL;
+
+ }
+ encoder->norm = *iarg;
+ }
+ break;
+
+ case ENCODER_SET_INPUT:
+ {
+ int *iarg = arg;
+
+#if 0
+ /* not much choice of inputs */
+ if (*iarg != 0) {
+ return -EINVAL;
+ }
+#else
+ /* RJ: *iarg = 0: input is from SA7111
+ *iarg = 1: input is from ZR36060 */
+
+ switch (*iarg) {
+
+ case 0:
+ /* Switch RTCE to 1 */
+ saa7185_write(encoder, 0x61, (encoder->reg[0x61] & 0xf7) | 0x08);
+ break;
+
+ case 1:
+ /* Switch RTCE to 0 */
+ saa7185_write(encoder, 0x61, (encoder->reg[0x61] & 0xf7) | 0x00);
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+#endif
+ }
+ break;
+
+ case ENCODER_SET_OUTPUT:
+ {
+ int *iarg = arg;
+
+ /* not much choice of outputs */
+ if (*iarg != 0) {
+ return -EINVAL;
+ }
+ }
+ break;
+
+ case ENCODER_ENABLE_OUTPUT:
+ {
+ int *iarg = arg;
+
+ encoder->enable = !!*iarg;
+ saa7185_write(encoder, 0x61, (encoder->reg[0x61] & 0xbf) | (encoder->enable ? 0x00 : 0x40));
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+struct i2c_driver i2c_driver_saa7185 =
+{
+ "saa7185", /* name */
+ I2C_DRIVERID_VIDEOENCODER, /* ID */
+ I2C_SAA7185, I2C_SAA7185 + 1,
+
+ saa7185_attach,
+ saa7185_detach,
+ saa7185_command
+};
+
+EXPORT_NO_SYMBOLS;
+
+#ifdef MODULE
+int init_module(void)
+#else
+int saa7185_init(void)
+#endif
+{
+ return i2c_register_driver(&i2c_driver_saa7185);
+}
+
+
+
+#ifdef MODULE
+
+void cleanup_module(void)
+{
+ i2c_unregister_driver(&i2c_driver_saa7185);
+}
+
+#endif
diff --git a/drivers/media/video/saa7196.h b/drivers/media/video/saa7196.h
new file mode 100644
index 000000000..f92f21cfb
--- /dev/null
+++ b/drivers/media/video/saa7196.h
@@ -0,0 +1,117 @@
+/*
+ Definitions for the Philips SAA7196 digital video decoder,
+ scaler, and clock generator circuit (DESCpro), as used in
+ the PlanB video input of the Powermac 7x00/8x00 series.
+
+ Copyright (C) 1998 Michel Lanners (mlan@cpu.lu)
+
+ The register defines are shamelessly copied from the meteor
+ driver out of NetBSD (with permission),
+ and are copyrighted (c) 1995 Mark Tinguely and Jim Lowe
+ (Thanks !)
+
+ Additional debugging and coding by Takashi Oe (toe@unlinfo.unl.edu)
+
+ The default values used for PlanB are my mistakes.
+*/
+
+/* $Id: saa7196.h,v 1.5 1999/03/26 23:28:47 mlan Exp $ */
+
+#ifndef _SAA7196_H_
+#define _SAA7196_H_
+
+#define SAA7196_NUMREGS 0x31 /* Number of registers (used)*/
+#define NUM_SUPPORTED_NORM 3 /* Number of supported norms by PlanB */
+
+/* Decoder part: */
+#define SAA7196_IDEL 0x00 /* Increment delay */
+#define SAA7196_HSB5 0x01 /* H-sync begin; 50 hz */
+#define SAA7196_HSS5 0x02 /* H-sync stop; 50 hz */
+#define SAA7196_HCB5 0x03 /* H-clamp begin; 50 hz */
+#define SAA7196_HCS5 0x04 /* H-clamp stop; 50 hz */
+#define SAA7196_HSP5 0x05 /* H-sync after PHI1; 50 hz */
+#define SAA7196_LUMC 0x06 /* Luminance control */
+#define SAA7196_HUEC 0x07 /* Hue control */
+#define SAA7196_CKTQ 0x08 /* Colour Killer Threshold QAM (PAL, NTSC) */
+#define SAA7196_CKTS 0x09 /* Colour Killer Threshold SECAM */
+#define SAA7196_PALS 0x0a /* PAL switch sensitivity */
+#define SAA7196_SECAMS 0x0b /* SECAM switch sensitivity */
+#define SAA7196_CGAINC 0x0c /* Chroma gain control */
+#define SAA7196_STDC 0x0d /* Standard/Mode control */
+#define SAA7196_IOCC 0x0e /* I/O and Clock Control */
+#define SAA7196_CTRL1 0x0f /* Control #1 */
+#define SAA7196_CTRL2 0x10 /* Control #2 */
+#define SAA7196_CGAINR 0x11 /* Chroma Gain Reference */
+#define SAA7196_CSAT 0x12 /* Chroma Saturation */
+#define SAA7196_CONT 0x13 /* Luminance Contrast */
+#define SAA7196_HSB6 0x14 /* H-sync begin; 60 hz */
+#define SAA7196_HSS6 0x15 /* H-sync stop; 60 hz */
+#define SAA7196_HCB6 0x16 /* H-clamp begin; 60 hz */
+#define SAA7196_HCS6 0x17 /* H-clamp stop; 60 hz */
+#define SAA7196_HSP6 0x18 /* H-sync after PHI1; 60 hz */
+#define SAA7196_BRIG 0x19 /* Luminance Brightness */
+
+/* Scaler part: */
+#define SAA7196_FMTS 0x20 /* Formats and sequence */
+#define SAA7196_OUTPIX 0x21 /* Output data pixel/line */
+#define SAA7196_INPIX 0x22 /* Input data pixel/line */
+#define SAA7196_HWS 0x23 /* Horiz. window start */
+#define SAA7196_HFILT 0x24 /* Horiz. filter */
+#define SAA7196_OUTLINE 0x25 /* Output data lines/field */
+#define SAA7196_INLINE 0x26 /* Input data lines/field */
+#define SAA7196_VWS 0x27 /* Vertical window start */
+#define SAA7196_VYP 0x28 /* AFS/vertical Y processing */
+#define SAA7196_VBS 0x29 /* Vertical Bypass start */
+#define SAA7196_VBCNT 0x2a /* Vertical Bypass count */
+#define SAA7196_VBP 0x2b /* veritcal Bypass Polarity */
+#define SAA7196_VLOW 0x2c /* Colour-keying lower V limit */
+#define SAA7196_VHIGH 0x2d /* Colour-keying upper V limit */
+#define SAA7196_ULOW 0x2e /* Colour-keying lower U limit */
+#define SAA7196_UHIGH 0x2f /* Colour-keying upper U limit */
+#define SAA7196_DPATH 0x30 /* Data path setting */
+
+/* Initialization default values: */
+
+unsigned char saa_regs[NUM_SUPPORTED_NORM][SAA7196_NUMREGS] = {
+
+/* PAL, 768x576 (no scaling), composite video-in */
+/* Decoder: */
+ { 0x50, 0x30, 0x00, 0xe8, 0xb6, 0xe5, 0x63, 0xff,
+ 0xfe, 0xf0, 0xfe, 0xe0, 0x20, 0x06, 0x3b, 0x98,
+ 0x00, 0x59, 0x41, 0x45, 0x34, 0x0a, 0xf4, 0xd2,
+ 0xe9, 0xa2,
+/* Padding */
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+/* Scaler: */
+ 0x72, 0x80, 0x00, 0x03, 0x8d, 0x20, 0x20, 0x12,
+ 0xa5, 0x12, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00,
+ 0x87 },
+
+/* NTSC, 640x480? (no scaling), composite video-in */
+/* Decoder: */
+ { 0x50, 0x30, 0x00, 0xe8, 0xb6, 0xe5, 0x50, 0x00,
+ 0xf8, 0xf0, 0xfe, 0xe0, 0x00, 0x06, 0x3b, 0x98,
+ 0x00, 0x2c, 0x3d, 0x40, 0x34, 0x0a, 0xf4, 0xd2,
+ 0xe9, 0x98,
+/* Padding */
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+/* Scaler: */
+ 0x72, 0x80, 0x80, 0x03, 0x89, 0xf0, 0xf0, 0x0d,
+ 0xa0, 0x0d, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00,
+ 0x87 },
+
+/* SECAM, 768x576 (no scaling), composite video-in */
+/* Decoder: */
+ { 0x50, 0x30, 0x00, 0xe8, 0xb6, 0xe5, 0x63, 0xff,
+ 0xfe, 0xf0, 0xfe, 0xe0, 0x20, 0x07, 0x3b, 0x98,
+ 0x00, 0x59, 0x41, 0x45, 0x34, 0x0a, 0xf4, 0xd2,
+ 0xe9, 0xa2,
+/* Padding */
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+/* Scaler: */
+ 0x72, 0x80, 0x00, 0x03, 0x8d, 0x20, 0x20, 0x12,
+ 0xa5, 0x12, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00,
+ 0x87 }
+ };
+
+#endif /* _SAA7196_H_ */
diff --git a/drivers/media/video/stallion.c b/drivers/media/video/stallion.c
new file mode 100644
index 000000000..a0faeada4
--- /dev/null
+++ b/drivers/media/video/stallion.c
@@ -0,0 +1,5329 @@
+/*****************************************************************************/
+
+/*
+ * stallion.c -- stallion multiport serial driver.
+ *
+ * Copyright (C) 1996-1999 Stallion Technologies (support@stallion.oz.au).
+ * Copyright (C) 1994-1996 Greg Ungerer.
+ *
+ * This code is loosely based on the Linux serial driver, written by
+ * Linus Torvalds, Theodore T'so and others.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*****************************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/version.h> /* for linux/stallion.h */
+#include <linux/malloc.h>
+#include <linux/interrupt.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/cd1400.h>
+#include <linux/sc26198.h>
+#include <linux/comstats.h>
+#include <linux/stallion.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_PCI
+#include <linux/pci.h>
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Define different board types. Use the standard Stallion "assigned"
+ * board numbers. Boards supported in this driver are abbreviated as
+ * EIO = EasyIO and ECH = EasyConnection 8/32.
+ */
+#define BRD_EASYIO 20
+#define BRD_ECH 21
+#define BRD_ECHMC 22
+#define BRD_ECHPCI 26
+#define BRD_ECH64PCI 27
+#define BRD_EASYIOPCI 28
+
+/*
+ * Define a configuration structure to hold the board configuration.
+ * Need to set this up in the code (for now) with the boards that are
+ * to be configured into the system. This is what needs to be modified
+ * when adding/removing/modifying boards. Each line entry in the
+ * stl_brdconf[] array is a board. Each line contains io/irq/memory
+ * ranges for that board (as well as what type of board it is).
+ * Some examples:
+ * { BRD_EASYIO, 0x2a0, 0, 0, 10, 0 },
+ * This line would configure an EasyIO board (4 or 8, no difference),
+ * at io address 2a0 and irq 10.
+ * Another example:
+ * { BRD_ECH, 0x2a8, 0x280, 0, 12, 0 },
+ * This line will configure an EasyConnection 8/32 board at primary io
+ * address 2a8, secondary io address 280 and irq 12.
+ * Enter as many lines into this array as you want (only the first 4
+ * will actually be used!). Any combination of EasyIO and EasyConnection
+ * boards can be specified. EasyConnection 8/32 boards can share their
+ * secondary io addresses between each other.
+ *
+ * NOTE: there is no need to put any entries in this table for PCI
+ * boards. They will be found automatically by the driver - provided
+ * PCI BIOS32 support is compiled into the kernel.
+ */
+
+typedef struct {
+ int brdtype;
+ int ioaddr1;
+ int ioaddr2;
+ unsigned long memaddr;
+ int irq;
+ int irqtype;
+} stlconf_t;
+
+static stlconf_t stl_brdconf[] = {
+ /*{ BRD_EASYIO, 0x2a0, 0, 0, 10, 0 },*/
+};
+
+static int stl_nrbrds = sizeof(stl_brdconf) / sizeof(stlconf_t);
+
+/*****************************************************************************/
+
+/*
+ * Define some important driver characteristics. Device major numbers
+ * allocated as per Linux Device Registry.
+ */
+#ifndef STL_SIOMEMMAJOR
+#define STL_SIOMEMMAJOR 28
+#endif
+#ifndef STL_SERIALMAJOR
+#define STL_SERIALMAJOR 24
+#endif
+#ifndef STL_CALLOUTMAJOR
+#define STL_CALLOUTMAJOR 25
+#endif
+
+#define STL_DRVTYPSERIAL 1
+#define STL_DRVTYPCALLOUT 2
+
+/*
+ * Set the TX buffer size. Bigger is better, but we don't want
+ * to chew too much memory with buffers!
+ */
+#define STL_TXBUFLOW 512
+#define STL_TXBUFSIZE 4096
+
+/*****************************************************************************/
+
+/*
+ * Define our local driver identity first. Set up stuff to deal with
+ * all the local structures required by a serial tty driver.
+ */
+static char *stl_drvtitle = "Stallion Multiport Serial Driver";
+static char *stl_drvname = "stallion";
+static char *stl_drvversion = "5.6.0";
+static char *stl_serialname = "ttyE";
+static char *stl_calloutname = "cue";
+
+static struct tty_driver stl_serial;
+static struct tty_driver stl_callout;
+static struct tty_struct *stl_ttys[STL_MAXDEVS];
+static struct termios *stl_termios[STL_MAXDEVS];
+static struct termios *stl_termioslocked[STL_MAXDEVS];
+static int stl_refcount = 0;
+
+/*
+ * We will need to allocate a temporary write buffer for chars that
+ * come direct from user space. The problem is that a copy from user
+ * space might cause a page fault (typically on a system that is
+ * swapping!). All ports will share one buffer - since if the system
+ * is already swapping a shared buffer won't make things any worse.
+ */
+static char *stl_tmpwritebuf;
+static DECLARE_MUTEX(stl_tmpwritesem);
+
+/*
+ * Define a local default termios struct. All ports will be created
+ * with this termios initially. Basically all it defines is a raw port
+ * at 9600, 8 data bits, 1 stop bit.
+ */
+static struct termios stl_deftermios = {
+ 0,
+ 0,
+ (B9600 | CS8 | CREAD | HUPCL | CLOCAL),
+ 0,
+ 0,
+ INIT_C_CC
+};
+
+/*
+ * Define global stats structures. Not used often, and can be
+ * re-used for each stats call.
+ */
+static comstats_t stl_comstats;
+static combrd_t stl_brdstats;
+static stlbrd_t stl_dummybrd;
+static stlport_t stl_dummyport;
+
+/*
+ * Define global place to put buffer overflow characters.
+ */
+static char stl_unwanted[SC26198_RXFIFOSIZE];
+
+/*
+ * Keep track of what interrupts we have requested for us.
+ * We don't need to request an interrupt twice if it is being
+ * shared with another Stallion board.
+ */
+static int stl_gotintrs[STL_MAXBRDS];
+static int stl_numintrs = 0;
+
+/*****************************************************************************/
+
+static stlbrd_t *stl_brds[STL_MAXBRDS];
+
+/*
+ * Per board state flags. Used with the state field of the board struct.
+ * Not really much here!
+ */
+#define BRD_FOUND 0x1
+
+/*
+ * Define the port structure istate flags. These set of flags are
+ * modified at interrupt time - so setting and reseting them needs
+ * to be atomic. Use the bit clear/setting routines for this.
+ */
+#define ASYI_TXBUSY 1
+#define ASYI_TXLOW 2
+#define ASYI_DCDCHANGE 3
+#define ASYI_TXFLOWED 4
+
+/*
+ * Define an array of board names as printable strings. Handy for
+ * referencing boards when printing trace and stuff.
+ */
+static char *stl_brdnames[] = {
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ "EasyIO",
+ "EC8/32-AT",
+ "EC8/32-MC",
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ "EC8/32-PCI",
+ "EC8/64-PCI",
+ "EasyIO-PCI",
+};
+
+/*****************************************************************************/
+
+#ifdef MODULE
+/*
+ * Define some string labels for arguments passed from the module
+ * load line. These allow for easy board definitions, and easy
+ * modification of the io, memory and irq resoucres.
+ */
+
+static char *board0[4];
+static char *board1[4];
+static char *board2[4];
+static char *board3[4];
+
+static char **stl_brdsp[] = {
+ (char **) &board0,
+ (char **) &board1,
+ (char **) &board2,
+ (char **) &board3
+};
+
+/*
+ * Define a set of common board names, and types. This is used to
+ * parse any module arguments.
+ */
+
+typedef struct stlbrdtype {
+ char *name;
+ int type;
+} stlbrdtype_t;
+
+static stlbrdtype_t stl_brdstr[] = {
+ { "easyio", BRD_EASYIO },
+ { "eio", BRD_EASYIO },
+ { "20", BRD_EASYIO },
+ { "ec8/32", BRD_ECH },
+ { "ec8/32-at", BRD_ECH },
+ { "ec8/32-isa", BRD_ECH },
+ { "ech", BRD_ECH },
+ { "echat", BRD_ECH },
+ { "21", BRD_ECH },
+ { "ec8/32-mc", BRD_ECHMC },
+ { "ec8/32-mca", BRD_ECHMC },
+ { "echmc", BRD_ECHMC },
+ { "echmca", BRD_ECHMC },
+ { "22", BRD_ECHMC },
+ { "ec8/32-pc", BRD_ECHPCI },
+ { "ec8/32-pci", BRD_ECHPCI },
+ { "26", BRD_ECHPCI },
+ { "ec8/64-pc", BRD_ECH64PCI },
+ { "ec8/64-pci", BRD_ECH64PCI },
+ { "ech-pci", BRD_ECH64PCI },
+ { "echpci", BRD_ECH64PCI },
+ { "echpc", BRD_ECH64PCI },
+ { "27", BRD_ECH64PCI },
+ { "easyio-pc", BRD_EASYIOPCI },
+ { "easyio-pci", BRD_EASYIOPCI },
+ { "eio-pci", BRD_EASYIOPCI },
+ { "eiopci", BRD_EASYIOPCI },
+ { "28", BRD_EASYIOPCI },
+};
+
+/*
+ * Define the module agruments.
+ */
+MODULE_AUTHOR("Greg Ungerer");
+MODULE_DESCRIPTION("Stallion Multiport Serial Driver");
+
+MODULE_PARM(board0, "1-4s");
+MODULE_PARM_DESC(board0, "Board 0 config -> name[,ioaddr[,ioaddr2][,irq]]");
+MODULE_PARM(board1, "1-4s");
+MODULE_PARM_DESC(board1, "Board 1 config -> name[,ioaddr[,ioaddr2][,irq]]");
+MODULE_PARM(board2, "1-4s");
+MODULE_PARM_DESC(board2, "Board 2 config -> name[,ioaddr[,ioaddr2][,irq]]");
+MODULE_PARM(board3, "1-4s");
+MODULE_PARM_DESC(board3, "Board 3 config -> name[,ioaddr[,ioaddr2][,irq]]");
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Hardware ID bits for the EasyIO and ECH boards. These defines apply
+ * to the directly accessible io ports of these boards (not the uarts -
+ * they are in cd1400.h and sc26198.h).
+ */
+#define EIO_8PORTRS 0x04
+#define EIO_4PORTRS 0x05
+#define EIO_8PORTDI 0x00
+#define EIO_8PORTM 0x06
+#define EIO_MK3 0x03
+#define EIO_IDBITMASK 0x07
+
+#define EIO_BRDMASK 0xf0
+#define ID_BRD4 0x10
+#define ID_BRD8 0x20
+#define ID_BRD16 0x30
+
+#define EIO_INTRPEND 0x08
+#define EIO_INTEDGE 0x00
+#define EIO_INTLEVEL 0x08
+#define EIO_0WS 0x10
+
+#define ECH_ID 0xa0
+#define ECH_IDBITMASK 0xe0
+#define ECH_BRDENABLE 0x08
+#define ECH_BRDDISABLE 0x00
+#define ECH_INTENABLE 0x01
+#define ECH_INTDISABLE 0x00
+#define ECH_INTLEVEL 0x02
+#define ECH_INTEDGE 0x00
+#define ECH_INTRPEND 0x01
+#define ECH_BRDRESET 0x01
+
+#define ECHMC_INTENABLE 0x01
+#define ECHMC_BRDRESET 0x02
+
+#define ECH_PNLSTATUS 2
+#define ECH_PNL16PORT 0x20
+#define ECH_PNLIDMASK 0x07
+#define ECH_PNLXPID 0x40
+#define ECH_PNLINTRPEND 0x80
+
+#define ECH_ADDR2MASK 0x1e0
+
+/*
+ * Define the vector mapping bits for the programmable interrupt board
+ * hardware. These bits encode the interrupt for the board to use - it
+ * is software selectable (except the EIO-8M).
+ */
+static unsigned char stl_vecmap[] = {
+ 0xff, 0xff, 0xff, 0x04, 0x06, 0x05, 0xff, 0x07,
+ 0xff, 0xff, 0x00, 0x02, 0x01, 0xff, 0xff, 0x03
+};
+
+/*
+ * Set up enable and disable macros for the ECH boards. They require
+ * the secondary io address space to be activated and deactivated.
+ * This way all ECH boards can share their secondary io region.
+ * If this is an ECH-PCI board then also need to set the page pointer
+ * to point to the correct page.
+ */
+#define BRDENABLE(brdnr,pagenr) \
+ if (stl_brds[(brdnr)]->brdtype == BRD_ECH) \
+ outb((stl_brds[(brdnr)]->ioctrlval | ECH_BRDENABLE), \
+ stl_brds[(brdnr)]->ioctrl); \
+ else if (stl_brds[(brdnr)]->brdtype == BRD_ECHPCI) \
+ outb((pagenr), stl_brds[(brdnr)]->ioctrl);
+
+#define BRDDISABLE(brdnr) \
+ if (stl_brds[(brdnr)]->brdtype == BRD_ECH) \
+ outb((stl_brds[(brdnr)]->ioctrlval | ECH_BRDDISABLE), \
+ stl_brds[(brdnr)]->ioctrl);
+
+#define STL_CD1400MAXBAUD 230400
+#define STL_SC26198MAXBAUD 460800
+
+#define STL_BAUDBASE 115200
+#define STL_CLOSEDELAY (5 * HZ / 10)
+
+/*****************************************************************************/
+
+#ifdef CONFIG_PCI
+
+/*
+ * Define the Stallion PCI vendor and device IDs.
+ */
+#ifndef PCI_VENDOR_ID_STALLION
+#define PCI_VENDOR_ID_STALLION 0x124d
+#endif
+#ifndef PCI_DEVICE_ID_ECHPCI832
+#define PCI_DEVICE_ID_ECHPCI832 0x0000
+#endif
+#ifndef PCI_DEVICE_ID_ECHPCI864
+#define PCI_DEVICE_ID_ECHPCI864 0x0002
+#endif
+#ifndef PCI_DEVICE_ID_EIOPCI
+#define PCI_DEVICE_ID_EIOPCI 0x0003
+#endif
+
+/*
+ * Define structure to hold all Stallion PCI boards.
+ */
+typedef struct stlpcibrd {
+ unsigned short vendid;
+ unsigned short devid;
+ int brdtype;
+} stlpcibrd_t;
+
+static stlpcibrd_t stl_pcibrds[] = {
+ { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECHPCI864, BRD_ECH64PCI },
+ { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_EIOPCI, BRD_EASYIOPCI },
+ { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECHPCI832, BRD_ECHPCI },
+ { PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_87410, BRD_ECHPCI },
+};
+
+static int stl_nrpcibrds = sizeof(stl_pcibrds) / sizeof(stlpcibrd_t);
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Define macros to extract a brd/port number from a minor number.
+ */
+#define MINOR2BRD(min) (((min) & 0xc0) >> 6)
+#define MINOR2PORT(min) ((min) & 0x3f)
+
+/*
+ * Define a baud rate table that converts termios baud rate selector
+ * into the actual baud rate value. All baud rate calculations are
+ * based on the actual baud rate required.
+ */
+static unsigned int stl_baudrates[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
+};
+
+/*
+ * Define some handy local macros...
+ */
+#undef MIN
+#define MIN(a,b) (((a) <= (b)) ? (a) : (b))
+
+#undef TOLOWER
+#define TOLOWER(x) ((((x) >= 'A') && ((x) <= 'Z')) ? ((x) + 0x20) : (x))
+
+/*****************************************************************************/
+
+/*
+ * Declare all those functions in this driver!
+ */
+
+#ifdef MODULE
+int init_module(void);
+void cleanup_module(void);
+static void stl_argbrds(void);
+static int stl_parsebrd(stlconf_t *confp, char **argp);
+
+static unsigned long stl_atol(char *str);
+#endif
+
+int stl_init(void);
+static int stl_open(struct tty_struct *tty, struct file *filp);
+static void stl_close(struct tty_struct *tty, struct file *filp);
+static int stl_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count);
+static void stl_putchar(struct tty_struct *tty, unsigned char ch);
+static void stl_flushchars(struct tty_struct *tty);
+static int stl_writeroom(struct tty_struct *tty);
+static int stl_charsinbuffer(struct tty_struct *tty);
+static int stl_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg);
+static void stl_settermios(struct tty_struct *tty, struct termios *old);
+static void stl_throttle(struct tty_struct *tty);
+static void stl_unthrottle(struct tty_struct *tty);
+static void stl_stop(struct tty_struct *tty);
+static void stl_start(struct tty_struct *tty);
+static void stl_flushbuffer(struct tty_struct *tty);
+static void stl_breakctl(struct tty_struct *tty, int state);
+static void stl_waituntilsent(struct tty_struct *tty, int timeout);
+static void stl_sendxchar(struct tty_struct *tty, char ch);
+static void stl_hangup(struct tty_struct *tty);
+static int stl_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg);
+static int stl_portinfo(stlport_t *portp, int portnr, char *pos);
+static int stl_readproc(char *page, char **start, off_t off, int count, int *eof, void *data);
+
+static int stl_brdinit(stlbrd_t *brdp);
+static int stl_initports(stlbrd_t *brdp, stlpanel_t *panelp);
+static int stl_mapirq(int irq, char *name);
+static void stl_getserial(stlport_t *portp, struct serial_struct *sp);
+static int stl_setserial(stlport_t *portp, struct serial_struct *sp);
+static int stl_getbrdstats(combrd_t *bp);
+static int stl_getportstats(stlport_t *portp, comstats_t *cp);
+static int stl_clrportstats(stlport_t *portp, comstats_t *cp);
+static int stl_getportstruct(unsigned long arg);
+static int stl_getbrdstruct(unsigned long arg);
+static int stl_waitcarrier(stlport_t *portp, struct file *filp);
+static void stl_delay(int len);
+static void stl_intr(int irq, void *dev_id, struct pt_regs *regs);
+static void stl_eiointr(stlbrd_t *brdp);
+static void stl_echatintr(stlbrd_t *brdp);
+static void stl_echmcaintr(stlbrd_t *brdp);
+static void stl_echpciintr(stlbrd_t *brdp);
+static void stl_echpci64intr(stlbrd_t *brdp);
+static void stl_offintr(void *private);
+static void *stl_memalloc(int len);
+static stlbrd_t *stl_allocbrd(void);
+static stlport_t *stl_getport(int brdnr, int panelnr, int portnr);
+
+static inline int stl_initbrds(void);
+static inline int stl_initeio(stlbrd_t *brdp);
+static inline int stl_initech(stlbrd_t *brdp);
+static inline int stl_getbrdnr(void);
+
+#ifdef CONFIG_PCI
+static inline int stl_findpcibrds(void);
+static inline int stl_initpcibrd(int brdtype, struct pci_dev *devp);
+#endif
+
+/*
+ * CD1400 uart specific handling functions.
+ */
+static void stl_cd1400setreg(stlport_t *portp, int regnr, int value);
+static int stl_cd1400getreg(stlport_t *portp, int regnr);
+static int stl_cd1400updatereg(stlport_t *portp, int regnr, int value);
+static int stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp);
+static void stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp);
+static void stl_cd1400setport(stlport_t *portp, struct termios *tiosp);
+static int stl_cd1400getsignals(stlport_t *portp);
+static void stl_cd1400setsignals(stlport_t *portp, int dtr, int rts);
+static void stl_cd1400ccrwait(stlport_t *portp);
+static void stl_cd1400enablerxtx(stlport_t *portp, int rx, int tx);
+static void stl_cd1400startrxtx(stlport_t *portp, int rx, int tx);
+static void stl_cd1400disableintrs(stlport_t *portp);
+static void stl_cd1400sendbreak(stlport_t *portp, int len);
+static void stl_cd1400flowctrl(stlport_t *portp, int state);
+static void stl_cd1400sendflow(stlport_t *portp, int state);
+static void stl_cd1400flush(stlport_t *portp);
+static int stl_cd1400datastate(stlport_t *portp);
+static void stl_cd1400eiointr(stlpanel_t *panelp, unsigned int iobase);
+static void stl_cd1400echintr(stlpanel_t *panelp, unsigned int iobase);
+static void stl_cd1400txisr(stlpanel_t *panelp, int ioaddr);
+static void stl_cd1400rxisr(stlpanel_t *panelp, int ioaddr);
+static void stl_cd1400mdmisr(stlpanel_t *panelp, int ioaddr);
+
+static inline int stl_cd1400breakisr(stlport_t *portp, int ioaddr);
+
+/*
+ * SC26198 uart specific handling functions.
+ */
+static void stl_sc26198setreg(stlport_t *portp, int regnr, int value);
+static int stl_sc26198getreg(stlport_t *portp, int regnr);
+static int stl_sc26198updatereg(stlport_t *portp, int regnr, int value);
+static int stl_sc26198getglobreg(stlport_t *portp, int regnr);
+static int stl_sc26198panelinit(stlbrd_t *brdp, stlpanel_t *panelp);
+static void stl_sc26198portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp);
+static void stl_sc26198setport(stlport_t *portp, struct termios *tiosp);
+static int stl_sc26198getsignals(stlport_t *portp);
+static void stl_sc26198setsignals(stlport_t *portp, int dtr, int rts);
+static void stl_sc26198enablerxtx(stlport_t *portp, int rx, int tx);
+static void stl_sc26198startrxtx(stlport_t *portp, int rx, int tx);
+static void stl_sc26198disableintrs(stlport_t *portp);
+static void stl_sc26198sendbreak(stlport_t *portp, int len);
+static void stl_sc26198flowctrl(stlport_t *portp, int state);
+static void stl_sc26198sendflow(stlport_t *portp, int state);
+static void stl_sc26198flush(stlport_t *portp);
+static int stl_sc26198datastate(stlport_t *portp);
+static void stl_sc26198wait(stlport_t *portp);
+static void stl_sc26198txunflow(stlport_t *portp, struct tty_struct *tty);
+static void stl_sc26198intr(stlpanel_t *panelp, unsigned int iobase);
+static void stl_sc26198txisr(stlport_t *port);
+static void stl_sc26198rxisr(stlport_t *port, unsigned int iack);
+static void stl_sc26198rxbadch(stlport_t *portp, unsigned char status, char ch);
+static void stl_sc26198rxbadchars(stlport_t *portp);
+static void stl_sc26198otherisr(stlport_t *port, unsigned int iack);
+
+/*****************************************************************************/
+
+/*
+ * Generic UART support structure.
+ */
+typedef struct uart {
+ int (*panelinit)(stlbrd_t *brdp, stlpanel_t *panelp);
+ void (*portinit)(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp);
+ void (*setport)(stlport_t *portp, struct termios *tiosp);
+ int (*getsignals)(stlport_t *portp);
+ void (*setsignals)(stlport_t *portp, int dtr, int rts);
+ void (*enablerxtx)(stlport_t *portp, int rx, int tx);
+ void (*startrxtx)(stlport_t *portp, int rx, int tx);
+ void (*disableintrs)(stlport_t *portp);
+ void (*sendbreak)(stlport_t *portp, int len);
+ void (*flowctrl)(stlport_t *portp, int state);
+ void (*sendflow)(stlport_t *portp, int state);
+ void (*flush)(stlport_t *portp);
+ int (*datastate)(stlport_t *portp);
+ void (*intr)(stlpanel_t *panelp, unsigned int iobase);
+} uart_t;
+
+/*
+ * Define some macros to make calling these functions nice and clean.
+ */
+#define stl_panelinit (* ((uart_t *) panelp->uartp)->panelinit)
+#define stl_portinit (* ((uart_t *) portp->uartp)->portinit)
+#define stl_setport (* ((uart_t *) portp->uartp)->setport)
+#define stl_getsignals (* ((uart_t *) portp->uartp)->getsignals)
+#define stl_setsignals (* ((uart_t *) portp->uartp)->setsignals)
+#define stl_enablerxtx (* ((uart_t *) portp->uartp)->enablerxtx)
+#define stl_startrxtx (* ((uart_t *) portp->uartp)->startrxtx)
+#define stl_disableintrs (* ((uart_t *) portp->uartp)->disableintrs)
+#define stl_sendbreak (* ((uart_t *) portp->uartp)->sendbreak)
+#define stl_flowctrl (* ((uart_t *) portp->uartp)->flowctrl)
+#define stl_sendflow (* ((uart_t *) portp->uartp)->sendflow)
+#define stl_flush (* ((uart_t *) portp->uartp)->flush)
+#define stl_datastate (* ((uart_t *) portp->uartp)->datastate)
+
+/*****************************************************************************/
+
+/*
+ * CD1400 UART specific data initialization.
+ */
+static uart_t stl_cd1400uart = {
+ stl_cd1400panelinit,
+ stl_cd1400portinit,
+ stl_cd1400setport,
+ stl_cd1400getsignals,
+ stl_cd1400setsignals,
+ stl_cd1400enablerxtx,
+ stl_cd1400startrxtx,
+ stl_cd1400disableintrs,
+ stl_cd1400sendbreak,
+ stl_cd1400flowctrl,
+ stl_cd1400sendflow,
+ stl_cd1400flush,
+ stl_cd1400datastate,
+ stl_cd1400eiointr
+};
+
+/*
+ * Define the offsets within the register bank of a cd1400 based panel.
+ * These io address offsets are common to the EasyIO board as well.
+ */
+#define EREG_ADDR 0
+#define EREG_DATA 4
+#define EREG_RXACK 5
+#define EREG_TXACK 6
+#define EREG_MDACK 7
+
+#define EREG_BANKSIZE 8
+
+#define CD1400_CLK 25000000
+#define CD1400_CLK8M 20000000
+
+/*
+ * Define the cd1400 baud rate clocks. These are used when calculating
+ * what clock and divisor to use for the required baud rate. Also
+ * define the maximum baud rate allowed, and the default base baud.
+ */
+static int stl_cd1400clkdivs[] = {
+ CD1400_CLK0, CD1400_CLK1, CD1400_CLK2, CD1400_CLK3, CD1400_CLK4
+};
+
+/*****************************************************************************/
+
+/*
+ * SC26198 UART specific data initization.
+ */
+static uart_t stl_sc26198uart = {
+ stl_sc26198panelinit,
+ stl_sc26198portinit,
+ stl_sc26198setport,
+ stl_sc26198getsignals,
+ stl_sc26198setsignals,
+ stl_sc26198enablerxtx,
+ stl_sc26198startrxtx,
+ stl_sc26198disableintrs,
+ stl_sc26198sendbreak,
+ stl_sc26198flowctrl,
+ stl_sc26198sendflow,
+ stl_sc26198flush,
+ stl_sc26198datastate,
+ stl_sc26198intr
+};
+
+/*
+ * Define the offsets within the register bank of a sc26198 based panel.
+ */
+#define XP_DATA 0
+#define XP_ADDR 1
+#define XP_MODID 2
+#define XP_STATUS 2
+#define XP_IACK 3
+
+#define XP_BANKSIZE 4
+
+/*
+ * Define the sc26198 baud rate table. Offsets within the table
+ * represent the actual baud rate selector of sc26198 registers.
+ */
+static unsigned int sc26198_baudtable[] = {
+ 50, 75, 150, 200, 300, 450, 600, 900, 1200, 1800, 2400, 3600,
+ 4800, 7200, 9600, 14400, 19200, 28800, 38400, 57600, 115200,
+ 230400, 460800, 921600
+};
+
+#define SC26198_NRBAUDS (sizeof(sc26198_baudtable) / sizeof(unsigned int))
+
+/*****************************************************************************/
+
+/*
+ * Define the driver info for a user level control device. Used mainly
+ * to get at port stats - only not using the port device itself.
+ */
+static struct file_operations stl_fsiomem = {
+ owner: THIS_MODULE,
+ ioctl: stl_memioctl,
+};
+
+/*****************************************************************************/
+
+static devfs_handle_t devfs_handle = NULL;
+
+#ifdef MODULE
+
+/*
+ * Loadable module initialization stuff.
+ */
+
+int init_module()
+{
+ unsigned long flags;
+
+#if DEBUG
+ printk("init_module()\n");
+#endif
+
+ save_flags(flags);
+ cli();
+ stl_init();
+ restore_flags(flags);
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+void cleanup_module()
+{
+ stlbrd_t *brdp;
+ stlpanel_t *panelp;
+ stlport_t *portp;
+ unsigned long flags;
+ int i, j, k;
+
+#if DEBUG
+ printk("cleanup_module()\n");
+#endif
+
+ printk(KERN_INFO "Unloading %s: version %s\n", stl_drvtitle,
+ stl_drvversion);
+
+ save_flags(flags);
+ cli();
+
+/*
+ * Free up all allocated resources used by the ports. This includes
+ * memory and interrupts. As part of this process we will also do
+ * a hangup on every open port - to try to flush out any processes
+ * hanging onto ports.
+ */
+ i = tty_unregister_driver(&stl_serial);
+ j = tty_unregister_driver(&stl_callout);
+ if (i || j) {
+ printk("STALLION: failed to un-register tty driver, "
+ "errno=%d,%d\n", -i, -j);
+ restore_flags(flags);
+ return;
+ }
+ devfs_unregister (devfs_handle);
+ if ((i = devfs_unregister_chrdev(STL_SIOMEMMAJOR, "staliomem")))
+ printk("STALLION: failed to un-register serial memory device, "
+ "errno=%d\n", -i);
+
+ if (stl_tmpwritebuf != (char *) NULL)
+ kfree(stl_tmpwritebuf);
+
+ for (i = 0; (i < stl_nrbrds); i++) {
+ if ((brdp = stl_brds[i]) == (stlbrd_t *) NULL)
+ continue;
+ for (j = 0; (j < STL_MAXPANELS); j++) {
+ panelp = brdp->panels[j];
+ if (panelp == (stlpanel_t *) NULL)
+ continue;
+ for (k = 0; (k < STL_PORTSPERPANEL); k++) {
+ portp = panelp->ports[k];
+ if (portp == (stlport_t *) NULL)
+ continue;
+ if (portp->tty != (struct tty_struct *) NULL)
+ stl_hangup(portp->tty);
+ if (portp->tx.buf != (char *) NULL)
+ kfree(portp->tx.buf);
+ kfree(portp);
+ }
+ kfree(panelp);
+ }
+
+ release_region(brdp->ioaddr1, brdp->iosize1);
+ if (brdp->iosize2 > 0)
+ release_region(brdp->ioaddr2, brdp->iosize2);
+
+ kfree(brdp);
+ stl_brds[i] = (stlbrd_t *) NULL;
+ }
+
+ for (i = 0; (i < stl_numintrs); i++)
+ free_irq(stl_gotintrs[i], NULL);
+
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Check for any arguments passed in on the module load command line.
+ */
+
+static void stl_argbrds()
+{
+ stlconf_t conf;
+ stlbrd_t *brdp;
+ int nrargs, i;
+
+#if DEBUG
+ printk("stl_argbrds()\n");
+#endif
+
+ nrargs = sizeof(stl_brdsp) / sizeof(char **);
+
+ for (i = stl_nrbrds; (i < nrargs); i++) {
+ memset(&conf, 0, sizeof(conf));
+ if (stl_parsebrd(&conf, stl_brdsp[i]) == 0)
+ continue;
+ if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL)
+ continue;
+ stl_nrbrds = i + 1;
+ brdp->brdnr = i;
+ brdp->brdtype = conf.brdtype;
+ brdp->ioaddr1 = conf.ioaddr1;
+ brdp->ioaddr2 = conf.ioaddr2;
+ brdp->irq = conf.irq;
+ brdp->irqtype = conf.irqtype;
+ stl_brdinit(brdp);
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Convert an ascii string number into an unsigned long.
+ */
+
+static unsigned long stl_atol(char *str)
+{
+ unsigned long val;
+ int base, c;
+ char *sp;
+
+ val = 0;
+ sp = str;
+ if ((*sp == '0') && (*(sp+1) == 'x')) {
+ base = 16;
+ sp += 2;
+ } else if (*sp == '0') {
+ base = 8;
+ sp++;
+ } else {
+ base = 10;
+ }
+
+ for (; (*sp != 0); sp++) {
+ c = (*sp > '9') ? (TOLOWER(*sp) - 'a' + 10) : (*sp - '0');
+ if ((c < 0) || (c >= base)) {
+ printk("STALLION: invalid argument %s\n", str);
+ val = 0;
+ break;
+ }
+ val = (val * base) + c;
+ }
+ return(val);
+}
+
+/*****************************************************************************/
+
+/*
+ * Parse the supplied argument string, into the board conf struct.
+ */
+
+static int stl_parsebrd(stlconf_t *confp, char **argp)
+{
+ char *sp;
+ int nrbrdnames, i;
+
+#if DEBUG
+ printk("stl_parsebrd(confp=%x,argp=%x)\n", (int) confp, (int) argp);
+#endif
+
+ if ((argp[0] == (char *) NULL) || (*argp[0] == 0))
+ return(0);
+
+ for (sp = argp[0], i = 0; ((*sp != 0) && (i < 25)); sp++, i++)
+ *sp = TOLOWER(*sp);
+
+ nrbrdnames = sizeof(stl_brdstr) / sizeof(stlbrdtype_t);
+ for (i = 0; (i < nrbrdnames); i++) {
+ if (strcmp(stl_brdstr[i].name, argp[0]) == 0)
+ break;
+ }
+ if (i >= nrbrdnames) {
+ printk("STALLION: unknown board name, %s?\n", argp[0]);
+ return(0);
+ }
+
+ confp->brdtype = stl_brdstr[i].type;
+
+ i = 1;
+ if ((argp[i] != (char *) NULL) && (*argp[i] != 0))
+ confp->ioaddr1 = stl_atol(argp[i]);
+ i++;
+ if (confp->brdtype == BRD_ECH) {
+ if ((argp[i] != (char *) NULL) && (*argp[i] != 0))
+ confp->ioaddr2 = stl_atol(argp[i]);
+ i++;
+ }
+ if ((argp[i] != (char *) NULL) && (*argp[i] != 0))
+ confp->irq = stl_atol(argp[i]);
+ return(1);
+}
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Local driver kernel memory allocation routine.
+ */
+
+static void *stl_memalloc(int len)
+{
+ return((void *) kmalloc(len, GFP_KERNEL));
+}
+
+/*****************************************************************************/
+
+/*
+ * Allocate a new board structure. Fill out the basic info in it.
+ */
+
+static stlbrd_t *stl_allocbrd()
+{
+ stlbrd_t *brdp;
+
+ brdp = (stlbrd_t *) stl_memalloc(sizeof(stlbrd_t));
+ if (brdp == (stlbrd_t *) NULL) {
+ printk("STALLION: failed to allocate memory (size=%d)\n",
+ sizeof(stlbrd_t));
+ return((stlbrd_t *) NULL);
+ }
+
+ memset(brdp, 0, sizeof(stlbrd_t));
+ brdp->magic = STL_BOARDMAGIC;
+ return(brdp);
+}
+
+/*****************************************************************************/
+
+static int stl_open(struct tty_struct *tty, struct file *filp)
+{
+ stlport_t *portp;
+ stlbrd_t *brdp;
+ unsigned int minordev;
+ int brdnr, panelnr, portnr, rc;
+
+#if DEBUG
+ printk("stl_open(tty=%x,filp=%x): device=%x\n", (int) tty,
+ (int) filp, tty->device);
+#endif
+
+ minordev = MINOR(tty->device);
+ brdnr = MINOR2BRD(minordev);
+ if (brdnr >= stl_nrbrds)
+ return(-ENODEV);
+ brdp = stl_brds[brdnr];
+ if (brdp == (stlbrd_t *) NULL)
+ return(-ENODEV);
+ minordev = MINOR2PORT(minordev);
+ for (portnr = -1, panelnr = 0; (panelnr < STL_MAXPANELS); panelnr++) {
+ if (brdp->panels[panelnr] == (stlpanel_t *) NULL)
+ break;
+ if (minordev < brdp->panels[panelnr]->nrports) {
+ portnr = minordev;
+ break;
+ }
+ minordev -= brdp->panels[panelnr]->nrports;
+ }
+ if (portnr < 0)
+ return(-ENODEV);
+
+ portp = brdp->panels[panelnr]->ports[portnr];
+ if (portp == (stlport_t *) NULL)
+ return(-ENODEV);
+
+ MOD_INC_USE_COUNT;
+
+/*
+ * On the first open of the device setup the port hardware, and
+ * initialize the per port data structure.
+ */
+ portp->tty = tty;
+ tty->driver_data = portp;
+ portp->refcount++;
+
+ if ((portp->flags & ASYNC_INITIALIZED) == 0) {
+ if (portp->tx.buf == (char *) NULL) {
+ portp->tx.buf = (char *) stl_memalloc(STL_TXBUFSIZE);
+ if (portp->tx.buf == (char *) NULL)
+ return(-ENOMEM);
+ portp->tx.head = portp->tx.buf;
+ portp->tx.tail = portp->tx.buf;
+ }
+ stl_setport(portp, tty->termios);
+ portp->sigs = stl_getsignals(portp);
+ stl_setsignals(portp, 1, 1);
+ stl_enablerxtx(portp, 1, 1);
+ stl_startrxtx(portp, 1, 0);
+ clear_bit(TTY_IO_ERROR, &tty->flags);
+ portp->flags |= ASYNC_INITIALIZED;
+ }
+
+/*
+ * Check if this port is in the middle of closing. If so then wait
+ * until it is closed then return error status, based on flag settings.
+ * The sleep here does not need interrupt protection since the wakeup
+ * for it is done with the same context.
+ */
+ if (portp->flags & ASYNC_CLOSING) {
+ interruptible_sleep_on(&portp->close_wait);
+ if (portp->flags & ASYNC_HUP_NOTIFY)
+ return(-EAGAIN);
+ return(-ERESTARTSYS);
+ }
+
+/*
+ * Based on type of open being done check if it can overlap with any
+ * previous opens still in effect. If we are a normal serial device
+ * then also we might have to wait for carrier.
+ */
+ if (tty->driver.subtype == STL_DRVTYPCALLOUT) {
+ if (portp->flags & ASYNC_NORMAL_ACTIVE)
+ return(-EBUSY);
+ if (portp->flags & ASYNC_CALLOUT_ACTIVE) {
+ if ((portp->flags & ASYNC_SESSION_LOCKOUT) &&
+ (portp->session != current->session))
+ return(-EBUSY);
+ if ((portp->flags & ASYNC_PGRP_LOCKOUT) &&
+ (portp->pgrp != current->pgrp))
+ return(-EBUSY);
+ }
+ portp->flags |= ASYNC_CALLOUT_ACTIVE;
+ } else {
+ if (filp->f_flags & O_NONBLOCK) {
+ if (portp->flags & ASYNC_CALLOUT_ACTIVE)
+ return(-EBUSY);
+ } else {
+ if ((rc = stl_waitcarrier(portp, filp)) != 0)
+ return(rc);
+ }
+ portp->flags |= ASYNC_NORMAL_ACTIVE;
+ }
+
+ if ((portp->refcount == 1) && (portp->flags & ASYNC_SPLIT_TERMIOS)) {
+ if (tty->driver.subtype == STL_DRVTYPSERIAL)
+ *tty->termios = portp->normaltermios;
+ else
+ *tty->termios = portp->callouttermios;
+ stl_setport(portp, tty->termios);
+ }
+
+ portp->session = current->session;
+ portp->pgrp = current->pgrp;
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Possibly need to wait for carrier (DCD signal) to come high. Say
+ * maybe because if we are clocal then we don't need to wait...
+ */
+
+static int stl_waitcarrier(stlport_t *portp, struct file *filp)
+{
+ unsigned long flags;
+ int rc, doclocal;
+
+#if DEBUG
+ printk("stl_waitcarrier(portp=%x,filp=%x)\n", (int) portp, (int) filp);
+#endif
+
+ rc = 0;
+ doclocal = 0;
+
+ if (portp->flags & ASYNC_CALLOUT_ACTIVE) {
+ if (portp->normaltermios.c_cflag & CLOCAL)
+ doclocal++;
+ } else {
+ if (portp->tty->termios->c_cflag & CLOCAL)
+ doclocal++;
+ }
+
+ save_flags(flags);
+ cli();
+ portp->openwaitcnt++;
+ if (! tty_hung_up_p(filp))
+ portp->refcount--;
+
+ for (;;) {
+ if ((portp->flags & ASYNC_CALLOUT_ACTIVE) == 0)
+ stl_setsignals(portp, 1, 1);
+ if (tty_hung_up_p(filp) ||
+ ((portp->flags & ASYNC_INITIALIZED) == 0)) {
+ if (portp->flags & ASYNC_HUP_NOTIFY)
+ rc = -EBUSY;
+ else
+ rc = -ERESTARTSYS;
+ break;
+ }
+ if (((portp->flags & ASYNC_CALLOUT_ACTIVE) == 0) &&
+ ((portp->flags & ASYNC_CLOSING) == 0) &&
+ (doclocal || (portp->sigs & TIOCM_CD))) {
+ break;
+ }
+ if (signal_pending(current)) {
+ rc = -ERESTARTSYS;
+ break;
+ }
+ interruptible_sleep_on(&portp->open_wait);
+ }
+
+ if (! tty_hung_up_p(filp))
+ portp->refcount++;
+ portp->openwaitcnt--;
+ restore_flags(flags);
+
+ return(rc);
+}
+
+/*****************************************************************************/
+
+static void stl_close(struct tty_struct *tty, struct file *filp)
+{
+ stlport_t *portp;
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_close(tty=%x,filp=%x)\n", (int) tty, (int) filp);
+#endif
+
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ if (tty_hung_up_p(filp)) {
+ MOD_DEC_USE_COUNT;
+ restore_flags(flags);
+ return;
+ }
+ if ((tty->count == 1) && (portp->refcount != 1))
+ portp->refcount = 1;
+ if (portp->refcount-- > 1) {
+ MOD_DEC_USE_COUNT;
+ restore_flags(flags);
+ return;
+ }
+
+ portp->refcount = 0;
+ portp->flags |= ASYNC_CLOSING;
+
+ if (portp->flags & ASYNC_NORMAL_ACTIVE)
+ portp->normaltermios = *tty->termios;
+ if (portp->flags & ASYNC_CALLOUT_ACTIVE)
+ portp->callouttermios = *tty->termios;
+
+/*
+ * May want to wait for any data to drain before closing. The BUSY
+ * flag keeps track of whether we are still sending or not - it is
+ * very accurate for the cd1400, not quite so for the sc26198.
+ * (The sc26198 has no "end-of-data" interrupt only empty FIFO)
+ */
+ tty->closing = 1;
+ if (portp->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ tty_wait_until_sent(tty, portp->closing_wait);
+ stl_waituntilsent(tty, (HZ / 2));
+
+ portp->flags &= ~ASYNC_INITIALIZED;
+ stl_disableintrs(portp);
+ if (tty->termios->c_cflag & HUPCL)
+ stl_setsignals(portp, 0, 0);
+ stl_enablerxtx(portp, 0, 0);
+ stl_flushbuffer(tty);
+ portp->istate = 0;
+ if (portp->tx.buf != (char *) NULL) {
+ kfree(portp->tx.buf);
+ portp->tx.buf = (char *) NULL;
+ portp->tx.head = (char *) NULL;
+ portp->tx.tail = (char *) NULL;
+ }
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ if (tty->ldisc.flush_buffer)
+ (tty->ldisc.flush_buffer)(tty);
+
+ tty->closing = 0;
+ portp->tty = (struct tty_struct *) NULL;
+
+ if (portp->openwaitcnt) {
+ if (portp->close_delay)
+ stl_delay(portp->close_delay);
+ wake_up_interruptible(&portp->open_wait);
+ }
+
+ portp->flags &= ~(ASYNC_CALLOUT_ACTIVE | ASYNC_NORMAL_ACTIVE |
+ ASYNC_CLOSING);
+ wake_up_interruptible(&portp->close_wait);
+ MOD_DEC_USE_COUNT;
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Wait for a specified delay period, this is not a busy-loop. It will
+ * give up the processor while waiting. Unfortunately this has some
+ * rather intimate knowledge of the process management stuff.
+ */
+
+static void stl_delay(int len)
+{
+#if DEBUG
+ printk("stl_delay(len=%d)\n", len);
+#endif
+ if (len > 0) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(len);
+ current->state = TASK_RUNNING;
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Write routine. Take data and stuff it in to the TX ring queue.
+ * If transmit interrupts are not running then start them.
+ */
+
+static int stl_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count)
+{
+ stlport_t *portp;
+ unsigned int len, stlen;
+ unsigned char *chbuf;
+ char *head, *tail;
+
+#if DEBUG
+ printk("stl_write(tty=%x,from_user=%d,buf=%x,count=%d)\n",
+ (int) tty, from_user, (int) buf, count);
+#endif
+
+ if ((tty == (struct tty_struct *) NULL) ||
+ (stl_tmpwritebuf == (char *) NULL))
+ return(0);
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return(0);
+ if (portp->tx.buf == (char *) NULL)
+ return(0);
+
+/*
+ * If copying direct from user space we must cater for page faults,
+ * causing us to "sleep" here for a while. To handle this copy in all
+ * the data we need now, into a local buffer. Then when we got it all
+ * copy it into the TX buffer.
+ */
+ chbuf = (unsigned char *) buf;
+ if (from_user) {
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ len = (head >= tail) ? (STL_TXBUFSIZE - (head - tail) - 1) :
+ (tail - head - 1);
+ count = MIN(len, count);
+
+ down(&stl_tmpwritesem);
+ copy_from_user(stl_tmpwritebuf, chbuf, count);
+ chbuf = &stl_tmpwritebuf[0];
+ }
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ if (head >= tail) {
+ len = STL_TXBUFSIZE - (head - tail) - 1;
+ stlen = STL_TXBUFSIZE - (head - portp->tx.buf);
+ } else {
+ len = tail - head - 1;
+ stlen = len;
+ }
+
+ len = MIN(len, count);
+ count = 0;
+ while (len > 0) {
+ stlen = MIN(len, stlen);
+ memcpy(head, chbuf, stlen);
+ len -= stlen;
+ chbuf += stlen;
+ count += stlen;
+ head += stlen;
+ if (head >= (portp->tx.buf + STL_TXBUFSIZE)) {
+ head = portp->tx.buf;
+ stlen = tail - head;
+ }
+ }
+ portp->tx.head = head;
+
+ clear_bit(ASYI_TXLOW, &portp->istate);
+ stl_startrxtx(portp, -1, 1);
+
+ if (from_user)
+ up(&stl_tmpwritesem);
+
+ return(count);
+}
+
+/*****************************************************************************/
+
+static void stl_putchar(struct tty_struct *tty, unsigned char ch)
+{
+ stlport_t *portp;
+ unsigned int len;
+ char *head, *tail;
+
+#if DEBUG
+ printk("stl_putchar(tty=%x,ch=%x)\n", (int) tty, (int) ch);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ if (portp->tx.buf == (char *) NULL)
+ return;
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+
+ len = (head >= tail) ? (STL_TXBUFSIZE - (head - tail)) : (tail - head);
+ len--;
+
+ if (len > 0) {
+ *head++ = ch;
+ if (head >= (portp->tx.buf + STL_TXBUFSIZE))
+ head = portp->tx.buf;
+ }
+ portp->tx.head = head;
+}
+
+/*****************************************************************************/
+
+/*
+ * If there are any characters in the buffer then make sure that TX
+ * interrupts are on and get'em out. Normally used after the putchar
+ * routine has been called.
+ */
+
+static void stl_flushchars(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#if DEBUG
+ printk("stl_flushchars(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ if (portp->tx.buf == (char *) NULL)
+ return;
+
+#if 0
+ if (tty->stopped || tty->hw_stopped ||
+ (portp->tx.head == portp->tx.tail))
+ return;
+#endif
+ stl_startrxtx(portp, -1, 1);
+}
+
+/*****************************************************************************/
+
+static int stl_writeroom(struct tty_struct *tty)
+{
+ stlport_t *portp;
+ char *head, *tail;
+
+#if DEBUG
+ printk("stl_writeroom(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return(0);
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return(0);
+ if (portp->tx.buf == (char *) NULL)
+ return(0);
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ return((head >= tail) ? (STL_TXBUFSIZE - (head - tail) - 1) : (tail - head - 1));
+}
+
+/*****************************************************************************/
+
+/*
+ * Return number of chars in the TX buffer. Normally we would just
+ * calculate the number of chars in the buffer and return that, but if
+ * the buffer is empty and TX interrupts are still on then we return
+ * that the buffer still has 1 char in it. This way whoever called us
+ * will not think that ALL chars have drained - since the UART still
+ * must have some chars in it (we are busy after all).
+ */
+
+static int stl_charsinbuffer(struct tty_struct *tty)
+{
+ stlport_t *portp;
+ unsigned int size;
+ char *head, *tail;
+
+#if DEBUG
+ printk("stl_charsinbuffer(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return(0);
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return(0);
+ if (portp->tx.buf == (char *) NULL)
+ return(0);
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ size = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head));
+ if ((size == 0) && test_bit(ASYI_TXBUSY, &portp->istate))
+ size = 1;
+ return(size);
+}
+
+/*****************************************************************************/
+
+/*
+ * Generate the serial struct info.
+ */
+
+static void stl_getserial(stlport_t *portp, struct serial_struct *sp)
+{
+ struct serial_struct sio;
+ stlbrd_t *brdp;
+
+#if DEBUG
+ printk("stl_getserial(portp=%x,sp=%x)\n", (int) portp, (int) sp);
+#endif
+
+ memset(&sio, 0, sizeof(struct serial_struct));
+ sio.line = portp->portnr;
+ sio.port = portp->ioaddr;
+ sio.flags = portp->flags;
+ sio.baud_base = portp->baud_base;
+ sio.close_delay = portp->close_delay;
+ sio.closing_wait = portp->closing_wait;
+ sio.custom_divisor = portp->custom_divisor;
+ sio.hub6 = 0;
+ if (portp->uartp == &stl_cd1400uart) {
+ sio.type = PORT_CIRRUS;
+ sio.xmit_fifo_size = CD1400_TXFIFOSIZE;
+ } else {
+ sio.type = PORT_UNKNOWN;
+ sio.xmit_fifo_size = SC26198_TXFIFOSIZE;
+ }
+
+ brdp = stl_brds[portp->brdnr];
+ if (brdp != (stlbrd_t *) NULL)
+ sio.irq = brdp->irq;
+
+ copy_to_user(sp, &sio, sizeof(struct serial_struct));
+}
+
+/*****************************************************************************/
+
+/*
+ * Set port according to the serial struct info.
+ * At this point we do not do any auto-configure stuff, so we will
+ * just quietly ignore any requests to change irq, etc.
+ */
+
+static int stl_setserial(stlport_t *portp, struct serial_struct *sp)
+{
+ struct serial_struct sio;
+
+#if DEBUG
+ printk("stl_setserial(portp=%x,sp=%x)\n", (int) portp, (int) sp);
+#endif
+
+ copy_from_user(&sio, sp, sizeof(struct serial_struct));
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((sio.baud_base != portp->baud_base) ||
+ (sio.close_delay != portp->close_delay) ||
+ ((sio.flags & ~ASYNC_USR_MASK) !=
+ (portp->flags & ~ASYNC_USR_MASK)))
+ return(-EPERM);
+ }
+
+ portp->flags = (portp->flags & ~ASYNC_USR_MASK) |
+ (sio.flags & ASYNC_USR_MASK);
+ portp->baud_base = sio.baud_base;
+ portp->close_delay = sio.close_delay;
+ portp->closing_wait = sio.closing_wait;
+ portp->custom_divisor = sio.custom_divisor;
+ stl_setport(portp, portp->tty->termios);
+ return(0);
+}
+
+/*****************************************************************************/
+
+static int stl_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ stlport_t *portp;
+ unsigned int ival;
+ int rc;
+
+#if DEBUG
+ printk("stl_ioctl(tty=%x,file=%x,cmd=%x,arg=%x)\n",
+ (int) tty, (int) file, cmd, (int) arg);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return(-ENODEV);
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return(-ENODEV);
+
+ if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+ (cmd != COM_GETPORTSTATS) && (cmd != COM_CLRPORTSTATS)) {
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return(-EIO);
+ }
+
+ rc = 0;
+
+ switch (cmd) {
+ case TIOCGSOFTCAR:
+ rc = put_user(((tty->termios->c_cflag & CLOCAL) ? 1 : 0),
+ (unsigned int *) arg);
+ break;
+ case TIOCSSOFTCAR:
+ if ((rc = verify_area(VERIFY_READ, (void *) arg,
+ sizeof(int))) == 0) {
+ get_user(ival, (unsigned int *) arg);
+ tty->termios->c_cflag =
+ (tty->termios->c_cflag & ~CLOCAL) |
+ (ival ? CLOCAL : 0);
+ }
+ break;
+ case TIOCMGET:
+ if ((rc = verify_area(VERIFY_WRITE, (void *) arg,
+ sizeof(unsigned int))) == 0) {
+ ival = stl_getsignals(portp);
+ put_user(ival, (unsigned int *) arg);
+ }
+ break;
+ case TIOCMBIS:
+ if ((rc = verify_area(VERIFY_READ, (void *) arg,
+ sizeof(unsigned int))) == 0) {
+ get_user(ival, (unsigned int *) arg);
+ stl_setsignals(portp, ((ival & TIOCM_DTR) ? 1 : -1),
+ ((ival & TIOCM_RTS) ? 1 : -1));
+ }
+ break;
+ case TIOCMBIC:
+ if ((rc = verify_area(VERIFY_READ, (void *) arg,
+ sizeof(unsigned int))) == 0) {
+ get_user(ival, (unsigned int *) arg);
+ stl_setsignals(portp, ((ival & TIOCM_DTR) ? 0 : -1),
+ ((ival & TIOCM_RTS) ? 0 : -1));
+ }
+ break;
+ case TIOCMSET:
+ if ((rc = verify_area(VERIFY_READ, (void *) arg,
+ sizeof(unsigned int))) == 0) {
+ get_user(ival, (unsigned int *) arg);
+ stl_setsignals(portp, ((ival & TIOCM_DTR) ? 1 : 0),
+ ((ival & TIOCM_RTS) ? 1 : 0));
+ }
+ break;
+ case TIOCGSERIAL:
+ if ((rc = verify_area(VERIFY_WRITE, (void *) arg,
+ sizeof(struct serial_struct))) == 0)
+ stl_getserial(portp, (struct serial_struct *) arg);
+ break;
+ case TIOCSSERIAL:
+ if ((rc = verify_area(VERIFY_READ, (void *) arg,
+ sizeof(struct serial_struct))) == 0)
+ rc = stl_setserial(portp, (struct serial_struct *) arg);
+ break;
+ case COM_GETPORTSTATS:
+ if ((rc = verify_area(VERIFY_WRITE, (void *) arg,
+ sizeof(comstats_t))) == 0)
+ rc = stl_getportstats(portp, (comstats_t *) arg);
+ break;
+ case COM_CLRPORTSTATS:
+ if ((rc = verify_area(VERIFY_WRITE, (void *) arg,
+ sizeof(comstats_t))) == 0)
+ rc = stl_clrportstats(portp, (comstats_t *) arg);
+ break;
+ case TIOCSERCONFIG:
+ case TIOCSERGWILD:
+ case TIOCSERSWILD:
+ case TIOCSERGETLSR:
+ case TIOCSERGSTRUCT:
+ case TIOCSERGETMULTI:
+ case TIOCSERSETMULTI:
+ default:
+ rc = -ENOIOCTLCMD;
+ break;
+ }
+
+ return(rc);
+}
+
+/*****************************************************************************/
+
+static void stl_settermios(struct tty_struct *tty, struct termios *old)
+{
+ stlport_t *portp;
+ struct termios *tiosp;
+
+#if DEBUG
+ printk("stl_settermios(tty=%x,old=%x)\n", (int) tty, (int) old);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ tiosp = tty->termios;
+ if ((tiosp->c_cflag == old->c_cflag) &&
+ (tiosp->c_iflag == old->c_iflag))
+ return;
+
+ stl_setport(portp, tiosp);
+ stl_setsignals(portp, ((tiosp->c_cflag & (CBAUD & ~CBAUDEX)) ? 1 : 0),
+ -1);
+ if ((old->c_cflag & CRTSCTS) && ((tiosp->c_cflag & CRTSCTS) == 0)) {
+ tty->hw_stopped = 0;
+ stl_start(tty);
+ }
+ if (((old->c_cflag & CLOCAL) == 0) && (tiosp->c_cflag & CLOCAL))
+ wake_up_interruptible(&portp->open_wait);
+}
+
+/*****************************************************************************/
+
+/*
+ * Attempt to flow control who ever is sending us data. Based on termios
+ * settings use software or/and hardware flow control.
+ */
+
+static void stl_throttle(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#if DEBUG
+ printk("stl_throttle(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ stl_flowctrl(portp, 0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Unflow control the device sending us data...
+ */
+
+static void stl_unthrottle(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#if DEBUG
+ printk("stl_unthrottle(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ stl_flowctrl(portp, 1);
+}
+
+/*****************************************************************************/
+
+/*
+ * Stop the transmitter. Basically to do this we will just turn TX
+ * interrupts off.
+ */
+
+static void stl_stop(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#if DEBUG
+ printk("stl_stop(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ stl_startrxtx(portp, -1, 0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Start the transmitter again. Just turn TX interrupts back on.
+ */
+
+static void stl_start(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#if DEBUG
+ printk("stl_start(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ stl_startrxtx(portp, -1, 1);
+}
+
+/*****************************************************************************/
+
+/*
+ * Hangup this port. This is pretty much like closing the port, only
+ * a little more brutal. No waiting for data to drain. Shutdown the
+ * port and maybe drop signals.
+ */
+
+static void stl_hangup(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#if DEBUG
+ printk("stl_hangup(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ portp->flags &= ~ASYNC_INITIALIZED;
+ stl_disableintrs(portp);
+ if (tty->termios->c_cflag & HUPCL)
+ stl_setsignals(portp, 0, 0);
+ stl_enablerxtx(portp, 0, 0);
+ stl_flushbuffer(tty);
+ portp->istate = 0;
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ if (portp->tx.buf != (char *) NULL) {
+ kfree(portp->tx.buf);
+ portp->tx.buf = (char *) NULL;
+ portp->tx.head = (char *) NULL;
+ portp->tx.tail = (char *) NULL;
+ }
+ portp->tty = (struct tty_struct *) NULL;
+ portp->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CALLOUT_ACTIVE);
+ portp->refcount = 0;
+ wake_up_interruptible(&portp->open_wait);
+}
+
+/*****************************************************************************/
+
+static void stl_flushbuffer(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#if DEBUG
+ printk("stl_flushbuffer(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ stl_flush(portp);
+ wake_up_interruptible(&tty->write_wait);
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ tty->ldisc.write_wakeup)
+ (tty->ldisc.write_wakeup)(tty);
+}
+
+/*****************************************************************************/
+
+static void stl_breakctl(struct tty_struct *tty, int state)
+{
+ stlport_t *portp;
+
+#if DEBUG
+ printk("stl_breakctl(tty=%x,state=%d)\n", (int) tty, state);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ stl_sendbreak(portp, ((state == -1) ? 1 : 2));
+}
+
+/*****************************************************************************/
+
+static void stl_waituntilsent(struct tty_struct *tty, int timeout)
+{
+ stlport_t *portp;
+ unsigned long tend;
+
+#if DEBUG
+ printk("stl_waituntilsent(tty=%x,timeout=%d)\n", (int) tty, timeout);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ if (timeout == 0)
+ timeout = HZ;
+ tend = jiffies + timeout;
+
+ while (stl_datastate(portp)) {
+ if (signal_pending(current))
+ break;
+ stl_delay(2);
+ if (time_after_eq(jiffies, tend))
+ break;
+ }
+}
+
+/*****************************************************************************/
+
+static void stl_sendxchar(struct tty_struct *tty, char ch)
+{
+ stlport_t *portp;
+
+#if DEBUG
+ printk("stl_sendxchar(tty=%x,ch=%x)\n", (int) tty, ch);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ if (ch == STOP_CHAR(tty))
+ stl_sendflow(portp, 0);
+ else if (ch == START_CHAR(tty))
+ stl_sendflow(portp, 1);
+ else
+ stl_putchar(tty, ch);
+}
+
+/*****************************************************************************/
+
+#define MAXLINE 80
+
+/*
+ * Format info for a specified port. The line is deliberately limited
+ * to 80 characters. (If it is too long it will be truncated, if too
+ * short then padded with spaces).
+ */
+
+static int stl_portinfo(stlport_t *portp, int portnr, char *pos)
+{
+ char *sp;
+ int sigs, cnt;
+
+ sp = pos;
+ sp += sprintf(sp, "%d: uart:%s tx:%d rx:%d",
+ portnr, (portp->hwid == 1) ? "SC26198" : "CD1400",
+ (int) portp->stats.txtotal, (int) portp->stats.rxtotal);
+
+ if (portp->stats.rxframing)
+ sp += sprintf(sp, " fe:%d", (int) portp->stats.rxframing);
+ if (portp->stats.rxparity)
+ sp += sprintf(sp, " pe:%d", (int) portp->stats.rxparity);
+ if (portp->stats.rxbreaks)
+ sp += sprintf(sp, " brk:%d", (int) portp->stats.rxbreaks);
+ if (portp->stats.rxoverrun)
+ sp += sprintf(sp, " oe:%d", (int) portp->stats.rxoverrun);
+
+ sigs = stl_getsignals(portp);
+ cnt = sprintf(sp, "%s%s%s%s%s ",
+ (sigs & TIOCM_RTS) ? "|RTS" : "",
+ (sigs & TIOCM_CTS) ? "|CTS" : "",
+ (sigs & TIOCM_DTR) ? "|DTR" : "",
+ (sigs & TIOCM_CD) ? "|DCD" : "",
+ (sigs & TIOCM_DSR) ? "|DSR" : "");
+ *sp = ' ';
+ sp += cnt;
+
+ for (cnt = (sp - pos); (cnt < (MAXLINE - 1)); cnt++)
+ *sp++ = ' ';
+ if (cnt >= MAXLINE)
+ pos[(MAXLINE - 2)] = '+';
+ pos[(MAXLINE - 1)] = '\n';
+
+ return(MAXLINE);
+}
+
+/*****************************************************************************/
+
+/*
+ * Port info, read from the /proc file system.
+ */
+
+static int stl_readproc(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ stlbrd_t *brdp;
+ stlpanel_t *panelp;
+ stlport_t *portp;
+ int brdnr, panelnr, portnr, totalport;
+ int curoff, maxoff;
+ char *pos;
+
+#if DEBUG
+ printk("stl_readproc(page=%x,start=%x,off=%x,count=%d,eof=%x,"
+ "data=%x\n", (int) page, (int) start, (int) off, count,
+ (int) eof, (int) data);
+#endif
+
+ pos = page;
+ totalport = 0;
+ curoff = 0;
+
+ if (off == 0) {
+ pos += sprintf(pos, "%s: version %s", stl_drvtitle,
+ stl_drvversion);
+ while (pos < (page + MAXLINE - 1))
+ *pos++ = ' ';
+ *pos++ = '\n';
+ }
+ curoff = MAXLINE;
+
+/*
+ * We scan through for each board, panel and port. The offset is
+ * calculated on the fly, and irrelevant ports are skipped.
+ */
+ for (brdnr = 0; (brdnr < stl_nrbrds); brdnr++) {
+ brdp = stl_brds[brdnr];
+ if (brdp == (stlbrd_t *) NULL)
+ continue;
+ if (brdp->state == 0)
+ continue;
+
+ maxoff = curoff + (brdp->nrports * MAXLINE);
+ if (off >= maxoff) {
+ curoff = maxoff;
+ continue;
+ }
+
+ totalport = brdnr * STL_MAXPORTS;
+ for (panelnr = 0; (panelnr < brdp->nrpanels); panelnr++) {
+ panelp = brdp->panels[panelnr];
+ if (panelp == (stlpanel_t *) NULL)
+ continue;
+
+ maxoff = curoff + (panelp->nrports * MAXLINE);
+ if (off >= maxoff) {
+ curoff = maxoff;
+ totalport += panelp->nrports;
+ continue;
+ }
+
+ for (portnr = 0; (portnr < panelp->nrports); portnr++,
+ totalport++) {
+ portp = panelp->ports[portnr];
+ if (portp == (stlport_t *) NULL)
+ continue;
+ if (off >= (curoff += MAXLINE))
+ continue;
+ if ((pos - page + MAXLINE) > count)
+ goto stl_readdone;
+ pos += stl_portinfo(portp, totalport, pos);
+ }
+ }
+ }
+
+ *eof = 1;
+
+stl_readdone:
+ *start = page;
+ return(pos - page);
+}
+
+/*****************************************************************************/
+
+/*
+ * All board interrupts are vectored through here first. This code then
+ * calls off to the approrpriate board interrupt handlers.
+ */
+
+static void stl_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+ stlbrd_t *brdp;
+ int i;
+
+#if DEBUG
+ printk("stl_intr(irq=%d,regs=%x)\n", irq, (int) regs);
+#endif
+
+ for (i = 0; (i < stl_nrbrds); i++) {
+ if ((brdp = stl_brds[i]) == (stlbrd_t *) NULL)
+ continue;
+ if (brdp->state == 0)
+ continue;
+ (* brdp->isr)(brdp);
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for EasyIO board types.
+ */
+
+static void stl_eiointr(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int iobase;
+
+ panelp = brdp->panels[0];
+ iobase = panelp->iobase;
+ while (inb(brdp->iostatus) & EIO_INTRPEND)
+ (* panelp->isr)(panelp, iobase);
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for ECH-AT board types.
+ */
+
+static void stl_echatintr(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int ioaddr;
+ int bnknr;
+
+ outb((brdp->ioctrlval | ECH_BRDENABLE), brdp->ioctrl);
+
+ while (inb(brdp->iostatus) & ECH_INTRPEND) {
+ for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+ ioaddr = brdp->bnkstataddr[bnknr];
+ if (inb(ioaddr) & ECH_PNLINTRPEND) {
+ panelp = brdp->bnk2panel[bnknr];
+ (* panelp->isr)(panelp, (ioaddr & 0xfffc));
+ }
+ }
+ }
+
+ outb((brdp->ioctrlval | ECH_BRDDISABLE), brdp->ioctrl);
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for ECH-MCA board types.
+ */
+
+static void stl_echmcaintr(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int ioaddr;
+ int bnknr;
+
+ while (inb(brdp->iostatus) & ECH_INTRPEND) {
+ for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+ ioaddr = brdp->bnkstataddr[bnknr];
+ if (inb(ioaddr) & ECH_PNLINTRPEND) {
+ panelp = brdp->bnk2panel[bnknr];
+ (* panelp->isr)(panelp, (ioaddr & 0xfffc));
+ }
+ }
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for ECH-PCI board types.
+ */
+
+static void stl_echpciintr(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int ioaddr;
+ int bnknr, recheck;
+
+ while (1) {
+ recheck = 0;
+ for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+ outb(brdp->bnkpageaddr[bnknr], brdp->ioctrl);
+ ioaddr = brdp->bnkstataddr[bnknr];
+ if (inb(ioaddr) & ECH_PNLINTRPEND) {
+ panelp = brdp->bnk2panel[bnknr];
+ (* panelp->isr)(panelp, (ioaddr & 0xfffc));
+ recheck++;
+ }
+ }
+ if (! recheck)
+ break;
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for ECH-8/64-PCI board types.
+ */
+
+static void stl_echpci64intr(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int ioaddr;
+ int bnknr;
+
+ while (inb(brdp->ioctrl) & 0x1) {
+ for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+ ioaddr = brdp->bnkstataddr[bnknr];
+ if (inb(ioaddr) & ECH_PNLINTRPEND) {
+ panelp = brdp->bnk2panel[bnknr];
+ (* panelp->isr)(panelp, (ioaddr & 0xfffc));
+ }
+ }
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Service an off-level request for some channel.
+ */
+static void stl_offintr(void *private)
+{
+ stlport_t *portp;
+ struct tty_struct *tty;
+ unsigned int oldsigs;
+
+ portp = private;
+
+#if DEBUG
+ printk("stl_offintr(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return;
+
+ lock_kernel();
+ if (test_bit(ASYI_TXLOW, &portp->istate)) {
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ tty->ldisc.write_wakeup)
+ (tty->ldisc.write_wakeup)(tty);
+ wake_up_interruptible(&tty->write_wait);
+ }
+ if (test_bit(ASYI_DCDCHANGE, &portp->istate)) {
+ clear_bit(ASYI_DCDCHANGE, &portp->istate);
+ oldsigs = portp->sigs;
+ portp->sigs = stl_getsignals(portp);
+ if ((portp->sigs & TIOCM_CD) && ((oldsigs & TIOCM_CD) == 0))
+ wake_up_interruptible(&portp->open_wait);
+ if ((oldsigs & TIOCM_CD) && ((portp->sigs & TIOCM_CD) == 0)) {
+ if (portp->flags & ASYNC_CHECK_CD) {
+ if (! ((portp->flags & ASYNC_CALLOUT_ACTIVE) &&
+ (portp->flags & ASYNC_CALLOUT_NOHUP))) {
+ tty_hangup(tty);
+ }
+ }
+ }
+ }
+ unlock_kernel();
+}
+
+/*****************************************************************************/
+
+/*
+ * Map in interrupt vector to this driver. Check that we don't
+ * already have this vector mapped, we might be sharing this
+ * interrupt across multiple boards.
+ */
+
+static int __init stl_mapirq(int irq, char *name)
+{
+ int rc, i;
+
+#if DEBUG
+ printk("stl_mapirq(irq=%d,name=%s)\n", irq, name);
+#endif
+
+ rc = 0;
+ for (i = 0; (i < stl_numintrs); i++) {
+ if (stl_gotintrs[i] == irq)
+ break;
+ }
+ if (i >= stl_numintrs) {
+ if (request_irq(irq, stl_intr, SA_SHIRQ, name, NULL) != 0) {
+ printk("STALLION: failed to register interrupt "
+ "routine for %s irq=%d\n", name, irq);
+ rc = -ENODEV;
+ } else {
+ stl_gotintrs[stl_numintrs++] = irq;
+ }
+ }
+ return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ * Initialize all the ports on a panel.
+ */
+
+static int __init stl_initports(stlbrd_t *brdp, stlpanel_t *panelp)
+{
+ stlport_t *portp;
+ int chipmask, i;
+
+#if DEBUG
+ printk("stl_initports(brdp=%x,panelp=%x)\n", (int) brdp, (int) panelp);
+#endif
+
+ chipmask = stl_panelinit(brdp, panelp);
+
+/*
+ * All UART's are initialized (if found!). Now go through and setup
+ * each ports data structures.
+ */
+ for (i = 0; (i < panelp->nrports); i++) {
+ portp = (stlport_t *) stl_memalloc(sizeof(stlport_t));
+ if (portp == (stlport_t *) NULL) {
+ printk("STALLION: failed to allocate memory "
+ "(size=%d)\n", sizeof(stlport_t));
+ break;
+ }
+ memset(portp, 0, sizeof(stlport_t));
+
+ portp->magic = STL_PORTMAGIC;
+ portp->portnr = i;
+ portp->brdnr = panelp->brdnr;
+ portp->panelnr = panelp->panelnr;
+ portp->uartp = panelp->uartp;
+ portp->clk = brdp->clk;
+ portp->baud_base = STL_BAUDBASE;
+ portp->close_delay = STL_CLOSEDELAY;
+ portp->closing_wait = 30 * HZ;
+ portp->normaltermios = stl_deftermios;
+ portp->callouttermios = stl_deftermios;
+ portp->tqueue.routine = stl_offintr;
+ portp->tqueue.data = portp;
+ init_waitqueue_head(&portp->open_wait);
+ init_waitqueue_head(&portp->close_wait);
+ portp->stats.brd = portp->brdnr;
+ portp->stats.panel = portp->panelnr;
+ portp->stats.port = portp->portnr;
+ panelp->ports[i] = portp;
+ stl_portinit(brdp, panelp, portp);
+ }
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Try to find and initialize an EasyIO board.
+ */
+
+static inline int stl_initeio(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int status;
+ char *name;
+ int rc;
+
+#if DEBUG
+ printk("stl_initeio(brdp=%x)\n", (int) brdp);
+#endif
+
+ brdp->ioctrl = brdp->ioaddr1 + 1;
+ brdp->iostatus = brdp->ioaddr1 + 2;
+
+ status = inb(brdp->iostatus);
+ if ((status & EIO_IDBITMASK) == EIO_MK3)
+ brdp->ioctrl++;
+
+/*
+ * Handle board specific stuff now. The real difference is PCI
+ * or not PCI.
+ */
+ if (brdp->brdtype == BRD_EASYIOPCI) {
+ brdp->iosize1 = 0x80;
+ brdp->iosize2 = 0x80;
+ name = "serial(EIO-PCI)";
+ outb(0x41, (brdp->ioaddr2 + 0x4c));
+ } else {
+ brdp->iosize1 = 8;
+ name = "serial(EIO)";
+ if ((brdp->irq < 0) || (brdp->irq > 15) ||
+ (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+ printk("STALLION: invalid irq=%d for brd=%d\n",
+ brdp->irq, brdp->brdnr);
+ return(-EINVAL);
+ }
+ outb((stl_vecmap[brdp->irq] | EIO_0WS |
+ ((brdp->irqtype) ? EIO_INTLEVEL : EIO_INTEDGE)),
+ brdp->ioctrl);
+ }
+
+ if (check_region(brdp->ioaddr1, brdp->iosize1)) {
+ printk("STALLION: Warning, board %d I/O address %x conflicts "
+ "with another device\n", brdp->brdnr, brdp->ioaddr1);
+ }
+ if (brdp->iosize2 > 0) {
+ if (check_region(brdp->ioaddr2, brdp->iosize2)) {
+ printk("STALLION: Warning, board %d I/O address %x "
+ "conflicts with another device\n",
+ brdp->brdnr, brdp->ioaddr2);
+ }
+ }
+
+/*
+ * Everything looks OK, so let's go ahead and probe for the hardware.
+ */
+ brdp->clk = CD1400_CLK;
+ brdp->isr = stl_eiointr;
+
+ switch (status & EIO_IDBITMASK) {
+ case EIO_8PORTM:
+ brdp->clk = CD1400_CLK8M;
+ /* fall thru */
+ case EIO_8PORTRS:
+ case EIO_8PORTDI:
+ brdp->nrports = 8;
+ break;
+ case EIO_4PORTRS:
+ brdp->nrports = 4;
+ break;
+ case EIO_MK3:
+ switch (status & EIO_BRDMASK) {
+ case ID_BRD4:
+ brdp->nrports = 4;
+ break;
+ case ID_BRD8:
+ brdp->nrports = 8;
+ break;
+ case ID_BRD16:
+ brdp->nrports = 16;
+ break;
+ default:
+ return(-ENODEV);
+ }
+ break;
+ default:
+ return(-ENODEV);
+ }
+
+/*
+ * We have verfied that the board is actually present, so now we
+ * can complete the setup.
+ */
+ request_region(brdp->ioaddr1, brdp->iosize1, name);
+ if (brdp->iosize2 > 0)
+ request_region(brdp->ioaddr2, brdp->iosize2, name);
+
+ panelp = (stlpanel_t *) stl_memalloc(sizeof(stlpanel_t));
+ if (panelp == (stlpanel_t *) NULL) {
+ printk("STALLION: failed to allocate memory (size=%d)\n",
+ sizeof(stlpanel_t));
+ return(-ENOMEM);
+ }
+ memset(panelp, 0, sizeof(stlpanel_t));
+
+ panelp->magic = STL_PANELMAGIC;
+ panelp->brdnr = brdp->brdnr;
+ panelp->panelnr = 0;
+ panelp->nrports = brdp->nrports;
+ panelp->iobase = brdp->ioaddr1;
+ panelp->hwid = status;
+ if ((status & EIO_IDBITMASK) == EIO_MK3) {
+ panelp->uartp = (void *) &stl_sc26198uart;
+ panelp->isr = stl_sc26198intr;
+ } else {
+ panelp->uartp = (void *) &stl_cd1400uart;
+ panelp->isr = stl_cd1400eiointr;
+ }
+
+ brdp->panels[0] = panelp;
+ brdp->nrpanels = 1;
+ brdp->state |= BRD_FOUND;
+ brdp->hwid = status;
+ rc = stl_mapirq(brdp->irq, name);
+ return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ * Try to find an ECH board and initialize it. This code is capable of
+ * dealing with all types of ECH board.
+ */
+
+static int inline stl_initech(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int status, nxtid, ioaddr, conflict;
+ int panelnr, banknr, i;
+ char *name;
+
+#if DEBUG
+ printk("stl_initech(brdp=%x)\n", (int) brdp);
+#endif
+
+ status = 0;
+ conflict = 0;
+
+/*
+ * Set up the initial board register contents for boards. This varies a
+ * bit between the different board types. So we need to handle each
+ * separately. Also do a check that the supplied IRQ is good.
+ */
+ switch (brdp->brdtype) {
+
+ case BRD_ECH:
+ brdp->isr = stl_echatintr;
+ brdp->ioctrl = brdp->ioaddr1 + 1;
+ brdp->iostatus = brdp->ioaddr1 + 1;
+ status = inb(brdp->iostatus);
+ if ((status & ECH_IDBITMASK) != ECH_ID)
+ return(-ENODEV);
+ if ((brdp->irq < 0) || (brdp->irq > 15) ||
+ (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+ printk("STALLION: invalid irq=%d for brd=%d\n",
+ brdp->irq, brdp->brdnr);
+ return(-EINVAL);
+ }
+ status = ((brdp->ioaddr2 & ECH_ADDR2MASK) >> 1);
+ status |= (stl_vecmap[brdp->irq] << 1);
+ outb((status | ECH_BRDRESET), brdp->ioaddr1);
+ brdp->ioctrlval = ECH_INTENABLE |
+ ((brdp->irqtype) ? ECH_INTLEVEL : ECH_INTEDGE);
+ for (i = 0; (i < 10); i++)
+ outb((brdp->ioctrlval | ECH_BRDENABLE), brdp->ioctrl);
+ brdp->iosize1 = 2;
+ brdp->iosize2 = 32;
+ name = "serial(EC8/32)";
+ outb(status, brdp->ioaddr1);
+ break;
+
+ case BRD_ECHMC:
+ brdp->isr = stl_echmcaintr;
+ brdp->ioctrl = brdp->ioaddr1 + 0x20;
+ brdp->iostatus = brdp->ioctrl;
+ status = inb(brdp->iostatus);
+ if ((status & ECH_IDBITMASK) != ECH_ID)
+ return(-ENODEV);
+ if ((brdp->irq < 0) || (brdp->irq > 15) ||
+ (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+ printk("STALLION: invalid irq=%d for brd=%d\n",
+ brdp->irq, brdp->brdnr);
+ return(-EINVAL);
+ }
+ outb(ECHMC_BRDRESET, brdp->ioctrl);
+ outb(ECHMC_INTENABLE, brdp->ioctrl);
+ brdp->iosize1 = 64;
+ name = "serial(EC8/32-MC)";
+ break;
+
+ case BRD_ECHPCI:
+ brdp->isr = stl_echpciintr;
+ brdp->ioctrl = brdp->ioaddr1 + 2;
+ brdp->iosize1 = 4;
+ brdp->iosize2 = 8;
+ name = "serial(EC8/32-PCI)";
+ break;
+
+ case BRD_ECH64PCI:
+ brdp->isr = stl_echpci64intr;
+ brdp->ioctrl = brdp->ioaddr2 + 0x40;
+ outb(0x43, (brdp->ioaddr1 + 0x4c));
+ brdp->iosize1 = 0x80;
+ brdp->iosize2 = 0x80;
+ name = "serial(EC8/64-PCI)";
+ break;
+
+ default:
+ printk("STALLION: unknown board type=%d\n", brdp->brdtype);
+ return(-EINVAL);
+ break;
+ }
+
+/*
+ * Check boards for possible IO address conflicts. We won't actually
+ * do anything about it here, just issue a warning...
+ */
+ conflict = check_region(brdp->ioaddr1, brdp->iosize1) ?
+ brdp->ioaddr1 : 0;
+ if ((conflict == 0) && (brdp->iosize2 > 0))
+ conflict = check_region(brdp->ioaddr2, brdp->iosize2) ?
+ brdp->ioaddr2 : 0;
+ if (conflict) {
+ printk("STALLION: Warning, board %d I/O address %x conflicts "
+ "with another device\n", brdp->brdnr, conflict);
+ }
+
+ request_region(brdp->ioaddr1, brdp->iosize1, name);
+ if (brdp->iosize2 > 0)
+ request_region(brdp->ioaddr2, brdp->iosize2, name);
+
+/*
+ * Scan through the secondary io address space looking for panels.
+ * As we find'em allocate and initialize panel structures for each.
+ */
+ brdp->clk = CD1400_CLK;
+ brdp->hwid = status;
+
+ ioaddr = brdp->ioaddr2;
+ banknr = 0;
+ panelnr = 0;
+ nxtid = 0;
+
+ for (i = 0; (i < STL_MAXPANELS); i++) {
+ if (brdp->brdtype == BRD_ECHPCI) {
+ outb(nxtid, brdp->ioctrl);
+ ioaddr = brdp->ioaddr2;
+ }
+ status = inb(ioaddr + ECH_PNLSTATUS);
+ if ((status & ECH_PNLIDMASK) != nxtid)
+ break;
+ panelp = (stlpanel_t *) stl_memalloc(sizeof(stlpanel_t));
+ if (panelp == (stlpanel_t *) NULL) {
+ printk("STALLION: failed to allocate memory "
+ "(size=%d)\n", sizeof(stlpanel_t));
+ break;
+ }
+ memset(panelp, 0, sizeof(stlpanel_t));
+ panelp->magic = STL_PANELMAGIC;
+ panelp->brdnr = brdp->brdnr;
+ panelp->panelnr = panelnr;
+ panelp->iobase = ioaddr;
+ panelp->pagenr = nxtid;
+ panelp->hwid = status;
+ brdp->bnk2panel[banknr] = panelp;
+ brdp->bnkpageaddr[banknr] = nxtid;
+ brdp->bnkstataddr[banknr++] = ioaddr + ECH_PNLSTATUS;
+
+ if (status & ECH_PNLXPID) {
+ panelp->uartp = (void *) &stl_sc26198uart;
+ panelp->isr = stl_sc26198intr;
+ if (status & ECH_PNL16PORT) {
+ panelp->nrports = 16;
+ brdp->bnk2panel[banknr] = panelp;
+ brdp->bnkpageaddr[banknr] = nxtid;
+ brdp->bnkstataddr[banknr++] = ioaddr + 4 +
+ ECH_PNLSTATUS;
+ } else {
+ panelp->nrports = 8;
+ }
+ } else {
+ panelp->uartp = (void *) &stl_cd1400uart;
+ panelp->isr = stl_cd1400echintr;
+ if (status & ECH_PNL16PORT) {
+ panelp->nrports = 16;
+ panelp->ackmask = 0x80;
+ if (brdp->brdtype != BRD_ECHPCI)
+ ioaddr += EREG_BANKSIZE;
+ brdp->bnk2panel[banknr] = panelp;
+ brdp->bnkpageaddr[banknr] = ++nxtid;
+ brdp->bnkstataddr[banknr++] = ioaddr +
+ ECH_PNLSTATUS;
+ } else {
+ panelp->nrports = 8;
+ panelp->ackmask = 0xc0;
+ }
+ }
+
+ nxtid++;
+ ioaddr += EREG_BANKSIZE;
+ brdp->nrports += panelp->nrports;
+ brdp->panels[panelnr++] = panelp;
+ if ((brdp->brdtype != BRD_ECHPCI) &&
+ (ioaddr >= (brdp->ioaddr2 + brdp->iosize2)))
+ break;
+ }
+
+ brdp->nrpanels = panelnr;
+ brdp->nrbnks = banknr;
+ if (brdp->brdtype == BRD_ECH)
+ outb((brdp->ioctrlval | ECH_BRDDISABLE), brdp->ioctrl);
+
+ brdp->state |= BRD_FOUND;
+ i = stl_mapirq(brdp->irq, name);
+ return(i);
+}
+
+/*****************************************************************************/
+
+/*
+ * Initialize and configure the specified board.
+ * Scan through all the boards in the configuration and see what we
+ * can find. Handle EIO and the ECH boards a little differently here
+ * since the initial search and setup is very different.
+ */
+
+static int __init stl_brdinit(stlbrd_t *brdp)
+{
+ int i;
+
+#if DEBUG
+ printk("stl_brdinit(brdp=%x)\n", (int) brdp);
+#endif
+
+ switch (brdp->brdtype) {
+ case BRD_EASYIO:
+ case BRD_EASYIOPCI:
+ stl_initeio(brdp);
+ break;
+ case BRD_ECH:
+ case BRD_ECHMC:
+ case BRD_ECHPCI:
+ case BRD_ECH64PCI:
+ stl_initech(brdp);
+ break;
+ default:
+ printk("STALLION: board=%d is unknown board type=%d\n",
+ brdp->brdnr, brdp->brdtype);
+ return(ENODEV);
+ }
+
+ stl_brds[brdp->brdnr] = brdp;
+ if ((brdp->state & BRD_FOUND) == 0) {
+ printk("STALLION: %s board not found, board=%d io=%x irq=%d\n",
+ stl_brdnames[brdp->brdtype], brdp->brdnr,
+ brdp->ioaddr1, brdp->irq);
+ return(ENODEV);
+ }
+
+ for (i = 0; (i < STL_MAXPANELS); i++)
+ if (brdp->panels[i] != (stlpanel_t *) NULL)
+ stl_initports(brdp, brdp->panels[i]);
+
+ printk("STALLION: %s found, board=%d io=%x irq=%d "
+ "nrpanels=%d nrports=%d\n", stl_brdnames[brdp->brdtype],
+ brdp->brdnr, brdp->ioaddr1, brdp->irq, brdp->nrpanels,
+ brdp->nrports);
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Find the next available board number that is free.
+ */
+
+static inline int stl_getbrdnr()
+{
+ int i;
+
+ for (i = 0; (i < STL_MAXBRDS); i++) {
+ if (stl_brds[i] == (stlbrd_t *) NULL) {
+ if (i >= stl_nrbrds)
+ stl_nrbrds = i + 1;
+ return(i);
+ }
+ }
+ return(-1);
+}
+
+/*****************************************************************************/
+
+#ifdef CONFIG_PCI
+
+/*
+ * We have a Stallion board. Allocate a board structure and
+ * initialize it. Read its IO and IRQ resources from PCI
+ * configuration space.
+ */
+
+static inline int stl_initpcibrd(int brdtype, struct pci_dev *devp)
+{
+ stlbrd_t *brdp;
+
+#if DEBUG
+ printk("stl_initpcibrd(brdtype=%d,busnr=%x,devnr=%x)\n", brdtype,
+ devp->bus->number, devp->devfn);
+#endif
+
+ if (pci_enable_device(devp))
+ return(-EIO);
+ if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL)
+ return(-ENOMEM);
+ if ((brdp->brdnr = stl_getbrdnr()) < 0) {
+ printk("STALLION: too many boards found, "
+ "maximum supported %d\n", STL_MAXBRDS);
+ return(0);
+ }
+ brdp->brdtype = brdtype;
+
+/*
+ * Different Stallion boards use the BAR registers in different ways,
+ * so set up io addresses based on board type.
+ */
+#if DEBUG
+ printk("%s(%d): BAR[]=%x,%x,%x,%x IRQ=%x\n", __FILE__, __LINE__,
+ pci_resource_start(devp, 0), pci_resource_start(devp, 1),
+ pci_resource_start(devp, 2), pci_resource_start(devp, 3), devp->irq);
+#endif
+
+/*
+ * We have all resources from the board, so let's setup the actual
+ * board structure now.
+ */
+ switch (brdtype) {
+ case BRD_ECHPCI:
+ brdp->ioaddr2 = pci_resource_start(devp, 0);
+ brdp->ioaddr1 = pci_resource_start(devp, 1);
+ break;
+ case BRD_ECH64PCI:
+ brdp->ioaddr2 = pci_resource_start(devp, 2);
+ brdp->ioaddr1 = pci_resource_start(devp, 1);
+ break;
+ case BRD_EASYIOPCI:
+ brdp->ioaddr1 = pci_resource_start(devp, 2);
+ brdp->ioaddr2 = pci_resource_start(devp, 1);
+ break;
+ default:
+ printk("STALLION: unknown PCI board type=%d\n", brdtype);
+ break;
+ }
+
+ brdp->irq = devp->irq;
+ stl_brdinit(brdp);
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Find all Stallion PCI boards that might be installed. Initialize each
+ * one as it is found.
+ */
+
+
+static inline int stl_findpcibrds()
+{
+ struct pci_dev *dev = NULL;
+ int i, rc;
+
+#if DEBUG
+ printk("stl_findpcibrds()\n");
+#endif
+
+ if (! pci_present())
+ return(0);
+
+ for (i = 0; (i < stl_nrpcibrds); i++)
+ while ((dev = pci_find_device(stl_pcibrds[i].vendid,
+ stl_pcibrds[i].devid, dev))) {
+
+/*
+ * Found a device on the PCI bus that has our vendor and
+ * device ID. Need to check now that it is really us.
+ */
+ if ((dev->class >> 8) == PCI_CLASS_STORAGE_IDE)
+ continue;
+
+ rc = stl_initpcibrd(stl_pcibrds[i].brdtype, dev);
+ if (rc)
+ return(rc);
+ }
+
+ return(0);
+}
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Scan through all the boards in the configuration and see what we
+ * can find. Handle EIO and the ECH boards a little differently here
+ * since the initial search and setup is too different.
+ */
+
+static inline int stl_initbrds()
+{
+ stlbrd_t *brdp;
+ stlconf_t *confp;
+ int i;
+
+#if DEBUG
+ printk("stl_initbrds()\n");
+#endif
+
+ if (stl_nrbrds > STL_MAXBRDS) {
+ printk("STALLION: too many boards in configuration table, "
+ "truncating to %d\n", STL_MAXBRDS);
+ stl_nrbrds = STL_MAXBRDS;
+ }
+
+/*
+ * Firstly scan the list of static boards configured. Allocate
+ * resources and initialize the boards as found.
+ */
+ for (i = 0; (i < stl_nrbrds); i++) {
+ confp = &stl_brdconf[i];
+#ifdef MODULE
+ stl_parsebrd(confp, stl_brdsp[i]);
+#endif
+ if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL)
+ return(-ENOMEM);
+ brdp->brdnr = i;
+ brdp->brdtype = confp->brdtype;
+ brdp->ioaddr1 = confp->ioaddr1;
+ brdp->ioaddr2 = confp->ioaddr2;
+ brdp->irq = confp->irq;
+ brdp->irqtype = confp->irqtype;
+ stl_brdinit(brdp);
+ }
+
+/*
+ * Find any dynamically supported boards. That is via module load
+ * line options or auto-detected on the PCI bus.
+ */
+#ifdef MODULE
+ stl_argbrds();
+#endif
+#ifdef CONFIG_PCI
+ stl_findpcibrds();
+#endif
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the board stats structure to user app.
+ */
+
+static int stl_getbrdstats(combrd_t *bp)
+{
+ stlbrd_t *brdp;
+ stlpanel_t *panelp;
+ int i;
+
+ copy_from_user(&stl_brdstats, bp, sizeof(combrd_t));
+ if (stl_brdstats.brd >= STL_MAXBRDS)
+ return(-ENODEV);
+ brdp = stl_brds[stl_brdstats.brd];
+ if (brdp == (stlbrd_t *) NULL)
+ return(-ENODEV);
+
+ memset(&stl_brdstats, 0, sizeof(combrd_t));
+ stl_brdstats.brd = brdp->brdnr;
+ stl_brdstats.type = brdp->brdtype;
+ stl_brdstats.hwid = brdp->hwid;
+ stl_brdstats.state = brdp->state;
+ stl_brdstats.ioaddr = brdp->ioaddr1;
+ stl_brdstats.ioaddr2 = brdp->ioaddr2;
+ stl_brdstats.irq = brdp->irq;
+ stl_brdstats.nrpanels = brdp->nrpanels;
+ stl_brdstats.nrports = brdp->nrports;
+ for (i = 0; (i < brdp->nrpanels); i++) {
+ panelp = brdp->panels[i];
+ stl_brdstats.panels[i].panel = i;
+ stl_brdstats.panels[i].hwid = panelp->hwid;
+ stl_brdstats.panels[i].nrports = panelp->nrports;
+ }
+
+ copy_to_user(bp, &stl_brdstats, sizeof(combrd_t));
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Resolve the referenced port number into a port struct pointer.
+ */
+
+static stlport_t *stl_getport(int brdnr, int panelnr, int portnr)
+{
+ stlbrd_t *brdp;
+ stlpanel_t *panelp;
+
+ if ((brdnr < 0) || (brdnr >= STL_MAXBRDS))
+ return((stlport_t *) NULL);
+ brdp = stl_brds[brdnr];
+ if (brdp == (stlbrd_t *) NULL)
+ return((stlport_t *) NULL);
+ if ((panelnr < 0) || (panelnr >= brdp->nrpanels))
+ return((stlport_t *) NULL);
+ panelp = brdp->panels[panelnr];
+ if (panelp == (stlpanel_t *) NULL)
+ return((stlport_t *) NULL);
+ if ((portnr < 0) || (portnr >= panelp->nrports))
+ return((stlport_t *) NULL);
+ return(panelp->ports[portnr]);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the port stats structure to user app. A NULL port struct
+ * pointer passed in means that we need to find out from the app
+ * what port to get stats for (used through board control device).
+ */
+
+static int stl_getportstats(stlport_t *portp, comstats_t *cp)
+{
+ unsigned char *head, *tail;
+ unsigned long flags;
+
+ if (portp == (stlport_t *) NULL) {
+ copy_from_user(&stl_comstats, cp, sizeof(comstats_t));
+ portp = stl_getport(stl_comstats.brd, stl_comstats.panel,
+ stl_comstats.port);
+ if (portp == (stlport_t *) NULL)
+ return(-ENODEV);
+ }
+
+ portp->stats.state = portp->istate;
+ portp->stats.flags = portp->flags;
+ portp->stats.hwid = portp->hwid;
+
+ portp->stats.ttystate = 0;
+ portp->stats.cflags = 0;
+ portp->stats.iflags = 0;
+ portp->stats.oflags = 0;
+ portp->stats.lflags = 0;
+ portp->stats.rxbuffered = 0;
+
+ save_flags(flags);
+ cli();
+ if (portp->tty != (struct tty_struct *) NULL) {
+ if (portp->tty->driver_data == portp) {
+ portp->stats.ttystate = portp->tty->flags;
+ portp->stats.rxbuffered = portp->tty->flip.count;
+ if (portp->tty->termios != (struct termios *) NULL) {
+ portp->stats.cflags = portp->tty->termios->c_cflag;
+ portp->stats.iflags = portp->tty->termios->c_iflag;
+ portp->stats.oflags = portp->tty->termios->c_oflag;
+ portp->stats.lflags = portp->tty->termios->c_lflag;
+ }
+ }
+ }
+ restore_flags(flags);
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ portp->stats.txbuffered = ((head >= tail) ? (head - tail) :
+ (STL_TXBUFSIZE - (tail - head)));
+
+ portp->stats.signals = (unsigned long) stl_getsignals(portp);
+
+ copy_to_user(cp, &portp->stats, sizeof(comstats_t));
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Clear the port stats structure. We also return it zeroed out...
+ */
+
+static int stl_clrportstats(stlport_t *portp, comstats_t *cp)
+{
+ if (portp == (stlport_t *) NULL) {
+ copy_from_user(&stl_comstats, cp, sizeof(comstats_t));
+ portp = stl_getport(stl_comstats.brd, stl_comstats.panel,
+ stl_comstats.port);
+ if (portp == (stlport_t *) NULL)
+ return(-ENODEV);
+ }
+
+ memset(&portp->stats, 0, sizeof(comstats_t));
+ portp->stats.brd = portp->brdnr;
+ portp->stats.panel = portp->panelnr;
+ portp->stats.port = portp->portnr;
+ copy_to_user(cp, &portp->stats, sizeof(comstats_t));
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the entire driver ports structure to a user app.
+ */
+
+static int stl_getportstruct(unsigned long arg)
+{
+ stlport_t *portp;
+
+ copy_from_user(&stl_dummyport, (void *) arg, sizeof(stlport_t));
+ portp = stl_getport(stl_dummyport.brdnr, stl_dummyport.panelnr,
+ stl_dummyport.portnr);
+ if (portp == (stlport_t *) NULL)
+ return(-ENODEV);
+ copy_to_user((void *) arg, portp, sizeof(stlport_t));
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the entire driver board structure to a user app.
+ */
+
+static int stl_getbrdstruct(unsigned long arg)
+{
+ stlbrd_t *brdp;
+
+ copy_from_user(&stl_dummybrd, (void *) arg, sizeof(stlbrd_t));
+ if ((stl_dummybrd.brdnr < 0) || (stl_dummybrd.brdnr >= STL_MAXBRDS))
+ return(-ENODEV);
+ brdp = stl_brds[stl_dummybrd.brdnr];
+ if (brdp == (stlbrd_t *) NULL)
+ return(-ENODEV);
+ copy_to_user((void *) arg, brdp, sizeof(stlbrd_t));
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * The "staliomem" device is also required to do some special operations
+ * on the board and/or ports. In this driver it is mostly used for stats
+ * collection.
+ */
+
+static int stl_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ int brdnr, rc;
+
+#if DEBUG
+ printk("stl_memioctl(ip=%x,fp=%x,cmd=%x,arg=%x)\n", (int) ip,
+ (int) fp, cmd, (int) arg);
+#endif
+
+ brdnr = MINOR(ip->i_rdev);
+ if (brdnr >= STL_MAXBRDS)
+ return(-ENODEV);
+ rc = 0;
+
+ switch (cmd) {
+ case COM_GETPORTSTATS:
+ if ((rc = verify_area(VERIFY_WRITE, (void *) arg,
+ sizeof(comstats_t))) == 0)
+ rc = stl_getportstats((stlport_t *) NULL,
+ (comstats_t *) arg);
+ break;
+ case COM_CLRPORTSTATS:
+ if ((rc = verify_area(VERIFY_WRITE, (void *) arg,
+ sizeof(comstats_t))) == 0)
+ rc = stl_clrportstats((stlport_t *) NULL,
+ (comstats_t *) arg);
+ break;
+ case COM_GETBRDSTATS:
+ if ((rc = verify_area(VERIFY_WRITE, (void *) arg,
+ sizeof(combrd_t))) == 0)
+ rc = stl_getbrdstats((combrd_t *) arg);
+ break;
+ case COM_READPORT:
+ if ((rc = verify_area(VERIFY_WRITE, (void *) arg,
+ sizeof(stlport_t))) == 0)
+ rc = stl_getportstruct(arg);
+ break;
+ case COM_READBOARD:
+ if ((rc = verify_area(VERIFY_WRITE, (void *) arg,
+ sizeof(stlbrd_t))) == 0)
+ rc = stl_getbrdstruct(arg);
+ break;
+ default:
+ rc = -ENOIOCTLCMD;
+ break;
+ }
+
+ return(rc);
+}
+
+/*****************************************************************************/
+
+int __init stl_init(void)
+{
+ printk(KERN_INFO "%s: version %s\n", stl_drvtitle, stl_drvversion);
+
+ stl_initbrds();
+
+/*
+ * Allocate a temporary write buffer.
+ */
+ stl_tmpwritebuf = (char *) stl_memalloc(STL_TXBUFSIZE);
+ if (stl_tmpwritebuf == (char *) NULL)
+ printk("STALLION: failed to allocate memory (size=%d)\n",
+ STL_TXBUFSIZE);
+
+/*
+ * Set up a character driver for per board stuff. This is mainly used
+ * to do stats ioctls on the ports.
+ */
+ if (devfs_register_chrdev(STL_SIOMEMMAJOR, "staliomem", &stl_fsiomem))
+ printk("STALLION: failed to register serial board device\n");
+ devfs_handle = devfs_mk_dir (NULL, "staliomem", NULL);
+ devfs_register_series (devfs_handle, "%u", 4, DEVFS_FL_DEFAULT,
+ STL_SIOMEMMAJOR, 0,
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ &stl_fsiomem, NULL);
+
+/*
+ * Set up the tty driver structure and register us as a driver.
+ * Also setup the callout tty device.
+ */
+ memset(&stl_serial, 0, sizeof(struct tty_driver));
+ stl_serial.magic = TTY_DRIVER_MAGIC;
+ stl_serial.driver_name = stl_drvname;
+ stl_serial.name = stl_serialname;
+ stl_serial.major = STL_SERIALMAJOR;
+ stl_serial.minor_start = 0;
+ stl_serial.num = STL_MAXBRDS * STL_MAXPORTS;
+ stl_serial.type = TTY_DRIVER_TYPE_SERIAL;
+ stl_serial.subtype = STL_DRVTYPSERIAL;
+ stl_serial.init_termios = stl_deftermios;
+ stl_serial.flags = TTY_DRIVER_REAL_RAW;
+ stl_serial.refcount = &stl_refcount;
+ stl_serial.table = stl_ttys;
+ stl_serial.termios = stl_termios;
+ stl_serial.termios_locked = stl_termioslocked;
+
+ stl_serial.open = stl_open;
+ stl_serial.close = stl_close;
+ stl_serial.write = stl_write;
+ stl_serial.put_char = stl_putchar;
+ stl_serial.flush_chars = stl_flushchars;
+ stl_serial.write_room = stl_writeroom;
+ stl_serial.chars_in_buffer = stl_charsinbuffer;
+ stl_serial.ioctl = stl_ioctl;
+ stl_serial.set_termios = stl_settermios;
+ stl_serial.throttle = stl_throttle;
+ stl_serial.unthrottle = stl_unthrottle;
+ stl_serial.stop = stl_stop;
+ stl_serial.start = stl_start;
+ stl_serial.hangup = stl_hangup;
+ stl_serial.flush_buffer = stl_flushbuffer;
+ stl_serial.break_ctl = stl_breakctl;
+ stl_serial.wait_until_sent = stl_waituntilsent;
+ stl_serial.send_xchar = stl_sendxchar;
+ stl_serial.read_proc = stl_readproc;
+
+ stl_callout = stl_serial;
+ stl_callout.name = stl_calloutname;
+ stl_callout.major = STL_CALLOUTMAJOR;
+ stl_callout.subtype = STL_DRVTYPCALLOUT;
+ stl_callout.read_proc = 0;
+
+ if (tty_register_driver(&stl_serial))
+ printk("STALLION: failed to register serial driver\n");
+ if (tty_register_driver(&stl_callout))
+ printk("STALLION: failed to register callout driver\n");
+
+ return(0);
+}
+
+/*****************************************************************************/
+/* CD1400 HARDWARE FUNCTIONS */
+/*****************************************************************************/
+
+/*
+ * These functions get/set/update the registers of the cd1400 UARTs.
+ * Access to the cd1400 registers is via an address/data io port pair.
+ * (Maybe should make this inline...)
+ */
+
+static int stl_cd1400getreg(stlport_t *portp, int regnr)
+{
+ outb((regnr + portp->uartaddr), portp->ioaddr);
+ return(inb(portp->ioaddr + EREG_DATA));
+}
+
+static void stl_cd1400setreg(stlport_t *portp, int regnr, int value)
+{
+ outb((regnr + portp->uartaddr), portp->ioaddr);
+ outb(value, portp->ioaddr + EREG_DATA);
+}
+
+static int stl_cd1400updatereg(stlport_t *portp, int regnr, int value)
+{
+ outb((regnr + portp->uartaddr), portp->ioaddr);
+ if (inb(portp->ioaddr + EREG_DATA) != value) {
+ outb(value, portp->ioaddr + EREG_DATA);
+ return(1);
+ }
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Inbitialize the UARTs in a panel. We don't care what sort of board
+ * these ports are on - since the port io registers are almost
+ * identical when dealing with ports.
+ */
+
+static int stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp)
+{
+ unsigned int gfrcr;
+ int chipmask, i, j;
+ int nrchips, uartaddr, ioaddr;
+
+#if DEBUG
+ printk("stl_panelinit(brdp=%x,panelp=%x)\n", (int) brdp, (int) panelp);
+#endif
+
+ BRDENABLE(panelp->brdnr, panelp->pagenr);
+
+/*
+ * Check that each chip is present and started up OK.
+ */
+ chipmask = 0;
+ nrchips = panelp->nrports / CD1400_PORTS;
+ for (i = 0; (i < nrchips); i++) {
+ if (brdp->brdtype == BRD_ECHPCI) {
+ outb((panelp->pagenr + (i >> 1)), brdp->ioctrl);
+ ioaddr = panelp->iobase;
+ } else {
+ ioaddr = panelp->iobase + (EREG_BANKSIZE * (i >> 1));
+ }
+ uartaddr = (i & 0x01) ? 0x080 : 0;
+ outb((GFRCR + uartaddr), ioaddr);
+ outb(0, (ioaddr + EREG_DATA));
+ outb((CCR + uartaddr), ioaddr);
+ outb(CCR_RESETFULL, (ioaddr + EREG_DATA));
+ outb(CCR_RESETFULL, (ioaddr + EREG_DATA));
+ outb((GFRCR + uartaddr), ioaddr);
+ for (j = 0; (j < CCR_MAXWAIT); j++) {
+ if ((gfrcr = inb(ioaddr + EREG_DATA)) != 0)
+ break;
+ }
+ if ((j >= CCR_MAXWAIT) || (gfrcr < 0x40) || (gfrcr > 0x60)) {
+ printk("STALLION: cd1400 not responding, "
+ "brd=%d panel=%d chip=%d\n",
+ panelp->brdnr, panelp->panelnr, i);
+ continue;
+ }
+ chipmask |= (0x1 << i);
+ outb((PPR + uartaddr), ioaddr);
+ outb(PPR_SCALAR, (ioaddr + EREG_DATA));
+ }
+
+ BRDDISABLE(panelp->brdnr);
+ return(chipmask);
+}
+
+/*****************************************************************************/
+
+/*
+ * Initialize hardware specific port registers.
+ */
+
+static void stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp)
+{
+#if DEBUG
+ printk("stl_cd1400portinit(brdp=%x,panelp=%x,portp=%x)\n",
+ (int) brdp, (int) panelp, (int) portp);
+#endif
+
+ if ((brdp == (stlbrd_t *) NULL) || (panelp == (stlpanel_t *) NULL) ||
+ (portp == (stlport_t *) NULL))
+ return;
+
+ portp->ioaddr = panelp->iobase + (((brdp->brdtype == BRD_ECHPCI) ||
+ (portp->portnr < 8)) ? 0 : EREG_BANKSIZE);
+ portp->uartaddr = (portp->portnr & 0x04) << 5;
+ portp->pagenr = panelp->pagenr + (portp->portnr >> 3);
+
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400setreg(portp, LIVR, (portp->portnr << 3));
+ portp->hwid = stl_cd1400getreg(portp, GFRCR);
+ BRDDISABLE(portp->brdnr);
+}
+
+/*****************************************************************************/
+
+/*
+ * Wait for the command register to be ready. We will poll this,
+ * since it won't usually take too long to be ready.
+ */
+
+static void stl_cd1400ccrwait(stlport_t *portp)
+{
+ int i;
+
+ for (i = 0; (i < CCR_MAXWAIT); i++) {
+ if (stl_cd1400getreg(portp, CCR) == 0) {
+ return;
+ }
+ }
+
+ printk("STALLION: cd1400 not responding, port=%d panel=%d brd=%d\n",
+ portp->portnr, portp->panelnr, portp->brdnr);
+}
+
+/*****************************************************************************/
+
+/*
+ * Set up the cd1400 registers for a port based on the termios port
+ * settings.
+ */
+
+static void stl_cd1400setport(stlport_t *portp, struct termios *tiosp)
+{
+ stlbrd_t *brdp;
+ unsigned long flags;
+ unsigned int clkdiv, baudrate;
+ unsigned char cor1, cor2, cor3;
+ unsigned char cor4, cor5, ccr;
+ unsigned char srer, sreron, sreroff;
+ unsigned char mcor1, mcor2, rtpr;
+ unsigned char clk, div;
+
+ cor1 = 0;
+ cor2 = 0;
+ cor3 = 0;
+ cor4 = 0;
+ cor5 = 0;
+ ccr = 0;
+ rtpr = 0;
+ clk = 0;
+ div = 0;
+ mcor1 = 0;
+ mcor2 = 0;
+ sreron = 0;
+ sreroff = 0;
+
+ brdp = stl_brds[portp->brdnr];
+ if (brdp == (stlbrd_t *) NULL)
+ return;
+
+/*
+ * Set up the RX char ignore mask with those RX error types we
+ * can ignore. We can get the cd1400 to help us out a little here,
+ * it will ignore parity errors and breaks for us.
+ */
+ portp->rxignoremsk = 0;
+ if (tiosp->c_iflag & IGNPAR) {
+ portp->rxignoremsk |= (ST_PARITY | ST_FRAMING | ST_OVERRUN);
+ cor1 |= COR1_PARIGNORE;
+ }
+ if (tiosp->c_iflag & IGNBRK) {
+ portp->rxignoremsk |= ST_BREAK;
+ cor4 |= COR4_IGNBRK;
+ }
+
+ portp->rxmarkmsk = ST_OVERRUN;
+ if (tiosp->c_iflag & (INPCK | PARMRK))
+ portp->rxmarkmsk |= (ST_PARITY | ST_FRAMING);
+ if (tiosp->c_iflag & BRKINT)
+ portp->rxmarkmsk |= ST_BREAK;
+
+/*
+ * Go through the char size, parity and stop bits and set all the
+ * option register appropriately.
+ */
+ switch (tiosp->c_cflag & CSIZE) {
+ case CS5:
+ cor1 |= COR1_CHL5;
+ break;
+ case CS6:
+ cor1 |= COR1_CHL6;
+ break;
+ case CS7:
+ cor1 |= COR1_CHL7;
+ break;
+ default:
+ cor1 |= COR1_CHL8;
+ break;
+ }
+
+ if (tiosp->c_cflag & CSTOPB)
+ cor1 |= COR1_STOP2;
+ else
+ cor1 |= COR1_STOP1;
+
+ if (tiosp->c_cflag & PARENB) {
+ if (tiosp->c_cflag & PARODD)
+ cor1 |= (COR1_PARENB | COR1_PARODD);
+ else
+ cor1 |= (COR1_PARENB | COR1_PAREVEN);
+ } else {
+ cor1 |= COR1_PARNONE;
+ }
+
+/*
+ * Set the RX FIFO threshold at 6 chars. This gives a bit of breathing
+ * space for hardware flow control and the like. This should be set to
+ * VMIN. Also here we will set the RX data timeout to 10ms - this should
+ * really be based on VTIME.
+ */
+ cor3 |= FIFO_RXTHRESHOLD;
+ rtpr = 2;
+
+/*
+ * Calculate the baud rate timers. For now we will just assume that
+ * the input and output baud are the same. Could have used a baud
+ * table here, but this way we can generate virtually any baud rate
+ * we like!
+ */
+ baudrate = tiosp->c_cflag & CBAUD;
+ if (baudrate & CBAUDEX) {
+ baudrate &= ~CBAUDEX;
+ if ((baudrate < 1) || (baudrate > 4))
+ tiosp->c_cflag &= ~CBAUDEX;
+ else
+ baudrate += 15;
+ }
+ baudrate = stl_baudrates[baudrate];
+ if ((tiosp->c_cflag & CBAUD) == B38400) {
+ if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ baudrate = 57600;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ baudrate = 115200;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ baudrate = 230400;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ baudrate = 460800;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+ baudrate = (portp->baud_base / portp->custom_divisor);
+ }
+ if (baudrate > STL_CD1400MAXBAUD)
+ baudrate = STL_CD1400MAXBAUD;
+
+ if (baudrate > 0) {
+ for (clk = 0; (clk < CD1400_NUMCLKS); clk++) {
+ clkdiv = ((portp->clk / stl_cd1400clkdivs[clk]) / baudrate);
+ if (clkdiv < 0x100)
+ break;
+ }
+ div = (unsigned char) clkdiv;
+ }
+
+/*
+ * Check what form of modem signaling is required and set it up.
+ */
+ if ((tiosp->c_cflag & CLOCAL) == 0) {
+ mcor1 |= MCOR1_DCD;
+ mcor2 |= MCOR2_DCD;
+ sreron |= SRER_MODEM;
+ portp->flags |= ASYNC_CHECK_CD;
+ } else {
+ portp->flags &= ~ASYNC_CHECK_CD;
+ }
+
+/*
+ * Setup cd1400 enhanced modes if we can. In particular we want to
+ * handle as much of the flow control as possible automatically. As
+ * well as saving a few CPU cycles it will also greatly improve flow
+ * control reliability.
+ */
+ if (tiosp->c_iflag & IXON) {
+ cor2 |= COR2_TXIBE;
+ cor3 |= COR3_SCD12;
+ if (tiosp->c_iflag & IXANY)
+ cor2 |= COR2_IXM;
+ }
+
+ if (tiosp->c_cflag & CRTSCTS) {
+ cor2 |= COR2_CTSAE;
+ mcor1 |= FIFO_RTSTHRESHOLD;
+ }
+
+/*
+ * All cd1400 register values calculated so go through and set
+ * them all up.
+ */
+
+#if DEBUG
+ printk("SETPORT: portnr=%d panelnr=%d brdnr=%d\n",
+ portp->portnr, portp->panelnr, portp->brdnr);
+ printk(" cor1=%x cor2=%x cor3=%x cor4=%x cor5=%x\n",
+ cor1, cor2, cor3, cor4, cor5);
+ printk(" mcor1=%x mcor2=%x rtpr=%x sreron=%x sreroff=%x\n",
+ mcor1, mcor2, rtpr, sreron, sreroff);
+ printk(" tcor=%x tbpr=%x rcor=%x rbpr=%x\n", clk, div, clk, div);
+ printk(" schr1=%x schr2=%x schr3=%x schr4=%x\n",
+ tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP],
+ tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP]);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x3));
+ srer = stl_cd1400getreg(portp, SRER);
+ stl_cd1400setreg(portp, SRER, 0);
+ if (stl_cd1400updatereg(portp, COR1, cor1))
+ ccr = 1;
+ if (stl_cd1400updatereg(portp, COR2, cor2))
+ ccr = 1;
+ if (stl_cd1400updatereg(portp, COR3, cor3))
+ ccr = 1;
+ if (ccr) {
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_CORCHANGE);
+ }
+ stl_cd1400setreg(portp, COR4, cor4);
+ stl_cd1400setreg(portp, COR5, cor5);
+ stl_cd1400setreg(portp, MCOR1, mcor1);
+ stl_cd1400setreg(portp, MCOR2, mcor2);
+ if (baudrate > 0) {
+ stl_cd1400setreg(portp, TCOR, clk);
+ stl_cd1400setreg(portp, TBPR, div);
+ stl_cd1400setreg(portp, RCOR, clk);
+ stl_cd1400setreg(portp, RBPR, div);
+ }
+ stl_cd1400setreg(portp, SCHR1, tiosp->c_cc[VSTART]);
+ stl_cd1400setreg(portp, SCHR2, tiosp->c_cc[VSTOP]);
+ stl_cd1400setreg(portp, SCHR3, tiosp->c_cc[VSTART]);
+ stl_cd1400setreg(portp, SCHR4, tiosp->c_cc[VSTOP]);
+ stl_cd1400setreg(portp, RTPR, rtpr);
+ mcor1 = stl_cd1400getreg(portp, MSVR1);
+ if (mcor1 & MSVR1_DCD)
+ portp->sigs |= TIOCM_CD;
+ else
+ portp->sigs &= ~TIOCM_CD;
+ stl_cd1400setreg(portp, SRER, ((srer & ~sreroff) | sreron));
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Set the state of the DTR and RTS signals.
+ */
+
+static void stl_cd1400setsignals(stlport_t *portp, int dtr, int rts)
+{
+ unsigned char msvr1, msvr2;
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_cd1400setsignals(portp=%x,dtr=%d,rts=%d)\n",
+ (int) portp, dtr, rts);
+#endif
+
+ msvr1 = 0;
+ msvr2 = 0;
+ if (dtr > 0)
+ msvr1 = MSVR1_DTR;
+ if (rts > 0)
+ msvr2 = MSVR2_RTS;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ if (rts >= 0)
+ stl_cd1400setreg(portp, MSVR2, msvr2);
+ if (dtr >= 0)
+ stl_cd1400setreg(portp, MSVR1, msvr1);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the state of the signals.
+ */
+
+static int stl_cd1400getsignals(stlport_t *portp)
+{
+ unsigned char msvr1, msvr2;
+ unsigned long flags;
+ int sigs;
+
+#if DEBUG
+ printk("stl_cd1400getsignals(portp=%x)\n", (int) portp);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ msvr1 = stl_cd1400getreg(portp, MSVR1);
+ msvr2 = stl_cd1400getreg(portp, MSVR2);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+
+ sigs = 0;
+ sigs |= (msvr1 & MSVR1_DCD) ? TIOCM_CD : 0;
+ sigs |= (msvr1 & MSVR1_CTS) ? TIOCM_CTS : 0;
+ sigs |= (msvr1 & MSVR1_DTR) ? TIOCM_DTR : 0;
+ sigs |= (msvr2 & MSVR2_RTS) ? TIOCM_RTS : 0;
+#if 0
+ sigs |= (msvr1 & MSVR1_RI) ? TIOCM_RI : 0;
+ sigs |= (msvr1 & MSVR1_DSR) ? TIOCM_DSR : 0;
+#else
+ sigs |= TIOCM_DSR;
+#endif
+ return(sigs);
+}
+
+/*****************************************************************************/
+
+/*
+ * Enable/Disable the Transmitter and/or Receiver.
+ */
+
+static void stl_cd1400enablerxtx(stlport_t *portp, int rx, int tx)
+{
+ unsigned char ccr;
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_cd1400enablerxtx(portp=%x,rx=%d,tx=%d)\n",
+ (int) portp, rx, tx);
+#endif
+ ccr = 0;
+
+ if (tx == 0)
+ ccr |= CCR_TXDISABLE;
+ else if (tx > 0)
+ ccr |= CCR_TXENABLE;
+ if (rx == 0)
+ ccr |= CCR_RXDISABLE;
+ else if (rx > 0)
+ ccr |= CCR_RXENABLE;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, ccr);
+ stl_cd1400ccrwait(portp);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Start/stop the Transmitter and/or Receiver.
+ */
+
+static void stl_cd1400startrxtx(stlport_t *portp, int rx, int tx)
+{
+ unsigned char sreron, sreroff;
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_cd1400startrxtx(portp=%x,rx=%d,tx=%d)\n",
+ (int) portp, rx, tx);
+#endif
+
+ sreron = 0;
+ sreroff = 0;
+ if (tx == 0)
+ sreroff |= (SRER_TXDATA | SRER_TXEMPTY);
+ else if (tx == 1)
+ sreron |= SRER_TXDATA;
+ else if (tx >= 2)
+ sreron |= SRER_TXEMPTY;
+ if (rx == 0)
+ sreroff |= SRER_RXDATA;
+ else if (rx > 0)
+ sreron |= SRER_RXDATA;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400setreg(portp, SRER,
+ ((stl_cd1400getreg(portp, SRER) & ~sreroff) | sreron));
+ BRDDISABLE(portp->brdnr);
+ if (tx > 0)
+ set_bit(ASYI_TXBUSY, &portp->istate);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Disable all interrupts from this port.
+ */
+
+static void stl_cd1400disableintrs(stlport_t *portp)
+{
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_cd1400disableintrs(portp=%x)\n", (int) portp);
+#endif
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400setreg(portp, SRER, 0);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_cd1400sendbreak(stlport_t *portp, int len)
+{
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_cd1400sendbreak(portp=%x,len=%d)\n", (int) portp, len);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400setreg(portp, SRER,
+ ((stl_cd1400getreg(portp, SRER) & ~SRER_TXDATA) |
+ SRER_TXEMPTY));
+ BRDDISABLE(portp->brdnr);
+ portp->brklen = len;
+ if (len == 1)
+ portp->stats.txbreaks++;
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Take flow control actions...
+ */
+
+static void stl_cd1400flowctrl(stlport_t *portp, int state)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_cd1400flowctrl(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+
+ if (state) {
+ if (tty->termios->c_iflag & IXOFF) {
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_SENDSCHR1);
+ portp->stats.rxxon++;
+ stl_cd1400ccrwait(portp);
+ }
+/*
+ * Question: should we return RTS to what it was before? It may
+ * have been set by an ioctl... Suppose not, since if you have
+ * hardware flow control set then it is pretty silly to go and
+ * set the RTS line by hand.
+ */
+ if (tty->termios->c_cflag & CRTSCTS) {
+ stl_cd1400setreg(portp, MCOR1,
+ (stl_cd1400getreg(portp, MCOR1) |
+ FIFO_RTSTHRESHOLD));
+ stl_cd1400setreg(portp, MSVR2, MSVR2_RTS);
+ portp->stats.rxrtson++;
+ }
+ } else {
+ if (tty->termios->c_iflag & IXOFF) {
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_SENDSCHR2);
+ portp->stats.rxxoff++;
+ stl_cd1400ccrwait(portp);
+ }
+ if (tty->termios->c_cflag & CRTSCTS) {
+ stl_cd1400setreg(portp, MCOR1,
+ (stl_cd1400getreg(portp, MCOR1) & 0xf0));
+ stl_cd1400setreg(portp, MSVR2, 0);
+ portp->stats.rxrtsoff++;
+ }
+ }
+
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Send a flow control character...
+ */
+
+static void stl_cd1400sendflow(stlport_t *portp, int state)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_cd1400sendflow(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ if (state) {
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_SENDSCHR1);
+ portp->stats.rxxon++;
+ stl_cd1400ccrwait(portp);
+ } else {
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_SENDSCHR2);
+ portp->stats.rxxoff++;
+ stl_cd1400ccrwait(portp);
+ }
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_cd1400flush(stlport_t *portp)
+{
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_cd1400flush(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_TXFLUSHFIFO);
+ stl_cd1400ccrwait(portp);
+ portp->tx.tail = portp->tx.head;
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the current state of data flow on this port. This is only
+ * really interresting when determining if data has fully completed
+ * transmission or not... This is easy for the cd1400, it accurately
+ * maintains the busy port flag.
+ */
+
+static int stl_cd1400datastate(stlport_t *portp)
+{
+#if DEBUG
+ printk("stl_cd1400datastate(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return(0);
+
+ return(test_bit(ASYI_TXBUSY, &portp->istate) ? 1 : 0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for cd1400 EasyIO boards.
+ */
+
+static void stl_cd1400eiointr(stlpanel_t *panelp, unsigned int iobase)
+{
+ unsigned char svrtype;
+
+#if DEBUG
+ printk("stl_cd1400eiointr(panelp=%x,iobase=%x)\n",
+ (int) panelp, iobase);
+#endif
+
+ outb(SVRR, iobase);
+ svrtype = inb(iobase + EREG_DATA);
+ if (panelp->nrports > 4) {
+ outb((SVRR + 0x80), iobase);
+ svrtype |= inb(iobase + EREG_DATA);
+ }
+
+ if (svrtype & SVRR_RX)
+ stl_cd1400rxisr(panelp, iobase);
+ else if (svrtype & SVRR_TX)
+ stl_cd1400txisr(panelp, iobase);
+ else if (svrtype & SVRR_MDM)
+ stl_cd1400mdmisr(panelp, iobase);
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for cd1400 panels.
+ */
+
+static void stl_cd1400echintr(stlpanel_t *panelp, unsigned int iobase)
+{
+ unsigned char svrtype;
+
+#if DEBUG
+ printk("stl_cd1400echintr(panelp=%x,iobase=%x)\n", (int) panelp,
+ iobase);
+#endif
+
+ outb(SVRR, iobase);
+ svrtype = inb(iobase + EREG_DATA);
+ outb((SVRR + 0x80), iobase);
+ svrtype |= inb(iobase + EREG_DATA);
+ if (svrtype & SVRR_RX)
+ stl_cd1400rxisr(panelp, iobase);
+ else if (svrtype & SVRR_TX)
+ stl_cd1400txisr(panelp, iobase);
+ else if (svrtype & SVRR_MDM)
+ stl_cd1400mdmisr(panelp, iobase);
+}
+
+
+/*****************************************************************************/
+
+/*
+ * Unfortunately we need to handle breaks in the TX data stream, since
+ * this is the only way to generate them on the cd1400.
+ */
+
+static inline int stl_cd1400breakisr(stlport_t *portp, int ioaddr)
+{
+ if (portp->brklen == 1) {
+ outb((COR2 + portp->uartaddr), ioaddr);
+ outb((inb(ioaddr + EREG_DATA) | COR2_ETC),
+ (ioaddr + EREG_DATA));
+ outb((TDR + portp->uartaddr), ioaddr);
+ outb(ETC_CMD, (ioaddr + EREG_DATA));
+ outb(ETC_STARTBREAK, (ioaddr + EREG_DATA));
+ outb((SRER + portp->uartaddr), ioaddr);
+ outb((inb(ioaddr + EREG_DATA) & ~(SRER_TXDATA | SRER_TXEMPTY)),
+ (ioaddr + EREG_DATA));
+ return(1);
+ } else if (portp->brklen > 1) {
+ outb((TDR + portp->uartaddr), ioaddr);
+ outb(ETC_CMD, (ioaddr + EREG_DATA));
+ outb(ETC_STOPBREAK, (ioaddr + EREG_DATA));
+ portp->brklen = -1;
+ return(1);
+ } else {
+ outb((COR2 + portp->uartaddr), ioaddr);
+ outb((inb(ioaddr + EREG_DATA) & ~COR2_ETC),
+ (ioaddr + EREG_DATA));
+ portp->brklen = 0;
+ }
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Transmit interrupt handler. This has gotta be fast! Handling TX
+ * chars is pretty simple, stuff as many as possible from the TX buffer
+ * into the cd1400 FIFO. Must also handle TX breaks here, since they
+ * are embedded as commands in the data stream. Oh no, had to use a goto!
+ * This could be optimized more, will do when I get time...
+ * In practice it is possible that interrupts are enabled but that the
+ * port has been hung up. Need to handle not having any TX buffer here,
+ * this is done by using the side effect that head and tail will also
+ * be NULL if the buffer has been freed.
+ */
+
+static void stl_cd1400txisr(stlpanel_t *panelp, int ioaddr)
+{
+ stlport_t *portp;
+ int len, stlen;
+ char *head, *tail;
+ unsigned char ioack, srer;
+
+#if DEBUG
+ printk("stl_cd1400txisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
+#endif
+
+ ioack = inb(ioaddr + EREG_TXACK);
+ if (((ioack & panelp->ackmask) != 0) ||
+ ((ioack & ACK_TYPMASK) != ACK_TYPTX)) {
+ printk("STALLION: bad TX interrupt ack value=%x\n", ioack);
+ return;
+ }
+ portp = panelp->ports[(ioack >> 3)];
+
+/*
+ * Unfortunately we need to handle breaks in the data stream, since
+ * this is the only way to generate them on the cd1400. Do it now if
+ * a break is to be sent.
+ */
+ if (portp->brklen != 0)
+ if (stl_cd1400breakisr(portp, ioaddr))
+ goto stl_txalldone;
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ len = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head));
+ if ((len == 0) || ((len < STL_TXBUFLOW) &&
+ (test_bit(ASYI_TXLOW, &portp->istate) == 0))) {
+ set_bit(ASYI_TXLOW, &portp->istate);
+ queue_task(&portp->tqueue, &tq_scheduler);
+ }
+
+ if (len == 0) {
+ outb((SRER + portp->uartaddr), ioaddr);
+ srer = inb(ioaddr + EREG_DATA);
+ if (srer & SRER_TXDATA) {
+ srer = (srer & ~SRER_TXDATA) | SRER_TXEMPTY;
+ } else {
+ srer &= ~(SRER_TXDATA | SRER_TXEMPTY);
+ clear_bit(ASYI_TXBUSY, &portp->istate);
+ }
+ outb(srer, (ioaddr + EREG_DATA));
+ } else {
+ len = MIN(len, CD1400_TXFIFOSIZE);
+ portp->stats.txtotal += len;
+ stlen = MIN(len, ((portp->tx.buf + STL_TXBUFSIZE) - tail));
+ outb((TDR + portp->uartaddr), ioaddr);
+ outsb((ioaddr + EREG_DATA), tail, stlen);
+ len -= stlen;
+ tail += stlen;
+ if (tail >= (portp->tx.buf + STL_TXBUFSIZE))
+ tail = portp->tx.buf;
+ if (len > 0) {
+ outsb((ioaddr + EREG_DATA), tail, len);
+ tail += len;
+ }
+ portp->tx.tail = tail;
+ }
+
+stl_txalldone:
+ outb((EOSRR + portp->uartaddr), ioaddr);
+ outb(0, (ioaddr + EREG_DATA));
+}
+
+/*****************************************************************************/
+
+/*
+ * Receive character interrupt handler. Determine if we have good chars
+ * or bad chars and then process appropriately. Good chars are easy
+ * just shove the lot into the RX buffer and set all status byte to 0.
+ * If a bad RX char then process as required. This routine needs to be
+ * fast! In practice it is possible that we get an interrupt on a port
+ * that is closed. This can happen on hangups - since they completely
+ * shutdown a port not in user context. Need to handle this case.
+ */
+
+static void stl_cd1400rxisr(stlpanel_t *panelp, int ioaddr)
+{
+ stlport_t *portp;
+ struct tty_struct *tty;
+ unsigned int ioack, len, buflen;
+ unsigned char status;
+ char ch;
+
+#if DEBUG
+ printk("stl_cd1400rxisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
+#endif
+
+ ioack = inb(ioaddr + EREG_RXACK);
+ if ((ioack & panelp->ackmask) != 0) {
+ printk("STALLION: bad RX interrupt ack value=%x\n", ioack);
+ return;
+ }
+ portp = panelp->ports[(ioack >> 3)];
+ tty = portp->tty;
+
+ if ((ioack & ACK_TYPMASK) == ACK_TYPRXGOOD) {
+ outb((RDCR + portp->uartaddr), ioaddr);
+ len = inb(ioaddr + EREG_DATA);
+ if ((tty == (struct tty_struct *) NULL) ||
+ (tty->flip.char_buf_ptr == (char *) NULL) ||
+ ((buflen = TTY_FLIPBUF_SIZE - tty->flip.count) == 0)) {
+ len = MIN(len, sizeof(stl_unwanted));
+ outb((RDSR + portp->uartaddr), ioaddr);
+ insb((ioaddr + EREG_DATA), &stl_unwanted[0], len);
+ portp->stats.rxlost += len;
+ portp->stats.rxtotal += len;
+ } else {
+ len = MIN(len, buflen);
+ if (len > 0) {
+ outb((RDSR + portp->uartaddr), ioaddr);
+ insb((ioaddr + EREG_DATA), tty->flip.char_buf_ptr, len);
+ memset(tty->flip.flag_buf_ptr, 0, len);
+ tty->flip.flag_buf_ptr += len;
+ tty->flip.char_buf_ptr += len;
+ tty->flip.count += len;
+ tty_schedule_flip(tty);
+ portp->stats.rxtotal += len;
+ }
+ }
+ } else if ((ioack & ACK_TYPMASK) == ACK_TYPRXBAD) {
+ outb((RDSR + portp->uartaddr), ioaddr);
+ status = inb(ioaddr + EREG_DATA);
+ ch = inb(ioaddr + EREG_DATA);
+ if (status & ST_PARITY)
+ portp->stats.rxparity++;
+ if (status & ST_FRAMING)
+ portp->stats.rxframing++;
+ if (status & ST_OVERRUN)
+ portp->stats.rxoverrun++;
+ if (status & ST_BREAK)
+ portp->stats.rxbreaks++;
+ if (status & ST_SCHARMASK) {
+ if ((status & ST_SCHARMASK) == ST_SCHAR1)
+ portp->stats.txxon++;
+ if ((status & ST_SCHARMASK) == ST_SCHAR2)
+ portp->stats.txxoff++;
+ goto stl_rxalldone;
+ }
+ if ((tty != (struct tty_struct *) NULL) &&
+ ((portp->rxignoremsk & status) == 0)) {
+ if (portp->rxmarkmsk & status) {
+ if (status & ST_BREAK) {
+ status = TTY_BREAK;
+ if (portp->flags & ASYNC_SAK) {
+ do_SAK(tty);
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ }
+ } else if (status & ST_PARITY) {
+ status = TTY_PARITY;
+ } else if (status & ST_FRAMING) {
+ status = TTY_FRAME;
+ } else if(status & ST_OVERRUN) {
+ status = TTY_OVERRUN;
+ } else {
+ status = 0;
+ }
+ } else {
+ status = 0;
+ }
+ if (tty->flip.char_buf_ptr != (char *) NULL) {
+ if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+ *tty->flip.flag_buf_ptr++ = status;
+ *tty->flip.char_buf_ptr++ = ch;
+ tty->flip.count++;
+ }
+ tty_schedule_flip(tty);
+ }
+ }
+ } else {
+ printk("STALLION: bad RX interrupt ack value=%x\n", ioack);
+ return;
+ }
+
+stl_rxalldone:
+ outb((EOSRR + portp->uartaddr), ioaddr);
+ outb(0, (ioaddr + EREG_DATA));
+}
+
+/*****************************************************************************/
+
+/*
+ * Modem interrupt handler. The is called when the modem signal line
+ * (DCD) has changed state. Leave most of the work to the off-level
+ * processing routine.
+ */
+
+static void stl_cd1400mdmisr(stlpanel_t *panelp, int ioaddr)
+{
+ stlport_t *portp;
+ unsigned int ioack;
+ unsigned char misr;
+
+#if DEBUG
+ printk("stl_cd1400mdmisr(panelp=%x)\n", (int) panelp);
+#endif
+
+ ioack = inb(ioaddr + EREG_MDACK);
+ if (((ioack & panelp->ackmask) != 0) ||
+ ((ioack & ACK_TYPMASK) != ACK_TYPMDM)) {
+ printk("STALLION: bad MODEM interrupt ack value=%x\n", ioack);
+ return;
+ }
+ portp = panelp->ports[(ioack >> 3)];
+
+ outb((MISR + portp->uartaddr), ioaddr);
+ misr = inb(ioaddr + EREG_DATA);
+ if (misr & MISR_DCD) {
+ set_bit(ASYI_DCDCHANGE, &portp->istate);
+ queue_task(&portp->tqueue, &tq_scheduler);
+ portp->stats.modem++;
+ }
+
+ outb((EOSRR + portp->uartaddr), ioaddr);
+ outb(0, (ioaddr + EREG_DATA));
+}
+
+/*****************************************************************************/
+/* SC26198 HARDWARE FUNCTIONS */
+/*****************************************************************************/
+
+/*
+ * These functions get/set/update the registers of the sc26198 UARTs.
+ * Access to the sc26198 registers is via an address/data io port pair.
+ * (Maybe should make this inline...)
+ */
+
+static int stl_sc26198getreg(stlport_t *portp, int regnr)
+{
+ outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR));
+ return(inb(portp->ioaddr + XP_DATA));
+}
+
+static void stl_sc26198setreg(stlport_t *portp, int regnr, int value)
+{
+ outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR));
+ outb(value, (portp->ioaddr + XP_DATA));
+}
+
+static int stl_sc26198updatereg(stlport_t *portp, int regnr, int value)
+{
+ outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR));
+ if (inb(portp->ioaddr + XP_DATA) != value) {
+ outb(value, (portp->ioaddr + XP_DATA));
+ return(1);
+ }
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Functions to get and set the sc26198 global registers.
+ */
+
+static int stl_sc26198getglobreg(stlport_t *portp, int regnr)
+{
+ outb(regnr, (portp->ioaddr + XP_ADDR));
+ return(inb(portp->ioaddr + XP_DATA));
+}
+
+#if 0
+static void stl_sc26198setglobreg(stlport_t *portp, int regnr, int value)
+{
+ outb(regnr, (portp->ioaddr + XP_ADDR));
+ outb(value, (portp->ioaddr + XP_DATA));
+}
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Inbitialize the UARTs in a panel. We don't care what sort of board
+ * these ports are on - since the port io registers are almost
+ * identical when dealing with ports.
+ */
+
+static int stl_sc26198panelinit(stlbrd_t *brdp, stlpanel_t *panelp)
+{
+ int chipmask, i;
+ int nrchips, ioaddr;
+
+#if DEBUG
+ printk("stl_sc26198panelinit(brdp=%x,panelp=%x)\n",
+ (int) brdp, (int) panelp);
+#endif
+
+ BRDENABLE(panelp->brdnr, panelp->pagenr);
+
+/*
+ * Check that each chip is present and started up OK.
+ */
+ chipmask = 0;
+ nrchips = (panelp->nrports + 4) / SC26198_PORTS;
+ if (brdp->brdtype == BRD_ECHPCI)
+ outb(panelp->pagenr, brdp->ioctrl);
+
+ for (i = 0; (i < nrchips); i++) {
+ ioaddr = panelp->iobase + (i * 4);
+ outb(SCCR, (ioaddr + XP_ADDR));
+ outb(CR_RESETALL, (ioaddr + XP_DATA));
+ outb(TSTR, (ioaddr + XP_ADDR));
+ if (inb(ioaddr + XP_DATA) != 0) {
+ printk("STALLION: sc26198 not responding, "
+ "brd=%d panel=%d chip=%d\n",
+ panelp->brdnr, panelp->panelnr, i);
+ continue;
+ }
+ chipmask |= (0x1 << i);
+ outb(GCCR, (ioaddr + XP_ADDR));
+ outb(GCCR_IVRTYPCHANACK, (ioaddr + XP_DATA));
+ outb(WDTRCR, (ioaddr + XP_ADDR));
+ outb(0xff, (ioaddr + XP_DATA));
+ }
+
+ BRDDISABLE(panelp->brdnr);
+ return(chipmask);
+}
+
+/*****************************************************************************/
+
+/*
+ * Initialize hardware specific port registers.
+ */
+
+static void stl_sc26198portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp)
+{
+#if DEBUG
+ printk("stl_sc26198portinit(brdp=%x,panelp=%x,portp=%x)\n",
+ (int) brdp, (int) panelp, (int) portp);
+#endif
+
+ if ((brdp == (stlbrd_t *) NULL) || (panelp == (stlpanel_t *) NULL) ||
+ (portp == (stlport_t *) NULL))
+ return;
+
+ portp->ioaddr = panelp->iobase + ((portp->portnr < 8) ? 0 : 4);
+ portp->uartaddr = (portp->portnr & 0x07) << 4;
+ portp->pagenr = panelp->pagenr;
+ portp->hwid = 0x1;
+
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, IOPCR, IOPCR_SETSIGS);
+ BRDDISABLE(portp->brdnr);
+}
+
+/*****************************************************************************/
+
+/*
+ * Set up the sc26198 registers for a port based on the termios port
+ * settings.
+ */
+
+static void stl_sc26198setport(stlport_t *portp, struct termios *tiosp)
+{
+ stlbrd_t *brdp;
+ unsigned long flags;
+ unsigned int baudrate;
+ unsigned char mr0, mr1, mr2, clk;
+ unsigned char imron, imroff, iopr, ipr;
+
+ mr0 = 0;
+ mr1 = 0;
+ mr2 = 0;
+ clk = 0;
+ iopr = 0;
+ imron = 0;
+ imroff = 0;
+
+ brdp = stl_brds[portp->brdnr];
+ if (brdp == (stlbrd_t *) NULL)
+ return;
+
+/*
+ * Set up the RX char ignore mask with those RX error types we
+ * can ignore.
+ */
+ portp->rxignoremsk = 0;
+ if (tiosp->c_iflag & IGNPAR)
+ portp->rxignoremsk |= (SR_RXPARITY | SR_RXFRAMING |
+ SR_RXOVERRUN);
+ if (tiosp->c_iflag & IGNBRK)
+ portp->rxignoremsk |= SR_RXBREAK;
+
+ portp->rxmarkmsk = SR_RXOVERRUN;
+ if (tiosp->c_iflag & (INPCK | PARMRK))
+ portp->rxmarkmsk |= (SR_RXPARITY | SR_RXFRAMING);
+ if (tiosp->c_iflag & BRKINT)
+ portp->rxmarkmsk |= SR_RXBREAK;
+
+/*
+ * Go through the char size, parity and stop bits and set all the
+ * option register appropriately.
+ */
+ switch (tiosp->c_cflag & CSIZE) {
+ case CS5:
+ mr1 |= MR1_CS5;
+ break;
+ case CS6:
+ mr1 |= MR1_CS6;
+ break;
+ case CS7:
+ mr1 |= MR1_CS7;
+ break;
+ default:
+ mr1 |= MR1_CS8;
+ break;
+ }
+
+ if (tiosp->c_cflag & CSTOPB)
+ mr2 |= MR2_STOP2;
+ else
+ mr2 |= MR2_STOP1;
+
+ if (tiosp->c_cflag & PARENB) {
+ if (tiosp->c_cflag & PARODD)
+ mr1 |= (MR1_PARENB | MR1_PARODD);
+ else
+ mr1 |= (MR1_PARENB | MR1_PAREVEN);
+ } else {
+ mr1 |= MR1_PARNONE;
+ }
+
+ mr1 |= MR1_ERRBLOCK;
+
+/*
+ * Set the RX FIFO threshold at 8 chars. This gives a bit of breathing
+ * space for hardware flow control and the like. This should be set to
+ * VMIN.
+ */
+ mr2 |= MR2_RXFIFOHALF;
+
+/*
+ * Calculate the baud rate timers. For now we will just assume that
+ * the input and output baud are the same. The sc26198 has a fixed
+ * baud rate table, so only discrete baud rates possible.
+ */
+ baudrate = tiosp->c_cflag & CBAUD;
+ if (baudrate & CBAUDEX) {
+ baudrate &= ~CBAUDEX;
+ if ((baudrate < 1) || (baudrate > 4))
+ tiosp->c_cflag &= ~CBAUDEX;
+ else
+ baudrate += 15;
+ }
+ baudrate = stl_baudrates[baudrate];
+ if ((tiosp->c_cflag & CBAUD) == B38400) {
+ if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ baudrate = 57600;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ baudrate = 115200;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ baudrate = 230400;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ baudrate = 460800;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+ baudrate = (portp->baud_base / portp->custom_divisor);
+ }
+ if (baudrate > STL_SC26198MAXBAUD)
+ baudrate = STL_SC26198MAXBAUD;
+
+ if (baudrate > 0) {
+ for (clk = 0; (clk < SC26198_NRBAUDS); clk++) {
+ if (baudrate <= sc26198_baudtable[clk])
+ break;
+ }
+ }
+
+/*
+ * Check what form of modem signaling is required and set it up.
+ */
+ if (tiosp->c_cflag & CLOCAL) {
+ portp->flags &= ~ASYNC_CHECK_CD;
+ } else {
+ iopr |= IOPR_DCDCOS;
+ imron |= IR_IOPORT;
+ portp->flags |= ASYNC_CHECK_CD;
+ }
+
+/*
+ * Setup sc26198 enhanced modes if we can. In particular we want to
+ * handle as much of the flow control as possible automatically. As
+ * well as saving a few CPU cycles it will also greatly improve flow
+ * control reliability.
+ */
+ if (tiosp->c_iflag & IXON) {
+ mr0 |= MR0_SWFTX | MR0_SWFT;
+ imron |= IR_XONXOFF;
+ } else {
+ imroff |= IR_XONXOFF;
+ }
+ if (tiosp->c_iflag & IXOFF)
+ mr0 |= MR0_SWFRX;
+
+ if (tiosp->c_cflag & CRTSCTS) {
+ mr2 |= MR2_AUTOCTS;
+ mr1 |= MR1_AUTORTS;
+ }
+
+/*
+ * All sc26198 register values calculated so go through and set
+ * them all up.
+ */
+
+#if DEBUG
+ printk("SETPORT: portnr=%d panelnr=%d brdnr=%d\n",
+ portp->portnr, portp->panelnr, portp->brdnr);
+ printk(" mr0=%x mr1=%x mr2=%x clk=%x\n", mr0, mr1, mr2, clk);
+ printk(" iopr=%x imron=%x imroff=%x\n", iopr, imron, imroff);
+ printk(" schr1=%x schr2=%x schr3=%x schr4=%x\n",
+ tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP],
+ tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP]);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, IMR, 0);
+ stl_sc26198updatereg(portp, MR0, mr0);
+ stl_sc26198updatereg(portp, MR1, mr1);
+ stl_sc26198setreg(portp, SCCR, CR_RXERRBLOCK);
+ stl_sc26198updatereg(portp, MR2, mr2);
+ stl_sc26198updatereg(portp, IOPIOR,
+ ((stl_sc26198getreg(portp, IOPIOR) & ~IPR_CHANGEMASK) | iopr));
+
+ if (baudrate > 0) {
+ stl_sc26198setreg(portp, TXCSR, clk);
+ stl_sc26198setreg(portp, RXCSR, clk);
+ }
+
+ stl_sc26198setreg(portp, XONCR, tiosp->c_cc[VSTART]);
+ stl_sc26198setreg(portp, XOFFCR, tiosp->c_cc[VSTOP]);
+
+ ipr = stl_sc26198getreg(portp, IPR);
+ if (ipr & IPR_DCD)
+ portp->sigs &= ~TIOCM_CD;
+ else
+ portp->sigs |= TIOCM_CD;
+
+ portp->imr = (portp->imr & ~imroff) | imron;
+ stl_sc26198setreg(portp, IMR, portp->imr);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Set the state of the DTR and RTS signals.
+ */
+
+static void stl_sc26198setsignals(stlport_t *portp, int dtr, int rts)
+{
+ unsigned char iopioron, iopioroff;
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_sc26198setsignals(portp=%x,dtr=%d,rts=%d)\n",
+ (int) portp, dtr, rts);
+#endif
+
+ iopioron = 0;
+ iopioroff = 0;
+ if (dtr == 0)
+ iopioroff |= IPR_DTR;
+ else if (dtr > 0)
+ iopioron |= IPR_DTR;
+ if (rts == 0)
+ iopioroff |= IPR_RTS;
+ else if (rts > 0)
+ iopioron |= IPR_RTS;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, IOPIOR,
+ ((stl_sc26198getreg(portp, IOPIOR) & ~iopioroff) | iopioron));
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the state of the signals.
+ */
+
+static int stl_sc26198getsignals(stlport_t *portp)
+{
+ unsigned char ipr;
+ unsigned long flags;
+ int sigs;
+
+#if DEBUG
+ printk("stl_sc26198getsignals(portp=%x)\n", (int) portp);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ ipr = stl_sc26198getreg(portp, IPR);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+
+ sigs = 0;
+ sigs |= (ipr & IPR_DCD) ? 0 : TIOCM_CD;
+ sigs |= (ipr & IPR_CTS) ? 0 : TIOCM_CTS;
+ sigs |= (ipr & IPR_DTR) ? 0: TIOCM_DTR;
+ sigs |= (ipr & IPR_RTS) ? 0: TIOCM_RTS;
+ sigs |= TIOCM_DSR;
+ return(sigs);
+}
+
+/*****************************************************************************/
+
+/*
+ * Enable/Disable the Transmitter and/or Receiver.
+ */
+
+static void stl_sc26198enablerxtx(stlport_t *portp, int rx, int tx)
+{
+ unsigned char ccr;
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_sc26198enablerxtx(portp=%x,rx=%d,tx=%d)\n",
+ (int) portp, rx, tx);
+#endif
+
+ ccr = portp->crenable;
+ if (tx == 0)
+ ccr &= ~CR_TXENABLE;
+ else if (tx > 0)
+ ccr |= CR_TXENABLE;
+ if (rx == 0)
+ ccr &= ~CR_RXENABLE;
+ else if (rx > 0)
+ ccr |= CR_RXENABLE;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, SCCR, ccr);
+ BRDDISABLE(portp->brdnr);
+ portp->crenable = ccr;
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Start/stop the Transmitter and/or Receiver.
+ */
+
+static void stl_sc26198startrxtx(stlport_t *portp, int rx, int tx)
+{
+ unsigned char imr;
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_sc26198startrxtx(portp=%x,rx=%d,tx=%d)\n",
+ (int) portp, rx, tx);
+#endif
+
+ imr = portp->imr;
+ if (tx == 0)
+ imr &= ~IR_TXRDY;
+ else if (tx == 1)
+ imr |= IR_TXRDY;
+ if (rx == 0)
+ imr &= ~(IR_RXRDY | IR_RXBREAK | IR_RXWATCHDOG);
+ else if (rx > 0)
+ imr |= IR_RXRDY | IR_RXBREAK | IR_RXWATCHDOG;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, IMR, imr);
+ BRDDISABLE(portp->brdnr);
+ portp->imr = imr;
+ if (tx > 0)
+ set_bit(ASYI_TXBUSY, &portp->istate);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Disable all interrupts from this port.
+ */
+
+static void stl_sc26198disableintrs(stlport_t *portp)
+{
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_sc26198disableintrs(portp=%x)\n", (int) portp);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ portp->imr = 0;
+ stl_sc26198setreg(portp, IMR, 0);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_sc26198sendbreak(stlport_t *portp, int len)
+{
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_sc26198sendbreak(portp=%x,len=%d)\n", (int) portp, len);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ if (len == 1) {
+ stl_sc26198setreg(portp, SCCR, CR_TXSTARTBREAK);
+ portp->stats.txbreaks++;
+ } else {
+ stl_sc26198setreg(portp, SCCR, CR_TXSTOPBREAK);
+ }
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Take flow control actions...
+ */
+
+static void stl_sc26198flowctrl(stlport_t *portp, int state)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+ unsigned char mr0;
+
+#if DEBUG
+ printk("stl_sc26198flowctrl(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+
+ if (state) {
+ if (tty->termios->c_iflag & IXOFF) {
+ mr0 = stl_sc26198getreg(portp, MR0);
+ stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+ stl_sc26198setreg(portp, SCCR, CR_TXSENDXON);
+ mr0 |= MR0_SWFRX;
+ portp->stats.rxxon++;
+ stl_sc26198wait(portp);
+ stl_sc26198setreg(portp, MR0, mr0);
+ }
+/*
+ * Question: should we return RTS to what it was before? It may
+ * have been set by an ioctl... Suppose not, since if you have
+ * hardware flow control set then it is pretty silly to go and
+ * set the RTS line by hand.
+ */
+ if (tty->termios->c_cflag & CRTSCTS) {
+ stl_sc26198setreg(portp, MR1,
+ (stl_sc26198getreg(portp, MR1) | MR1_AUTORTS));
+ stl_sc26198setreg(portp, IOPIOR,
+ (stl_sc26198getreg(portp, IOPIOR) | IOPR_RTS));
+ portp->stats.rxrtson++;
+ }
+ } else {
+ if (tty->termios->c_iflag & IXOFF) {
+ mr0 = stl_sc26198getreg(portp, MR0);
+ stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+ stl_sc26198setreg(portp, SCCR, CR_TXSENDXOFF);
+ mr0 &= ~MR0_SWFRX;
+ portp->stats.rxxoff++;
+ stl_sc26198wait(portp);
+ stl_sc26198setreg(portp, MR0, mr0);
+ }
+ if (tty->termios->c_cflag & CRTSCTS) {
+ stl_sc26198setreg(portp, MR1,
+ (stl_sc26198getreg(portp, MR1) & ~MR1_AUTORTS));
+ stl_sc26198setreg(portp, IOPIOR,
+ (stl_sc26198getreg(portp, IOPIOR) & ~IOPR_RTS));
+ portp->stats.rxrtsoff++;
+ }
+ }
+
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Send a flow control character.
+ */
+
+static void stl_sc26198sendflow(stlport_t *portp, int state)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+ unsigned char mr0;
+
+#if DEBUG
+ printk("stl_sc26198sendflow(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ if (state) {
+ mr0 = stl_sc26198getreg(portp, MR0);
+ stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+ stl_sc26198setreg(portp, SCCR, CR_TXSENDXON);
+ mr0 |= MR0_SWFRX;
+ portp->stats.rxxon++;
+ stl_sc26198wait(portp);
+ stl_sc26198setreg(portp, MR0, mr0);
+ } else {
+ mr0 = stl_sc26198getreg(portp, MR0);
+ stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+ stl_sc26198setreg(portp, SCCR, CR_TXSENDXOFF);
+ mr0 &= ~MR0_SWFRX;
+ portp->stats.rxxoff++;
+ stl_sc26198wait(portp);
+ stl_sc26198setreg(portp, MR0, mr0);
+ }
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_sc26198flush(stlport_t *portp)
+{
+ unsigned long flags;
+
+#if DEBUG
+ printk("stl_sc26198flush(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, SCCR, CR_TXRESET);
+ stl_sc26198setreg(portp, SCCR, portp->crenable);
+ BRDDISABLE(portp->brdnr);
+ portp->tx.tail = portp->tx.head;
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the current state of data flow on this port. This is only
+ * really interresting when determining if data has fully completed
+ * transmission or not... The sc26198 interrupt scheme cannot
+ * determine when all data has actually drained, so we need to
+ * check the port statusy register to be sure.
+ */
+
+static int stl_sc26198datastate(stlport_t *portp)
+{
+ unsigned long flags;
+ unsigned char sr;
+
+#if DEBUG
+ printk("stl_sc26198datastate(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return(0);
+ if (test_bit(ASYI_TXBUSY, &portp->istate))
+ return(1);
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ sr = stl_sc26198getreg(portp, SR);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+
+ return((sr & SR_TXEMPTY) ? 0 : 1);
+}
+
+/*****************************************************************************/
+
+/*
+ * Delay for a small amount of time, to give the sc26198 a chance
+ * to process a command...
+ */
+
+static void stl_sc26198wait(stlport_t *portp)
+{
+ int i;
+
+#if DEBUG
+ printk("stl_sc26198wait(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ for (i = 0; (i < 20); i++)
+ stl_sc26198getglobreg(portp, TSTR);
+}
+
+/*****************************************************************************/
+
+/*
+ * If we are TX flow controlled and in IXANY mode then we may
+ * need to unflow control here. We gotta do this because of the
+ * automatic flow control modes of the sc26198.
+ */
+
+static inline void stl_sc26198txunflow(stlport_t *portp, struct tty_struct *tty)
+{
+ unsigned char mr0;
+
+ mr0 = stl_sc26198getreg(portp, MR0);
+ stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+ stl_sc26198setreg(portp, SCCR, CR_HOSTXON);
+ stl_sc26198wait(portp);
+ stl_sc26198setreg(portp, MR0, mr0);
+ clear_bit(ASYI_TXFLOWED, &portp->istate);
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for sc26198 panels.
+ */
+
+static void stl_sc26198intr(stlpanel_t *panelp, unsigned int iobase)
+{
+ stlport_t *portp;
+ unsigned int iack;
+
+/*
+ * Work around bug in sc26198 chip... Cannot have A6 address
+ * line of UART high, else iack will be returned as 0.
+ */
+ outb(0, (iobase + 1));
+
+ iack = inb(iobase + XP_IACK);
+ portp = panelp->ports[(iack & IVR_CHANMASK) + ((iobase & 0x4) << 1)];
+
+ if (iack & IVR_RXDATA)
+ stl_sc26198rxisr(portp, iack);
+ else if (iack & IVR_TXDATA)
+ stl_sc26198txisr(portp);
+ else
+ stl_sc26198otherisr(portp, iack);
+}
+
+/*****************************************************************************/
+
+/*
+ * Transmit interrupt handler. This has gotta be fast! Handling TX
+ * chars is pretty simple, stuff as many as possible from the TX buffer
+ * into the sc26198 FIFO.
+ * In practice it is possible that interrupts are enabled but that the
+ * port has been hung up. Need to handle not having any TX buffer here,
+ * this is done by using the side effect that head and tail will also
+ * be NULL if the buffer has been freed.
+ */
+
+static void stl_sc26198txisr(stlport_t *portp)
+{
+ unsigned int ioaddr;
+ unsigned char mr0;
+ int len, stlen;
+ char *head, *tail;
+
+#if DEBUG
+ printk("stl_sc26198txisr(portp=%x)\n", (int) portp);
+#endif
+
+ ioaddr = portp->ioaddr;
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ len = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head));
+ if ((len == 0) || ((len < STL_TXBUFLOW) &&
+ (test_bit(ASYI_TXLOW, &portp->istate) == 0))) {
+ set_bit(ASYI_TXLOW, &portp->istate);
+ queue_task(&portp->tqueue, &tq_scheduler);
+ }
+
+ if (len == 0) {
+ outb((MR0 | portp->uartaddr), (ioaddr + XP_ADDR));
+ mr0 = inb(ioaddr + XP_DATA);
+ if ((mr0 & MR0_TXMASK) == MR0_TXEMPTY) {
+ portp->imr &= ~IR_TXRDY;
+ outb((IMR | portp->uartaddr), (ioaddr + XP_ADDR));
+ outb(portp->imr, (ioaddr + XP_DATA));
+ clear_bit(ASYI_TXBUSY, &portp->istate);
+ } else {
+ mr0 |= ((mr0 & ~MR0_TXMASK) | MR0_TXEMPTY);
+ outb(mr0, (ioaddr + XP_DATA));
+ }
+ } else {
+ len = MIN(len, SC26198_TXFIFOSIZE);
+ portp->stats.txtotal += len;
+ stlen = MIN(len, ((portp->tx.buf + STL_TXBUFSIZE) - tail));
+ outb(GTXFIFO, (ioaddr + XP_ADDR));
+ outsb((ioaddr + XP_DATA), tail, stlen);
+ len -= stlen;
+ tail += stlen;
+ if (tail >= (portp->tx.buf + STL_TXBUFSIZE))
+ tail = portp->tx.buf;
+ if (len > 0) {
+ outsb((ioaddr + XP_DATA), tail, len);
+ tail += len;
+ }
+ portp->tx.tail = tail;
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Receive character interrupt handler. Determine if we have good chars
+ * or bad chars and then process appropriately. Good chars are easy
+ * just shove the lot into the RX buffer and set all status byte to 0.
+ * If a bad RX char then process as required. This routine needs to be
+ * fast! In practice it is possible that we get an interrupt on a port
+ * that is closed. This can happen on hangups - since they completely
+ * shutdown a port not in user context. Need to handle this case.
+ */
+
+static void stl_sc26198rxisr(stlport_t *portp, unsigned int iack)
+{
+ struct tty_struct *tty;
+ unsigned int len, buflen, ioaddr;
+
+#if DEBUG
+ printk("stl_sc26198rxisr(portp=%x,iack=%x)\n", (int) portp, iack);
+#endif
+
+ tty = portp->tty;
+ ioaddr = portp->ioaddr;
+ outb(GIBCR, (ioaddr + XP_ADDR));
+ len = inb(ioaddr + XP_DATA) + 1;
+
+ if ((iack & IVR_TYPEMASK) == IVR_RXDATA) {
+ if ((tty == (struct tty_struct *) NULL) ||
+ (tty->flip.char_buf_ptr == (char *) NULL) ||
+ ((buflen = TTY_FLIPBUF_SIZE - tty->flip.count) == 0)) {
+ len = MIN(len, sizeof(stl_unwanted));
+ outb(GRXFIFO, (ioaddr + XP_ADDR));
+ insb((ioaddr + XP_DATA), &stl_unwanted[0], len);
+ portp->stats.rxlost += len;
+ portp->stats.rxtotal += len;
+ } else {
+ len = MIN(len, buflen);
+ if (len > 0) {
+ outb(GRXFIFO, (ioaddr + XP_ADDR));
+ insb((ioaddr + XP_DATA), tty->flip.char_buf_ptr, len);
+ memset(tty->flip.flag_buf_ptr, 0, len);
+ tty->flip.flag_buf_ptr += len;
+ tty->flip.char_buf_ptr += len;
+ tty->flip.count += len;
+ tty_schedule_flip(tty);
+ portp->stats.rxtotal += len;
+ }
+ }
+ } else {
+ stl_sc26198rxbadchars(portp);
+ }
+
+/*
+ * If we are TX flow controlled and in IXANY mode then we may need
+ * to unflow control here. We gotta do this because of the automatic
+ * flow control modes of the sc26198.
+ */
+ if (test_bit(ASYI_TXFLOWED, &portp->istate)) {
+ if ((tty != (struct tty_struct *) NULL) &&
+ (tty->termios != (struct termios *) NULL) &&
+ (tty->termios->c_iflag & IXANY)) {
+ stl_sc26198txunflow(portp, tty);
+ }
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Process an RX bad character.
+ */
+
+static void inline stl_sc26198rxbadch(stlport_t *portp, unsigned char status, char ch)
+{
+ struct tty_struct *tty;
+ unsigned int ioaddr;
+
+ tty = portp->tty;
+ ioaddr = portp->ioaddr;
+
+ if (status & SR_RXPARITY)
+ portp->stats.rxparity++;
+ if (status & SR_RXFRAMING)
+ portp->stats.rxframing++;
+ if (status & SR_RXOVERRUN)
+ portp->stats.rxoverrun++;
+ if (status & SR_RXBREAK)
+ portp->stats.rxbreaks++;
+
+ if ((tty != (struct tty_struct *) NULL) &&
+ ((portp->rxignoremsk & status) == 0)) {
+ if (portp->rxmarkmsk & status) {
+ if (status & SR_RXBREAK) {
+ status = TTY_BREAK;
+ if (portp->flags & ASYNC_SAK) {
+ do_SAK(tty);
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ }
+ } else if (status & SR_RXPARITY) {
+ status = TTY_PARITY;
+ } else if (status & SR_RXFRAMING) {
+ status = TTY_FRAME;
+ } else if(status & SR_RXOVERRUN) {
+ status = TTY_OVERRUN;
+ } else {
+ status = 0;
+ }
+ } else {
+ status = 0;
+ }
+
+ if (tty->flip.char_buf_ptr != (char *) NULL) {
+ if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+ *tty->flip.flag_buf_ptr++ = status;
+ *tty->flip.char_buf_ptr++ = ch;
+ tty->flip.count++;
+ }
+ tty_schedule_flip(tty);
+ }
+
+ if (status == 0)
+ portp->stats.rxtotal++;
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Process all characters in the RX FIFO of the UART. Check all char
+ * status bytes as well, and process as required. We need to check
+ * all bytes in the FIFO, in case some more enter the FIFO while we
+ * are here. To get the exact character error type we need to switch
+ * into CHAR error mode (that is why we need to make sure we empty
+ * the FIFO).
+ */
+
+static void stl_sc26198rxbadchars(stlport_t *portp)
+{
+ unsigned char status, mr1;
+ char ch;
+
+/*
+ * To get the precise error type for each character we must switch
+ * back into CHAR error mode.
+ */
+ mr1 = stl_sc26198getreg(portp, MR1);
+ stl_sc26198setreg(portp, MR1, (mr1 & ~MR1_ERRBLOCK));
+
+ while ((status = stl_sc26198getreg(portp, SR)) & SR_RXRDY) {
+ stl_sc26198setreg(portp, SCCR, CR_CLEARRXERR);
+ ch = stl_sc26198getreg(portp, RXFIFO);
+ stl_sc26198rxbadch(portp, status, ch);
+ }
+
+/*
+ * To get correct interrupt class we must switch back into BLOCK
+ * error mode.
+ */
+ stl_sc26198setreg(portp, MR1, mr1);
+}
+
+/*****************************************************************************/
+
+/*
+ * Other interrupt handler. This includes modem signals, flow
+ * control actions, etc. Most stuff is left to off-level interrupt
+ * processing time.
+ */
+
+static void stl_sc26198otherisr(stlport_t *portp, unsigned int iack)
+{
+ unsigned char cir, ipr, xisr;
+
+#if DEBUG
+ printk("stl_sc26198otherisr(portp=%x,iack=%x)\n", (int) portp, iack);
+#endif
+
+ cir = stl_sc26198getglobreg(portp, CIR);
+
+ switch (cir & CIR_SUBTYPEMASK) {
+ case CIR_SUBCOS:
+ ipr = stl_sc26198getreg(portp, IPR);
+ if (ipr & IPR_DCDCHANGE) {
+ set_bit(ASYI_DCDCHANGE, &portp->istate);
+ queue_task(&portp->tqueue, &tq_scheduler);
+ portp->stats.modem++;
+ }
+ break;
+ case CIR_SUBXONXOFF:
+ xisr = stl_sc26198getreg(portp, XISR);
+ if (xisr & XISR_RXXONGOT) {
+ set_bit(ASYI_TXFLOWED, &portp->istate);
+ portp->stats.txxoff++;
+ }
+ if (xisr & XISR_RXXOFFGOT) {
+ clear_bit(ASYI_TXFLOWED, &portp->istate);
+ portp->stats.txxon++;
+ }
+ break;
+ case CIR_SUBBREAK:
+ stl_sc26198setreg(portp, SCCR, CR_BREAKRESET);
+ stl_sc26198rxbadchars(portp);
+ break;
+ default:
+ break;
+ }
+}
+
+/*****************************************************************************/
diff --git a/drivers/media/video/stradis.c b/drivers/media/video/stradis.c
new file mode 100644
index 000000000..03ca27a25
--- /dev/null
+++ b/drivers/media/video/stradis.c
@@ -0,0 +1,2287 @@
+/*
+ * stradis.c - stradis 4:2:2 mpeg decoder driver
+ *
+ * Stradis 4:2:2 MPEG-2 Decoder Driver
+ * Copyright (C) 1999 Nathan Laredo <laredo@gnu.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/pci.h>
+#include <linux/signal.h>
+#include <asm/io.h>
+#include <linux/ioport.h>
+#include <asm/pgtable.h>
+#include <asm/page.h>
+#include <linux/sched.h>
+#include <asm/segment.h>
+#include <asm/types.h>
+#include <linux/types.h>
+#include <linux/wrapper.h>
+#include <linux/interrupt.h>
+#include <asm/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/videodev.h>
+#include <linux/i2c-old.h>
+
+#include "saa7146.h"
+#include "saa7146reg.h"
+#include "ibmmpeg2.h"
+#include "saa7121.h"
+#include "cs8420.h"
+
+#define DEBUG(x) /* debug driver */
+#undef IDEBUG(x) /* debug irq handler */
+#undef MDEBUG(x) /* debug memory management */
+
+#define SAA7146_MAX 6
+
+static struct saa7146 saa7146s[SAA7146_MAX];
+
+static int saa_num = 0; /* number of SAA7146s in use */
+
+#define nDebNormal 0x00480000
+#define nDebNoInc 0x00480000
+#define nDebVideo 0xd0480000
+#define nDebAudio 0xd0400000
+#define nDebDMA 0x02c80000
+
+#define oDebNormal 0x13c80000
+#define oDebNoInc 0x13c80000
+#define oDebVideo 0xd1080000
+#define oDebAudio 0xd1080000
+#define oDebDMA 0x03080000
+
+#define NewCard (saa->boardcfg[3])
+#define ChipControl (saa->boardcfg[1])
+#define NTSCFirstActive (saa->boardcfg[4])
+#define PALFirstActive (saa->boardcfg[5])
+#define NTSCLastActive (saa->boardcfg[54])
+#define PALLastActive (saa->boardcfg[55])
+#define Have2MB (saa->boardcfg[18] & 0x40)
+#define HaveCS8420 (saa->boardcfg[18] & 0x04)
+#define IBMMPEGCD20 (saa->boardcfg[18] & 0x20)
+#define HaveCS3310 (saa->boardcfg[18] & 0x01)
+#define CS3310MaxLvl ((saa->boardcfg[30] << 8) | saa->boardcfg[31])
+#define HaveCS4341 (saa->boardcfg[40] == 2)
+#define SDIType (saa->boardcfg[27])
+#define CurrentMode (saa->boardcfg[2])
+
+#define debNormal (NewCard ? nDebNormal : oDebNormal)
+#define debNoInc (NewCard ? nDebNoInc : oDebNoInc)
+#define debVideo (NewCard ? nDebVideo : oDebVideo)
+#define debAudio (NewCard ? nDebAudio : oDebAudio)
+#define debDMA (NewCard ? nDebDMA : oDebDMA)
+
+#ifdef DEBUG
+int stradis_driver(void) /* for the benefit of ksymoops */
+{
+ return 1;
+}
+#endif
+
+#ifdef USE_RESCUE_EEPROM_SDM275
+static unsigned char rescue_eeprom[64] = {
+0x00,0x01,0x04,0x13,0x26,0x0f,0x10,0x00,0x00,0x00,0x43,0x63,0x22,0x01,0x29,0x15,0x73,0x00,0x1f, 'd', 'e', 'c', 'x', 'l', 'd', 'v', 'a',0x02,0x00,0x01,0x00,0xcc,0xa4,0x63,0x09,0xe2,0x10,0x00,0x0a,0x00,0x02,0x02, 'd', 'e', 'c', 'x', 'l', 'a',0x00,0x00,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+};
+#endif
+
+/* ----------------------------------------------------------------------- */
+/* Hardware I2C functions */
+static void I2CWipe(struct saa7146 *saa)
+{
+ int i;
+ /* set i2c to ~=100kHz, abort transfer, clear busy */
+ saawrite(0x600 | SAA7146_I2C_ABORT, SAA7146_I2C_STATUS);
+ saawrite((SAA7146_MC2_UPLD_I2C << 16) |
+ SAA7146_MC2_UPLD_I2C, SAA7146_MC2);
+ /* wait for i2c registers to be programmed */
+ for (i = 0; i < 1000 &&
+ !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++)
+ schedule();
+ saawrite(0x600, SAA7146_I2C_STATUS);
+ saawrite((SAA7146_MC2_UPLD_I2C << 16) |
+ SAA7146_MC2_UPLD_I2C, SAA7146_MC2);
+ /* wait for i2c registers to be programmed */
+ for (i = 0; i < 1000 &&
+ !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++)
+ schedule();
+ saawrite(0x600, SAA7146_I2C_STATUS);
+ saawrite((SAA7146_MC2_UPLD_I2C << 16) |
+ SAA7146_MC2_UPLD_I2C, SAA7146_MC2);
+ /* wait for i2c registers to be programmed */
+ for (i = 0; i < 1000 &&
+ !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++)
+ schedule();
+}
+/* read I2C */
+static int I2CRead(struct i2c_bus *bus, unsigned char addr,
+ unsigned char subaddr, int dosub)
+{
+ struct saa7146 *saa = (struct saa7146 *) bus->data;
+ int i;
+
+
+ if (saaread(SAA7146_I2C_STATUS) & 0x3c)
+ I2CWipe(saa);
+ for (i = 0; i < 1000 &&
+ (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_BUSY); i++)
+ schedule();
+ if (i == 1000)
+ I2CWipe(saa);
+ if (dosub)
+ saawrite(((addr & 0xfe) << 24) | (((addr | 1) & 0xff) << 8) |
+ ((subaddr & 0xff) << 16) | 0xed, SAA7146_I2C_TRANSFER);
+ else
+ saawrite(((addr & 0xfe) << 24) | (((addr | 1) & 0xff) << 16) |
+ 0xf1, SAA7146_I2C_TRANSFER);
+ saawrite((SAA7146_MC2_UPLD_I2C << 16) |
+ SAA7146_MC2_UPLD_I2C, SAA7146_MC2);
+ /* wait for i2c registers to be programmed */
+ for (i = 0; i < 1000 &&
+ !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++)
+ schedule();
+ /* wait for valid data */
+ for (i = 0; i < 1000 &&
+ (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_BUSY); i++)
+ schedule();
+ if (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_ERR)
+ return -1;
+ if (i == 1000)
+ printk("i2c setup read timeout\n");
+ saawrite(0x41, SAA7146_I2C_TRANSFER);
+ saawrite((SAA7146_MC2_UPLD_I2C << 16) |
+ SAA7146_MC2_UPLD_I2C, SAA7146_MC2);
+ /* wait for i2c registers to be programmed */
+ for (i = 0; i < 1000 &&
+ !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++)
+ schedule();
+ /* wait for valid data */
+ for (i = 0; i < 1000 &&
+ (saaread(SAA7146_I2C_TRANSFER) & SAA7146_I2C_BUSY); i++)
+ schedule();
+ if (saaread(SAA7146_I2C_TRANSFER) & SAA7146_I2C_ERR)
+ return -1;
+ if (i == 1000)
+ printk("i2c read timeout\n");
+ return ((saaread(SAA7146_I2C_TRANSFER) >> 24) & 0xff);
+}
+static int I2CReadOld(struct i2c_bus *bus, unsigned char addr)
+{
+ return I2CRead(bus, addr, 0, 0);
+}
+
+/* set both to write both bytes, reset it to write only b1 */
+
+static int I2CWrite(struct i2c_bus *bus, unsigned char addr, unsigned char b1,
+ unsigned char b2, int both)
+{
+ struct saa7146 *saa = (struct saa7146 *) bus->data;
+ int i;
+ u32 data;
+
+ if (saaread(SAA7146_I2C_STATUS) & 0x3c)
+ I2CWipe(saa);
+ for (i = 0; i < 1000 &&
+ (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_BUSY); i++)
+ schedule();
+ if (i == 1000)
+ I2CWipe(saa);
+ data = ((addr & 0xfe) << 24) | ((b1 & 0xff) << 16);
+ if (both)
+ data |= ((b2 & 0xff) << 8) | 0xe5;
+ else
+ data |= 0xd1;
+ saawrite(data, SAA7146_I2C_TRANSFER);
+ saawrite((SAA7146_MC2_UPLD_I2C << 16) | SAA7146_MC2_UPLD_I2C,
+ SAA7146_MC2);
+ return 0;
+}
+
+static void attach_inform(struct i2c_bus *bus, int id)
+{
+ struct saa7146 *saa = (struct saa7146 *) bus->data;
+ int i;
+
+ DEBUG(printk(KERN_DEBUG "stradis%d: i2c: device found=%02x\n", saa->nr, id));
+ if (id == 0xa0) { /* we have rev2 or later board, fill in info */
+ for (i = 0; i < 64; i++)
+ saa->boardcfg[i] = I2CRead(bus, 0xa0, i, 1);
+#ifdef USE_RESCUE_EEPROM_SDM275
+ if (saa->boardcfg[0] != 0) {
+ printk("stradis%d: WARNING: EEPROM STORED VALUES HAVE BEEN IGNORED\n", saa->nr);
+ for (i = 0; i < 64; i++)
+ saa->boardcfg[i] = rescue_eeprom[i];
+ }
+#endif
+ printk("stradis%d: config =", saa->nr);
+ for (i = 0; i < 51; i++) {
+ printk(" %02x",saa->boardcfg[i]);
+ }
+ printk("\n");
+ }
+}
+
+static void detach_inform(struct i2c_bus *bus, int id)
+{
+ struct saa7146 *saa = (struct saa7146 *) bus->data;
+ int i;
+ i = saa->nr;
+}
+
+static void I2CBusScan(struct i2c_bus *bus)
+{
+ int i;
+ for (i = 0; i < 0xff; i += 2)
+ if ((I2CRead(bus, i, 0, 0)) >= 0)
+ attach_inform(bus, i);
+}
+
+static struct i2c_bus saa7146_i2c_bus_template =
+{
+ "saa7146",
+ I2C_BUSID_BT848,
+ NULL,
+ SPIN_LOCK_UNLOCKED,
+ attach_inform,
+ detach_inform,
+ NULL,
+ NULL,
+ I2CReadOld,
+ I2CWrite,
+};
+
+static int debiwait_maxwait = 0;
+
+static int wait_for_debi_done(struct saa7146 *saa)
+{
+ int i;
+
+ /* wait for registers to be programmed */
+ for (i = 0; i < 100000 &&
+ !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_DEBI); i++)
+ saaread(SAA7146_MC2);
+ /* wait for transfer to complete */
+ for (i = 0; i < 500000 &&
+ (saaread(SAA7146_PSR) & SAA7146_PSR_DEBI_S); i++)
+ saaread(SAA7146_MC2);
+ if (i > debiwait_maxwait)
+ printk("wait-for-debi-done maxwait: %d\n",
+ debiwait_maxwait = i);
+
+ if (i == 500000)
+ return -1;
+ return 0;
+}
+
+static int debiwrite(struct saa7146 *saa, u32 config, int addr,
+ u32 val, int count)
+{
+ u32 cmd;
+ if (count <= 0 || count > 32764)
+ return -1;
+ if (wait_for_debi_done(saa) < 0)
+ return -1;
+ saawrite(config, SAA7146_DEBI_CONFIG);
+ if (count <= 4) /* immediate transfer */
+ saawrite(val, SAA7146_DEBI_AD);
+ else /* block transfer */
+ saawrite(virt_to_bus(saa->dmadebi), SAA7146_DEBI_AD);
+ saawrite((cmd = (count << 17) | (addr & 0xffff)), SAA7146_DEBI_COMMAND);
+ saawrite((SAA7146_MC2_UPLD_DEBI << 16) | SAA7146_MC2_UPLD_DEBI,
+ SAA7146_MC2);
+ return 0;
+}
+
+static u32 debiread(struct saa7146 *saa, u32 config, int addr, int count)
+{
+ u32 result = 0;
+
+ if (count > 32764 || count <= 0)
+ return 0;
+ if (wait_for_debi_done(saa) < 0)
+ return 0;
+ saawrite(virt_to_bus(saa->dmadebi), SAA7146_DEBI_AD);
+ saawrite((count << 17) | 0x10000 | (addr & 0xffff),
+ SAA7146_DEBI_COMMAND);
+ saawrite(config, SAA7146_DEBI_CONFIG);
+ saawrite((SAA7146_MC2_UPLD_DEBI << 16) | SAA7146_MC2_UPLD_DEBI,
+ SAA7146_MC2);
+ if (count > 4) /* not an immediate transfer */
+ return count;
+ wait_for_debi_done(saa);
+ result = saaread(SAA7146_DEBI_AD);
+ if (count == 1)
+ result &= 0xff;
+ if (count == 2)
+ result &= 0xffff;
+ if (count == 3)
+ result &= 0xffffff;
+ return result;
+}
+
+#if 0 /* unused */
+/* MUST be a multiple of 8 bytes and 8-byte aligned and < 32768 bytes */
+/* data copied into saa->dmadebi buffer, caller must re-enable interrupts */
+static void ibm_block_dram_read(struct saa7146 *saa, int address, int bytes)
+{
+ int i, j;
+ u32 *buf;
+ buf = (u32 *) saa->dmadebi;
+ if (bytes > 0x7000)
+ bytes = 0x7000;
+ saawrite(0, SAA7146_IER); /* disable interrupts */
+ for (i=0; i < 10000 &&
+ (debiread(saa, debNormal, IBM_MP2_DRAM_CMD_STAT, 2)
+ & 0x8000); i++)
+ saaread(SAA7146_MC2);
+ if (i == 10000)
+ printk(KERN_ERR "stradis%d: dram_busy never cleared\n",
+ saa->nr);
+ debiwrite(saa, debNormal, IBM_MP2_SRC_ADDR, (address<<16) |
+ (address>>16), 4);
+ debiwrite(saa, debNormal, IBM_MP2_BLOCK_SIZE, bytes, 2);
+ debiwrite(saa, debNormal, IBM_MP2_CMD_STAT, 0x8a10, 2);
+ for (j = 0; j < bytes/4; j++) {
+ for (i = 0; i < 10000 &&
+ (!(debiread(saa, debNormal, IBM_MP2_DRAM_CMD_STAT, 2)
+ & 0x4000)); i++)
+ saaread(SAA7146_MC2);
+ if (i == 10000)
+ printk(KERN_ERR "stradis%d: dram_ready never set\n",
+ saa->nr);
+ buf[j] = debiread(saa, debNormal, IBM_MP2_DRAM_DATA, 4);
+ }
+}
+#endif /* unused */
+
+static void do_irq_send_data(struct saa7146 *saa)
+{
+ int split, audbytes, vidbytes;
+
+ saawrite(SAA7146_PSR_PIN1, SAA7146_IER);
+ /* if special feature mode in effect, disable audio sending */
+ if (saa->playmode != VID_PLAY_NORMAL)
+ saa->audtail = saa->audhead = 0;
+ if (saa->audhead <= saa->audtail)
+ audbytes = saa->audtail - saa->audhead;
+ else
+ audbytes = 65536 - (saa->audhead - saa->audtail);
+ if (saa->vidhead <= saa->vidtail)
+ vidbytes = saa->vidtail - saa->vidhead;
+ else
+ vidbytes = 524288 - (saa->vidhead - saa->vidtail);
+ if (audbytes == 0 && vidbytes == 0 && saa->osdtail == saa->osdhead) {
+ saawrite(0, SAA7146_IER);
+ return;
+ }
+ /* if at least 1 block audio waiting and audio fifo isn't full */
+ if (audbytes >= 2048 && (debiread(saa, debNormal,
+ IBM_MP2_AUD_FIFO, 2) & 0xff) < 60) {
+ if (saa->audhead > saa->audtail)
+ split = 65536 - saa->audhead;
+ else
+ split = 0;
+ audbytes = 2048;
+ if (split > 0 && split < 2048) {
+ memcpy(saa->dmadebi, saa->audbuf + saa->audhead,
+ split);
+ saa->audhead = 0;
+ audbytes -= split;
+ } else
+ split = 0;
+ memcpy(saa->dmadebi + split, saa->audbuf + saa->audhead,
+ audbytes);
+ saa->audhead += audbytes;
+ saa->audhead &= 0xffff;
+ debiwrite(saa, debAudio, (NewCard? IBM_MP2_AUD_FIFO :
+ IBM_MP2_AUD_FIFOW), 0, 2048);
+ wake_up_interruptible(&saa->audq);
+ /* if at least 1 block video waiting and video fifo isn't full */
+ } else if (vidbytes >= 30720 && (debiread(saa, debNormal,
+ IBM_MP2_FIFO, 2)) < 16384) {
+ if (saa->vidhead > saa->vidtail)
+ split = 524288 - saa->vidhead;
+ else
+ split = 0;
+ vidbytes = 30720;
+ if (split > 0 && split < 30720) {
+ memcpy(saa->dmadebi, saa->vidbuf + saa->vidhead,
+ split);
+ saa->vidhead = 0;
+ vidbytes -= split;
+ } else
+ split = 0;
+ memcpy(saa->dmadebi + split, saa->vidbuf + saa->vidhead,
+ vidbytes);
+ saa->vidhead += vidbytes;
+ saa->vidhead &= 0x7ffff;
+ debiwrite(saa, debVideo, (NewCard ? IBM_MP2_FIFO :
+ IBM_MP2_FIFOW), 0, 30720);
+ wake_up_interruptible(&saa->vidq);
+ }
+ saawrite(SAA7146_PSR_DEBI_S | SAA7146_PSR_PIN1, SAA7146_IER);
+}
+
+static void send_osd_data(struct saa7146 *saa)
+{
+ int size = saa->osdtail - saa->osdhead;
+ if (size > 30720)
+ size = 30720;
+ /* ensure some multiple of 8 bytes is transferred */
+ size = 8 * ((size + 8)>>3);
+ if (size) {
+ debiwrite(saa, debNormal, IBM_MP2_OSD_ADDR,
+ (saa->osdhead>>3), 2);
+ memcpy(saa->dmadebi, &saa->osdbuf[saa->osdhead], size);
+ saa->osdhead += size;
+ /* block transfer of next 8 bytes to ~32k bytes */
+ debiwrite(saa, debNormal, IBM_MP2_OSD_DATA, 0, size);
+ }
+ if (saa->osdhead >= saa->osdtail) {
+ saa->osdhead = saa->osdtail = 0;
+ debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00c, 2);
+ }
+}
+
+static void saa7146_irq(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct saa7146 *saa = (struct saa7146 *) dev_id;
+ u32 stat, astat;
+ int count;
+
+ count = 0;
+ while (1) {
+ /* get/clear interrupt status bits */
+ stat = saaread(SAA7146_ISR);
+ astat = stat & saaread(SAA7146_IER);
+ if (!astat)
+ return;
+ saawrite(astat, SAA7146_ISR);
+ if (astat & SAA7146_PSR_DEBI_S) {
+ do_irq_send_data(saa);
+ }
+ if (astat & SAA7146_PSR_PIN1) {
+ int istat;
+ /* the following read will trigger DEBI_S */
+ istat = debiread(saa, debNormal, IBM_MP2_HOST_INT, 2);
+ if (istat & 1) {
+ saawrite(0, SAA7146_IER);
+ send_osd_data(saa);
+ saawrite(SAA7146_PSR_DEBI_S |
+ SAA7146_PSR_PIN1, SAA7146_IER);
+ }
+ if (istat & 0x20) { /* Video Start */
+ saa->vidinfo.frame_count++;
+ }
+ if (istat & 0x400) { /* Picture Start */
+ /* update temporal reference */
+ }
+ if (istat & 0x200) { /* Picture Resolution Change */
+ /* read new resolution */
+ }
+ if (istat & 0x100) { /* New User Data found */
+ /* read new user data */
+ }
+ if (istat & 0x1000) { /* new GOP/SMPTE */
+ /* read new SMPTE */
+ }
+ if (istat & 0x8000) { /* Sequence Start Code */
+ /* reset frame counter, load sizes */
+ saa->vidinfo.frame_count = 0;
+ saa->vidinfo.h_size = 704;
+ saa->vidinfo.v_size = 480;
+#if 0
+ if (saa->endmarkhead != saa->endmarktail) {
+ saa->audhead =
+ saa->endmark[saa->endmarkhead];
+ saa->endmarkhead++;
+ if (saa->endmarkhead >= MAX_MARKS)
+ saa->endmarkhead = 0;
+ }
+#endif
+ }
+ if (istat & 0x4000) { /* Sequence Error Code */
+ if (saa->endmarkhead != saa->endmarktail) {
+ saa->audhead =
+ saa->endmark[saa->endmarkhead];
+ saa->endmarkhead++;
+ if (saa->endmarkhead >= MAX_MARKS)
+ saa->endmarkhead = 0;
+ }
+ }
+ }
+#ifdef IDEBUG
+ if (astat & SAA7146_PSR_PPEF) {
+ IDEBUG(printk("stradis%d irq: PPEF\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_PABO) {
+ IDEBUG(printk("stradis%d irq: PABO\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_PPED) {
+ IDEBUG(printk("stradis%d irq: PPED\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_RPS_I1) {
+ IDEBUG(printk("stradis%d irq: RPS_I1\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_RPS_I0) {
+ IDEBUG(printk("stradis%d irq: RPS_I0\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_RPS_LATE1) {
+ IDEBUG(printk("stradis%d irq: RPS_LATE1\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_RPS_LATE0) {
+ IDEBUG(printk("stradis%d irq: RPS_LATE0\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_RPS_E1) {
+ IDEBUG(printk("stradis%d irq: RPS_E1\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_RPS_E0) {
+ IDEBUG(printk("stradis%d irq: RPS_E0\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_RPS_TO1) {
+ IDEBUG(printk("stradis%d irq: RPS_TO1\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_RPS_TO0) {
+ IDEBUG(printk("stradis%d irq: RPS_TO0\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_UPLD) {
+ IDEBUG(printk("stradis%d irq: UPLD\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_DEBI_E) {
+ IDEBUG(printk("stradis%d irq: DEBI_E\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_I2C_S) {
+ IDEBUG(printk("stradis%d irq: I2C_S\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_I2C_E) {
+ IDEBUG(printk("stradis%d irq: I2C_E\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_A2_IN) {
+ IDEBUG(printk("stradis%d irq: A2_IN\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_A2_OUT) {
+ IDEBUG(printk("stradis%d irq: A2_OUT\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_A1_IN) {
+ IDEBUG(printk("stradis%d irq: A1_IN\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_A1_OUT) {
+ IDEBUG(printk("stradis%d irq: A1_OUT\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_AFOU) {
+ IDEBUG(printk("stradis%d irq: AFOU\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_V_PE) {
+ IDEBUG(printk("stradis%d irq: V_PE\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_VFOU) {
+ IDEBUG(printk("stradis%d irq: VFOU\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_FIDA) {
+ IDEBUG(printk("stradis%d irq: FIDA\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_FIDB) {
+ IDEBUG(printk("stradis%d irq: FIDB\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_PIN3) {
+ IDEBUG(printk("stradis%d irq: PIN3\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_PIN2) {
+ IDEBUG(printk("stradis%d irq: PIN2\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_PIN0) {
+ IDEBUG(printk("stradis%d irq: PIN0\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_ECS) {
+ IDEBUG(printk("stradis%d irq: ECS\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_EC3S) {
+ IDEBUG(printk("stradis%d irq: EC3S\n", saa->nr));
+ }
+ if (astat & SAA7146_PSR_EC0S) {
+ IDEBUG(printk("stradis%d irq: EC0S\n", saa->nr));
+ }
+#endif
+ count++;
+ if (count > 15)
+ printk(KERN_WARNING "stradis%d: irq loop %d\n",
+ saa->nr, count);
+ if (count > 20) {
+ saawrite(0, SAA7146_IER);
+ printk(KERN_ERR
+ "stradis%d: IRQ loop cleared\n", saa->nr);
+ }
+ }
+}
+
+static int ibm_send_command(struct saa7146 *saa,
+ int command, int data, int chain)
+{
+ int i;
+
+ if (chain)
+ debiwrite(saa, debNormal, IBM_MP2_COMMAND, (command << 1) | 1, 2);
+ else
+ debiwrite(saa, debNormal, IBM_MP2_COMMAND, command << 1, 2);
+ debiwrite(saa, debNormal, IBM_MP2_CMD_DATA, data, 2);
+ debiwrite(saa, debNormal, IBM_MP2_CMD_STAT, 1, 2);
+ for (i = 0; i < 100 &&
+ (debiread(saa, debNormal, IBM_MP2_CMD_STAT, 2) & 1); i++)
+ schedule();
+ if (i == 100)
+ return -1;
+ return 0;
+}
+
+static void cs4341_setlevel(struct saa7146 *saa, int left, int right)
+{
+ I2CWrite(&(saa->i2c), 0x22, 0x03,
+ left > 94 ? 94 : left, 2);
+ I2CWrite(&(saa->i2c), 0x22, 0x04,
+ right > 94 ? 94 : right, 2);
+}
+
+static void initialize_cs4341(struct saa7146 *saa)
+{
+ int i;
+ for (i = 0; i < 200; i++) {
+ /* auto mute off, power on, no de-emphasis */
+ /* I2S data up to 24-bit 64xFs internal SCLK */
+ I2CWrite(&(saa->i2c), 0x22, 0x01, 0x11, 2);
+ /* ATAPI mixer setings */
+ I2CWrite(&(saa->i2c), 0x22, 0x02, 0x49, 2);
+ /* attenuation left 3db */
+ I2CWrite(&(saa->i2c), 0x22, 0x03, 0x00, 2);
+ /* attenuation right 3db */
+ I2CWrite(&(saa->i2c), 0x22, 0x04, 0x00, 2);
+ I2CWrite(&(saa->i2c), 0x22, 0x01, 0x10, 2);
+ if (I2CRead(&(saa->i2c), 0x22, 0x02, 1) == 0x49)
+ break;
+ schedule();
+ }
+ printk("stradis%d: CS4341 initialized (%d)\n", saa->nr, i);
+ return;
+}
+
+static void initialize_cs8420(struct saa7146 *saa, int pro)
+{
+ int i;
+ u8 *sequence;
+ if (pro)
+ sequence = mode8420pro;
+ else
+ sequence = mode8420con;
+ for (i = 0; i < INIT8420LEN; i++)
+ I2CWrite(&(saa->i2c), 0x20, init8420[i * 2],
+ init8420[i * 2 + 1], 2);
+ for (i = 0; i < MODE8420LEN; i++)
+ I2CWrite(&(saa->i2c), 0x20, sequence[i * 2],
+ sequence[i * 2 + 1], 2);
+ printk("stradis%d: CS8420 initialized\n", saa->nr);
+}
+
+static void initialize_saa7121(struct saa7146 *saa, int dopal)
+{
+ int i, mod;
+ u8 *sequence;
+ if (dopal)
+ sequence = init7121pal;
+ else
+ sequence = init7121ntsc;
+ mod = saaread(SAA7146_PSR) & 0x08;
+ /* initialize PAL/NTSC video encoder */
+ for (i = 0; i < INIT7121LEN; i++) {
+ if (NewCard) { /* handle new card encoder differences */
+ if (sequence[i*2] == 0x3a)
+ I2CWrite(&(saa->i2c), 0x88, 0x3a, 0x13, 2);
+ else if (sequence[i*2] == 0x6b)
+ I2CWrite(&(saa->i2c), 0x88, 0x6b, 0x20, 2);
+ else if (sequence[i*2] == 0x6c)
+ I2CWrite(&(saa->i2c), 0x88, 0x6c,
+ dopal ? 0x09 : 0xf5, 2);
+ else if (sequence[i*2] == 0x6d)
+ I2CWrite(&(saa->i2c), 0x88, 0x6d,
+ dopal ? 0x20 : 0x00, 2);
+ else if (sequence[i*2] == 0x7a)
+ I2CWrite(&(saa->i2c), 0x88, 0x7a,
+ dopal ? (PALFirstActive - 1) :
+ (NTSCFirstActive - 4), 2);
+ else if (sequence[i*2] == 0x7b)
+ I2CWrite(&(saa->i2c), 0x88, 0x7b,
+ dopal ? PALLastActive :
+ NTSCLastActive, 2);
+ else I2CWrite(&(saa->i2c), 0x88, sequence[i * 2],
+ sequence[i * 2 + 1], 2);
+ } else {
+ if (sequence[i*2] == 0x6b && mod)
+ I2CWrite(&(saa->i2c), 0x88, 0x6b,
+ (sequence[i * 2 + 1] ^ 0x09), 2);
+ else if (sequence[i*2] == 0x7a)
+ I2CWrite(&(saa->i2c), 0x88, 0x7a,
+ dopal ? (PALFirstActive - 1) :
+ (NTSCFirstActive - 4), 2);
+ else if (sequence[i*2] == 0x7b)
+ I2CWrite(&(saa->i2c), 0x88, 0x7b,
+ dopal ? PALLastActive :
+ NTSCLastActive, 2);
+ else
+ I2CWrite(&(saa->i2c), 0x88, sequence[i * 2],
+ sequence[i * 2 + 1], 2);
+ }
+ }
+}
+
+static void set_genlock_offset(struct saa7146 *saa, int noffset)
+{
+ int nCode;
+ int PixelsPerLine = 858;
+ if (CurrentMode == VIDEO_MODE_PAL)
+ PixelsPerLine = 864;
+ if (noffset > 500)
+ noffset = 500;
+ else if (noffset < -500)
+ noffset = -500;
+ nCode = noffset + 0x100;
+ if (nCode == 1)
+ nCode = 0x401;
+ else if (nCode < 1) nCode = 0x400 + PixelsPerLine + nCode;
+ debiwrite(saa, debNormal, XILINX_GLDELAY, nCode, 2);
+}
+
+static void set_out_format(struct saa7146 *saa, int mode)
+{
+ initialize_saa7121(saa, (mode == VIDEO_MODE_NTSC ? 0 : 1));
+ saa->boardcfg[2] = mode;
+ /* do not adjust analog video parameters here, use saa7121 init */
+ /* you will affect the SDI output on the new card */
+ if (mode == VIDEO_MODE_PAL) { /* PAL */
+ debiwrite(saa, debNormal, XILINX_CTL0, 0x0808, 2);
+ mdelay(50);
+ saawrite(0x012002c0, SAA7146_NUM_LINE_BYTE1);
+ if (NewCard) {
+ debiwrite(saa, debNormal, IBM_MP2_DISP_MODE,
+ 0xe100, 2);
+ mdelay(50);
+ }
+ debiwrite(saa, debNormal, IBM_MP2_DISP_MODE,
+ NewCard ? 0xe500: 0x6500, 2);
+ debiwrite(saa, debNormal, IBM_MP2_DISP_DLY,
+ (1 << 8) |
+ (NewCard ? PALFirstActive : PALFirstActive-6), 2);
+ } else { /* NTSC */
+ debiwrite(saa, debNormal, XILINX_CTL0, 0x0800, 2);
+ mdelay(50);
+ saawrite(0x00f002c0, SAA7146_NUM_LINE_BYTE1);
+ debiwrite(saa, debNormal, IBM_MP2_DISP_MODE,
+ NewCard ? 0xe100: 0x6100, 2);
+ debiwrite(saa, debNormal, IBM_MP2_DISP_DLY,
+ (1 << 8) |
+ (NewCard ? NTSCFirstActive : NTSCFirstActive-6), 2);
+ }
+}
+
+
+/* Intialize bitmangler to map from a byte value to the mangled word that
+ * must be output to program the Xilinx part through the DEBI port.
+ * Xilinx Data Bit->DEBI Bit: 0->15 1->7 2->6 3->12 4->11 5->2 6->1 7->0
+ * transfer FPGA code, init IBM chip, transfer IBM microcode
+ * rev2 card mangles: 0->7 1->6 2->5 3->4 4->3 5->2 6->1 7->0
+ */
+static u16 bitmangler[256];
+
+static int initialize_fpga(struct video_code *bitdata)
+{
+ int i, num, startindex, failure = 0, loadtwo, loadfile = 0;
+ u16 *dmabuf;
+ u8 *newdma;
+ struct saa7146 *saa;
+
+ /* verify fpga code */
+ for (startindex = 0; startindex < bitdata->datasize; startindex++)
+ if (bitdata->data[startindex] == 255)
+ break;
+ if (startindex == bitdata->datasize) {
+ printk(KERN_INFO "stradis: bad fpga code\n");
+ return -1;
+ }
+ /* initialize all detected cards */
+ for (num = 0; num < saa_num; num++) {
+ saa = &saa7146s[num];
+ if (saa->boardcfg[0] > 20)
+ continue; /* card was programmed */
+ loadtwo = (saa->boardcfg[18] & 0x10);
+ if (!NewCard) /* we have an old board */
+ for (i = 0; i < 256; i++)
+ bitmangler[i] = ((i & 0x01) << 15) |
+ ((i & 0x02) << 6) | ((i & 0x04) << 4) |
+ ((i & 0x08) << 9) | ((i & 0x10) << 7) |
+ ((i & 0x20) >> 3) | ((i & 0x40) >> 5) |
+ ((i & 0x80) >> 7);
+ else /* else we have a new board */
+ for (i = 0; i < 256; i++)
+ bitmangler[i] = ((i & 0x01) << 7) |
+ ((i & 0x02) << 5) | ((i & 0x04) << 3) |
+ ((i & 0x08) << 1) | ((i & 0x10) >> 1) |
+ ((i & 0x20) >> 3) | ((i & 0x40) >> 5) |
+ ((i & 0x80) >> 7);
+
+ dmabuf = (u16 *) saa->dmadebi;
+ newdma = (u8 *) saa->dmadebi;
+ if (NewCard) { /* SDM2xxx */
+ if (!strncmp(bitdata->loadwhat, "decoder2", 8))
+ continue; /* fpga not for this card */
+ if (!strncmp(&saa->boardcfg[42],
+ bitdata->loadwhat, 8)) {
+ loadfile = 1;
+ } else if (loadtwo && !strncmp(&saa->boardcfg[19],
+ bitdata->loadwhat, 8)) {
+ loadfile = 2;
+ } else if (!saa->boardcfg[42] && /* special */
+ !strncmp("decxl", bitdata->loadwhat, 8)) {
+ loadfile = 1;
+ } else
+ continue; /* fpga not for this card */
+ if (loadfile != 1 && loadfile != 2) {
+ continue; /* skip to next card */
+ }
+ if (saa->boardcfg[0] && loadfile == 1 )
+ continue; /* skip to next card */
+ if (saa->boardcfg[0] != 1 && loadfile == 2)
+ continue; /* skip to next card */
+ saa->boardcfg[0]++; /* mark fpga handled */
+ printk("stradis%d: loading %s\n", saa->nr,
+ bitdata->loadwhat);
+ if (loadtwo && loadfile == 2)
+ goto send_fpga_stuff;
+ /* turn on the Audio interface to set PROG low */
+ saawrite(0x00400040, SAA7146_GPIO_CTRL);
+ saaread(SAA7146_PSR); /* ensure posted write */
+ /* wait for everyone to reset */
+ mdelay(10);
+ saawrite(0x00400000, SAA7146_GPIO_CTRL);
+ } else { /* original card */
+ if (strncmp(bitdata->loadwhat, "decoder2", 8))
+ continue; /* fpga not for this card */
+ /* Pull the Xilinx PROG signal WS3 low */
+ saawrite(0x02000200, SAA7146_MC1);
+ /* Turn on the Audio interface so can set PROG low */
+ saawrite(0x000000c0, SAA7146_ACON1);
+ /* Pull the Xilinx INIT signal (GPIO2) low */
+ saawrite(0x00400000, SAA7146_GPIO_CTRL);
+ /* Make sure everybody resets */
+ saaread(SAA7146_PSR); /* ensure posted write */
+ mdelay(10);
+ /* Release the Xilinx PROG signal */
+ saawrite(0x00000000, SAA7146_ACON1);
+ /* Turn off the Audio interface */
+ saawrite(0x02000000, SAA7146_MC1);
+ }
+ /* Release Xilinx INIT signal (WS2) */
+ saawrite(0x00000000, SAA7146_GPIO_CTRL);
+ /* Wait for the INIT to go High */
+ for (i = 0; i < 10000 &&
+ !(saaread(SAA7146_PSR) & SAA7146_PSR_PIN2); i++)
+ schedule();
+ if (i == 1000) {
+ printk(KERN_INFO "stradis%d: no fpga INIT\n", saa->nr);
+ return -1;
+ }
+send_fpga_stuff:
+ if (NewCard) {
+ for (i = startindex; i < bitdata->datasize; i++)
+ newdma[i - startindex] =
+ bitmangler[bitdata->data[i]];
+ debiwrite(saa, 0x01420000, 0, 0,
+ ((bitdata->datasize - startindex) + 5));
+ if (loadtwo) {
+ if (loadfile == 1) {
+ printk("stradis%d: "
+ "awaiting 2nd FPGA bitfile\n",
+ saa->nr);
+ continue; /* skip to next card */
+ }
+
+ }
+ } else {
+ for (i = startindex; i < bitdata->datasize; i++)
+ dmabuf[i - startindex] =
+ bitmangler[bitdata->data[i]];
+ debiwrite(saa, 0x014a0000, 0, 0,
+ ((bitdata->datasize - startindex) + 5) * 2);
+ }
+ for (i = 0; i < 1000 &&
+ !(saaread(SAA7146_PSR) & SAA7146_PSR_PIN2); i++)
+ schedule();
+ if (i == 1000) {
+ printk(KERN_INFO "stradis%d: FPGA load failed\n",
+ saa->nr);
+ failure++;
+ continue;
+ }
+ if (!NewCard) {
+ /* Pull the Xilinx INIT signal (GPIO2) low */
+ saawrite(0x00400000, SAA7146_GPIO_CTRL);
+ saaread(SAA7146_PSR); /* ensure posted write */
+ mdelay(2);
+ saawrite(0x00000000, SAA7146_GPIO_CTRL);
+ mdelay(2);
+ }
+ printk(KERN_INFO "stradis%d: FPGA Loaded\n", saa->nr);
+ saa->boardcfg[0] = 26; /* mark fpga programmed */
+ /* set VXCO to its lowest frequency */
+ debiwrite(saa, debNormal, XILINX_PWM, 0, 2);
+ if (NewCard) {
+ /* mute CS3310 */
+ if (HaveCS3310)
+ debiwrite(saa, debNormal, XILINX_CS3310_CMPLT,
+ 0, 2);
+ /* set VXCO to PWM mode, release reset, blank on */
+ debiwrite(saa, debNormal, XILINX_CTL0, 0xffc4, 2);
+ mdelay(10);
+ /* unmute CS3310 */
+ if (HaveCS3310)
+ debiwrite(saa, debNormal, XILINX_CTL0,
+ 0x2020, 2);
+ }
+ /* set source Black */
+ debiwrite(saa, debNormal, XILINX_CTL0, 0x1707, 2);
+ saa->boardcfg[4] = 22; /* set NTSC First Active Line */
+ saa->boardcfg[5] = 23; /* set PAL First Active Line */
+ saa->boardcfg[54] = 2; /* set NTSC Last Active Line - 256 */
+ saa->boardcfg[55] = 54; /* set PAL Last Active Line - 256 */
+ set_out_format(saa, VIDEO_MODE_NTSC);
+ mdelay(50);
+ /* begin IBM chip init */
+ debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 4, 2);
+ saaread(SAA7146_PSR); /* wait for reset */
+ mdelay(5);
+ debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 0, 2);
+ debiread(saa, debNormal, IBM_MP2_CHIP_CONTROL, 2);
+ debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 0x10, 2);
+ debiwrite(saa, debNormal, IBM_MP2_CMD_ADDR, 0, 2);
+ debiwrite(saa, debNormal, IBM_MP2_CHIP_MODE, 0x2e, 2);
+ if (NewCard) {
+ mdelay(5);
+ /* set i2s rate converter to 48KHz */
+ debiwrite(saa, debNormal, 0x80c0, 6, 2);
+ /* we must init CS8420 first since rev b pulls i2s */
+ /* master clock low and CS4341 needs i2s master to */
+ /* run the i2c port. */
+ if (HaveCS8420) {
+ /* 0=consumer, 1=pro */
+ initialize_cs8420(saa, 0);
+ }
+ mdelay(5);
+ if (HaveCS4341)
+ initialize_cs4341(saa);
+ }
+ debiwrite(saa, debNormal, IBM_MP2_INFC_CTL, 0x48, 2);
+ debiwrite(saa, debNormal, IBM_MP2_BEEP_CTL, 0xa000, 2);
+ debiwrite(saa, debNormal, IBM_MP2_DISP_LBOR, 0, 2);
+ debiwrite(saa, debNormal, IBM_MP2_DISP_TBOR, 0, 2);
+ if (NewCard)
+ set_genlock_offset(saa, 0);
+ debiwrite(saa, debNormal, IBM_MP2_FRNT_ATTEN, 0, 2);
+#if 0
+ /* enable genlock */
+ debiwrite(saa, debNormal, XILINX_CTL0, 0x8000, 2);
+#else
+ /* disable genlock */
+ debiwrite(saa, debNormal, XILINX_CTL0, 0x8080, 2);
+#endif
+ }
+ return failure;
+}
+
+static int do_ibm_reset(struct saa7146 *saa)
+{
+ /* failure if decoder not previously programmed */
+ if (saa->boardcfg[0] < 37)
+ return -EIO;
+ /* mute CS3310 */
+ if (HaveCS3310)
+ debiwrite(saa, debNormal, XILINX_CS3310_CMPLT, 0, 2);
+ /* disable interrupts */
+ saawrite(0, SAA7146_IER);
+ saa->audhead = saa->audtail = 0;
+ saa->vidhead = saa->vidtail = 0;
+ /* tristate debi bus, disable debi transfers */
+ saawrite(0x00880000, SAA7146_MC1);
+ /* ensure posted write */
+ saaread(SAA7146_MC1);
+ mdelay(50);
+ /* re-enable debi transfers */
+ saawrite(0x00880088, SAA7146_MC1);
+ /* set source Black */
+ debiwrite(saa, debNormal, XILINX_CTL0, 0x1707, 2);
+ /* begin IBM chip init */
+ set_out_format(saa, CurrentMode);
+ debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 4, 2);
+ saaread(SAA7146_PSR); /* wait for reset */
+ mdelay(5);
+ debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 0, 2);
+ debiread(saa, debNormal, IBM_MP2_CHIP_CONTROL, 2);
+ debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, ChipControl, 2);
+ debiwrite(saa, debNormal, IBM_MP2_CHIP_MODE, 0x2e, 2);
+ if (NewCard) {
+ mdelay(5);
+ /* set i2s rate converter to 48KHz */
+ debiwrite(saa, debNormal, 0x80c0, 6, 2);
+ /* we must init CS8420 first since rev b pulls i2s */
+ /* master clock low and CS4341 needs i2s master to */
+ /* run the i2c port. */
+ if (HaveCS8420) {
+ /* 0=consumer, 1=pro */
+ initialize_cs8420(saa, 1);
+ }
+ mdelay(5);
+ if (HaveCS4341)
+ initialize_cs4341(saa);
+ }
+ debiwrite(saa, debNormal, IBM_MP2_INFC_CTL, 0x48, 2);
+ debiwrite(saa, debNormal, IBM_MP2_BEEP_CTL, 0xa000, 2);
+ debiwrite(saa, debNormal, IBM_MP2_DISP_LBOR, 0, 2);
+ debiwrite(saa, debNormal, IBM_MP2_DISP_TBOR, 0, 2);
+ if (NewCard)
+ set_genlock_offset(saa, 0);
+ debiwrite(saa, debNormal, IBM_MP2_FRNT_ATTEN, 0, 2);
+ debiwrite(saa, debNormal, IBM_MP2_OSD_SIZE, 0x2000, 2);
+ debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4552, 2);
+ if (ibm_send_command(saa, IBM_MP2_CONFIG_DECODER,
+ (ChipControl == 0x43 ? 0xe800 : 0xe000), 1)) {
+ printk(KERN_ERR "stradis%d: IBM config failed\n", saa->nr);
+ }
+ if (HaveCS3310) {
+ int i = CS3310MaxLvl;
+ debiwrite(saa, debNormal, XILINX_CS3310_CMPLT, ((i<<8)|i), 2);
+ }
+ /* start video decoder */
+ debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, ChipControl, 2);
+ /* 256k vid, 3520 bytes aud */
+ debiwrite(saa, debNormal, IBM_MP2_RB_THRESHOLD, 0x4037, 2);
+ debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4573, 2);
+ ibm_send_command(saa, IBM_MP2_PLAY, 0, 0);
+ /* enable buffer threshold irq */
+ debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00c, 2);
+ /* clear pending interrupts */
+ debiread(saa, debNormal, IBM_MP2_HOST_INT, 2);
+ debiwrite(saa, debNormal, XILINX_CTL0, 0x1711, 2);
+ return 0;
+}
+
+/* load the decoder microcode */
+static int initialize_ibmmpeg2(struct video_code *microcode)
+{
+ int i, num;
+ struct saa7146 *saa;
+
+ for (num = 0; num < saa_num; num++) {
+ saa = &saa7146s[num];
+ /* check that FPGA is loaded */
+ debiwrite(saa, debNormal, IBM_MP2_OSD_SIZE, 0xa55a, 2);
+ if ((i = debiread(saa, debNormal, IBM_MP2_OSD_SIZE, 2)) !=
+ 0xa55a) {
+ printk(KERN_INFO "stradis%d: %04x != 0xa55a\n",
+ saa->nr, i);
+#if 0
+ return -1;
+#endif
+ }
+ if (!strncmp(microcode->loadwhat, "decoder.vid", 11)) {
+ if (saa->boardcfg[0] > 27)
+ continue; /* skip to next card */
+ /* load video control store */
+ saa->boardcfg[1] = 0x13; /* no-sync default */
+ debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 1, 2);
+ debiwrite(saa, debNormal, IBM_MP2_PROC_IADDR, 0, 2);
+ for (i = 0; i < microcode->datasize / 2; i++)
+ debiwrite(saa, debNormal, IBM_MP2_PROC_IDATA,
+ (microcode->data[i * 2] << 8) |
+ microcode->data[i * 2 + 1], 2);
+ debiwrite(saa, debNormal, IBM_MP2_PROC_IADDR, 0, 2);
+ debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 0, 2);
+ debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL,
+ ChipControl, 2);
+ saa->boardcfg[0] = 28;
+ }
+ if (!strncmp(microcode->loadwhat, "decoder.aud", 11)) {
+ if (saa->boardcfg[0] > 35)
+ continue; /* skip to next card */
+ /* load audio control store */
+ debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 1, 2);
+ debiwrite(saa, debNormal, IBM_MP2_AUD_IADDR, 0, 2);
+ for (i = 0; i < microcode->datasize; i++)
+ debiwrite(saa, debNormal, IBM_MP2_AUD_IDATA,
+ microcode->data[i], 1);
+ debiwrite(saa, debNormal, IBM_MP2_AUD_IADDR, 0, 2);
+ debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 0, 2);
+ debiwrite(saa, debNormal, IBM_MP2_OSD_SIZE, 0x2000, 2);
+ debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4552, 2);
+ if (ibm_send_command(saa, IBM_MP2_CONFIG_DECODER,
+ 0xe000, 1)) {
+ printk(KERN_ERR
+ "stradis%d: IBM config failed\n",
+ saa->nr);
+ return -1;
+ }
+ /* set PWM to center value */
+ if (NewCard) {
+ debiwrite(saa, debNormal, XILINX_PWM,
+ saa->boardcfg[14] +
+ (saa->boardcfg[13]<<8), 2);
+ } else
+ debiwrite(saa, debNormal, XILINX_PWM,
+ 0x46, 2);
+ if (HaveCS3310) {
+ i = CS3310MaxLvl;
+ debiwrite(saa, debNormal,
+ XILINX_CS3310_CMPLT, ((i<<8)|i), 2);
+ }
+ printk(KERN_INFO
+ "stradis%d: IBM MPEGCD%d Initialized\n",
+ saa->nr, 18 + (debiread(saa, debNormal,
+ IBM_MP2_CHIP_CONTROL, 2) >> 12));
+ /* start video decoder */
+ debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL,
+ ChipControl, 2);
+ debiwrite(saa, debNormal, IBM_MP2_RB_THRESHOLD,
+ 0x4037, 2); /* 256k vid, 3520 bytes aud */
+ debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4573, 2);
+ ibm_send_command(saa, IBM_MP2_PLAY, 0, 0);
+ /* enable buffer threshold irq */
+ debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00c, 2);
+ debiread(saa, debNormal, IBM_MP2_HOST_INT, 2);
+ /* enable gpio irq */
+ saawrite(0x00002000, SAA7146_GPIO_CTRL);
+ /* enable decoder output to HPS */
+ debiwrite(saa, debNormal, XILINX_CTL0, 0x1711, 2);
+ saa->boardcfg[0] = 37;
+ }
+ }
+ return 0;
+}
+
+static u32 palette2fmt[] =
+{ /* some of these YUV translations are wrong */
+ 0xffffffff, 0x86000000, 0x87000000, 0x80000000, 0x8100000, 0x82000000,
+ 0x83000000, 0x00000000, 0x03000000, 0x03000000, 0x0a00000, 0x03000000,
+ 0x06000000, 0x00000000, 0x03000000, 0x0a000000, 0x0300000
+};
+static int bpp2fmt[4] =
+{
+ VIDEO_PALETTE_HI240, VIDEO_PALETTE_RGB565, VIDEO_PALETTE_RGB24,
+ VIDEO_PALETTE_RGB32
+};
+
+/* I wish I could find a formula to calculate these... */
+static u32 h_prescale[64] =
+{
+ 0x10000000, 0x18040202, 0x18080000, 0x380c0606, 0x38100204, 0x38140808,
+ 0x38180000, 0x381c0000, 0x3820161c, 0x38242a3b, 0x38281230, 0x382c4460,
+ 0x38301040, 0x38340080, 0x38380000, 0x383c0000, 0x3840fefe, 0x3844ee9f,
+ 0x3848ee9f, 0x384cee9f, 0x3850ee9f, 0x38542a3b, 0x38581230, 0x385c0000,
+ 0x38600000, 0x38640000, 0x38680000, 0x386c0000, 0x38700000, 0x38740000,
+ 0x38780000, 0x387c0000, 0x30800000, 0x38840000, 0x38880000, 0x388c0000,
+ 0x38900000, 0x38940000, 0x38980000, 0x389c0000, 0x38a00000, 0x38a40000,
+ 0x38a80000, 0x38ac0000, 0x38b00000, 0x38b40000, 0x38b80000, 0x38bc0000,
+ 0x38c00000, 0x38c40000, 0x38c80000, 0x38cc0000, 0x38d00000, 0x38d40000,
+ 0x38d80000, 0x38dc0000, 0x38e00000, 0x38e40000, 0x38e80000, 0x38ec0000,
+ 0x38f00000, 0x38f40000, 0x38f80000, 0x38fc0000,
+};
+static u32 v_gain[64] =
+{
+ 0x016000ff, 0x016100ff, 0x016100ff, 0x016200ff, 0x016200ff, 0x016200ff,
+ 0x016200ff, 0x016300ff, 0x016300ff, 0x016300ff, 0x016300ff, 0x016300ff,
+ 0x016300ff, 0x016300ff, 0x016300ff, 0x016400ff, 0x016400ff, 0x016400ff,
+ 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff,
+ 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff,
+ 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff,
+ 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff,
+ 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff,
+ 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff,
+ 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff,
+ 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff,
+};
+
+
+static void saa7146_set_winsize(struct saa7146 *saa)
+{
+ u32 format;
+ int offset, yacl, ysci;
+ saa->win.color_fmt = format =
+ (saa->win.depth == 15) ? palette2fmt[VIDEO_PALETTE_RGB555] :
+ palette2fmt[bpp2fmt[(saa->win.bpp - 1) & 3]];
+ offset = saa->win.x * saa->win.bpp + saa->win.y * saa->win.bpl;
+ saawrite(saa->win.vidadr + offset, SAA7146_BASE_EVEN1);
+ saawrite(saa->win.vidadr + offset + saa->win.bpl, SAA7146_BASE_ODD1);
+ saawrite(saa->win.bpl * 2, SAA7146_PITCH1);
+ saawrite(saa->win.vidadr + saa->win.bpl * saa->win.sheight,
+ SAA7146_PROT_ADDR1);
+ saawrite(0, SAA7146_PAGE1);
+ saawrite(format|0x60, SAA7146_CLIP_FORMAT_CTRL);
+ offset = (704 / (saa->win.width - 1)) & 0x3f;
+ saawrite(h_prescale[offset], SAA7146_HPS_H_PRESCALE);
+ offset = (720896 / saa->win.width) / (offset + 1);
+ saawrite((offset<<12)|0x0c, SAA7146_HPS_H_SCALE);
+ if (CurrentMode == VIDEO_MODE_NTSC) {
+ yacl = /*(480 / saa->win.height - 1) & 0x3f*/ 0;
+ ysci = 1024 - (saa->win.height * 1024 / 480);
+ } else {
+ yacl = /*(576 / saa->win.height - 1) & 0x3f*/ 0;
+ ysci = 1024 - (saa->win.height * 1024 / 576);
+ }
+ saawrite((1<<31)|(ysci<<21)|(yacl<<15), SAA7146_HPS_V_SCALE);
+ saawrite(v_gain[yacl], SAA7146_HPS_V_GAIN);
+ saawrite(((SAA7146_MC2_UPLD_DMA1 | SAA7146_MC2_UPLD_HPS_V |
+ SAA7146_MC2_UPLD_HPS_H) << 16) | (SAA7146_MC2_UPLD_DMA1 |
+ SAA7146_MC2_UPLD_HPS_V | SAA7146_MC2_UPLD_HPS_H),
+ SAA7146_MC2);
+}
+
+/* clip_draw_rectangle(cm,x,y,w,h) -- handle clipping an area
+ * bitmap is fixed width, 128 bytes (1024 pixels represented)
+ * arranged most-sigificant-bit-left in 32-bit words
+ * based on saa7146 clipping hardware, it swaps bytes if LE
+ * much of this makes up for egcs brain damage -- so if you
+ * are wondering "why did he do this?" it is because the C
+ * was adjusted to generate the optimal asm output without
+ * writing non-portable __asm__ directives.
+ */
+
+static void clip_draw_rectangle(u32 *clipmap, int x, int y, int w, int h)
+{
+ register int startword, endword;
+ register u32 bitsleft, bitsright;
+ u32 *temp;
+ if (x < 0) {
+ w += x;
+ x = 0;
+ }
+ if (y < 0) {
+ h += y;
+ y = 0;
+ }
+ if (w <= 0 || h <= 0 || x > 1023 || y > 639)
+ return; /* throw away bad clips */
+ if (x + w > 1024)
+ w = 1024 - x;
+ if (y + h > 640)
+ h = 640 - y;
+ startword = (x >> 5);
+ endword = ((x + w) >> 5);
+ bitsleft = (0xffffffff >> (x & 31));
+ bitsright = (0xffffffff << (~((x + w) - (endword<<5))));
+ temp = &clipmap[(y<<5) + startword];
+ w = endword - startword;
+ if (!w) {
+ bitsleft |= bitsright;
+ for (y = 0; y < h; y++) {
+ *temp |= bitsleft;
+ temp += 32;
+ }
+ } else {
+ for (y = 0; y < h; y++) {
+ *temp++ |= bitsleft;
+ for (x = 1; x < w; x++)
+ *temp++ = 0xffffffff;
+ *temp |= bitsright;
+ temp += (32 - w);
+ }
+ }
+}
+
+static void make_clip_tab(struct saa7146 *saa, struct video_clip *cr, int ncr)
+{
+ int i, width, height;
+ u32 *clipmap;
+
+ clipmap = saa->dmavid2;
+ if((width=saa->win.width)>1023)
+ width = 1023; /* sanity check */
+ if((height=saa->win.height)>640)
+ height = 639; /* sanity check */
+ if (ncr > 0) { /* rectangles pased */
+ /* convert rectangular clips to a bitmap */
+ memset(clipmap, 0, VIDEO_CLIPMAP_SIZE); /* clear map */
+ for (i = 0; i < ncr; i++)
+ clip_draw_rectangle(clipmap, cr[i].x, cr[i].y,
+ cr[i].width, cr[i].height);
+ }
+ /* clip against viewing window AND screen
+ so we do not have to rely on the user program
+ */
+ clip_draw_rectangle(clipmap,(saa->win.x+width>saa->win.swidth) ?
+ (saa->win.swidth-saa->win.x) : width, 0, 1024, 768);
+ clip_draw_rectangle(clipmap,0,(saa->win.y+height>saa->win.sheight) ?
+ (saa->win.sheight-saa->win.y) : height,1024,768);
+ if (saa->win.x<0)
+ clip_draw_rectangle(clipmap, 0, 0, -(saa->win.x), 768);
+ if (saa->win.y<0)
+ clip_draw_rectangle(clipmap, 0, 0, 1024, -(saa->win.y));
+}
+
+static int saa_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct saa7146 *saa = (struct saa7146 *) dev;
+ switch (cmd) {
+ case VIDIOCGCAP:
+ {
+ struct video_capability b;
+ strcpy(b.name, saa->video_dev.name);
+ b.type = VID_TYPE_CAPTURE |
+ VID_TYPE_OVERLAY |
+ VID_TYPE_CLIPPING |
+ VID_TYPE_FRAMERAM |
+ VID_TYPE_SCALES;
+ b.channels = 1;
+ b.audios = 1;
+ b.maxwidth = 768;
+ b.maxheight = 576;
+ b.minwidth = 32;
+ b.minheight = 32;
+ if (copy_to_user(arg, &b, sizeof(b)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCGPICT:
+ {
+ struct video_picture p = saa->picture;
+ if (saa->win.depth == 8)
+ p.palette = VIDEO_PALETTE_HI240;
+ if (saa->win.depth == 15)
+ p.palette = VIDEO_PALETTE_RGB555;
+ if (saa->win.depth == 16)
+ p.palette = VIDEO_PALETTE_RGB565;
+ if (saa->win.depth == 24)
+ p.palette = VIDEO_PALETTE_RGB24;
+ if (saa->win.depth == 32)
+ p.palette = VIDEO_PALETTE_RGB32;
+ if (copy_to_user(arg, &p, sizeof(p)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSPICT:
+ {
+ struct video_picture p;
+ u32 format;
+ if (copy_from_user(&p, arg, sizeof(p)))
+ return -EFAULT;
+ if (p.palette < sizeof(palette2fmt) / sizeof(u32)) {
+ format = palette2fmt[p.palette];
+ saa->win.color_fmt = format;
+ saawrite(format|0x60, SAA7146_CLIP_FORMAT_CTRL);
+ }
+ saawrite(((p.brightness & 0xff00) << 16) |
+ ((p.contrast & 0xfe00) << 7) |
+ ((p.colour & 0xfe00) >> 9), SAA7146_BCS_CTRL);
+ saa->picture = p;
+ /* upload changed registers */
+ saawrite(((SAA7146_MC2_UPLD_HPS_H |
+ SAA7146_MC2_UPLD_HPS_V) << 16) |
+ SAA7146_MC2_UPLD_HPS_H | SAA7146_MC2_UPLD_HPS_V,
+ SAA7146_MC2);
+ return 0;
+ }
+ case VIDIOCSWIN:
+ {
+ struct video_window vw;
+ struct video_clip *vcp = NULL;
+
+ if (copy_from_user(&vw, arg, sizeof(vw)))
+ return -EFAULT;
+
+ if (vw.flags || vw.width < 16 || vw.height < 16) { /* stop capture */
+ saawrite((SAA7146_MC1_TR_E_1 << 16), SAA7146_MC1);
+ return -EINVAL;
+ }
+ if (saa->win.bpp < 4) { /* 32-bit align start and adjust width */
+ int i = vw.x;
+ vw.x = (vw.x + 3) & ~3;
+ i = vw.x - i;
+ vw.width -= i;
+ }
+ saa->win.x = vw.x;
+ saa->win.y = vw.y;
+ saa->win.width = vw.width;
+ if (saa->win.width > 768)
+ saa->win.width = 768;
+ saa->win.height = vw.height;
+ if (CurrentMode == VIDEO_MODE_NTSC) {
+ if (saa->win.height > 480)
+ saa->win.height = 480;
+ } else {
+ if (saa->win.height > 576)
+ saa->win.height = 576;
+ }
+
+ /* stop capture */
+ saawrite((SAA7146_MC1_TR_E_1 << 16), SAA7146_MC1);
+ saa7146_set_winsize(saa);
+
+ /*
+ * Do any clips.
+ */
+ if (vw.clipcount < 0) {
+ if (copy_from_user(saa->dmavid2, vw.clips,
+ VIDEO_CLIPMAP_SIZE))
+ return -EFAULT;
+ } else if (vw.clipcount > 0) {
+ if ((vcp = vmalloc(sizeof(struct video_clip) *
+ (vw.clipcount))) == NULL)
+ return -ENOMEM;
+ if (copy_from_user(vcp, vw.clips,
+ sizeof(struct video_clip) *
+ vw.clipcount)) {
+ vfree(vcp);
+ return -EFAULT;
+ }
+ } else /* nothing clipped */
+ memset(saa->dmavid2, 0, VIDEO_CLIPMAP_SIZE);
+ make_clip_tab(saa, vcp, vw.clipcount);
+ if (vw.clipcount > 0)
+ vfree(vcp);
+
+ /* start capture & clip dma if we have an address */
+ if ((saa->cap & 3) && saa->win.vidadr != 0)
+ saawrite(((SAA7146_MC1_TR_E_1 |
+ SAA7146_MC1_TR_E_2) << 16) | 0xffff,
+ SAA7146_MC1);
+ return 0;
+ }
+ case VIDIOCGWIN:
+ {
+ struct video_window vw;
+ vw.x = saa->win.x;
+ vw.y = saa->win.y;
+ vw.width = saa->win.width;
+ vw.height = saa->win.height;
+ vw.chromakey = 0;
+ vw.flags = 0;
+ if (copy_to_user(arg, &vw, sizeof(vw)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCCAPTURE:
+ {
+ int v;
+ if (copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ if (v == 0) {
+ saa->cap &= ~1;
+ saawrite((SAA7146_MC1_TR_E_1 << 16),
+ SAA7146_MC1);
+ } else {
+ if (saa->win.vidadr == 0 || saa->win.width == 0
+ || saa->win.height == 0)
+ return -EINVAL;
+ saa->cap |= 1;
+ saawrite((SAA7146_MC1_TR_E_1 << 16) | 0xffff,
+ SAA7146_MC1);
+ }
+ return 0;
+ }
+ case VIDIOCGFBUF:
+ {
+ struct video_buffer v;
+ v.base = (void *) saa->win.vidadr;
+ v.height = saa->win.sheight;
+ v.width = saa->win.swidth;
+ v.depth = saa->win.depth;
+ v.bytesperline = saa->win.bpl;
+ if (copy_to_user(arg, &v, sizeof(v)))
+ return -EFAULT;
+ return 0;
+
+ }
+ case VIDIOCSFBUF:
+ {
+ struct video_buffer v;
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ if (v.depth != 8 && v.depth != 15 && v.depth != 16 &&
+ v.depth != 24 && v.depth != 32 && v.width > 16 &&
+ v.height > 16 && v.bytesperline > 16)
+ return -EINVAL;
+ if (v.base)
+ saa->win.vidadr = (unsigned long) v.base;
+ saa->win.sheight = v.height;
+ saa->win.swidth = v.width;
+ saa->win.bpp = ((v.depth + 7) & 0x38) / 8;
+ saa->win.depth = v.depth;
+ saa->win.bpl = v.bytesperline;
+
+ DEBUG(printk("Display at %p is %d by %d, bytedepth %d, bpl %d\n",
+ v.base, v.width, v.height, saa->win.bpp, saa->win.bpl));
+ saa7146_set_winsize(saa);
+ return 0;
+ }
+ case VIDIOCKEY:
+ {
+ /* Will be handled higher up .. */
+ return 0;
+ }
+
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+ v = saa->audio_dev;
+ v.flags &= ~(VIDEO_AUDIO_MUTE | VIDEO_AUDIO_MUTABLE);
+ v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME;
+ strcpy(v.name, "MPEG");
+ v.mode = VIDEO_SOUND_STEREO;
+ if (copy_to_user(arg, &v, sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio v;
+ int i;
+ if (copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ i = (~(v.volume>>8))&0xff;
+ if (!HaveCS4341) {
+ if (v.flags & VIDEO_AUDIO_MUTE) {
+ debiwrite(saa, debNormal,
+ IBM_MP2_FRNT_ATTEN,
+ 0xffff, 2);
+ }
+ if (!(v.flags & VIDEO_AUDIO_MUTE))
+ debiwrite(saa, debNormal,
+ IBM_MP2_FRNT_ATTEN,
+ 0x0000, 2);
+ if (v.flags & VIDEO_AUDIO_VOLUME)
+ debiwrite(saa, debNormal,
+ IBM_MP2_FRNT_ATTEN,
+ (i<<8)|i, 2);
+ } else {
+ if (v.flags & VIDEO_AUDIO_MUTE)
+ cs4341_setlevel(saa, 0xff, 0xff);
+ if (!(v.flags & VIDEO_AUDIO_MUTE))
+ cs4341_setlevel(saa, 0, 0);
+ if (v.flags & VIDEO_AUDIO_VOLUME)
+ cs4341_setlevel(saa, i, i);
+ }
+ saa->audio_dev = v;
+ return 0;
+ }
+
+ case VIDIOCGUNIT:
+ {
+ struct video_unit vu;
+ vu.video = saa->video_dev.minor;
+ vu.vbi = VIDEO_NO_UNIT;
+ vu.radio = VIDEO_NO_UNIT;
+ vu.audio = VIDEO_NO_UNIT;
+ vu.teletext = VIDEO_NO_UNIT;
+ if (copy_to_user((void *) arg, (void *) &vu, sizeof(vu)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSPLAYMODE:
+ {
+ struct video_play_mode pmode;
+ if (copy_from_user((void *) &pmode, arg,
+ sizeof(struct video_play_mode)))
+ return -EFAULT;
+ switch (pmode.mode) {
+ case VID_PLAY_VID_OUT_MODE:
+ if (pmode.p1 != VIDEO_MODE_NTSC &&
+ pmode.p1 != VIDEO_MODE_PAL)
+ return -EINVAL;
+ set_out_format(saa, pmode.p1);
+ return 0;
+ case VID_PLAY_GENLOCK:
+ debiwrite(saa, debNormal,
+ XILINX_CTL0,
+ (pmode.p1 ? 0x8000 : 0x8080),
+ 2);
+ if (NewCard)
+ set_genlock_offset(saa,
+ pmode.p2);
+ return 0;
+ case VID_PLAY_NORMAL:
+ debiwrite(saa, debNormal,
+ IBM_MP2_CHIP_CONTROL,
+ ChipControl, 2);
+ ibm_send_command(saa,
+ IBM_MP2_PLAY, 0, 0);
+ saa->playmode = pmode.mode;
+ return 0;
+ case VID_PLAY_PAUSE:
+ /* IBM removed the PAUSE command */
+ /* they say use SINGLE_FRAME now */
+ case VID_PLAY_SINGLE_FRAME:
+ ibm_send_command(saa,
+ IBM_MP2_SINGLE_FRAME,
+ 0, 0);
+ if (saa->playmode == pmode.mode) {
+ debiwrite(saa, debNormal,
+ IBM_MP2_CHIP_CONTROL,
+ ChipControl, 2);
+ }
+ saa->playmode = pmode.mode;
+ return 0;
+ case VID_PLAY_FAST_FORWARD:
+ ibm_send_command(saa,
+ IBM_MP2_FAST_FORWARD, 0, 0);
+ saa->playmode = pmode.mode;
+ return 0;
+ case VID_PLAY_SLOW_MOTION:
+ ibm_send_command(saa,
+ IBM_MP2_SLOW_MOTION,
+ pmode.p1, 0);
+ saa->playmode = pmode.mode;
+ return 0;
+ case VID_PLAY_IMMEDIATE_NORMAL:
+ /* ensure transfers resume */
+ debiwrite(saa, debNormal,
+ IBM_MP2_CHIP_CONTROL,
+ ChipControl, 2);
+ ibm_send_command(saa,
+ IBM_MP2_IMED_NORM_PLAY, 0, 0);
+ saa->playmode = VID_PLAY_NORMAL;
+ return 0;
+ case VID_PLAY_SWITCH_CHANNELS:
+ saa->audhead = saa->audtail = 0;
+ saa->vidhead = saa->vidtail = 0;
+ ibm_send_command(saa,
+ IBM_MP2_FREEZE_FRAME, 0, 1);
+ ibm_send_command(saa,
+ IBM_MP2_RESET_AUD_RATE, 0, 1);
+ debiwrite(saa, debNormal,
+ IBM_MP2_CHIP_CONTROL, 0, 2);
+ ibm_send_command(saa,
+ IBM_MP2_CHANNEL_SWITCH, 0, 1);
+ debiwrite(saa, debNormal,
+ IBM_MP2_CHIP_CONTROL,
+ ChipControl, 2);
+ ibm_send_command(saa,
+ IBM_MP2_PLAY, 0, 0);
+ saa->playmode = VID_PLAY_NORMAL;
+ return 0;
+ case VID_PLAY_FREEZE_FRAME:
+ ibm_send_command(saa,
+ IBM_MP2_FREEZE_FRAME, 0, 0);
+ saa->playmode = pmode.mode;
+ return 0;
+ case VID_PLAY_STILL_MODE:
+ ibm_send_command(saa,
+ IBM_MP2_SET_STILL_MODE, 0, 0);
+ saa->playmode = pmode.mode;
+ return 0;
+ case VID_PLAY_MASTER_MODE:
+ if (pmode.p1 == VID_PLAY_MASTER_NONE)
+ saa->boardcfg[1] = 0x13;
+ else if (pmode.p1 ==
+ VID_PLAY_MASTER_VIDEO)
+ saa->boardcfg[1] = 0x23;
+ else if (pmode.p1 ==
+ VID_PLAY_MASTER_AUDIO)
+ saa->boardcfg[1] = 0x43;
+ else
+ return -EINVAL;
+ debiwrite(saa, debNormal,
+ IBM_MP2_CHIP_CONTROL,
+ ChipControl, 2);
+ return 0;
+ case VID_PLAY_ACTIVE_SCANLINES:
+ if (CurrentMode == VIDEO_MODE_PAL) {
+ if (pmode.p1 < 1 ||
+ pmode.p2 > 625)
+ return -EINVAL;
+ saa->boardcfg[5] = pmode.p1;
+ saa->boardcfg[55] = (pmode.p1 +
+ (pmode.p2/2) - 1) &
+ 0xff;
+ } else {
+ if (pmode.p1 < 4 ||
+ pmode.p2 > 525)
+ return -EINVAL;
+ saa->boardcfg[4] = pmode.p1;
+ saa->boardcfg[54] = (pmode.p1 +
+ (pmode.p2/2) - 4) &
+ 0xff;
+ }
+ set_out_format(saa, CurrentMode);
+ case VID_PLAY_RESET:
+ return do_ibm_reset(saa);
+ case VID_PLAY_END_MARK:
+ if (saa->endmarktail <
+ saa->endmarkhead) {
+ if (saa->endmarkhead -
+ saa->endmarktail < 2)
+ return -ENOSPC;
+ } else if (saa->endmarkhead <=
+ saa->endmarktail) {
+ if (saa->endmarktail -
+ saa->endmarkhead >
+ (MAX_MARKS - 2))
+ return -ENOSPC;
+ } else
+ return -ENOSPC;
+ saa->endmark[saa->endmarktail] =
+ saa->audtail;
+ saa->endmarktail++;
+ if (saa->endmarktail >= MAX_MARKS)
+ saa->endmarktail = 0;
+ }
+ return -EINVAL;
+ }
+ case VIDIOCSWRITEMODE:
+ {
+ int mode;
+ if (copy_from_user((void *) &mode, arg, sizeof(int)))
+ return -EFAULT;
+ if (mode == VID_WRITE_MPEG_AUD ||
+ mode == VID_WRITE_MPEG_VID ||
+ mode == VID_WRITE_CC ||
+ mode == VID_WRITE_TTX ||
+ mode == VID_WRITE_OSD) {
+ saa->writemode = mode;
+ return 0;
+ }
+ return -EINVAL;
+ }
+ case VIDIOCSMICROCODE:
+ {
+ struct video_code ucode;
+ __u8 *udata;
+ int i;
+ if (copy_from_user((void *) &ucode, arg,
+ sizeof(ucode)))
+ return -EFAULT;
+ if (ucode.datasize > 65536 || ucode.datasize < 1024 ||
+ strncmp(ucode.loadwhat, "dec", 3))
+ return -EINVAL;
+ if ((udata = vmalloc(ucode.datasize)) == NULL)
+ return -ENOMEM;
+ if (copy_from_user((void *) udata, ucode.data,
+ ucode.datasize)) {
+ vfree(udata);
+ return -EFAULT;
+ }
+ ucode.data = udata;
+ if (!strncmp(ucode.loadwhat, "decoder.aud", 11)
+ || !strncmp(ucode.loadwhat, "decoder.vid", 11))
+ i = initialize_ibmmpeg2(&ucode);
+ else
+ i = initialize_fpga(&ucode);
+ vfree(udata);
+ if (i)
+ return -EINVAL;
+ return 0;
+
+ }
+ case VIDIOCGCHAN: /* this makes xawtv happy */
+ {
+ struct video_channel v;
+ if (copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ v.flags = VIDEO_VC_AUDIO;
+ v.tuners = 0;
+ v.type = VID_TYPE_MPEG_DECODER;
+ v.norm = CurrentMode;
+ strcpy(v.name, "MPEG2");
+ if (copy_to_user(arg, &v, sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSCHAN: /* this makes xawtv happy */
+ {
+ struct video_channel v;
+ if (copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ /* do nothing */
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static int saa_init_done(struct video_device *dev)
+{
+ return 0;
+}
+
+static int saa_mmap(struct video_device *dev, const char *adr,
+ unsigned long size)
+{
+ struct saa7146 *saa = (struct saa7146 *) dev;
+ printk(KERN_DEBUG "stradis%d: saa_mmap called\n", saa->nr);
+ return -EINVAL;
+}
+
+static long saa_read(struct video_device *dev, char *buf,
+ unsigned long count, int nonblock)
+{
+ return -EINVAL;
+}
+
+static long saa_write(struct video_device *dev, const char *buf,
+ unsigned long count, int nonblock)
+{
+ struct saa7146 *saa = (struct saa7146 *) dev;
+ unsigned long todo = count;
+ int blocksize, split;
+ unsigned long flags;
+
+ while (todo > 0) {
+ if (saa->writemode == VID_WRITE_MPEG_AUD) {
+ spin_lock_irqsave(&saa->lock, flags);
+ if (saa->audhead <= saa->audtail)
+ blocksize = 65536-(saa->audtail - saa->audhead);
+ else
+ blocksize = saa->audhead - saa->audtail;
+ spin_unlock_irqrestore(&saa->lock, flags);
+ if (blocksize < 16384) {
+ saawrite(SAA7146_PSR_DEBI_S |
+ SAA7146_PSR_PIN1, SAA7146_IER);
+ saawrite(SAA7146_PSR_PIN1, SAA7146_PSR);
+ /* wait for buffer space to open */
+ interruptible_sleep_on(&saa->audq);
+ }
+ spin_lock_irqsave(&saa->lock, flags);
+ if (saa->audhead <= saa->audtail) {
+ blocksize = 65536-(saa->audtail - saa->audhead);
+ split = 65536 - saa->audtail;
+ } else {
+ blocksize = saa->audhead - saa->audtail;
+ split = 65536;
+ }
+ spin_unlock_irqrestore(&saa->lock, flags);
+ blocksize--;
+ if (blocksize > todo)
+ blocksize = todo;
+ /* double check that we really have space */
+ if (!blocksize)
+ return -ENOSPC;
+ if (split < blocksize) {
+ if (copy_from_user(saa->audbuf +
+ saa->audtail, buf, split))
+ return -EFAULT;
+ buf += split;
+ todo -= split;
+ blocksize -= split;
+ saa->audtail = 0;
+ }
+ if (copy_from_user(saa->audbuf + saa->audtail, buf,
+ blocksize))
+ return -EFAULT;
+ saa->audtail += blocksize;
+ todo -= blocksize;
+ buf += blocksize;
+ saa->audtail &= 0xffff;
+ } else if (saa->writemode == VID_WRITE_MPEG_VID) {
+ spin_lock_irqsave(&saa->lock, flags);
+ if (saa->vidhead <= saa->vidtail)
+ blocksize=524288-(saa->vidtail - saa->vidhead);
+ else
+ blocksize = saa->vidhead - saa->vidtail;
+ spin_unlock_irqrestore(&saa->lock, flags);
+ if (blocksize < 65536) {
+ saawrite(SAA7146_PSR_DEBI_S |
+ SAA7146_PSR_PIN1, SAA7146_IER);
+ saawrite(SAA7146_PSR_PIN1, SAA7146_PSR);
+ /* wait for buffer space to open */
+ interruptible_sleep_on(&saa->vidq);
+ }
+ spin_lock_irqsave(&saa->lock, flags);
+ if (saa->vidhead <= saa->vidtail) {
+ blocksize=524288-(saa->vidtail - saa->vidhead);
+ split = 524288 - saa->vidtail;
+ } else {
+ blocksize = saa->vidhead - saa->vidtail;
+ split = 524288;
+ }
+ spin_unlock_irqrestore(&saa->lock, flags);
+ blocksize--;
+ if (blocksize > todo)
+ blocksize = todo;
+ /* double check that we really have space */
+ if (!blocksize)
+ return -ENOSPC;
+ if (split < blocksize) {
+ if (copy_from_user(saa->vidbuf +
+ saa->vidtail, buf, split))
+ return -EFAULT;
+ buf += split;
+ todo -= split;
+ blocksize -= split;
+ saa->vidtail = 0;
+ }
+ if (copy_from_user(saa->vidbuf + saa->vidtail, buf,
+ blocksize))
+ return -EFAULT;
+ saa->vidtail += blocksize;
+ todo -= blocksize;
+ buf += blocksize;
+ saa->vidtail &= 0x7ffff;
+ } else if (saa->writemode == VID_WRITE_OSD) {
+ if (count > 131072)
+ return -ENOSPC;
+ if (copy_from_user(saa->osdbuf, buf, count))
+ return -EFAULT;
+ buf += count;
+ saa->osdhead = 0;
+ saa->osdtail = count;
+ debiwrite(saa, debNormal, IBM_MP2_OSD_ADDR, 0, 2);
+ debiwrite(saa, debNormal, IBM_MP2_OSD_LINK_ADDR, 0, 2);
+ debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00d, 2);
+ debiwrite(saa, debNormal, IBM_MP2_DISP_MODE,
+ debiread(saa, debNormal,
+ IBM_MP2_DISP_MODE, 2) | 1, 2);
+ /* trigger osd data transfer */
+ saawrite(SAA7146_PSR_DEBI_S |
+ SAA7146_PSR_PIN1, SAA7146_IER);
+ saawrite(SAA7146_PSR_PIN1, SAA7146_PSR);
+ }
+ }
+ return count;
+}
+
+static int saa_open(struct video_device *dev, int flags)
+{
+ struct saa7146 *saa = (struct saa7146 *) dev;
+
+ saa->video_dev.busy = 0;
+ saa->user++;
+ if (saa->user > 1)
+ return 0; /* device open already, don't reset */
+ saa->writemode = VID_WRITE_MPEG_VID; /* default to video */
+ return 0;
+}
+
+static void saa_close(struct video_device *dev)
+{
+ struct saa7146 *saa = (struct saa7146 *) dev;
+ saa->user--;
+ saa->video_dev.busy = 0;
+ if (saa->user > 0) /* still someone using device */
+ return;
+ saawrite(0x007f0000, SAA7146_MC1); /* stop all overlay dma */
+}
+
+/* template for video_device-structure */
+static struct video_device saa_template =
+{
+ "SAA7146A",
+ VID_TYPE_CAPTURE | VID_TYPE_OVERLAY,
+ VID_HARDWARE_SAA7146,
+ saa_open,
+ saa_close,
+ saa_read,
+ saa_write,
+ NULL, /* poll */
+ saa_ioctl,
+ saa_mmap,
+ saa_init_done,
+ NULL,
+ 0,
+ 0
+};
+
+static int configure_saa7146(struct pci_dev *dev, int num)
+{
+ int result;
+ struct saa7146 *saa;
+
+ saa = &saa7146s[num];
+
+ saa->endmarkhead = saa->endmarktail = 0;
+ saa->win.x = saa->win.y = 0;
+ saa->win.width = saa->win.cropwidth = 720;
+ saa->win.height = saa->win.cropheight = 480;
+ saa->win.cropx = saa->win.cropy = 0;
+ saa->win.bpp = 2;
+ saa->win.depth = 16;
+ saa->win.color_fmt = palette2fmt[VIDEO_PALETTE_RGB565];
+ saa->win.bpl = 1024 * saa->win.bpp;
+ saa->win.swidth = 1024;
+ saa->win.sheight = 768;
+ saa->picture.brightness = 32768;
+ saa->picture.contrast = 38768;
+ saa->picture.colour = 32768;
+ saa->cap = 0;
+ saa->dev = dev;
+ saa->nr = num;
+ saa->playmode = VID_PLAY_NORMAL;
+ memset(saa->boardcfg, 0, 64); /* clear board config area */
+ saa->saa7146_mem = NULL;
+ saa->dmavid1 = saa->dmavid2 = saa->dmavid3 = saa->dmaa1in =
+ saa->dmaa1out = saa->dmaa2in = saa->dmaa2out =
+ saa->pagevid1 = saa->pagevid2 = saa->pagevid3 = saa->pagea1in =
+ saa->pagea1out = saa->pagea2in = saa->pagea2out =
+ saa->pagedebi = saa->dmaRPS1 = saa->dmaRPS2 = saa->pageRPS1 =
+ saa->pageRPS2 = NULL;
+ saa->audbuf = saa->vidbuf = saa->osdbuf = saa->dmadebi = NULL;
+ saa->audhead = saa->vidtail = 0;
+
+ init_waitqueue_head(&saa->i2cq);
+ init_waitqueue_head(&saa->audq);
+ init_waitqueue_head(&saa->debiq);
+ init_waitqueue_head(&saa->vidq);
+ spin_lock_init(&saa->lock);
+
+ if (pci_enable_device(dev))
+ return -EIO;
+
+ saa->id = dev->device;
+ saa->irq = dev->irq;
+ saa->video_dev.minor = -1;
+ saa->saa7146_adr = pci_resource_start(dev, 0);
+ pci_read_config_byte(dev, PCI_CLASS_REVISION, &saa->revision);
+
+ saa->saa7146_mem = ioremap(saa->saa7146_adr, 0x200);
+ if (!saa->saa7146_mem)
+ return -EIO;
+
+ memcpy(&(saa->i2c), &saa7146_i2c_bus_template, sizeof(struct i2c_bus));
+ memcpy(&saa->video_dev, &saa_template, sizeof(saa_template));
+ sprintf(saa->i2c.name, "stradis%d", num);
+ saa->i2c.data = saa;
+ saawrite(0, SAA7146_IER); /* turn off all interrupts */
+ result = request_irq(saa->irq, saa7146_irq,
+ SA_SHIRQ | SA_INTERRUPT, "stradis", (void *) saa);
+ if (result == -EINVAL)
+ printk(KERN_ERR "stradis%d: Bad irq number or handler\n",
+ num);
+ if (result == -EBUSY)
+ printk(KERN_ERR "stradis%d: IRQ %ld busy, change your PnP"
+ " config in BIOS\n", num, saa->irq);
+ if (result < 0)
+ return result;
+ pci_set_master(dev);
+ if (video_register_device(&saa->video_dev, VFL_TYPE_GRABBER) < 0)
+ return -1;
+#if 0
+ /* i2c generic interface is currently BROKEN */
+ i2c_register_bus(&saa->i2c);
+#endif
+ return 0;
+}
+
+static int init_saa7146(int i)
+{
+ struct saa7146 *saa = &saa7146s[i];
+
+ saa->user = 0;
+ /* reset the saa7146 */
+ saawrite(0xffff0000, SAA7146_MC1);
+ mdelay(5);
+ /* enable debi and i2c transfers and pins */
+ saawrite(((SAA7146_MC1_EDP | SAA7146_MC1_EI2C |
+ SAA7146_MC1_TR_E_DEBI) << 16) | 0xffff, SAA7146_MC1);
+ /* ensure proper state of chip */
+ saawrite(0x00000000, SAA7146_PAGE1);
+ saawrite(0x00f302c0, SAA7146_NUM_LINE_BYTE1);
+ saawrite(0x00000000, SAA7146_PAGE2);
+ saawrite(0x01400080, SAA7146_NUM_LINE_BYTE2);
+ saawrite(0x00000000, SAA7146_DD1_INIT);
+ saawrite(0x00000000, SAA7146_DD1_STREAM_B);
+ saawrite(0x00000000, SAA7146_DD1_STREAM_A);
+ saawrite(0x00000000, SAA7146_BRS_CTRL);
+ saawrite(0x80400040, SAA7146_BCS_CTRL);
+ saawrite(0x0000e000 /*| (1<<29)*/, SAA7146_HPS_CTRL);
+ saawrite(0x00000060, SAA7146_CLIP_FORMAT_CTRL);
+ saawrite(0x00000000, SAA7146_ACON1);
+ saawrite(0x00000000, SAA7146_ACON2);
+ saawrite(0x00000600, SAA7146_I2C_STATUS);
+ saawrite(((SAA7146_MC2_UPLD_D1_B | SAA7146_MC2_UPLD_D1_A |
+ SAA7146_MC2_UPLD_BRS | SAA7146_MC2_UPLD_HPS_H |
+ SAA7146_MC2_UPLD_HPS_V | SAA7146_MC2_UPLD_DMA2 |
+ SAA7146_MC2_UPLD_DMA1 | SAA7146_MC2_UPLD_I2C) << 16) | 0xffff,
+ SAA7146_MC2);
+ /* setup arbitration control registers */
+ saawrite(0x1412121a, SAA7146_PCI_BT_V1);
+
+ /* allocate 32k dma buffer + 4k for page table */
+ if ((saa->dmadebi = kmalloc(32768 + 4096, GFP_KERNEL)) == NULL) {
+ printk(KERN_ERR "stradis%d: debi kmalloc failed\n", i);
+ return -1;
+ }
+#if 0
+ saa->pagedebi = saa->dmadebi + 32768; /* top 4k is for mmu */
+ saawrite(virt_to_bus(saa->pagedebi) /*|0x800 */ , SAA7146_DEBI_PAGE);
+ for (i = 0; i < 12; i++) /* setup mmu page table */
+ saa->pagedebi[i] = virt_to_bus((saa->dmadebi + i * 4096));
+#endif
+ saa->audhead = saa->vidhead = saa->osdhead = 0;
+ saa->audtail = saa->vidtail = saa->osdtail = 0;
+ if (saa->vidbuf == NULL)
+ if ((saa->vidbuf = vmalloc(524288)) == NULL) {
+ printk(KERN_ERR "stradis%d: malloc failed\n", saa->nr);
+ return -ENOMEM;
+ }
+ if (saa->audbuf == NULL)
+ if ((saa->audbuf = vmalloc(65536)) == NULL) {
+ printk(KERN_ERR "stradis%d: malloc failed\n", saa->nr);
+ vfree(saa->vidbuf);
+ saa->vidbuf = NULL;
+ return -ENOMEM;
+ }
+ if (saa->osdbuf == NULL)
+ if ((saa->osdbuf = vmalloc(131072)) == NULL) {
+ printk(KERN_ERR "stradis%d: malloc failed\n", saa->nr);
+ vfree(saa->vidbuf);
+ vfree(saa->audbuf);
+ saa->vidbuf = saa->audbuf = NULL;
+ return -ENOMEM;
+ }
+ /* allocate 81920 byte buffer for clipping */
+ if ((saa->dmavid2 = kmalloc(VIDEO_CLIPMAP_SIZE, GFP_KERNEL)) == NULL) {
+ printk(KERN_ERR "stradis%d: clip kmalloc failed\n", saa->nr);
+ vfree(saa->vidbuf);
+ vfree(saa->audbuf);
+ vfree(saa->osdbuf);
+ saa->vidbuf = saa->audbuf = saa->osdbuf = NULL;
+ saa->dmavid2 = NULL;
+ return -1;
+ }
+ memset(saa->dmavid2, 0x00, VIDEO_CLIPMAP_SIZE); /* clip everything */
+ /* setup clipping registers */
+ saawrite(virt_to_bus(saa->dmavid2), SAA7146_BASE_EVEN2);
+ saawrite(virt_to_bus(saa->dmavid2) + 128, SAA7146_BASE_ODD2);
+ saawrite(virt_to_bus(saa->dmavid2) + VIDEO_CLIPMAP_SIZE,
+ SAA7146_PROT_ADDR2);
+ saawrite(256, SAA7146_PITCH2);
+ saawrite(4, SAA7146_PAGE2); /* dma direction: read, no byteswap */
+ saawrite(((SAA7146_MC2_UPLD_DMA2) << 16) | SAA7146_MC2_UPLD_DMA2,
+ SAA7146_MC2);
+ I2CBusScan(&(saa->i2c));
+ return 0;
+}
+
+static void release_saa(void)
+{
+ u8 command;
+ int i;
+ struct saa7146 *saa;
+
+ for (i = 0; i < saa_num; i++) {
+ saa = &saa7146s[i];
+
+ /* turn off all capturing, DMA and IRQs */
+ saawrite(0xffff0000, SAA7146_MC1); /* reset chip */
+ saawrite(0, SAA7146_MC2);
+ saawrite(0, SAA7146_IER);
+ saawrite(0xffffffffUL, SAA7146_ISR);
+#if 0
+ /* unregister i2c_bus */
+ i2c_unregister_bus((&saa->i2c));
+#endif
+
+ /* disable PCI bus-mastering */
+ pci_read_config_byte(saa->dev, PCI_COMMAND, &command);
+ /* Should this be &=~ ?? */
+ command &= ~PCI_COMMAND_MASTER;
+ pci_write_config_byte(saa->dev, PCI_COMMAND, command);
+ /* unmap and free memory */
+ saa->audhead = saa->audtail = saa->osdhead = 0;
+ saa->vidhead = saa->vidtail = saa->osdtail = 0;
+ if (saa->vidbuf)
+ vfree(saa->vidbuf);
+ if (saa->audbuf)
+ vfree(saa->audbuf);
+ if (saa->osdbuf)
+ vfree(saa->osdbuf);
+ if (saa->dmavid2)
+ kfree((void *) saa->dmavid2);
+ saa->audbuf = saa->vidbuf = saa->osdbuf = NULL;
+ saa->dmavid2 = NULL;
+ if (saa->dmadebi)
+ kfree((void *) saa->dmadebi);
+ if (saa->dmavid1)
+ kfree((void *) saa->dmavid1);
+ if (saa->dmavid2)
+ kfree((void *) saa->dmavid2);
+ if (saa->dmavid3)
+ kfree((void *) saa->dmavid3);
+ if (saa->dmaa1in)
+ kfree((void *) saa->dmaa1in);
+ if (saa->dmaa1out)
+ kfree((void *) saa->dmaa1out);
+ if (saa->dmaa2in)
+ kfree((void *) saa->dmaa2in);
+ if (saa->dmaa2out)
+ kfree((void *) saa->dmaa2out);
+ if (saa->dmaRPS1)
+ kfree((void *) saa->dmaRPS1);
+ if (saa->dmaRPS2)
+ kfree((void *) saa->dmaRPS2);
+ free_irq(saa->irq, saa);
+ if (saa->saa7146_mem)
+ iounmap(saa->saa7146_mem);
+ if (saa->video_dev.minor != -1)
+ video_unregister_device(&saa->video_dev);
+ }
+}
+
+
+static int __init stradis_init (void)
+{
+ struct pci_dev *dev = NULL;
+ int result = 0, i;
+
+ saa_num = 0;
+
+ while ((dev = pci_find_device(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, dev))) {
+ if (!dev->subsystem_vendor)
+ printk(KERN_INFO "stradis%d: rev1 decoder\n", saa_num);
+ else
+ printk(KERN_INFO "stradis%d: SDM2xx found\n", saa_num);
+ result = configure_saa7146(dev, saa_num++);
+ if (result)
+ return result;
+ }
+ if (saa_num)
+ printk(KERN_INFO "stradis: %d card(s) found.\n", saa_num);
+ else
+ return -EINVAL;
+ for (i = 0; i < saa_num; i++)
+ if (init_saa7146(i) < 0) {
+ release_saa();
+ return -EIO;
+ }
+ return 0;
+}
+
+
+static void __exit stradis_exit (void)
+{
+ release_saa();
+ printk(KERN_INFO "stradis: module cleanup complete\n");
+}
+
+
+module_init(stradis_init);
+module_exit(stradis_exit);
+
diff --git a/drivers/media/video/tda7432.c b/drivers/media/video/tda7432.c
new file mode 100644
index 000000000..488091310
--- /dev/null
+++ b/drivers/media/video/tda7432.c
@@ -0,0 +1,505 @@
+/*
+ * For the STS-Thompson TDA7432 audio processor chip
+ *
+ * Handles audio functions: volume, balance, tone, loudness
+ * This driver will not complain if used with any
+ * other i2c device with the same address.
+ *
+ * Copyright (c) 2000 Eric Sandeen <eric_sandeen@bigfoot.com>
+ * This code is placed under the terms of the GNU General Public License
+ * Based on tda9855.c by Steve VanDeBogart (vandebo@uclink.berkeley.edu)
+ * Which was based on tda8425.c by Greg Alexander (c) 1998
+ *
+ * OPTIONS:
+ * debug - set to 1 if you'd like to see debug messages
+ * set to 2 if you'd like to be inundated with debug messages
+ *
+ * loudness - set between 0 and 15 for varying degrees of loudness effect
+ *
+ * TODO:
+ * Implement tone controls
+ *
+ * Revision: 0.3 - Fixed silly reversed volume controls. :)
+ * Revision: 0.2 - Cleaned up #defines
+ * fixed volume control
+ * Added I2C_DRIVERID_TDA7432
+ * added loudness insmod control
+ * Revision: 0.1 - initial version
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+
+#include "bttv.h"
+#include "audiochip.h"
+
+/* This driver ID is brand new, so define it if it's not in i2c-id.h yet */
+#ifndef I2C_DRIVERID_TDA7432
+ #define I2C_DRIVERID_TDA7432 27
+#endif
+
+
+MODULE_AUTHOR("Eric Sandeen <eric_sandeen@bigfoot.com>");
+MODULE_DESCRIPTION("bttv driver for the tda7432 audio processor chip");
+
+MODULE_PARM(debug,"i");
+MODULE_PARM(loudness,"i");
+static int loudness = 0; /* disable loudness by default */
+static int debug = 0; /* insmod parameter */
+
+
+/* Address to scan (I2C address of this chip) */
+static unsigned short normal_i2c[] = {
+ I2C_TDA7432 >> 1,
+ I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {I2C_CLIENT_END};
+static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static struct i2c_client_address_data addr_data = {
+ normal_i2c, normal_i2c_range,
+ probe, probe_range,
+ ignore, ignore_range,
+ force
+};
+
+/* Structure of address and subaddresses for the tda7432 */
+
+struct tda7432 {
+ int addr;
+ int input;
+ int volume;
+ int tone;
+ int lf, lr, rf, rr;
+ int loud;
+};
+
+static struct i2c_driver driver;
+static struct i2c_client client_template;
+
+#define dprintk if (debug) printk
+#define d2printk if (debug > 1) printk
+
+/* The TDA7432 is made by STS-Thompson
+ * http://www.st.com
+ * http://us.st.com/stonline/books/pdf/docs/4056.pdf
+ *
+ * TDA7432: I2C-bus controlled basic audio processor
+ *
+ * The TDA7432 controls basic audio functions like volume, balance,
+ * and tone control (including loudness). It also has four channel
+ * output (for front and rear). Since most vidcap cards probably
+ * don't have 4 channel output, this driver will set front & rear
+ * together (no independent control).
+ */
+
+ /* Subaddresses for TDA7432 */
+
+#define TDA7432_IN 0x00 /* Input select */
+#define TDA7432_VL 0x01 /* Volume */
+#define TDA7432_TN 0x02 /* Bass, Treble (Tone) */
+#define TDA7432_LF 0x03 /* Attenuation LF (Left Front) */
+#define TDA7432_LR 0x04 /* Attenuation LR (Left Rear) */
+#define TDA7432_RF 0x05 /* Attenuation RF (Right Front) */
+#define TDA7432_RR 0x06 /* Attenuation RR (Right Rear) */
+#define TDA7432_LD 0x07 /* Loudness */
+
+
+ /* Masks for bits in TDA7432 subaddresses */
+
+/* Many of these not used - just for documentation */
+
+/* Subaddress 0x00 - Input selection and bass control */
+
+/* Bits 0,1,2 control input:
+ * 0x00 - Stereo input
+ * 0x02 - Mono input
+ * 0x03 - Mute
+ * Mono probably isn't used - I'm guessing only the stereo
+ * input is connected on most cards, so we'll set it to stereo.
+ *
+ * Bit 3 controls bass cut: 0/1 is non-symmetric/symmetric bass cut
+ * Bit 4 controls bass range: 0/1 is extended/standard bass range
+ *
+ * Highest 3 bits not used
+ */
+
+#define TDA7432_STEREO_IN 0
+#define TDA7432_MONO_IN 2 /* Probably won't be used */
+#define TDA7432_MUTE 3 /* Probably won't be used */
+#define TDA7432_BASS_SYM 1 << 3
+#define TDA7432_BASS_NORM 1 << 4
+
+/* Subaddress 0x01 - Volume */
+
+/* Lower 7 bits control volume from -79dB to +32dB in 1dB steps
+ * Recommended maximum is +20 dB
+ *
+ * +32dB: 0x00
+ * +20dB: 0x0c
+ * 0dB: 0x20
+ * -79dB: 0x6f
+ *
+ * MSB (bit 7) controls loudness: 1/0 is loudness on/off
+ */
+
+#define TDA7432_VOL_0DB 0x20
+#define TDA7432_LD_ON 1 << 7
+
+
+/* Subaddress 0x02 - Tone control */
+
+/* Bits 0,1,2 control absolute treble gain from 0dB to 14dB
+ * 0x0 is 14dB, 0x7 is 0dB
+ *
+ * Bit 3 controls treble attenuation/gain (sign)
+ * 1 = gain (+)
+ * 0 = attenuation (-)
+ *
+ * Bits 4,5,6 control absolute bass gain from 0dB to 14dB
+ * (This is only true for normal base range, set in 0x00)
+ * 0x0 << 4 is 14dB, 0x7 is 0dB
+ *
+ * Bit 7 controls bass attenuation/gain (sign)
+ * 1 << 7 = gain (+)
+ * 0 << 7 = attenuation (-)
+ *
+ * Example:
+ * 1 1 0 1 0 1 0 1 is +4dB bass, -4dB treble
+ */
+
+#define TDA7432_TREBLE_0DB 0xf
+#define TDA7432_TREBLE 7
+#define TDA7432_TREBLE_GAIN 1 << 3
+#define TDA7432_BASS_0DB 0xf << 4
+#define TDA7432_BASS 7 << 4
+#define TDA7432_BASS_GAIN 1 << 7
+
+
+/* Subaddress 0x03 - Left Front attenuation */
+/* Subaddress 0x04 - Left Rear attenuation */
+/* Subaddress 0x05 - Right Front attenuation */
+/* Subaddress 0x06 - Right Rear attenuation */
+
+/* Bits 0,1,2,3,4 control attenuation from 0dB to -37.5dB
+ * in 1.5dB steps.
+ *
+ * 0x00 is 0dB
+ * 0x1f is -37.5dB
+ *
+ * Bit 5 mutes that channel when set (1 = mute, 0 = unmute)
+ * We'll use the mute on the input, though (above)
+ * Bits 6,7 unused
+ */
+
+#define TDA7432_ATTEN_0DB 0x00
+
+
+/* Subaddress 0x07 - Loudness Control */
+
+/* Bits 0,1,2,3 control loudness from 0dB to -15dB in 1dB steps
+ * when bit 4 is NOT set
+ *
+ * 0x0 is 0dB
+ * 0xf is -15dB
+ *
+ * If bit 4 is set, then there is a flat attenuation according to
+ * the lower 4 bits, as above.
+ *
+ * Bits 5,6,7 unused
+ */
+
+
+
+/* Begin code */
+
+static int tda7432_write(struct i2c_client *client, int subaddr, int val)
+{
+ unsigned char buffer[2];
+ d2printk("tda7432: In tda7432_write\n");
+ dprintk("tda7432: Writing %d 0x%x\n", subaddr, val);
+ buffer[0] = subaddr;
+ buffer[1] = val;
+ if (2 != i2c_master_send(client,buffer,2)) {
+ printk(KERN_WARNING "tda7432: I/O error, trying (write %d 0x%x)\n",
+ subaddr, val);
+ return -1;
+ }
+ return 0;
+}
+
+/* I don't think we ever actually _read_ the chip... */
+#if 0
+static int tda7432_read(struct i2c_client *client)
+{
+ unsigned char buffer;
+ d2printk("tda7432: In tda7432_read\n");
+ if (1 != i2c_master_recv(client,&buffer,1)) {
+ printk(KERN_WARNING "tda7432: I/O error, trying (read)\n");
+ return -1;
+ }
+ dprintk("tda7432: Read 0x%02x\n", buffer);
+ return buffer;
+}
+#endif
+
+static int tda7432_set(struct i2c_client *client)
+{
+ struct tda7432 *t = client->data;
+ unsigned char buf[16];
+ d2printk("tda7432: In tda7432_set\n");
+
+ dprintk(KERN_INFO
+ "tda7432: 7432_set(0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x)\n",
+ t->input,t->volume,t->tone,t->lf,t->lr,t->rf,t->rr,t->loud);
+ buf[0] = TDA7432_IN;
+ buf[1] = t->input;
+ buf[2] = t->volume;
+ buf[3] = t->tone;
+ buf[4] = t->lf;
+ buf[5] = t->lr;
+ buf[6] = t->rf;
+ buf[7] = t->rr;
+ buf[8] = t->loud;
+ if (9 != i2c_master_send(client,buf,9)) {
+ printk(KERN_WARNING "tda7432: I/O error, trying tda7432_set\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void do_tda7432_init(struct i2c_client *client)
+{
+ struct tda7432 *t = client->data;
+ d2printk("tda7432: In tda7432_init\n");
+
+ t->input = TDA7432_STEREO_IN | /* Main (stereo) input */
+ TDA7432_BASS_SYM | /* Symmetric bass cut */
+ TDA7432_BASS_NORM; /* Normal bass range */
+ t->volume = TDA7432_VOL_0DB; /* 0dB Volume */
+ if (loudness) /* Turn loudness on? */
+ t->volume |= TDA7432_LD_ON;
+ t->tone = TDA7432_TREBLE_0DB | /* 0dB Treble */
+ TDA7432_BASS_0DB; /* 0dB Bass */
+ t->lf = TDA7432_ATTEN_0DB; /* 0dB attenuation */
+ t->lr = TDA7432_ATTEN_0DB; /* 0dB attenuation */
+ t->rf = TDA7432_ATTEN_0DB; /* 0dB attenuation */
+ t->rr = TDA7432_ATTEN_0DB; /* 0dB attenuation */
+ t->loud = loudness; /* insmod parameter */
+
+ tda7432_set(client);
+}
+
+/* *********************** *
+ * i2c interface functions *
+ * *********************** */
+
+static int tda7432_attach(struct i2c_adapter *adap, int addr,
+ unsigned short flags, int kind)
+{
+ struct tda7432 *t;
+ struct i2c_client *client;
+ d2printk("tda7432: In tda7432_attach\n");
+ client = kmalloc(sizeof *client,GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+ memcpy(client,&client_template,sizeof(struct i2c_client));
+ client->adapter = adap;
+ client->addr = addr;
+
+ client->data = t = kmalloc(sizeof *t,GFP_KERNEL);
+ if (!t)
+ return -ENOMEM;
+ memset(t,0,sizeof *t);
+ do_tda7432_init(client);
+ MOD_INC_USE_COUNT;
+ strcpy(client->name,"TDA7432");
+ printk(KERN_INFO "tda7432: init\n");
+
+ i2c_attach_client(client);
+ return 0;
+}
+
+static int tda7432_probe(struct i2c_adapter *adap)
+{
+ if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848))
+ return i2c_probe(adap, &addr_data, tda7432_attach);
+ return 0;
+}
+
+static int tda7432_detach(struct i2c_client *client)
+{
+ struct tda7432 *t = client->data;
+
+ do_tda7432_init(client);
+ i2c_detach_client(client);
+
+ kfree(t);
+ kfree(client);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int tda7432_command(struct i2c_client *client,
+ unsigned int cmd, void *arg)
+{
+ struct tda7432 *t = client->data;
+ d2printk("tda7432: In tda7432_command\n");
+#if 0
+ __u16 *sarg = arg;
+#endif
+
+ switch (cmd) {
+ /* --- v4l ioctls --- */
+ /* take care: bttv does userspace copying, we'll get a
+ kernel pointer here... */
+
+ /* Query card - scale from TDA7432 settings to V4L settings */
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *va = arg;
+ dprintk("tda7432: VIDIOCGAUDIO\n");
+
+ va->flags |= VIDEO_AUDIO_VOLUME |
+ VIDEO_AUDIO_BASS |
+ VIDEO_AUDIO_TREBLE;
+
+ /* Master volume control
+ * V4L volume is min 0, max 65535
+ * TDA7432 Volume:
+ * Min (-79dB) is 0x6f
+ * Max (+20dB) is 0x07
+ * (Mask out bit 7 of vol - it's for the loudness setting)
+ */
+
+ va->volume = ( 0x6f - (t->volume & 0x7F) ) * 630;
+
+ /* Balance depends on L,R attenuation
+ * V4L balance is 0 to 65535, middle is 32768
+ * TDA7432 attenuation: min (0dB) is 0, max (-37.5dB) is 0x1f
+ * to scale up to V4L numbers, mult by 1057
+ * attenuation exists for lf, lr, rf, rr
+ * we use only lf and rf (front channels)
+ */
+
+ if ( (t->lf) < (t->rf) )
+ /* right is attenuated, balance shifted left */
+ va->balance = (32768 - 1057*(t->rf));
+ else
+ /* left is attenuated, balance shifted right */
+ va->balance = (32768 + 1057*(t->lf));
+
+ /* Bass/treble */
+ va->bass = 32768; /* brain hurts... set to middle for now */
+ va->treble = 32768; /* brain hurts... set to middle for now */
+
+ break; /* VIDIOCGAUDIO case */
+ }
+
+ /* Set card - scale from V4L settings to TDA7432 settings */
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *va = arg;
+ dprintk("tda7432: VIDEOCSAUDIO\n");
+
+ t->volume = 0x6f - ( (va->volume)/630 );
+
+ if (loudness) /* Turn on the loudness bit */
+ t->volume |= TDA7432_LD_ON;
+
+ if (va->balance < 32768) {
+ /* shifted to left, attenuate right */
+ t->rr = (32768 - va->balance)/1057;
+ t->rf = t->rr;
+ }
+ else {
+ /* shifted to right, attenuate left */
+ t->lf = (va->balance - 32768)/1057;
+ t->lr = t->lf;
+ }
+
+ /* t->tone = 0xff; */ /* Brain hurts - no tone control for now... */
+
+ tda7432_write(client,TDA7432_VL, t->volume);
+ /* tda7432_write(client,TDA7432_TN, t->tone); */
+ tda7432_write(client,TDA7432_LF, t->lf);
+ tda7432_write(client,TDA7432_LR, t->lr);
+ tda7432_write(client,TDA7432_RF, t->rf);
+ tda7432_write(client,TDA7432_RR, t->rr);
+
+ break;
+
+ } /* end of VIDEOCSAUDIO case */
+
+ default: /* Not VIDEOCGAUDIO or VIDEOCSAUDIO */
+
+ /* nothing */
+ d2printk("tda7432: Default\n");
+
+ } /* end of (cmd) switch */
+
+ return 0;
+}
+
+
+static struct i2c_driver driver = {
+ "i2c tda7432 driver",
+ I2C_DRIVERID_TDA7432,
+ I2C_DF_NOTIFY,
+ tda7432_probe,
+ tda7432_detach,
+ tda7432_command,
+};
+
+static struct i2c_client client_template =
+{
+ "(unset)", /* name */
+ -1,
+ 0,
+ 0,
+ NULL,
+ &driver
+};
+
+#ifdef MODULE
+int init_module(void)
+#else
+int tda7432_init(void)
+#endif
+{
+
+ if ( (loudness < 0) || (loudness > 15) )
+ {
+ printk(KERN_ERR "tda7432: loudness parameter must be between 0 and 15\n");
+ return -EINVAL;
+ }
+
+
+ i2c_add_driver(&driver);
+ return 0;
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ i2c_del_driver(&driver);
+}
+#endif
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/tda8425.c b/drivers/media/video/tda8425.c
new file mode 100644
index 000000000..a1dec22eb
--- /dev/null
+++ b/drivers/media/video/tda8425.c
@@ -0,0 +1,324 @@
+/*
+ * for the TDA8425 chip (I don't know which cards have this)
+ * WARNING: THIS DRIVER WILL LOAD WITHOUT COMPLAINTS EVEN IF A DIFFERENT
+ * CHIP IS AT ADDRESS 0x82 (it relies on i2c to make sure that there is a
+ * device acknowledging that address)
+ *
+ * Copyright (c) 1998 Greg Alexander <galexand@acm.org>
+ * This code is placed under the terms of the GNU General Public License
+ * Code liberally copied from msp3400.c, which is by Gerd Knorr
+ *
+ * All of this should work, though it would be nice to eventually support
+ * balance (different left,right values). Also, the chip seems (?) to have
+ * two stereo inputs, so if someone has this card, could they tell me if the
+ * second one can be used for anything (i.e., does it have an external input
+ * that you can't hear even if you set input to composite?)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+
+#include "bttv.h"
+#include "audiochip.h"
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = {
+ I2C_TDA8425 >> 1,
+ I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {I2C_CLIENT_END};
+static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static struct i2c_client_address_data addr_data = {
+ normal_i2c, normal_i2c_range,
+ probe, probe_range,
+ ignore, ignore_range,
+ force
+};
+
+MODULE_PARM(debug,"i");
+static int debug = 0; /* insmod parameter */
+#define dprintk if (debug) printk
+
+
+struct tda8425 {
+ int mode; /* set to AUDIO_{OFF,TUNER,RADIO,EXTERN} */
+ int stereo;
+ __u16 left,right;
+ __u16 bass,treble;
+};
+
+static struct i2c_driver driver;
+static struct i2c_client client_template;
+
+
+#define TDA8425_VL 0x00 /* volume left */
+#define TDA8425_VR 0x01 /* volume right */
+#define TDA8425_BA 0x02 /* bass */
+#define TDA8425_TR 0x03 /* treble */
+#define TDA8425_S1 0x08 /* switch functions */
+ /* values for those registers: */
+#define TDA8425_S1_OFF 0xEE /* audio off (mute on) */
+#define TDA8425_S1_ON 0xCE /* audio on (mute off) - "linear stereo" mode */
+
+
+/* ******************************** *
+ * functions for talking to TDA8425 *
+ * ******************************** */
+
+static int tda8425_write(struct i2c_client *client, int addr, int val)
+{
+ unsigned char buffer[2];
+
+ buffer[0] = addr;
+ buffer[1] = val;
+ if (2 != i2c_master_send(client,buffer,2)) {
+ printk(KERN_WARNING "tda8425: I/O error, trying (write %d 0x%x)\n",
+ addr, val);
+ return -1;
+ }
+ return 0;
+}
+
+static void tda8425_set(struct i2c_client *client)
+{
+ struct tda8425 *tda = client->data;
+
+ /* mode is ignored today */
+ dprintk(KERN_DEBUG "tda8425_set(%04x,%04x,%04x,%04x)\n",tda->left>>10,tda->right>>10,tda->bass>>12,tda->treble>>12);
+ tda8425_write(client, TDA8425_VL, tda->left>>10 |0xC0);
+ tda8425_write(client, TDA8425_VR, tda->right>>10 |0xC0);
+ tda8425_write(client, TDA8425_BA, tda->bass>>12 |0xF0);
+ tda8425_write(client, TDA8425_TR, tda->treble>>12|0xF0);
+}
+
+static void do_tda8425_init(struct i2c_client *client)
+{
+ struct tda8425 *tda = client->data;
+
+ tda->left=tda->right =61440; /* 0dB */
+ tda->bass=tda->treble=24576; /* 0dB */
+ tda->mode=AUDIO_OFF;
+ tda->stereo=1;
+ /* left=right=0x27<<10, bass=treble=0x07<<12 */
+ tda8425_write(client, TDA8425_S1, TDA8425_S1_OFF); /* mute */
+ tda8425_set(client);
+}
+
+static void tda8425_audio(struct i2c_client *client, int mode)
+{
+ struct tda8425 *tda = client->data;
+
+ /* valid for AUDIO_TUNER, RADIO, EXTERN, OFF */
+ dprintk(KERN_DEBUG "tda8425_audio:%d (T,R,E,I,O)\n",mode);
+ tda->mode=mode;
+ tda8425_write(client, TDA8425_S1,
+ (mode==AUDIO_OFF)?TDA8425_S1_OFF:TDA8425_S1_ON);
+ /* this is the function we'll need to change if it turns out the
+ * input-selecting capabilities should be used. */
+}
+
+
+/* *********************** *
+ * i2c interface functions *
+ * *********************** */
+
+static int tda8425_attach(struct i2c_adapter *adap, int addr,
+ unsigned short flags, int kind)
+{
+ struct tda8425 *tda;
+ struct i2c_client *client;
+
+ client = kmalloc(sizeof *client,GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+ memcpy(client,&client_template,sizeof(struct i2c_client));
+ client->adapter = adap;
+ client->addr = addr;
+
+ client->data = tda = kmalloc(sizeof *tda,GFP_KERNEL);
+ if (!tda)
+ return -ENOMEM;
+ memset(tda,0,sizeof *tda);
+ do_tda8425_init(client);
+ MOD_INC_USE_COUNT;
+ strcpy(client->name,"TDA8425");
+ printk(KERN_INFO "tda8425: init\n");
+
+ i2c_attach_client(client);
+ return 0;
+}
+
+static int tda8425_probe(struct i2c_adapter *adap)
+{
+ if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848))
+ return i2c_probe(adap, &addr_data, tda8425_attach);
+ return 0;
+}
+
+
+static int tda8425_detach(struct i2c_client *client)
+{
+ struct tda8425 *tda = client->data;
+
+ do_tda8425_init(client);
+ i2c_detach_client(client);
+
+ kfree(tda);
+ kfree(client);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int tda8425_command(struct i2c_client *client,
+ unsigned int cmd, void *arg)
+{
+ struct tda8425 *tda = client->data;
+ __u16 *sarg = arg;
+
+ switch (cmd) {
+ case AUDC_SET_RADIO:
+ tda8425_audio(client,AUDIO_RADIO);
+ break;
+ case AUDC_SET_INPUT:
+ tda8425_audio(client,*sarg);
+ break;
+
+ /* --- v4l ioctls --- */
+ /* take care: bttv does userspace copying, we'll get a
+ kernel pointer here... */
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *va = arg;
+
+ va->flags |= VIDEO_AUDIO_VOLUME |
+ VIDEO_AUDIO_BASS |
+ VIDEO_AUDIO_TREBLE;
+ va->volume=MAX(tda->left,tda->right);
+ va->balance=(32768*MIN(tda->left,tda->right))/
+ (va->volume ? va->volume : 1);
+ va->balance=(tda->left<tda->right)?
+ (65535-va->balance) : va->balance;
+ va->bass = tda->bass;
+ va->treble = tda->treble;
+ break;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *va = arg;
+
+ tda->left = (MIN(65536 - va->balance,32768) *
+ va->volume) / 32768;
+ tda->right = (MIN(va->balance,32768) *
+ va->volume) / 32768;
+ tda->bass = va->bass;
+ tda->treble = va->treble;
+ tda8425_set(client);
+ break;
+ }
+
+#if 0
+ /* --- old, obsolete interface --- */
+ case AUDC_GET_VOLUME_LEFT:
+ *sarg = tda->left;
+ break;
+ case AUDC_GET_VOLUME_RIGHT:
+ *sarg = tda->right;
+ break;
+ case AUDC_SET_VOLUME_LEFT:
+ tda->left = *sarg;
+ tda8425_set(client);
+ break;
+ case AUDC_SET_VOLUME_RIGHT:
+ tda->right = *sarg;
+ tda8425_set(client);
+ break;
+
+ case AUDC_GET_BASS:
+ *sarg = tda->bass;
+ break;
+ case AUDC_SET_BASS:
+ tda->bass = *sarg;
+ tda8425_set(client);
+ break;
+
+ case AUDC_GET_TREBLE:
+ *sarg = tda->treble;
+ break;
+ case AUDC_SET_TREBLE:
+ tda->treble = *sarg;
+ tda8425_set(client);
+ break;
+
+ case AUDC_GET_STEREO:
+ *sarg = tda->stereo?VIDEO_SOUND_STEREO:VIDEO_SOUND_MONO;
+ break;
+ case AUDC_SET_STEREO:
+ tda->stereo=(*sarg==VIDEO_SOUND_MONO)?0:1;
+ /* TODO: make this write to the TDA9850? */
+ break;
+
+/* case AUDC_SWITCH_MUTE: someday, maybe -- not a lot of point to
+ case AUDC_NEWCHANNEL: it and it would require preserving state
+ case AUDC_GET_DC: huh?? (not used by bttv.c)
+*/
+#endif
+ default:
+ /* nothing */
+ }
+ return 0;
+}
+
+
+static struct i2c_driver driver = {
+ "i2c tda8424 driver",
+ I2C_DRIVERID_TDA8425,
+ I2C_DF_NOTIFY,
+ tda8425_probe,
+ tda8425_detach,
+ tda8425_command,
+};
+
+static struct i2c_client client_template =
+{
+ "(unset)", /* name */
+ -1,
+ 0,
+ 0,
+ NULL,
+ &driver
+};
+
+#ifdef MODULE
+int init_module(void)
+#else
+int tda8425_init(void)
+#endif
+{
+ i2c_add_driver(&driver);
+ return 0;
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ i2c_del_driver(&driver);
+}
+#endif
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/tda985x.c b/drivers/media/video/tda985x.c
new file mode 100644
index 000000000..73fb9bd52
--- /dev/null
+++ b/drivers/media/video/tda985x.c
@@ -0,0 +1,536 @@
+/*
+ * For the TDA9850 and TDA9855 chips
+ * (The TDA9855 is used on the Diamond DTV2000 and the TDA9850 is used
+ * on STB cards. Other cards probably use these chips as well.)
+ * This driver will not complain if used with any
+ * other i2c device with the same address.
+ *
+ * Copyright (c) 1999 Gerd Knorr
+ * TDA9850 code and TDA9855.c merger by Eric Sandeen (eric_sandeen@bigfoot.com)
+ * This code is placed under the terms of the GNU General Public License
+ * Based on tda9855.c by Steve VanDeBogart (vandebo@uclink.berkeley.edu)
+ * Which was based on tda8425.c by Greg Alexander (c) 1998
+ *
+ * OPTIONS:
+ * debug - set to 1 if you'd like to see debug messages
+ * - set to 2 if you'd like to be flooded with debug messages
+ * chip - set to 9850 or 9855 to select your chip (default 9855)
+ *
+ * TODO:
+ * Fix channel change bug - sound goes out when changeing channels, mute
+ * and unmote to fix. - Is this still here?
+ * Fine tune sound
+ * Get rest of capabilities into video_audio struct...
+ *
+ * Revision 0.5 - cleaned up debugging messages, added debug level=2
+ * Revision: 0.4 - check for correct chip= insmod value
+ * also cleaned up comments a bit
+ * Revision: 0.3 - took out extraneous tda985x_write in tda985x_command
+ * Revision: 0.2 - added insmod option chip=
+ * Revision: 0.1 - original version
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+
+#include "bttv.h"
+#include "audiochip.h"
+
+MODULE_PARM(debug,"i");
+MODULE_PARM(chip,"i");
+MODULE_PARM_DESC(chip, "Type of chip to handle: 9850 or 9855");
+
+static int debug = 0; /* insmod parameter */
+static int chip = 9855; /* insmod parameter */
+
+/* Addresses to scan */
+#define I2C_TDA985x_L 0xb4
+#define I2C_TDA985x_H 0xb6
+static unsigned short normal_i2c[] = {I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {
+ I2C_TDA985x_L >> 1,
+ I2C_TDA985x_H >> 1,
+ I2C_CLIENT_END
+};
+static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static struct i2c_client_address_data addr_data = {
+ normal_i2c, normal_i2c_range,
+ probe, probe_range,
+ ignore, ignore_range,
+ force
+};
+
+/* This is a superset of the TDA9850 and TDA9855 members */
+
+struct tda985x {
+ int addr;
+ int rvol, lvol;
+ int bass, treble, sub;
+ int c4, c5, c6, c7;
+ int a1, a2, a3;
+};
+
+static struct i2c_driver driver;
+static struct i2c_client client_template;
+
+#define dprintk if (debug) printk
+#define d2printk if (debug == 2) printk
+
+/* The TDA9850 and TDA9855 are both made by Philips Semiconductor
+ * http://www.semiconductors.philips.com
+ * TDA9850: I2C-bus controlled BTSC stereo/SAP decoder
+ * TDA9855: I2C-bus controlled BTSC stereo/SAP decoder and audio processor
+ *
+ * The TDA9850 has more or less a subset of the functions that the TDA9855
+ * has. As a result, we can re-use many of these defines. Anything with
+ * TDA9855 is specific to that chip, anything with TDA9850 is specific
+ * to that chip, and anything with TDA985x is valid for either.
+ *
+ * To complicate things further, the TDA9850 uses labels C1 through C4
+ * for subaddresses 0x04 through 0x07, while the TDA9855 uses
+ * C1 through C3 for subadresses 0x05 through 0x07 - quite confusing.
+ * To help keep things straight, I have renamed the various C[1,4] labels
+ * to C[4,7] so that the numerical label matches the hex value of the
+ * subaddress for both chips. At least the A[1,3] labels line up. :)
+ */
+
+ /* subaddresses for TDA9855 */
+#define TDA9855_VR 0x00 /* Volume, right */
+#define TDA9855_VL 0x01 /* Volume, left */
+#define TDA9855_BA 0x02 /* Bass */
+#define TDA9855_TR 0x03 /* Treble */
+#define TDA9855_SW 0x04 /* Subwoofer - not connected on DTV2000 */
+
+ /* subaddresses for TDA9850 */
+#define TDA9850_C4 0x04 /* Control 1 for TDA9850 */
+
+ /* subaddesses for both chips */
+#define TDA985x_C5 0x05 /* Control 2 for TDA9850, Control 1 for TDA9855 */
+#define TDA985x_C6 0x06 /* Control 3 for TDA9850, Control 2 for TDA9855 */
+#define TDA985x_C7 0x07 /* Control 4 for TDA9850, Control 3 for TDA9855 */
+#define TDA985x_A1 0x08 /* Alignment 1 for both chips */
+#define TDA985x_A2 0x09 /* Alignment 2 for both chips */
+#define TDA985x_A3 0x0a /* Alignment 3 for both chips */
+
+ /* Masks for bits in TDA9855 subaddresses */
+/* 0x00 - VR in TDA9855 */
+/* 0x01 - VL in TDA9855 */
+/* lower 7 bits control gain from -71dB (0x28) to 16dB (0x7f)
+ * in 1dB steps - mute is 0x27 */
+
+
+/* 0x02 - BA in TDA9855 */
+/* lower 5 bits control bass gain from -12dB (0x06) to 16.5dB (0x19)
+ * in .5dB steps - 0 is 0x0E */
+
+
+/* 0x03 - TR in TDA9855 */
+/* 4 bits << 1 control treble gain from -12dB (0x3) to 12dB (0xb)
+ * in 3dB steps - 0 is 0x7 */
+
+ /* Masks for bits in both chips' subaddresses */
+/* 0x04 - SW in TDA9855, C4/Control 1 in TDA9850 */
+/* Unique to TDA9855: */
+/* 4 bits << 2 control subwoofer/surround gain from -14db (0x1) to 14db (0xf)
+ * in 3dB steps - mute is 0x0 */
+
+/* Unique to TDA9850: */
+/* lower 4 bits control stereo noise threshold, over which stereo turns off
+ * set to values of 0x00 through 0x0f for Ster1 through Ster16 */
+
+
+/* 0x05 - C5 - Control 1 in TDA9855 , Control 2 in TDA9850*/
+/* Unique to TDA9855: */
+#define TDA9855_MUTE 1<<7 /* GMU, Mute at outputs */
+#define TDA9855_AVL 1<<6 /* AVL, Automatic Volume Level */
+#define TDA9855_LOUD 1<<5 /* Loudness, 1==off */
+#define TDA9855_SUR 1<<3 /* Surround / Subwoofer 1==.5(L-R) 0==.5(L+R) */
+ /* Bits 0 to 3 select various combinations
+ * of line in and line out, only the
+ * interesting ones are defined */
+#define TDA9855_EXT 1<<2 /* Selects inputs LIR and LIL. Pins 41 & 12 */
+#define TDA9855_INT 0 /* Selects inputs LOR and LOL. (internal) */
+
+/* Unique to TDA9850: */
+/* lower 4 bits contol SAP noise threshold, over which SAP turns off
+ * set to values of 0x00 through 0x0f for SAP1 through SAP16 */
+
+
+/* 0x06 - C6 - Control 2 in TDA9855, Control 3 in TDA9850 */
+/* Common to TDA9855 and TDA9850: */
+#define TDA985x_SAP 3<<6 /* Selects SAP output, mute if not received */
+#define TDA985x_STEREO 1<<6 /* Selects Stereo ouput, mono if not received */
+#define TDA985x_MONO 0 /* Forces Mono output */
+#define TDA985x_LMU 1<<3 /* Mute (LOR/LOL for 9855, OUTL/OUTR for 9850) */
+
+/* Unique to TDA9855: */
+#define TDA9855_TZCM 1<<5 /* If set, don't mute till zero crossing */
+#define TDA9855_VZCM 1<<4 /* If set, don't change volume till zero crossing*/
+#define TDA9855_LINEAR 0 /* Linear Stereo */
+#define TDA9855_PSEUDO 1 /* Pseudo Stereo */
+#define TDA9855_SPAT_30 2 /* Spatial Stereo, 30% anti-phase crosstalk */
+#define TDA9855_SPAT_50 3 /* Spatial Stereo, 52% anti-phase crosstalk */
+#define TDA9855_E_MONO 7 /* Forced mono - mono select elseware, so useless*/
+
+
+/* 0x07 - C7 - Control 3 in TDA9855, Control 4 in TDA9850 */
+/* Common to both TDA9855 and TDA9850: */
+/* lower 4 bits control input gain from -3.5dB (0x0) to 4dB (0xF)
+ * in .5dB steps - 0dB is 0x7 */
+
+
+/* 0x08, 0x09 - A1 and A2 (read/write) */
+/* Common to both TDA9855 and TDA9850: */
+/* lower 5 bites are wideband and spectral expander alignment
+ * from 0x00 to 0x1f - nominal at 0x0f and 0x10 (read/write) */
+#define TDA985x_STP 1<<5 /* Stereo Pilot/detect (read-only) */
+#define TDA985x_SAPP 1<<6 /* SAP Pilot/detect (read-only) */
+#define TDA985x_STS 1<<7 /* Stereo trigger 1= <35mV 0= <30mV (write-only)*/
+
+
+/* 0x0a - A3 */
+/* Common to both TDA9855 and TDA9850: */
+/* lower 3 bits control timing current for alignment: -30% (0x0), -20% (0x1),
+ * -10% (0x2), nominal (0x3), +10% (0x6), +20% (0x5), +30% (0x4) */
+#define TDA985x_ADJ 1<<7 /* Stereo adjust on/off (wideband and spectral */
+
+/* Unique to TDA9855: */
+/* 2 bits << 5 control AVL attack time: 420ohm (0x0), 730ohm (0x2),
+ * 1200ohm (0x1), 2100ohm (0x3) */
+
+
+/* Begin code */
+
+static int tda985x_write(struct i2c_client *client, int subaddr, int val)
+{
+ unsigned char buffer[2];
+ d2printk("tda985x: In tda985x_write\n");
+ dprintk("tda985x: Writing %d 0x%x\n", subaddr, val);
+ buffer[0] = subaddr;
+ buffer[1] = val;
+ if (2 != i2c_master_send(client,buffer,2)) {
+ printk(KERN_WARNING "tda985x: I/O error, trying (write %d 0x%x)\n",
+ subaddr, val);
+ return -1;
+ }
+ return 0;
+}
+
+static int tda985x_read(struct i2c_client *client)
+{
+ unsigned char buffer;
+ d2printk("tda985x: In tda985x_read\n");
+ if (1 != i2c_master_recv(client,&buffer,1)) {
+ printk(KERN_WARNING "tda985x: I/O error, trying (read)\n");
+ return -1;
+ }
+ dprintk("tda985x: Read 0x%02x\n", buffer);
+ return buffer;
+}
+
+static int tda985x_set(struct i2c_client *client)
+{
+ struct tda985x *t = client->data;
+ unsigned char buf[16];
+ d2printk("tda985x: In tda985x_set\n");
+
+ if (chip == 9855)
+ {
+ dprintk(KERN_INFO
+ "tda985x: tda985x_set(0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x)\n",
+ t->rvol,t->lvol,t->bass,t->treble,t->sub,
+ t->c5,t->c6,t->c7,t->a1,t->a2,t->a3);
+ buf[0] = TDA9855_VR;
+ buf[1] = t->rvol;
+ buf[2] = t->lvol;
+ buf[3] = t->bass;
+ buf[4] = t->treble;
+ buf[5] = t->sub;
+ buf[6] = t->c5;
+ buf[7] = t->c6;
+ buf[8] = t->c7;
+ buf[9] = t->a1;
+ buf[10] = t->a2;
+ buf[11] = t->a3;
+ if (12 != i2c_master_send(client,buf,12)) {
+ printk(KERN_WARNING "tda985x: I/O error, trying tda985x_set\n");
+ return -1;
+ }
+ }
+
+ else if (chip == 9850)
+ {
+ dprintk(KERN_INFO
+ "tda986x: tda985x_set(0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x)\n",
+ t->c4,t->c5,t->c6,t->c7,t->a1,t->a2,t->a3);
+ buf[0] = TDA9850_C4;
+ buf[1] = t->c4;
+ buf[2] = t->c5;
+ buf[3] = t->c6;
+ buf[4] = t->c7;
+ buf[5] = t->a1;
+ buf[6] = t->a2;
+ buf[7] = t->a3;
+ if (8 != i2c_master_send(client,buf,8)) {
+ printk(KERN_WARNING "tda985x: I/O error, trying tda985x_set\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void do_tda985x_init(struct i2c_client *client)
+{
+ struct tda985x *t = client->data;
+ d2printk("tda985x: In tda985x_init\n");
+
+ if (chip == 9855)
+ {
+ printk("tda985x: Using tda9855 options\n");
+ t->rvol = 0x6f; /* 0dB */
+ t->lvol = 0x6f; /* 0dB */
+ t->bass = 0x0e; /* 0dB */
+ t->treble = (0x07 << 1); /* 0dB */
+ t->sub = 0x8 << 2; /* 0dB */
+ t->c5 = TDA9855_MUTE | TDA9855_AVL |
+ TDA9855_LOUD | TDA9855_INT;
+ /* Set Mute, AVL, Loudness off, Internal sound */
+ t->c6 = TDA985x_STEREO | TDA9855_LINEAR |
+ TDA9855_TZCM | TDA9855_VZCM;
+ /* Stereo linear mode, also wait til zero crossings */
+ t->c7 = 0x07; /* 0dB input gain */
+ }
+
+ else if (chip == 9850)
+ {
+ printk("tda985x: Using tda9850 options\n");
+ t->c4 = 0x08; /* Set stereo noise thresh to nominal */
+ t->c5 = 0x08; /* Set SAP noise threshold to nominal */
+ t->c6 = TDA985x_STEREO; /* Select Stereo mode for decoder */
+ t->c7 = 0x07; /* 0dB input gain */
+ }
+
+ /* The following is valid for both chip types */
+ t->a1 = 0x10; /* Select nominal wideband expander */
+ t->a2 = 0x10; /* Select nominal spectral expander and 30mV trigger */
+ t->a3 = 0x3; /* Set: nominal timing current, 420ohm AVL attack */
+
+ tda985x_set(client);
+}
+
+/* *********************** *
+ * i2c interface functions *
+ * *********************** */
+
+static int tda985x_attach(struct i2c_adapter *adap, int addr,
+ unsigned short flags, int kind)
+{
+ struct tda985x *t;
+ struct i2c_client *client;
+ d2printk("tda985x: In tda985x_attach\n");
+ client = kmalloc(sizeof *client,GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+ memcpy(client,&client_template,sizeof(struct i2c_client));
+ client->adapter = adap;
+ client->addr = addr;
+
+ client->data = t = kmalloc(sizeof *t,GFP_KERNEL);
+ if (!t)
+ return -ENOMEM;
+ memset(t,0,sizeof *t);
+ do_tda985x_init(client);
+ MOD_INC_USE_COUNT;
+ strcpy(client->name,"TDA985x");
+ printk(KERN_INFO "tda985x: init\n");
+
+ i2c_attach_client(client);
+ return 0;
+}
+
+static int tda985x_probe(struct i2c_adapter *adap)
+{
+ if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848))
+ return i2c_probe(adap, &addr_data, tda985x_attach);
+ return 0;
+}
+
+static int tda985x_detach(struct i2c_client *client)
+{
+ struct tda985x *t = client->data;
+
+ do_tda985x_init(client);
+ i2c_detach_client(client);
+
+ kfree(t);
+ kfree(client);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int tda985x_command(struct i2c_client *client,
+ unsigned int cmd, void *arg)
+{
+ struct tda985x *t = client->data;
+ d2printk("tda985x: In tda985x_command\n");
+#if 0
+ __u16 *sarg = arg;
+#endif
+
+ switch (cmd) {
+ /* --- v4l ioctls --- */
+ /* take care: bttv does userspace copying, we'll get a
+ kernel pointer here... */
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *va = arg;
+ dprintk("tda985x: VIDIOCGAUDIO\n");
+ if (chip == 9855)
+ {
+ int left,right;
+
+ va->flags |= VIDEO_AUDIO_VOLUME |
+ VIDEO_AUDIO_BASS |
+ VIDEO_AUDIO_TREBLE;
+
+ /* min is 0x27 max is 0x7f, vstep is 2e8 */
+ left = (t->lvol-0x27)*0x2e8;
+ right = (t->rvol-0x27)*0x2e8;
+ va->volume=MAX(left,right);
+ va->balance=(32768*MIN(left,right))/
+ (va->volume ? va->volume : 1);
+ va->balance=(left<right)?
+ (65535-va->balance) : va->balance;
+ va->bass = (t->bass-0x6)*0xccc; /* min 0x6 max 0x19 */
+ va->treble = ((t->treble>>1)-0x3)*0x1c71;
+ }
+
+ /* Valid for both chips: */
+ {
+ va->mode = ((TDA985x_STP | TDA985x_SAPP) &
+ tda985x_read(client)) >> 4;
+ /* Add mono mode regardless of SAP and stereo */
+ /* Allows forced mono */
+ va->mode |= VIDEO_SOUND_MONO;
+ }
+
+ break; /* VIDIOCGAUDIO case */
+ }
+
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *va = arg;
+ dprintk("tda985x: VIDEOCSAUDIO\n");
+ if (chip == 9855)
+ {
+ int left,right;
+
+ left = (MIN(65536 - va->balance,32768) *
+ va->volume) / 32768;
+ right = (MIN(va->balance,32768) *
+ va->volume) / 32768;
+ t->lvol = left/0x2e8+0x27;
+ t->rvol = right/0x2e8+0x27;
+ t->bass = va->bass/0xccc+0x6;
+ t->treble = (va->treble/0x1c71+0x3)<<1;
+ tda985x_write(client,TDA9855_VL,t->lvol);
+ tda985x_write(client,TDA9855_VR,t->rvol);
+ tda985x_write(client,TDA9855_BA, t->bass);
+ tda985x_write(client,TDA9855_TR,t->treble);
+ }
+
+ /* The following is valid for both chips */
+
+ switch (va->mode) {
+ case VIDEO_SOUND_MONO:
+ dprintk("tda985x: VIDEO_SOUND_MONO\n");
+ t->c6= TDA985x_MONO | (t->c6 & 0x3f);
+ tda985x_write(client,TDA985x_C6,t->c6);
+ break;
+ case VIDEO_SOUND_STEREO:
+ dprintk("tda985x: VIDEO_SOUND_STEREO\n");
+ t->c6= TDA985x_STEREO | (t->c6 & 0x3f);
+ tda985x_write(client,TDA985x_C6,t->c6);
+ break;
+ case VIDEO_SOUND_LANG1:
+ dprintk("tda985x: VIDEO_SOUND_LANG1\n");
+ t->c6= TDA985x_SAP | (t->c6 & 0x3f);
+ tda985x_write(client,TDA985x_C6,t->c6);
+ break;
+ } /* End of (va->mode) switch */
+
+ break;
+
+ } /* end of VIDEOCSAUDIO case */
+
+ default: /* Not VIDEOCGAUDIO or VIDEOCSAUDIO */
+
+ /* nothing */
+ d2printk("tda985x: Default\n");
+
+ } /* end of (cmd) switch */
+
+ return 0;
+}
+
+
+static struct i2c_driver driver = {
+ "i2c tda985x driver",
+ I2C_DRIVERID_TDA9855, /* Get new one for TDA985x? */
+ I2C_DF_NOTIFY,
+ tda985x_probe,
+ tda985x_detach,
+ tda985x_command,
+};
+
+static struct i2c_client client_template =
+{
+ "(unset)", /* name */
+ -1,
+ 0,
+ 0,
+ NULL,
+ &driver
+};
+
+#ifdef MODULE
+int init_module(void)
+#else
+int tda985x_init(void)
+#endif
+{
+ if ( (chip != 9850) && (chip != 9855) )
+ {
+ printk(KERN_ERR "tda985x: chip parameter must be 9850 or 9855\n");
+ return -EINVAL;
+ }
+ i2c_add_driver(&driver);
+ return 0;
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ i2c_del_driver(&driver);
+}
+#endif
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/tda9875.c b/drivers/media/video/tda9875.c
new file mode 100644
index 000000000..9f6ecd332
--- /dev/null
+++ b/drivers/media/video/tda9875.c
@@ -0,0 +1,403 @@
+/*
+ * For the TDA9875 chip
+ * (The TDA9875 is used on the Diamond DTV2000 french version
+ * Other cards probably use these chips as well.)
+ * This driver will not complain if used with any
+ * other i2c device with the same address.
+ *
+ * Copyright (c) 2000 Guillaume Delvit based on Gerd Knorr source and
+ * Eric Sandeen
+ * This code is placed under the terms of the GNU General Public License
+ * Based on tda9855.c by Steve VanDeBogart (vandebo@uclink.berkeley.edu)
+ * Which was based on tda8425.c by Greg Alexander (c) 1998
+ *
+ * OPTIONS:
+ * debug - set to 1 if you'd like to see debug messages
+ *
+ * Revision: 0.1 - original version
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+
+#include "bttv.h"
+#include "audiochip.h"
+
+/* This driver ID is brand new, so define it if it's not in i2c-id.h yet */
+#ifndef I2C_DRIVERID_TDA9875
+ #define I2C_DRIVERID_TDA9875 28
+#endif
+
+
+MODULE_PARM(debug,"i");
+
+static int debug = 0; /* insmod parameter */
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = {
+ I2C_TDA9875 >> 1,
+ I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {I2C_CLIENT_END};
+static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static struct i2c_client_address_data addr_data = {
+ normal_i2c, normal_i2c_range,
+ probe, probe_range,
+ ignore, ignore_range,
+ force
+};
+
+/* This is a superset of the TDA9875 */
+struct tda9875 {
+ int mode;
+ int rvol, lvol;
+ int bass, treble;
+};
+
+
+static struct i2c_driver driver;
+static struct i2c_client client_template;
+
+#define dprintk if (debug) printk
+
+/* The TDA9875 is made by Philips Semiconductor
+ * http://www.semiconductors.philips.com
+ * TDA9875: I2C-bus controlled DSP audio processor, FM demodulator
+ *
+ */
+
+ /* subaddresses for TDA9875 */
+#define TDA9875_MUT 0x12 /*General mute (value --> 0b11001100*/
+#define TDA9875_CFG 0x01 /* Config register (value --> 0b00000000 */
+#define TDA9875_DACOS 0x13 /*DAC i/o select (ADC) 0b0000100*/
+#define TDA9875_LOSR 0x16 /*Line output select regirter 0b0100 0001*/
+
+#define TDA9875_CH1V 0x0c /*Chanel 1 volume (mute)*/
+#define TDA9875_CH2V 0x0d /*Chanel 2 volume (mute)*/
+#define TDA9875_SC1 0x14 /*SCART 1 in (mono)*/
+#define TDA9875_SC2 0x15 /*SCART 2 in (mono)*/
+
+#define TDA9875_ADCIS 0x17 /*ADC input select (mono) 0b0110 000*/
+#define TDA9875_AER 0x19 /*Audio effect (AVL+Pseudo) 0b0000 0110*/
+#define TDA9875_MCS 0x18 /*Main channel select (DAC) 0b0000100*/
+#define TDA9875_MVL 0x1a /* Main volume gauche */
+#define TDA9875_MVR 0x1b /* Main volume droite */
+#define TDA9875_MBA 0x1d /* Main Basse */
+#define TDA9875_MTR 0x1e /* Main treble */
+#define TDA9875_ACS 0x1f /* Auxilary channel select (FM) 0b0000000*/
+#define TDA9875_AVL 0x20 /* Auxilary volume gauche */
+#define TDA9875_AVR 0x21 /* Auxilary volume droite */
+#define TDA9875_ABA 0x22 /* Auxilary Basse */
+#define TDA9875_ATR 0x23 /* Auxilary treble */
+
+#define TDA9875_MSR 0x02 /* Monitor select register */
+#define TDA9875_C1MSB 0x03 /* Carrier 1 (FM) frequency register MSB */
+#define TDA9875_C1MIB 0x04 /* Carrier 1 (FM) frequency register (16-8]b */
+#define TDA9875_C1LSB 0x05 /* Carrier 1 (FM) frequency register LSB */
+#define TDA9875_C2MSB 0x06 /* Carrier 2 (nicam) frequency register MSB */
+#define TDA9875_C2MIB 0x07 /* Carrier 2 (nicam) frequency register (16-8]b */
+#define TDA9875_C2LSB 0x08 /* Carrier 2 (nicam) frequency register LSB */
+#define TDA9875_DCR 0x09 /* Demodulateur configuration regirter*/
+#define TDA9875_DEEM 0x0a /* FM de-emphasis regirter*/
+#define TDA9875_FMAT 0x0b /* FM Matrix regirter*/
+
+/* values */
+#define TDA9875_MUTE_ON 0xff /* general mute */
+#define TDA9875_MUTE_OFF 0xcc /* general no mute */
+
+
+
+/* Begin code */
+
+static int tda9875_write(struct i2c_client *client, int subaddr, unsigned char val)
+{
+ unsigned char buffer[2];
+ dprintk("In tda9875_write\n");
+ dprintk("Writing %d 0x%x\n", subaddr, val);
+ buffer[0] = subaddr;
+ buffer[1] = val;
+ if (2 != i2c_master_send(client,buffer,2)) {
+ printk(KERN_WARNING "tda9875: I/O error, trying (write %d 0x%x)\n",
+ subaddr, val);
+ return -1;
+ }
+ return 0;
+}
+
+#if 0
+static int tda9875_read(struct i2c_client *client)
+{
+ unsigned char buffer;
+ dprintk("In tda9875_read\n");
+ if (1 != i2c_master_recv(client,&buffer,1)) {
+ printk(KERN_WARNING "tda9875: I/O error, trying (read)\n");
+ return -1;
+ }
+ dprintk("Read 0x%02x\n", buffer);
+ return buffer;
+}
+#endif
+
+static void tda9875_set(struct i2c_client *client)
+{
+ struct tda9875 *tda = client->data;
+ unsigned char a;
+
+ dprintk(KERN_DEBUG "tda9875_set(%04x,%04x,%04x,%04x)\n",tda->lvol,tda->rvol,tda->bass,tda->treble);
+
+
+ a = tda->lvol & 0xff;
+ tda9875_write(client, TDA9875_MVL, a);
+ a =tda->rvol & 0xff;
+ tda9875_write(client, TDA9875_MVR, a);
+ a =tda->bass & 0xff;
+ tda9875_write(client, TDA9875_MBA, a);
+ a =tda->treble & 0xff;
+ tda9875_write(client, TDA9875_MTR, a);
+}
+
+static void do_tda9875_init(struct i2c_client *client)
+{
+ struct tda9875 *t = client->data;
+ dprintk("In tda9875_init\n");
+ tda9875_write(client, TDA9875_CFG, 0xd0 ); /*reg de config 0 (reset)*/
+ tda9875_write(client, TDA9875_MSR, 0x03 ); /* Monitor 0b00000XXX*/
+ tda9875_write(client, TDA9875_C1MSB, 0x00 ); /*Car1(FM) MSB XMHz*/
+ tda9875_write(client, TDA9875_C1MIB, 0x00 ); /*Car1(FM) MIB XMHz*/
+ tda9875_write(client, TDA9875_C1LSB, 0x00 ); /*Car1(FM) LSB XMHz*/
+ tda9875_write(client, TDA9875_C2MSB, 0x00 ); /*Car2(NICAM) MSB XMHz*/
+ tda9875_write(client, TDA9875_C2MIB, 0x00 ); /*Car2(NICAM) MIB XMHz*/
+ tda9875_write(client, TDA9875_C2LSB, 0x00 ); /*Car2(NICAM) LSB XMHz*/
+ tda9875_write(client, TDA9875_DCR, 0x00 ); /*Demod config 0x00*/
+ tda9875_write(client, TDA9875_DEEM, 0x44 ); /*DE-Emph 0b0100 0100*/
+ tda9875_write(client, TDA9875_FMAT, 0x00 ); /*FM Matrix reg 0x00*/
+ tda9875_write(client, TDA9875_SC1, 0x00 ); /* SCART 1 (SC1)*/
+ tda9875_write(client, TDA9875_SC2, 0x01 ); /* SCART 2 (sc2)*/
+
+ tda9875_write(client, TDA9875_CH1V, 0x10 ); /* Chanel volume 1 mute*/
+ tda9875_write(client, TDA9875_CH2V, 0x10 ); /* Chanel volume 2 mute */
+ tda9875_write(client, TDA9875_DACOS, 0x02 ); /* sig DAC i/o(in:nicam)*/
+ tda9875_write(client, TDA9875_ADCIS, 0x6f ); /* sig ADC input(in:mono)*/
+ tda9875_write(client, TDA9875_LOSR, 0x00 ); /* line out (in:mono)*/
+ tda9875_write(client, TDA9875_AER, 0x00 ); /*06 Effect (AVL+PSEUDO) */
+ tda9875_write(client, TDA9875_MCS, 0x44 ); /* Main ch select (DAC) */
+ tda9875_write(client, TDA9875_MVL, 0x03 ); /* Vol Main left 10dB */
+ tda9875_write(client, TDA9875_MVR, 0x03 ); /* Vol Main right 10dB*/
+ tda9875_write(client, TDA9875_MBA, 0x00 ); /* Main Bass Main 0dB*/
+ tda9875_write(client, TDA9875_MTR, 0x00 ); /* Main Treble Main 0dB*/
+ tda9875_write(client, TDA9875_ACS, 0x44 ); /* Aux chan select (dac)*/
+ tda9875_write(client, TDA9875_AVL, 0x00 ); /* Vol Aux left 0dB*/
+ tda9875_write(client, TDA9875_AVR, 0x00 ); /* Vol Aux right 0dB*/
+ tda9875_write(client, TDA9875_ABA, 0x00 ); /* Aux Bass Main 0dB*/
+ tda9875_write(client, TDA9875_ATR, 0x00 ); /* Aux Aigus Main 0dB*/
+
+ tda9875_write(client, TDA9875_MUT, 0xcc ); /* General mute */
+
+ t->mode=AUDIO_UNMUTE;
+ t->lvol=t->rvol =0; /* 0dB */
+ t->bass=0; /* 0dB */
+ t->treble=0; /* 0dB */
+ tda9875_set(client);
+
+}
+
+
+/* *********************** *
+ * i2c interface functions *
+ * *********************** */
+
+static int tda9875_attach(struct i2c_adapter *adap, int addr,
+ unsigned short flags, int kind)
+{
+ struct tda9875 *t;
+ struct i2c_client *client;
+ dprintk("In tda9875_attach\n");
+ client = kmalloc(sizeof *client,GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+ memcpy(client,&client_template,sizeof(struct i2c_client));
+ client->adapter = adap;
+ client->addr = addr;
+
+ client->data = t = kmalloc(sizeof *t,GFP_KERNEL);
+ if (!t)
+ return -ENOMEM;
+ memset(t,0,sizeof *t);
+ do_tda9875_init(client);
+ MOD_INC_USE_COUNT;
+ strcpy(client->name,"TDA9875");
+ printk(KERN_INFO "tda9875: init\n");
+
+ i2c_attach_client(client);
+ return 0;
+}
+
+static int tda9875_probe(struct i2c_adapter *adap)
+{
+ if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848))
+ return i2c_probe(adap, &addr_data, tda9875_attach);
+ return 0;
+}
+
+static int tda9875_detach(struct i2c_client *client)
+{
+ struct tda9875 *t = client->data;
+
+ do_tda9875_init(client);
+ i2c_detach_client(client);
+
+ kfree(t);
+ kfree(client);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int tda9875_command(struct i2c_client *client,
+ unsigned int cmd, void *arg)
+{
+ struct tda9875 *t = client->data;
+
+ dprintk("In tda9875_command...\n");
+
+ switch (cmd) {
+ /* --- v4l ioctls --- */
+ /* take care: bttv does userspace copying, we'll get a
+ kernel pointer here... */
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *va = arg;
+ int left,right;
+
+ dprintk("VIDIOCGAUDIO\n");
+
+ va->flags |= VIDEO_AUDIO_VOLUME |
+ VIDEO_AUDIO_BASS |
+ VIDEO_AUDIO_TREBLE;
+
+ /* min is -84 max is 24 */
+ left = (t->lvol+84)*606;
+ right = (t->rvol+84)*606;
+ va->volume=MAX(left,right);
+ va->balance=(32768*MIN(left,right))/
+ (va->volume ? va->volume : 1);
+ va->balance=(left<right)?
+ (65535-va->balance) : va->balance;
+ va->bass = (t->bass+12)*2427; /* min -12 max +15 */
+ va->treble = (t->treble+12)*2730;/* min -12 max +12 */
+
+ va->mode |= VIDEO_SOUND_MONO;
+
+
+ break; /* VIDIOCGAUDIO case */
+ }
+
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *va = arg;
+ int left,right;
+
+ dprintk("VIDEOCSAUDIO...\n");
+ left = (MIN(65536 - va->balance,32768) *
+ va->volume) / 32768;
+ right = (MIN(va->balance,32768) *
+ va->volume) / 32768;
+ t->lvol = ((left/606)-84) & 0xff;
+ if (t->lvol > 24)
+ t->lvol = 24;
+ if (t->lvol < -84)
+ t->lvol = -84 & 0xff;
+
+ t->rvol = ((right/606)-84) & 0xff;
+ if (t->rvol > 24)
+ t->rvol = 24;
+ if (t->rvol < -84)
+ t->rvol = -84 & 0xff;
+
+ t->bass = ((va->bass/2400)-12) & 0xff;
+ if (t->bass > 15)
+ t->bass = 15;
+ if (t->bass < -12)
+ t->bass = -12 & 0xff;
+
+ t->treble = ((va->treble/2700)-12) & 0xff;
+ if (t->treble > 12)
+ t->treble = 12;
+ if (t->treble < -12)
+ t->treble = -12 & 0xff;
+
+
+
+//printk("tda9875 bal:%04x vol:%04x bass:%04x treble:%04x\n",va->balance,va->volume,va->bass,va->treble);
+
+
+ tda9875_set(client);
+
+ break;
+
+ } /* end of VIDEOCSAUDIO case */
+
+ default: /* Not VIDEOCGAUDIO or VIDEOCSAUDIO */
+
+ /* nothing */
+ dprintk("Default\n");
+
+ } /* end of (cmd) switch */
+
+ return 0;
+}
+
+
+static struct i2c_driver driver = {
+ "i2c tda9875 driver",
+ I2C_DRIVERID_TDA9875, /* Get new one for TDA9875 */
+ I2C_DF_NOTIFY,
+ tda9875_probe,
+ tda9875_detach,
+ tda9875_command,
+};
+
+static struct i2c_client client_template =
+{
+ "(unset)", /* name */
+ -1,
+ 0,
+ 0,
+ NULL,
+ &driver
+};
+
+#ifdef MODULE
+int init_module(void)
+#else
+int tda9875_init(void)
+#endif
+{
+ i2c_add_driver(&driver);
+ return 0;
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ i2c_del_driver(&driver);
+}
+#endif
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
+
diff --git a/drivers/media/video/tea6300.c b/drivers/media/video/tea6300.c
new file mode 100644
index 000000000..f5949c94f
--- /dev/null
+++ b/drivers/media/video/tea6300.c
@@ -0,0 +1,344 @@
+/*
+ * for the TEA6300 chip (only found on Gateway STB TV/FM cards tho the best
+ * of my knowledge)
+ * WARNING: THIS DRIVER WILL LOAD WITHOUT COMPLAINTS EVEN IF THE WRONG
+ * CHIP (i.e., an MSP3400) IS ON I2C ADDRESS 0x80 (it relies on i2c to
+ * make sure that there is a device acknowledging that address). This
+ * is a potential problem because the MSP3400 is very popular and does
+ * use this address! You have been warned!
+ *
+ * Copyright (c) 1998 Greg Alexander <galexand@acm.org>
+ * This code is placed under the terms of the GNU General Public License
+ * Code liberally copied from msp3400.c, which is by Gerd Knorr
+ *
+ * All of this should work, though it would be nice to eventually support
+ * balance (different left,right values) and, if someone ever finds a card
+ * with the support (or if you're careful with a soldering iron), fade
+ * (front/back).
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+
+#include "bttv.h"
+#include "audiochip.h"
+
+
+/* Addresses to scan */
+#define I2C_TEA6300 0x80
+static unsigned short normal_i2c[] = {
+ I2C_TEA6300 >> 1,
+ I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {I2C_CLIENT_END};
+static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static struct i2c_client_address_data addr_data = {
+ normal_i2c, normal_i2c_range,
+ probe, probe_range,
+ ignore, ignore_range,
+ force
+};
+
+
+MODULE_PARM(debug,"i");
+static int debug = 0; /* insmod parameter */
+
+#define dprintk if (debug) printk
+
+
+struct tea6300 {
+ int mode; /* set to AUDIO_{OFF,TUNER,RADIO,EXTERN} */
+ int stereo;
+ __u16 left,right;
+ __u16 bass,treble;
+};
+
+static struct i2c_driver driver;
+static struct i2c_client client_template;
+
+#define TEA6300_VL 0x00 /* volume left */
+#define TEA6300_VR 0x01 /* volume right */
+#define TEA6300_BA 0x02 /* bass */
+#define TEA6300_TR 0x03 /* treble */
+#define TEA6300_FA 0x04 /* fader control */
+#define TEA6300_S 0x05 /* switch register */
+ /* values for those registers: */
+#define TEA6300_S_SA 0x01 /* stereo A input */
+#define TEA6300_S_SB 0x02 /* stereo B */
+#define TEA6300_S_SC 0x04 /* stereo C */
+#define TEA6300_S_GMU 0x80 /* general mute */
+
+
+/* ******************************** *
+ * functions for talking to TEA6300 *
+ * ******************************** */
+
+static int tea6300_write(struct i2c_client *client, int addr, int val)
+{
+ unsigned char buffer[2];
+
+ buffer[0] = addr;
+ buffer[1] = val;
+ if (2 != i2c_master_send(client,buffer,2)) {
+ printk(KERN_WARNING "tea6300: I/O error, trying (write %d 0x%x)\n",
+ addr, val);
+ return -1;
+ }
+ return 0;
+}
+
+static void tea6300_set(struct i2c_client *client)
+{
+ struct tea6300 *tea = client->data;
+
+ /* mode is ignored today */
+ dprintk(KERN_DEBUG "tea6300_set(%04x,%04x,%04x,%04x)\n",tea->left>>10,tea->right>>10,tea->bass>>12,tea->treble>>12);
+ tea6300_write(client, TEA6300_VL, tea->left>>10 );
+ tea6300_write(client, TEA6300_VR, tea->right>>10 );
+ tea6300_write(client, TEA6300_BA, tea->bass>>12 );
+ tea6300_write(client, TEA6300_TR, tea->treble>>12);
+}
+
+static void do_tea6300_init(struct i2c_client *client)
+{
+ struct tea6300 *tea = client->data;
+
+ tea->left=tea->right =49152; /* -10dB (loud enough, but not beyond
+ normal line levels - so as to avoid
+ clipping */
+ tea->bass=tea->treble=28672; /* 0dB */
+ tea->mode=AUDIO_OFF;
+ tea->stereo=1;
+ /* left=right=0x27<<10, bass=treble=0x07<<12 */
+ tea6300_write(client, TEA6300_FA, 0x3f ); /* fader off */
+ tea6300_write(client, TEA6300_S , TEA6300_S_GMU); /* mute */
+ tea6300_set(client);
+}
+
+static void tea6300_audio(struct i2c_client *client, int mode)
+{
+ struct tea6300 *tea = client->data;
+
+ /* valid for AUDIO_TUNER, RADIO, EXTERN, OFF */
+ dprintk(KERN_DEBUG "tea6300_audio:%d (T,R,E,I,O)\n",mode);
+ tea->mode=mode;
+ if (mode==AUDIO_OFF) { /* just mute it */
+ tea6300_write(client, TEA6300_S, TEA6300_S_GMU);
+ return;
+ }
+ switch(mode) {
+ case AUDIO_TUNER:
+ tea6300_write(client, TEA6300_S, TEA6300_S_SA);
+ break;
+ case AUDIO_RADIO:
+ tea6300_write(client, TEA6300_S, TEA6300_S_SB);
+ break;
+ case AUDIO_EXTERN:
+ tea6300_write(client, TEA6300_S, TEA6300_S_SC);
+ break;
+ }
+}
+
+
+/* *********************** *
+ * i2c interface functions *
+ * *********************** */
+
+static int tea6300_attach(struct i2c_adapter *adap, int addr,
+ unsigned short flags, int kind)
+{
+ struct tea6300 *tea;
+ struct i2c_client *client;
+
+ client = kmalloc(sizeof *client,GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+ memcpy(client,&client_template,sizeof(struct i2c_client));
+ client->adapter = adap;
+ client->addr = addr;
+
+ client->data = tea = kmalloc(sizeof *tea,GFP_KERNEL);
+ if (!tea)
+ return -ENOMEM;
+ memset(tea,0,sizeof *tea);
+ do_tea6300_init(client);
+
+ MOD_INC_USE_COUNT;
+ strcpy(client->name,"TEA6300T");
+ printk(KERN_INFO "tea6300: initialized\n");
+
+ i2c_attach_client(client);
+ return 0;
+}
+
+static int tea6300_probe(struct i2c_adapter *adap)
+{
+ if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848))
+ return i2c_probe(adap, &addr_data, tea6300_attach);
+ return 0;
+}
+
+static int tea6300_detach(struct i2c_client *client)
+{
+ struct tea6300 *tea = client->data;
+
+ do_tea6300_init(client);
+ i2c_detach_client(client);
+
+ kfree(tea);
+ kfree(client);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int
+tea6300_command(struct i2c_client *client, unsigned int cmd, void *arg)
+{
+ struct tea6300 *tea = client->data;
+ __u16 *sarg = arg;
+
+ switch (cmd) {
+ case AUDC_SET_RADIO:
+ tea6300_audio(client,AUDIO_RADIO);
+ break;
+ case AUDC_SET_INPUT:
+ tea6300_audio(client,*sarg);
+ break;
+
+ /* --- v4l ioctls --- */
+ /* take care: bttv does userspace copying, we'll get a
+ kernel pointer here... */
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *va = arg;
+
+ va->flags |= VIDEO_AUDIO_VOLUME |
+ VIDEO_AUDIO_BASS |
+ VIDEO_AUDIO_TREBLE;
+ va->volume=MAX(tea->left,tea->right);
+ va->balance=(32768*MIN(tea->left,tea->right))/
+ (va->volume ? va->volume : 1);
+ va->balance=(tea->left<tea->right)?
+ (65535-va->balance) : va->balance;
+ va->bass = tea->bass;
+ va->treble = tea->treble;
+ break;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *va = arg;
+
+ tea->left = (MIN(65536 - va->balance,32768) *
+ va->volume) / 32768;
+ tea->right = (MIN(va->balance,32768) *
+ va->volume) / 32768;
+ tea->bass = va->bass;
+ tea->treble = va->treble;
+ tea6300_set(client);
+ break;
+ }
+#if 0
+ /* --- old, obsolete interface --- */
+ case AUDC_GET_VOLUME_LEFT:
+ *sarg = tea->left;
+ break;
+ case AUDC_GET_VOLUME_RIGHT:
+ *sarg = tea->right;
+ break;
+ case AUDC_SET_VOLUME_LEFT:
+ tea->left = *sarg;
+ tea6300_set(client);
+ break;
+ case AUDC_SET_VOLUME_RIGHT:
+ tea->right = *sarg;
+ tea6300_set(client);
+ break;
+
+ case AUDC_GET_BASS:
+ *sarg = tea->bass;
+ break;
+ case AUDC_SET_BASS:
+ tea->bass = *sarg;
+ tea6300_set(client);
+ break;
+
+ case AUDC_GET_TREBLE:
+ *sarg = tea->treble;
+ break;
+ case AUDC_SET_TREBLE:
+ tea->treble = *sarg;
+ tea6300_set(client);
+ break;
+
+ case AUDC_GET_STEREO:
+ *sarg = tea->stereo?VIDEO_SOUND_STEREO:VIDEO_SOUND_MONO;
+ break;
+ case AUDC_SET_STEREO:
+ tea->stereo=(*sarg==VIDEO_SOUND_MONO)?0:1;
+ /* TODO: make this write to the TDA9850? */
+ break;
+
+/* case AUDC_SWITCH_MUTE: someday, maybe -- not a lot of point to
+ case AUDC_NEWCHANNEL: it and it would require preserving state
+ case AUDC_GET_DC: huh?? (not used by bttv.c)
+*/
+#endif
+ default:
+ /* nothing */
+ }
+ return 0;
+}
+
+static struct i2c_driver driver = {
+ "i2c tea6300 driver",
+ I2C_DRIVERID_TEA6300,
+ I2C_DF_NOTIFY,
+ tea6300_probe,
+ tea6300_detach,
+ tea6300_command,
+};
+
+static struct i2c_client client_template =
+{
+ "(unset)", /* name */
+ -1,
+ 0,
+ 0,
+ NULL,
+ &driver
+};
+
+#ifdef MODULE
+int init_module(void)
+#else
+int tea6300_init(void)
+#endif
+{
+ i2c_add_driver(&driver);
+ return 0;
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ i2c_del_driver(&driver);
+}
+#endif
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/tea6420.c b/drivers/media/video/tea6420.c
new file mode 100644
index 000000000..3a1d63517
--- /dev/null
+++ b/drivers/media/video/tea6420.c
@@ -0,0 +1,273 @@
+/*
+ * for the TEA6420 chip (only found on 3DFX (STB) TV/FM cards to the best
+ * of my knowledge)
+ * Copyright (C) 2000 Dave Stuart <justdave@ynn.com>
+ * This code is placed under the terms of the GNU General Public License
+ * Code liberally copied from tea6300 by . . .
+ *
+ * Copyright (c) 1998 Greg Alexander <galexand@acm.org>
+ * This code is placed under the terms of the GNU General Public License
+ * Code liberally copied from msp3400.c, which is by Gerd Knorr
+ *
+ * Changes:
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 08/14/2000
+ * - resource allocation fixes in tea6300_attach
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+
+#include "bttv.h"
+#include "audiochip.h"
+
+
+/* Addresses to scan */
+#define I2C_TEA6420 0x98
+static unsigned short normal_i2c[] = {
+ I2C_TEA6420 >> 1,
+ I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {I2C_CLIENT_END};
+static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static struct i2c_client_address_data addr_data = {
+ normal_i2c, normal_i2c_range,
+ probe, probe_range,
+ ignore, ignore_range,
+ force
+};
+
+
+MODULE_PARM(debug,"i");
+static int debug = 0; /* insmod parameter */
+
+#define dprintk if (debug) printk
+
+
+struct tea6420 {
+ int mode; /* set to AUDIO_{OFF,TUNER,RADIO,EXTERN} */
+ int stereo;
+};
+
+static struct i2c_driver driver;
+static struct i2c_client client_template;
+
+#define TEA6420_S_SA 0x00 /* stereo A input */
+#define TEA6420_S_SB 0x01 /* stereo B */
+#define TEA6420_S_SC 0x02 /* stereo C */
+#define TEA6420_S_SD 0x03 /* stereo D */
+#define TEA6420_S_SE 0x04 /* stereo E */
+#define TEA6420_S_GMU 0x05 /* general mute */
+
+
+/* ******************************** *
+ * functions for talking to TEA6420 *
+ * ******************************** */
+
+static int tea6420_write(struct i2c_client *client, int val)
+{
+ unsigned char buffer[2];
+ int result;
+
+/* buffer[0] = addr; */
+ buffer[0] = val;
+ result = i2c_master_send(client,buffer,1);
+ if (1 != result) {
+ printk(KERN_WARNING "tea6420: I/O error, trying (write
+0x%x) result = %d\n", val, result);
+ return -1;
+ }
+ return 0;
+}
+
+
+static void do_tea6420_init(struct i2c_client *client)
+{
+ struct tea6420 *tea = client->data;
+
+ tea->mode=AUDIO_OFF;
+ tea->stereo=1;
+ tea6420_write(client, TEA6420_S_GMU); /* mute */
+}
+
+static void tea6420_audio(struct i2c_client *client, int mode)
+{
+ struct tea6420 *tea = client->data;
+
+ /* valid for AUDIO_TUNER, RADIO, EXTERN, OFF */
+ dprintk(KERN_DEBUG "tea6420_audio:%d (T,R,E,I,O)\n",mode);
+ tea->mode=mode;
+ if (mode==AUDIO_OFF) { /* just mute it */
+ tea6420_write(client, TEA6420_S_GMU);
+ return;
+ }
+ switch(mode) {
+ case AUDIO_TUNER:
+ tea6420_write(client, TEA6420_S_SA);
+ break;
+ case AUDIO_RADIO:
+ tea6420_write(client, TEA6420_S_SB);
+ break;
+ case AUDIO_EXTERN:
+ tea6420_write(client, TEA6420_S_SC);
+ break;
+ }
+}
+
+
+/* *********************** *
+ * i2c interface functions *
+ * *********************** */
+
+static int tea6420_attach(struct i2c_adapter *adap, int addr,
+ unsigned short flags, int kind)
+{
+ struct tea6420 *tea;
+ struct i2c_client *client;
+
+ client = kmalloc(sizeof *client,GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+ memcpy(client,&client_template,sizeof(struct i2c_client));
+ client->adapter = adap;
+ client->addr = addr;
+
+ client->data = tea = kmalloc(sizeof *tea,GFP_KERNEL);
+ if (!tea) {
+ kfree(client);
+ return -ENOMEM;
+ }
+ memset(tea,0,sizeof *tea);
+ do_tea6420_init(client);
+
+ MOD_INC_USE_COUNT;
+ strcpy(client->name,"TEA6420");
+ printk(KERN_INFO "tea6420: initialized\n");
+
+ i2c_attach_client(client);
+ return 0;
+}
+
+static int tea6420_probe(struct i2c_adapter *adap)
+{
+ if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848))
+ return i2c_probe(adap, &addr_data, tea6420_attach);
+ return 0;
+}
+
+static int tea6420_detach(struct i2c_client *client)
+{
+ struct tea6420 *tea = client->data;
+
+ do_tea6420_init(client);
+ i2c_detach_client(client);
+
+ kfree(tea);
+ kfree(client);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int
+tea6420_command(struct i2c_client *client, unsigned int cmd, void *arg)
+{
+ __u16 *sarg = arg;
+
+ switch (cmd) {
+ case AUDC_SET_RADIO:
+ tea6420_audio(client,AUDIO_RADIO);
+ break;
+ case AUDC_SET_INPUT:
+ tea6420_audio(client,*sarg);
+ break;
+
+ /* --- v4l ioctls --- */
+ /* take care: bttv does userspace copying, we'll get a
+ kernel pointer here... */
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *va = arg;
+
+ va->flags |= VIDEO_AUDIO_VOLUME |
+ VIDEO_AUDIO_BASS |
+ VIDEO_AUDIO_TREBLE;
+/* va->volume=MAX(tea->left,tea->right);
+ va->balance=(32768*MIN(tea->left,tea->right))/
+ (va->volume ? va->volume : 1);
+ va->balance=(tea->left<tea->right)?
+ (65535-va->balance) : va->balance;
+ va->bass = tea->bass;
+ va->treble = tea->treble;
+*/ break;
+ }
+ case VIDIOCSAUDIO:
+ {
+
+/* tea->left = (MIN(65536 - va->balance,32768) *
+ va->volume) / 32768;
+ tea->right = (MIN(va->balance,32768) *
+ va->volume) / 32768;
+ tea->bass = va->bass;
+ tea->treble = va->treble;
+ tea6420_set(client);
+*/ break;
+ }
+
+default:
+ /* nothing */
+ }
+ return 0;
+}
+
+static struct i2c_driver driver = {
+ "i2c tea6420 driver",
+ I2C_DRIVERID_TEA6420,
+ I2C_DF_NOTIFY,
+ tea6420_probe,
+ tea6420_detach,
+ tea6420_command,
+};
+
+static struct i2c_client client_template =
+{
+ "(unset)", /* name */
+ -1,
+ 0,
+ 0,
+ NULL,
+ &driver
+};
+
+#ifdef MODULE
+int init_module(void)
+#else
+int tea6420_init(void)
+#endif
+{
+ i2c_add_driver(&driver);
+ return 0;
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ i2c_del_driver(&driver);
+}
+#endif
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/tuner-3036.c b/drivers/media/video/tuner-3036.c
new file mode 100644
index 000000000..807ae339b
--- /dev/null
+++ b/drivers/media/video/tuner-3036.c
@@ -0,0 +1,227 @@
+/*
+ * Driver for Philips SAB3036 "CITAC" tuner control chip.
+ *
+ * Author: Phil Blundell <philb@gnu.org>
+ *
+ * The SAB3036 is just about different enough from the chips that
+ * tuner.c copes with to make it not worth the effort to crowbar
+ * the support into that file. So instead we have a separate driver.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/version.h>
+#include <linux/init.h>
+
+#include <linux/i2c.h>
+#include <linux/videodev.h>
+
+#include "tuner.h"
+
+static int debug; /* insmod parameter */
+static int this_adap;
+
+static struct i2c_client client_template;
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = {I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {0x60, 0x61, I2C_CLIENT_END};
+static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+
+static struct i2c_client_address_data addr_data = {
+ normal_i2c, normal_i2c_range,
+ probe, probe_range,
+ ignore, ignore_range,
+ force
+};
+
+/* ---------------------------------------------------------------------- */
+
+static unsigned char
+tuner_getstatus (struct i2c_client *c)
+{
+ unsigned char byte;
+ if (i2c_master_recv(c, &byte, 1) != 1)
+ printk(KERN_ERR "tuner-3036: I/O error.\n");
+ return byte;
+}
+
+#define TUNER_FL 0x80
+
+static int
+tuner_islocked (struct i2c_client *c)
+{
+ return (tuner_getstatus(c) & TUNER_FL);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void
+set_tv_freq(struct i2c_client *c, int freq)
+{
+ u16 div = ((freq * 20) / 16);
+ unsigned long give_up = jiffies + HZ;
+ unsigned char buffer[2];
+
+ if (debug)
+ printk(KERN_DEBUG "tuner: setting frequency %dMHz, divisor %x\n", freq / 16, div);
+
+ /* Select high tuning current */
+ buffer[0] = 0x29;
+ buffer[1] = 0x3e;
+
+ if (i2c_master_send(c, buffer, 2) != 2)
+ printk("tuner: i2c i/o error 1\n");
+
+ buffer[0] = 0x80 | ((div>>8) & 0x7f);
+ buffer[1] = div & 0xff;
+
+ if (i2c_master_send(c, buffer, 2) != 2)
+ printk("tuner: i2c i/o error 2\n");
+
+ while (!tuner_islocked(c) && time_before(jiffies, give_up))
+ schedule();
+
+ if (!tuner_islocked(c))
+ printk(KERN_WARNING "tuner: failed to achieve PLL lock\n");
+
+ /* Select low tuning current and engage AFC */
+ buffer[0] = 0x29;
+ buffer[1] = 0xb2;
+
+ if (i2c_master_send(c, buffer, 2) != 2)
+ printk("tuner: i2c i/o error 3\n");
+
+ if (debug)
+ printk(KERN_DEBUG "tuner: status %02x\n", tuner_getstatus(c));
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int
+tuner_attach(struct i2c_adapter *adap, int addr,
+ unsigned short flags, int kind)
+{
+ static unsigned char buffer[] = { 0x29, 0x32, 0x2a, 0, 0x2b, 0 };
+
+ struct i2c_client *client;
+
+ if (this_adap > 0)
+ return -1;
+ this_adap++;
+
+ client_template.adapter = adap;
+ client_template.addr = addr;
+
+ client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
+ if (client == NULL)
+ return -ENOMEM;
+ memcpy(client, &client_template, sizeof(struct i2c_client));
+
+ printk("tuner: SAB3036 found, status %02x\n", tuner_getstatus(client));
+
+ i2c_attach_client(client);
+ MOD_INC_USE_COUNT;
+
+ if (i2c_master_send(client, buffer, 2) != 2)
+ printk("tuner: i2c i/o error 1\n");
+ if (i2c_master_send(client, buffer+2, 2) != 2)
+ printk("tuner: i2c i/o error 2\n");
+ if (i2c_master_send(client, buffer+4, 2) != 2)
+ printk("tuner: i2c i/o error 3\n");
+ return 0;
+}
+
+static int
+tuner_detach(struct i2c_client *c)
+{
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int
+tuner_command(struct i2c_client *client, unsigned int cmd, void *arg)
+{
+ int *iarg = (int*)arg;
+
+ switch (cmd)
+ {
+ case TUNER_SET_TVFREQ:
+ set_tv_freq(client, *iarg);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int
+tuner_probe(struct i2c_adapter *adap)
+{
+ this_adap = 0;
+ if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_LP))
+ return i2c_probe(adap, &addr_data, tuner_attach);
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static struct i2c_driver
+i2c_driver_tuner =
+{
+ "sab3036", /* name */
+ I2C_DRIVERID_SAB3036, /* ID */
+ I2C_DF_NOTIFY,
+ tuner_probe,
+ tuner_detach,
+ tuner_command
+};
+
+static struct i2c_client client_template =
+{
+ "SAB3036", /* name */
+ -1,
+ 0,
+ 0,
+ NULL,
+ &i2c_driver_tuner
+};
+
+EXPORT_NO_SYMBOLS;
+
+int __init
+tuner3036_init(void)
+{
+ i2c_add_driver(&i2c_driver_tuner);
+ return 0;
+}
+
+void __exit
+tuner3036_exit(void)
+{
+ i2c_del_driver(&i2c_driver_tuner);
+}
+
+MODULE_DESCRIPTION("SAB3036 tuner driver");
+MODULE_AUTHOR("Philip Blundell <philb@gnu.org>");
+MODULE_PARM(debug,"i");
+MODULE_PARM_DESC(debug,"Enable debugging output");
+
+module_init(tuner3036_init);
+module_exit(tuner3036_exit);
diff --git a/drivers/media/video/tuner.c b/drivers/media/video/tuner.c
new file mode 100644
index 000000000..dfbf0f6af
--- /dev/null
+++ b/drivers/media/video/tuner.c
@@ -0,0 +1,451 @@
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/poll.h>
+#include <linux/i2c.h>
+#include <linux/types.h>
+#include <linux/videodev.h>
+#include <linux/init.h>
+
+#include "tuner.h"
+#include "audiochip.h"
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = {I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {0x60,0x6f,I2C_CLIENT_END};
+static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static struct i2c_client_address_data addr_data = {
+ normal_i2c, normal_i2c_range,
+ probe, probe_range,
+ ignore, ignore_range,
+ force
+};
+
+static int debug = 0; /* insmod parameter */
+static int type = -1; /* insmod parameter */
+
+static int addr = 0;
+static int this_adap;
+
+#define dprintk if (debug) printk
+
+MODULE_PARM(debug,"i");
+MODULE_PARM(type,"i");
+MODULE_PARM(addr,"i");
+
+struct tuner
+{
+ int type; /* chip type */
+ int freq; /* keep track of the current settings */
+ int std;
+
+ int radio;
+ int mode; /* PAL(0)/SECAM(1) mode (PHILIPS_SECAM only) */
+};
+
+static struct i2c_driver driver;
+static struct i2c_client client_template;
+
+/* ---------------------------------------------------------------------- */
+
+struct tunertype
+{
+ char *name;
+ unsigned char Vendor;
+ unsigned char Type;
+
+ unsigned short thresh1; /* frequency Range for UHF,VHF-L, VHF_H */
+ unsigned short thresh2;
+ unsigned char VHF_L;
+ unsigned char VHF_H;
+ unsigned char UHF;
+ unsigned char config;
+ unsigned short IFPCoff;
+ unsigned char mode; /* mode change value (tested PHILIPS_SECAM only) */
+ /* 0x01 -> ??? no change ??? */
+ /* 0x02 -> PAL BDGHI / SECAM L */
+ /* 0x04 -> ??? PAL others / SECAM others ??? */
+ int capability;
+};
+
+/*
+ * The floats in the tuner struct are computed at compile time
+ * by gcc and cast back to integers. Thus we don't violate the
+ * "no float in kernel" rule.
+ */
+static struct tunertype tuners[] = {
+ { "Temic PAL", TEMIC, PAL,
+ 16*140.25,16*463.25,0x02,0x04,0x01,0x8e,623},
+ { "Philips PAL_I", Philips, PAL_I,
+ 16*140.25,16*463.25,0xa0,0x90,0x30,0x8e,623},
+ { "Philips NTSC", Philips, NTSC,
+ 16*157.25,16*451.25,0xA0,0x90,0x30,0x8e,732},
+ { "Philips SECAM", Philips, SECAM,
+ 16*168.25,16*447.25,0xA7,0x97,0x37,0x8e,623,0x02},
+ { "NoTuner", NoTuner, NOTUNER,
+ 0,0,0x00,0x00,0x00,0x00,0x00,000},
+ { "Philips PAL", Philips, PAL,
+ 16*168.25,16*447.25,0xA0,0x90,0x30,0x8e,623},
+ { "Temic NTSC", TEMIC, NTSC,
+ 16*157.25,16*463.25,0x02,0x04,0x01,0x8e,732},
+ { "Temic PAL_I", TEMIC, PAL_I,
+ 16*170.00,16*450.00,0x02,0x04,0x01,0x8e,623},
+ { "Temic 4036 FY5 NTSC", TEMIC, NTSC,
+ 16*157.25,16*463.25,0xa0,0x90,0x30,0x8e,732},
+ { "Alps HSBH1", TEMIC, NTSC,
+ 16*137.25,16*385.25,0x01,0x02,0x08,0x8e,732},
+ { "Alps TSBE1",TEMIC,PAL,
+ 16*137.25,16*385.25,0x01,0x02,0x08,0x8e,732},
+ { "Alps TSBB5", Alps, PAL_I, /* tested (UK UHF) with Modtec MM205 */
+ 16*133.25,16*351.25,0x01,0x02,0x08,0x8e,632},
+ { "Alps TSBE5", Alps, PAL, /* untested - data sheet guess. Only IF differs. */
+ 16*133.25,16*351.25,0x01,0x02,0x08,0x8e,622},
+ { "Alps TSBC5", Alps, PAL, /* untested - data sheet guess. Only IF differs. */
+ 16*133.25,16*351.25,0x01,0x02,0x08,0x8e,608},
+ { "Temic 4006FH5", TEMIC, PAL_I,
+ 16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623},
+};
+#define TUNERS (sizeof(tuners)/sizeof(struct tunertype))
+
+/* ---------------------------------------------------------------------- */
+
+static int tuner_getstatus(struct i2c_client *c)
+{
+ unsigned char byte;
+
+ if (1 != i2c_master_recv(c,&byte,1))
+ return 0;
+ return byte;
+}
+
+#define TUNER_POR 0x80
+#define TUNER_FL 0x40
+#define TUNER_MODE 0x38
+#define TUNER_AFC 0x07
+
+static int tuner_islocked (struct i2c_client *c)
+{
+ return (tuner_getstatus (c) & TUNER_FL);
+}
+
+static int tuner_afcstatus (struct i2c_client *c)
+{
+ return (tuner_getstatus (c) & TUNER_AFC) - 2;
+}
+
+#if 0 /* unused */
+static int tuner_mode (struct i2c_client *c)
+{
+ return (tuner_getstatus (c) & TUNER_MODE) >> 3;
+}
+#endif
+
+static void set_tv_freq(struct i2c_client *c, int freq)
+{
+ u8 config;
+ u16 div;
+ struct tunertype *tun;
+ struct tuner *t = c->data;
+ unsigned char buffer[4];
+ int rc;
+
+ if (t->type == -1) {
+ printk("tuner: tuner type not set\n");
+ return;
+ }
+
+ tun=&tuners[t->type];
+ if (freq < tun->thresh1)
+ config = tun->VHF_L;
+ else if (freq < tun->thresh2)
+ config = tun->VHF_H;
+ else
+ config = tun->UHF;
+
+#if 1 // Fix colorstandard mode change
+ if (t->type == TUNER_PHILIPS_SECAM && t->mode)
+ config |= tun->mode;
+ else
+ config &= ~tun->mode;
+#else
+ config &= ~tun->mode;
+#endif
+
+ div=freq + tun->IFPCoff;
+
+ /*
+ * Philips FI1216MK2 remark from specification :
+ * for channel selection involving band switching, and to ensure
+ * smooth tuning to the desired channel without causing
+ * unnecessary charge pump action, it is recommended to consider
+ * the difference between wanted channel frequency and the
+ * current channel frequency. Unnecessary charge pump action
+ * will result in very low tuning voltage which may drive the
+ * oscillator to extreme conditions.
+ */
+ /*
+ * Progfou: specification says to send config data before
+ * frequency in case (wanted frequency < current frequency).
+ */
+
+ if (t->type == TUNER_PHILIPS_SECAM && freq < t->freq) {
+ buffer[0] = tun->config;
+ buffer[1] = config;
+ buffer[2] = (div>>8) & 0x7f;
+ buffer[3] = div & 0xff;
+ } else {
+ buffer[0] = (div>>8) & 0x7f;
+ buffer[1] = div & 0xff;
+ buffer[2] = tun->config;
+ buffer[3] = config;
+ }
+
+ if (4 != (rc = i2c_master_send(c,buffer,4)))
+ printk("tuner: i2c i/o error: rc == %d (should be 4)\n",rc);
+
+}
+
+static void set_radio_freq(struct i2c_client *c, int freq)
+{
+ u8 config;
+ u16 div;
+ struct tunertype *tun;
+ struct tuner *t = (struct tuner*)c->data;
+ unsigned char buffer[4];
+ int rc;
+
+ if (t->type == -1) {
+ printk("tuner: tuner type not set\n");
+ return;
+ }
+
+ tun=&tuners[t->type];
+ config = 0xa5;
+ div=freq + (int)(16*10.7);
+ div&=0x7fff;
+
+ buffer[0] = (div>>8) & 0x7f;
+ buffer[1] = div & 0xff;
+ buffer[2] = tun->config;
+ buffer[3] = config;
+ if (4 != (rc = i2c_master_send(c,buffer,4)))
+ printk("tuner: i2c i/o error: rc == %d (should be 4)\n",rc);
+
+ if (debug) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/10);
+
+ if (tuner_islocked (c))
+ printk ("tuner: PLL locked\n");
+ else
+ printk ("tuner: PLL not locked\n");
+
+ printk ("tuner: AFC: %d\n", tuner_afcstatus (c));
+ }
+}
+/* ---------------------------------------------------------------------- */
+
+
+static int tuner_attach(struct i2c_adapter *adap, int addr,
+ unsigned short flags, int kind)
+{
+ struct tuner *t;
+ struct i2c_client *client;
+
+ if (this_adap > 0)
+ return -1;
+ this_adap++;
+
+ client_template.adapter = adap;
+ client_template.addr = addr;
+
+ printk("tuner: chip found @ 0x%x\n",addr);
+
+ if (NULL == (client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL)))
+ return -ENOMEM;
+ memcpy(client,&client_template,sizeof(struct i2c_client));
+ client->data = t = kmalloc(sizeof(struct tuner),GFP_KERNEL);
+ if (NULL == t) {
+ kfree(client);
+ return -ENOMEM;
+ }
+ memset(t,0,sizeof(struct tuner));
+ if (type >= 0 && type < TUNERS) {
+ t->type = type;
+ strncpy(client->name, tuners[t->type].name, sizeof(client->name));
+ } else {
+ t->type = -1;
+ }
+ i2c_attach_client(client);
+ MOD_INC_USE_COUNT;
+
+ return 0;
+}
+
+static int tuner_probe(struct i2c_adapter *adap)
+{
+ if (0 != addr) {
+ normal_i2c_range[0] = addr;
+ normal_i2c_range[1] = addr;
+ }
+ this_adap = 0;
+ if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848))
+ return i2c_probe(adap, &addr_data, tuner_attach);
+ return 0;
+}
+
+static int tuner_detach(struct i2c_client *client)
+{
+ struct tuner *t = (struct tuner*)client->data;
+
+ i2c_detach_client(client);
+ kfree(t);
+ kfree(client);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int
+tuner_command(struct i2c_client *client, unsigned int cmd, void *arg)
+{
+ struct tuner *t = (struct tuner*)client->data;
+ int *iarg = (int*)arg;
+#if 0
+ __u16 *sarg = (__u16*)arg;
+#endif
+
+ switch (cmd) {
+
+ /* --- configuration --- */
+ case TUNER_SET_TYPE:
+ if (t->type != -1)
+ return 0;
+ if (*iarg < 0 || *iarg >= TUNERS)
+ return 0;
+ t->type = *iarg;
+ dprintk("tuner: type set to %d (%s)\n",
+ t->type,tuners[t->type].name);
+ strncpy(client->name, tuners[t->type].name, sizeof(client->name));
+ break;
+ case AUDC_SET_RADIO:
+ t->radio = 1;
+ break;
+
+ /* --- v4l ioctls --- */
+ /* take care: bttv does userspace copying, we'll get a
+ kernel pointer here... */
+ case VIDIOCSCHAN:
+ {
+ struct video_channel *vc = arg;
+
+ t->radio = 0;
+ if (t->type == TUNER_PHILIPS_SECAM) {
+ t->mode = (vc->norm == VIDEO_MODE_SECAM) ? 1 : 0;
+ set_tv_freq(client,t->freq);
+ }
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *v = arg;
+
+ t->freq = *v;
+ if (t->radio) {
+ dprintk("tuner: radio freq set to %d.%02d\n",
+ (*iarg)/16,(*iarg)%16*100/16);
+ set_radio_freq(client,t->freq);
+ } else {
+ dprintk("tuner: tv freq set to %d.%02d\n",
+ (*iarg)/16,(*iarg)%16*100/16);
+ set_tv_freq(client,t->freq);
+ }
+ return 0;
+ }
+#if 0
+ /* --- old, obsolete interface --- */
+ case TUNER_SET_TVFREQ:
+ dprintk("tuner: tv freq set to %d.%02d\n",
+ (*iarg)/16,(*iarg)%16*100/16);
+ set_tv_freq(client,*iarg);
+ t->radio = 0;
+ t->freq = *iarg;
+ break;
+
+ case TUNER_SET_RADIOFREQ:
+ dprintk("tuner: radio freq set to %d.%02d\n",
+ (*iarg)/16,(*iarg)%16*100/16);
+ set_radio_freq(client,*iarg);
+ t->radio = 1;
+ t->freq = *iarg;
+ break;
+ case TUNER_SET_MODE:
+ if (t->type != TUNER_PHILIPS_SECAM) {
+ dprintk("tuner: trying to change mode for other than TUNER_PHILIPS_SECAM\n");
+ } else {
+ int mode=(*sarg==VIDEO_MODE_SECAM)?1:0;
+ dprintk("tuner: mode set to %d\n", *sarg);
+ t->mode = mode;
+ set_tv_freq(client,t->freq);
+ }
+ break;
+#endif
+ default:
+ /* nothing */
+ }
+
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static struct i2c_driver driver = {
+ "i2c TV tuner driver",
+ I2C_DRIVERID_TUNER,
+ I2C_DF_NOTIFY,
+ tuner_probe,
+ tuner_detach,
+ tuner_command,
+};
+
+static struct i2c_client client_template =
+{
+ "(unset)", /* name */
+ -1,
+ 0,
+ 0,
+ NULL,
+ &driver
+};
+
+EXPORT_NO_SYMBOLS;
+
+int tuner_init_module(void)
+{
+ i2c_add_driver(&driver);
+ return 0;
+}
+
+void tuner_cleanup_module(void)
+{
+ i2c_del_driver(&driver);
+}
+
+module_init(tuner_init_module);
+module_exit(tuner_cleanup_module);
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/tuner.h b/drivers/media/video/tuner.h
new file mode 100644
index 000000000..96fcd3021
--- /dev/null
+++ b/drivers/media/video/tuner.h
@@ -0,0 +1,57 @@
+/*
+ tuner.h - definition for different tuners
+
+ Copyright (C) 1997 Markus Schroeder (schroedm@uni-duesseldorf.de)
+ minor modifications by Ralph Metzler (rjkm@thp.uni-koeln.de)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef _TUNER_H
+#define _TUNER_H
+
+#define TUNER_TEMIC_PAL 0 /* Miro Gpio Coding -1 */
+#define TUNER_PHILIPS_PAL_I 1
+#define TUNER_PHILIPS_NTSC 2
+#define TUNER_PHILIPS_SECAM 3
+#define TUNER_ABSENT 4
+#define TUNER_PHILIPS_PAL 5
+#define TUNER_TEMIC_NTSC 6
+#define TUNER_TEMIC_PAL_I 7
+#define TUNER_TEMIC_4036FY5_NTSC 8
+#define TUNER_ALPS_TSBH1_NTSC 9
+#define TUNER_ALPS_TSBE1_PAL 10
+#define TUNER_ALPS_TSBB5_PAL_I 11
+#define TUNER_ALPS_TSBE5_PAL 12
+#define TUNER_ALPS_TSBC5_PAL 13
+
+#define NOTUNER 0
+#define PAL 1
+#define PAL_I 2
+#define NTSC 3
+#define SECAM 4
+
+#define NoTuner 0
+#define Philips 1
+#define TEMIC 2
+#define Sony 3
+#define Alps 4
+
+#define TUNER_SET_TYPE _IOW('t',1,int) /* set tuner type */
+#define TUNER_SET_TVFREQ _IOW('t',2,int) /* set tv freq */
+#define TUNER_SET_RADIOFREQ _IOW('t',3,int) /* set radio freq */
+#define TUNER_SET_MODE _IOW('t',4,int) /* set tuner mode */
+
+#endif
diff --git a/drivers/media/video/tvmixer.c b/drivers/media/video/tvmixer.c
new file mode 100644
index 000000000..e1034a152
--- /dev/null
+++ b/drivers/media/video/tvmixer.c
@@ -0,0 +1,353 @@
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/i2c.h>
+#include <linux/videodev.h>
+#include <asm/semaphore.h>
+#include <linux/init.h>
+
+#include <linux/sound.h>
+#include <linux/soundcard.h>
+#include <asm/uaccess.h>
+
+#include "audiochip.h"
+
+#define DEV_MAX 4
+
+static int debug = 0;
+static int devnr = -1;
+
+MODULE_PARM(debug,"i");
+MODULE_PARM(devnr,"i");
+
+/* ----------------------------------------------------------------------- */
+
+struct TVMIXER {
+ struct i2c_client *dev;
+ int minor;
+ int count;
+};
+
+static struct TVMIXER devices[DEV_MAX];
+
+static int tvmixer_adapters(struct i2c_adapter *adap);
+static int tvmixer_clients(struct i2c_client *client);
+
+static int tvmixer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
+static int tvmixer_open(struct inode *inode, struct file *file);
+static int tvmixer_release(struct inode *inode, struct file *file);
+static loff_t tvmixer_llseek(struct file *file, loff_t offset, int origin);
+
+
+static struct i2c_driver driver = {
+ "tv card mixer driver",
+ 42 /* I2C_DRIVERID_FIXME */,
+ I2C_DF_DUMMY,
+ tvmixer_adapters,
+ tvmixer_clients,
+};
+
+static struct file_operations tvmixer_fops = {
+ owner: THIS_MODULE,
+ llseek: tvmixer_llseek,
+ ioctl: tvmixer_ioctl,
+ open: tvmixer_open,
+ release: tvmixer_release,
+};
+
+/* ----------------------------------------------------------------------- */
+
+static int mix_to_v4l(int i)
+{
+ int r;
+
+ r = ((i & 0xff) * 65536 + 50) / 100;
+ if (r > 65535) r = 65535;
+ if (r < 0) r = 0;
+ return r;
+}
+
+static int v4l_to_mix(int i)
+{
+ int r;
+
+ r = (i * 100 + 32768) / 65536;
+ if (r > 100) r = 100;
+ if (r < 0) r = 0;
+ return r | (r << 8);
+}
+
+static int v4l_to_mix2(int l, int r)
+{
+ r = (r * 100 + 32768) / 65536;
+ if (r > 100) r = 100;
+ if (r < 0) r = 0;
+ l = (l * 100 + 32768) / 65536;
+ if (l > 100) l = 100;
+ if (l < 0) l = 0;
+ return (r << 8) | l;
+}
+
+static int tvmixer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct video_audio va;
+ int left,right,ret,val = 0;
+ struct TVMIXER *mix = file->private_data;
+ struct i2c_client *client = mix->dev;
+
+ if (NULL == client)
+ return -ENODEV;
+
+ if (cmd == SOUND_MIXER_INFO) {
+ mixer_info info;
+ strncpy(info.id, "tv card", sizeof(info.id));
+ strncpy(info.name, client->name, sizeof(info.name));
+ info.modify_counter = 42 /* FIXME */;
+ if (copy_to_user((void *)arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+ if (cmd == SOUND_OLD_MIXER_INFO) {
+ _old_mixer_info info;
+ strncpy(info.id, "tv card", sizeof(info.id));
+ strncpy(info.name, client->name, sizeof(info.name));
+ if (copy_to_user((void *)arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+ if (cmd == OSS_GETVERSION)
+ return put_user(SOUND_VERSION, (int *)arg);
+
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+ if (get_user(val, (int *)arg))
+ return -EFAULT;
+
+ /* read state */
+ memset(&va,0,sizeof(va));
+ client->driver->command(client,VIDIOCGAUDIO,&va);
+
+ switch (cmd) {
+ case MIXER_READ(SOUND_MIXER_RECMASK):
+ case MIXER_READ(SOUND_MIXER_CAPS):
+ case MIXER_READ(SOUND_MIXER_RECSRC):
+ case MIXER_WRITE(SOUND_MIXER_RECSRC):
+ ret = 0;
+ break;
+
+ case MIXER_READ(SOUND_MIXER_STEREODEVS):
+ ret = SOUND_MASK_VOLUME;
+ break;
+ case MIXER_READ(SOUND_MIXER_DEVMASK):
+ ret = SOUND_MASK_VOLUME;
+ if (va.flags & VIDEO_AUDIO_BASS)
+ ret |= SOUND_MASK_BASS;
+ if (va.flags & VIDEO_AUDIO_TREBLE)
+ ret |= SOUND_MASK_TREBLE;
+ break;
+
+ case MIXER_WRITE(SOUND_MIXER_VOLUME):
+ left = mix_to_v4l(val);
+ right = mix_to_v4l(val >> 8);
+ va.volume = MAX(left,right);
+ va.balance = (32768*MIN(left,right)) / (va.volume ? va.volume : 1);
+ va.balance = (left<right) ? (65535-va.balance) : va.balance;
+ client->driver->command(client,VIDIOCSAUDIO,&va);
+ client->driver->command(client,VIDIOCGAUDIO,&va);
+ /* fall throuth */
+ case MIXER_READ(SOUND_MIXER_VOLUME):
+ left = (MIN(65536 - va.balance,32768) *
+ va.volume) / 32768;
+ right = (MIN(va.balance,32768) *
+ va.volume) / 32768;
+ ret = v4l_to_mix2(left,right);
+ break;
+
+ case MIXER_WRITE(SOUND_MIXER_BASS):
+ va.bass = mix_to_v4l(val);
+ client->driver->command(client,VIDIOCSAUDIO,&va);
+ client->driver->command(client,VIDIOCGAUDIO,&va);
+ /* fall throuth */
+ case MIXER_READ(SOUND_MIXER_BASS):
+ ret = v4l_to_mix(va.bass);
+ break;
+
+ case MIXER_WRITE(SOUND_MIXER_TREBLE):
+ va.treble = mix_to_v4l(val);
+ client->driver->command(client,VIDIOCSAUDIO,&va);
+ client->driver->command(client,VIDIOCGAUDIO,&va);
+ /* fall throuth */
+ case MIXER_READ(SOUND_MIXER_TREBLE):
+ ret = v4l_to_mix(va.treble);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ if (put_user(ret, (int *)arg))
+ return -EFAULT;
+ return 0;
+}
+
+static int tvmixer_open(struct inode *inode, struct file *file)
+{
+ int i, minor = MINOR(inode->i_rdev);
+ struct TVMIXER *mix = NULL;
+ struct i2c_client *client = NULL;
+
+ for (i = 0; i < DEV_MAX; i++) {
+ if (devices[i].minor == minor) {
+ mix = devices+i;
+ client = mix->dev;
+ break;
+ }
+ }
+
+ if (NULL == client)
+ return -ENODEV;
+
+ /* lock bttv in memory while the mixer is in use */
+ file->private_data = mix;
+ if (client->adapter->inc_use)
+ client->adapter->inc_use(client->adapter);
+ return 0;
+}
+
+static int tvmixer_release(struct inode *inode, struct file *file)
+{
+ struct TVMIXER *mix = file->private_data;
+ struct i2c_client *client;
+
+ client = mix->dev;
+ if (NULL == client) {
+ return -ENODEV;
+ }
+
+ if (client->adapter->dec_use)
+ client->adapter->dec_use(client->adapter);
+ return 0;
+}
+
+static loff_t tvmixer_llseek(struct file *file, loff_t offset, int origin)
+{
+ return -ESPIPE;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static int tvmixer_adapters(struct i2c_adapter *adap)
+{
+ return 0;
+}
+
+static int tvmixer_clients(struct i2c_client *client)
+{
+ struct video_audio va;
+ int i,minor;
+
+ /* TV card ??? */
+ if (client->adapter->id != (I2C_ALGO_BIT | I2C_HW_B_BT848)) {
+ if (debug)
+ printk("tvmixer: %s is not a tv card\n",
+ client->adapter->name);
+ return -1;
+ }
+ printk("tvmixer: debug: %s\n",client->name);
+
+ /* unregister ?? */
+ for (i = 0; i < DEV_MAX; i++) {
+ if (devices[i].dev == client) {
+ /* unregister */
+ unregister_sound_mixer(devices[i].minor);
+ devices[i].dev = NULL;
+ devices[i].minor = -1;
+ printk("tvmixer: %s unregistered (#1)\n",client->name);
+ return 0;
+ }
+ }
+
+ /* look for a free slot */
+ for (i = 0; i < DEV_MAX; i++)
+ if (NULL == devices[i].dev)
+ break;
+ if (i == DEV_MAX) {
+ printk(KERN_WARNING "tvmixer: DEV_MAX too small\n");
+ return -1;
+ }
+
+ /* audio chip with mixer ??? */
+ if (NULL == client->driver->command) {
+ if (debug)
+ printk("tvmixer: %s: driver->command is NULL\n",
+ client->driver->name);
+ return -1;
+ }
+ memset(&va,0,sizeof(va));
+ if (0 != client->driver->command(client,VIDIOCGAUDIO,&va)) {
+ if (debug)
+ printk("tvmixer: %s: VIDIOCGAUDIO failed\n",
+ client->name);
+ return -1;
+ }
+ if (0 == (va.flags & VIDEO_AUDIO_VOLUME)) {
+ if (debug)
+ printk("tvmixer: %s: has no volume control\n",
+ client->name);
+ return -1;
+ }
+
+ /* everything is fine, register */
+ if ((minor = register_sound_mixer(&tvmixer_fops,devnr)) < 0) {
+ printk(KERN_ERR "tvmixer: cannot allocate mixer device\n");
+ return -1;
+ }
+
+ devices[i].minor = minor;
+ devices[i].count = 0;
+ devices[i].dev = client;
+ printk("tvmixer: %s (%s) registered with minor %d\n",
+ client->name,client->adapter->name,minor);
+
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+int tvmixer_init_module(void)
+{
+ int i;
+
+ for (i = 0; i < DEV_MAX; i++)
+ devices[i].minor = -1;
+ i2c_add_driver(&driver);
+ return 0;
+}
+
+void tvmixer_cleanup_module(void)
+{
+ int i;
+
+ i2c_del_driver(&driver);
+ for (i = 0; i < DEV_MAX; i++) {
+ if (devices[i].minor != -1) {
+ unregister_sound_mixer(devices[i].minor);
+ printk("tvmixer: %s unregistered (#2)\n",
+ devices[i].dev->name);
+ }
+ }
+}
+
+module_init(tvmixer_init_module);
+module_exit(tvmixer_cleanup_module);
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/videodev.c b/drivers/media/video/videodev.c
new file mode 100644
index 000000000..c806ff264
--- /dev/null
+++ b/drivers/media/video/videodev.c
@@ -0,0 +1,581 @@
+/*
+ * Video capture interface for Linux
+ *
+ * A generic video device interface for the LINUX operating system
+ * using a set of device structures/vectors for low level operations.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Author: Alan Cox, <alan@redhat.com>
+ *
+ * Fixes: 20000516 Claudio Matsuoka <claudio@conectiva.com>
+ * - Added procfs support
+ */
+
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/smp_lock.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/videodev.h>
+#include <linux/init.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#include <linux/kmod.h>
+
+
+#define VIDEO_NUM_DEVICES 256
+
+/*
+ * Active devices
+ */
+
+static struct video_device *video_device[VIDEO_NUM_DEVICES];
+
+
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
+
+#include <linux/proc_fs.h>
+
+struct videodev_proc_data {
+ struct list_head proc_list;
+ char name[16];
+ struct video_device *vdev;
+ struct proc_dir_entry *proc_entry;
+};
+
+static struct proc_dir_entry *video_dev_proc_entry = NULL;
+struct proc_dir_entry *video_proc_entry = NULL;
+EXPORT_SYMBOL(video_proc_entry);
+LIST_HEAD(videodev_proc_list);
+
+#endif /* CONFIG_PROC_FS && CONFIG_VIDEO_PROC_FS */
+
+
+#ifdef CONFIG_VIDEO_BWQCAM
+extern int init_bw_qcams(struct video_init *);
+#endif
+#ifdef CONFIG_VIDEO_CPIA
+extern int cpia_init(struct video_init *);
+#endif
+#ifdef CONFIG_VIDEO_PLANB
+extern int init_planbs(struct video_init *);
+#endif
+#ifdef CONFIG_VIDEO_ZORAN
+extern int init_zoran_cards(struct video_init *);
+#endif
+
+static struct video_init video_init_list[]={
+#ifdef CONFIG_VIDEO_BWQCAM
+ {"bw-qcam", init_bw_qcams},
+#endif
+#ifdef CONFIG_VIDEO_CPIA
+ {"cpia", cpia_init},
+#endif
+#ifdef CONFIG_VIDEO_PLANB
+ {"planb", init_planbs},
+#endif
+#ifdef CONFIG_VIDEO_ZORAN
+ {"zoran", init_zoran_cards},
+#endif
+ {"end", NULL}
+};
+
+/*
+ * Read will do some smarts later on. Buffer pin etc.
+ */
+
+static ssize_t video_read(struct file *file,
+ char *buf, size_t count, loff_t *ppos)
+{
+ struct video_device *vfl=video_device[MINOR(file->f_dentry->d_inode->i_rdev)];
+ if(vfl->read)
+ return vfl->read(vfl, buf, count, file->f_flags&O_NONBLOCK);
+ else
+ return -EINVAL;
+}
+
+
+/*
+ * Write for now does nothing. No reason it shouldnt do overlay setting
+ * for some boards I guess..
+ */
+
+static ssize_t video_write(struct file *file, const char *buf,
+ size_t count, loff_t *ppos)
+{
+ struct video_device *vfl=video_device[MINOR(file->f_dentry->d_inode->i_rdev)];
+ if(vfl->write)
+ return vfl->write(vfl, buf, count, file->f_flags&O_NONBLOCK);
+ else
+ return 0;
+}
+
+/*
+ * Poll to see if we're readable, can probably be used for timing on incoming
+ * frames, etc..
+ */
+
+static unsigned int video_poll(struct file *file, poll_table * wait)
+{
+ struct video_device *vfl=video_device[MINOR(file->f_dentry->d_inode->i_rdev)];
+ if(vfl->poll)
+ return vfl->poll(vfl, file, wait);
+ else
+ return 0;
+}
+
+
+/*
+ * Open a video device.
+ */
+
+static int video_open(struct inode *inode, struct file *file)
+{
+ unsigned int minor = MINOR(inode->i_rdev);
+ int err;
+ struct video_device *vfl;
+
+ if(minor>=VIDEO_NUM_DEVICES)
+ return -ENODEV;
+
+ vfl=video_device[minor];
+ if(vfl==NULL) {
+ char modname[20];
+
+ sprintf (modname, "char-major-%d-%d", VIDEO_MAJOR, minor);
+ request_module(modname);
+ vfl=video_device[minor];
+ if (vfl==NULL)
+ return -ENODEV;
+ }
+ if(vfl->busy)
+ return -EBUSY;
+ vfl->busy=1; /* In case vfl->open sleeps */
+
+ if(vfl->open)
+ {
+ err=vfl->open(vfl,0); /* Tell the device it is open */
+ if(err)
+ {
+ vfl->busy=0;
+ return err;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Last close of a video for Linux device
+ */
+
+static int video_release(struct inode *inode, struct file *file)
+{
+ struct video_device *vfl;
+ lock_kernel();
+ vfl=video_device[MINOR(inode->i_rdev)];
+ if(vfl->close)
+ vfl->close(vfl);
+ vfl->busy=0;
+ unlock_kernel();
+ return 0;
+}
+
+/*
+ * Question: Should we be able to capture and then seek around the
+ * image ?
+ */
+
+static long long video_lseek(struct file * file,
+ long long offset, int origin)
+{
+ return -ESPIPE;
+}
+
+static int video_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct video_device *vfl=video_device[MINOR(inode->i_rdev)];
+ int err=vfl->ioctl(vfl, cmd, (void *)arg);
+
+ if(err!=-ENOIOCTLCMD)
+ return err;
+
+ switch(cmd)
+ {
+ default:
+ return -EINVAL;
+ }
+}
+
+/*
+ * We need to do MMAP support
+ */
+
+
+int video_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ int ret = -EINVAL;
+ struct video_device *vfl=video_device[MINOR(file->f_dentry->d_inode->i_rdev)];
+ if(vfl->mmap) {
+ lock_kernel();
+ ret = vfl->mmap(vfl, (char *)vma->vm_start,
+ (unsigned long)(vma->vm_end-vma->vm_start));
+ unlock_kernel();
+ }
+ return ret;
+}
+
+/*
+ * /proc support
+ */
+
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
+
+/* Hmm... i'd like to see video_capability information here, but
+ * how can I access it (without changing the other drivers? -claudio
+ */
+static int videodev_proc_read(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = page;
+ struct video_device *vfd = data;
+ struct videodev_proc_data *d;
+ struct list_head *tmp;
+ int len;
+ char c = ' ';
+
+ list_for_each (tmp, &videodev_proc_list) {
+ d = list_entry(tmp, struct videodev_proc_data, proc_list);
+ if (vfd == d->vdev)
+ break;
+ }
+
+ /* Sanity check */
+ if (tmp == &videodev_proc_list)
+ goto skip;
+
+#define PRINT_VID_TYPE(x) do { if (vfd->type & x) \
+ out += sprintf (out, "%c%s", c, #x); c='|';} while (0)
+
+ out += sprintf (out, "name : %s\n", vfd->name);
+ out += sprintf (out, "type :");
+ PRINT_VID_TYPE(VID_TYPE_CAPTURE);
+ PRINT_VID_TYPE(VID_TYPE_TUNER);
+ PRINT_VID_TYPE(VID_TYPE_TELETEXT);
+ PRINT_VID_TYPE(VID_TYPE_OVERLAY);
+ PRINT_VID_TYPE(VID_TYPE_CHROMAKEY);
+ PRINT_VID_TYPE(VID_TYPE_CLIPPING);
+ PRINT_VID_TYPE(VID_TYPE_FRAMERAM);
+ PRINT_VID_TYPE(VID_TYPE_SCALES);
+ PRINT_VID_TYPE(VID_TYPE_MONOCHROME);
+ PRINT_VID_TYPE(VID_TYPE_SUBCAPTURE);
+ PRINT_VID_TYPE(VID_TYPE_MPEG_DECODER);
+ PRINT_VID_TYPE(VID_TYPE_MPEG_ENCODER);
+ PRINT_VID_TYPE(VID_TYPE_MJPEG_DECODER);
+ PRINT_VID_TYPE(VID_TYPE_MJPEG_ENCODER);
+ out += sprintf (out, "\n");
+ out += sprintf (out, "hardware : 0x%x\n", vfd->hardware);
+#if 0
+ out += sprintf (out, "channels : %d\n", d->vcap.channels);
+ out += sprintf (out, "audios : %d\n", d->vcap.audios);
+ out += sprintf (out, "maxwidth : %d\n", d->vcap.maxwidth);
+ out += sprintf (out, "maxheight : %d\n", d->vcap.maxheight);
+ out += sprintf (out, "minwidth : %d\n", d->vcap.minwidth);
+ out += sprintf (out, "minheight : %d\n", d->vcap.minheight);
+#endif
+
+skip:
+ len = out - page;
+ len -= off;
+ if (len < count) {
+ *eof = 1;
+ if (len <= 0)
+ return 0;
+ } else
+ len = count;
+
+ *start = page + off;
+
+ return len;
+}
+
+static void videodev_proc_create(void)
+{
+ video_proc_entry = create_proc_entry("video", S_IFDIR, &proc_root);
+
+ if (video_proc_entry == NULL) {
+ printk("video_dev: unable to initialise /proc/video\n");
+ return;
+ }
+
+ video_proc_entry->owner = THIS_MODULE;
+ video_dev_proc_entry = create_proc_entry("dev", S_IFDIR, video_proc_entry);
+
+ if (video_dev_proc_entry == NULL) {
+ printk("video_dev: unable to initialise /proc/video/dev\n");
+ return;
+ }
+
+ video_dev_proc_entry->owner = THIS_MODULE;
+}
+
+#ifdef MODULE
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
+static void videodev_proc_destroy(void)
+{
+ if (video_dev_proc_entry != NULL)
+ remove_proc_entry("dev", video_proc_entry);
+
+ if (video_proc_entry != NULL)
+ remove_proc_entry("video", &proc_root);
+}
+#endif
+#endif
+
+static void videodev_proc_create_dev (struct video_device *vfd, char *name)
+{
+ struct videodev_proc_data *d;
+ struct proc_dir_entry *p;
+
+ if (video_dev_proc_entry == NULL)
+ return;
+
+ d = kmalloc (sizeof (struct videodev_proc_data), GFP_KERNEL);
+ if (!d)
+ return;
+
+ p = create_proc_entry(name, S_IFREG|S_IRUGO|S_IWUSR, video_dev_proc_entry);
+ p->data = vfd;
+ p->read_proc = videodev_proc_read;
+
+ d->proc_entry = p;
+ d->vdev = vfd;
+ strcpy (d->name, name);
+
+ /* How can I get capability information ? */
+
+ list_add (&d->proc_list, &videodev_proc_list);
+}
+
+static void videodev_proc_destroy_dev (struct video_device *vfd)
+{
+ struct list_head *tmp;
+ struct videodev_proc_data *d;
+
+ list_for_each (tmp, &videodev_proc_list) {
+ d = list_entry(tmp, struct videodev_proc_data, proc_list);
+ if (vfd == d->vdev) {
+ remove_proc_entry(d->name, video_dev_proc_entry);
+ list_del (&d->proc_list);
+ kfree (d);
+ break;
+ }
+ }
+}
+
+#endif /* CONFIG_VIDEO_PROC_FS */
+
+extern struct file_operations video_fops;
+
+/**
+ * video_register_device - register video4linux devices
+ * @vfd: video device structure we want to register
+ * @type: type of device to register
+ * FIXME: needs a semaphore on 2.3.x
+ *
+ * The registration code assigns minor numbers based on the type
+ * requested. -ENFILE is returned in all the device slots for this
+ * category are full. If not then the minor field is set and the
+ * driver initialize function is called (if non %NULL).
+ *
+ * Zero is returned on success.
+ *
+ * Valid types are
+ *
+ * %VFL_TYPE_GRABBER - A frame grabber
+ *
+ * %VFL_TYPE_VTX - A teletext device
+ *
+ * %VFL_TYPE_VBI - Vertical blank data (undecoded)
+ *
+ * %VFL_TYPE_RADIO - A radio card
+ */
+
+int video_register_device(struct video_device *vfd, int type)
+{
+ int i=0;
+ int base;
+ int err;
+ int end;
+ char *name_base;
+
+ switch(type)
+ {
+ case VFL_TYPE_GRABBER:
+ base=0;
+ end=64;
+ name_base = "video";
+ break;
+ case VFL_TYPE_VTX:
+ base=192;
+ end=224;
+ name_base = "vtx";
+ break;
+ case VFL_TYPE_VBI:
+ base=224;
+ end=240;
+ name_base = "vbi";
+ break;
+ case VFL_TYPE_RADIO:
+ base=64;
+ end=128;
+ name_base = "radio";
+ break;
+ default:
+ return -1;
+ }
+
+ for(i=base;i<end;i++)
+ {
+ if(video_device[i]==NULL)
+ {
+ char name[16];
+
+ video_device[i]=vfd;
+ vfd->minor=i;
+ /* The init call may sleep so we book the slot out
+ then call */
+ MOD_INC_USE_COUNT;
+ if(vfd->initialize)
+ {
+ err=vfd->initialize(vfd);
+ if(err<0)
+ {
+ video_device[i]=NULL;
+ MOD_DEC_USE_COUNT;
+ return err;
+ }
+ }
+ sprintf (name, "v4l/%s%d", name_base, i - base);
+ /*
+ * Start the device root only. Anything else
+ * has serious privacy issues.
+ */
+ vfd->devfs_handle =
+ devfs_register (NULL, name, DEVFS_FL_DEFAULT,
+ VIDEO_MAJOR, vfd->minor,
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ &video_fops, NULL);
+
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
+ sprintf (name, "%s%d", name_base, i - base);
+ videodev_proc_create_dev (vfd, name);
+#endif
+
+
+ return 0;
+ }
+ }
+ return -ENFILE;
+}
+
+/**
+ * video_unregister_device - unregister a video4linux device
+ * @vfd: the device to unregister
+ *
+ * This unregisters the passed device and deassigns the minor
+ * number. Future open calls will be met with errors.
+ */
+
+void video_unregister_device(struct video_device *vfd)
+{
+ if(video_device[vfd->minor]!=vfd)
+ panic("vfd: bad unregister");
+
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
+ videodev_proc_destroy_dev (vfd);
+#endif
+
+ devfs_unregister (vfd->devfs_handle);
+ video_device[vfd->minor]=NULL;
+ MOD_DEC_USE_COUNT;
+}
+
+
+static struct file_operations video_fops=
+{
+ owner: THIS_MODULE,
+ llseek: video_lseek,
+ read: video_read,
+ write: video_write,
+ ioctl: video_ioctl,
+ mmap: video_mmap,
+ open: video_open,
+ release: video_release,
+ poll: video_poll,
+};
+
+/*
+ * Initialise video for linux
+ */
+
+int __init videodev_init(void)
+{
+ struct video_init *vfli = video_init_list;
+
+ printk(KERN_INFO "Linux video capture interface: v1.00\n");
+ if(devfs_register_chrdev(VIDEO_MAJOR,"video_capture", &video_fops))
+ {
+ printk("video_dev: unable to get major %d\n", VIDEO_MAJOR);
+ return -EIO;
+ }
+
+ /*
+ * Init kernel installed video drivers
+ */
+
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
+ videodev_proc_create ();
+#endif
+
+ while(vfli->init!=NULL)
+ {
+ vfli->init(vfli);
+ vfli++;
+ }
+ return 0;
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+ return videodev_init();
+}
+
+void cleanup_module(void)
+{
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS)
+ videodev_proc_destroy ();
+#endif
+
+ devfs_unregister_chrdev(VIDEO_MAJOR, "video_capture");
+}
+
+#endif
+
+EXPORT_SYMBOL(video_register_device);
+EXPORT_SYMBOL(video_unregister_device);
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("Device registrar for Video4Linux drivers");
diff --git a/drivers/media/video/vino.c b/drivers/media/video/vino.c
new file mode 100644
index 000000000..1ef64ae81
--- /dev/null
+++ b/drivers/media/video/vino.c
@@ -0,0 +1,275 @@
+/* $Id: vino.c,v 1.5 1999/10/09 00:01:14 ralf Exp $
+ * drivers/char/vino.c
+ *
+ * (incomplete) Driver for the Vino Video input system found in SGI Indys.
+ *
+ * Copyright (C) 1999 Ulf Carlsson (ulfc@bun.falkenberg.se)
+ *
+ * This isn't complete yet, please don't expect any video until I've written
+ * some more code.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/errno.h>
+#include <linux/videodev.h>
+
+#include <asm/addrspace.h>
+#include <asm/system.h>
+
+#include "vino.h"
+
+struct vino_device {
+ struct video_device vdev;
+
+ unsigned long chan;
+#define VINO_CHAN_A 0
+#define VINO_CHAN_B 1
+
+ unsigned long flags;
+#define VINO_DMA_ACTIVE (1<<0)
+};
+
+/* We can actually receive TV and IndyCam input at the same time. Believe it or
+ * not..
+ */
+static struct vino_device vino[2];
+
+/* Those registers have to be accessed by either *one* 64 bit write or *one* 64
+ * bit read. We need some asm to fix this. We can't use mips3 as standard
+ * because we just save 32 bits at context switch.
+ */
+
+static __inline__ unsigned long long vino_reg_read(unsigned long addr)
+{
+ unsigned long long ret __attribute__ ((aligned (64)));
+ unsigned long virt_addr = KSEG1ADDR(addr + VINO_BASE);
+ unsigned long flags;
+
+ save_and_cli(flags);
+ __asm__ __volatile__(
+ ".set\tmips3\n\t"
+ ".set\tnoat\n\t"
+ "ld\t$1,(%0)\n\t"
+ "sd\t$1,(%1)\n\t"
+ ".set\tat\n\t"
+ ".set\tmips0"
+ :
+ :"r" (virt_addr),
+ "r" (&ret)
+ :"$1");
+ restore_flags(flags);
+
+ return ret;
+}
+
+static __inline__ void vino_reg_write(unsigned long long value,
+ unsigned long addr)
+{
+ unsigned long virt_addr = KSEG1ADDR(addr + VINO_BASE);
+ unsigned long flags;
+
+ /* we might lose the upper parts of the registers which are not saved
+ * if there comes an interrupt in our way, play safe */
+
+ save_and_cli(flags);
+ __asm__ __volatile__(
+ ".set\tmips3\n\t"
+ ".set\tnoat\n\t"
+ "ld\t$1,(%0)\n\t"
+ "sd\t$1,(%1)\n\t"
+ ".set\tat\n\t"
+ ".set\tmips0"
+ :
+ :"r" (&value),
+ "r" (virt_addr)
+ :"$1");
+ restore_flags(flags);
+}
+
+static __inline__ void vino_reg_and(unsigned long long value,
+ unsigned long addr)
+{
+ unsigned long virt_addr = KSEG1ADDR(addr + VINO_BASE);
+ unsigned long flags;
+
+ save_and_cli(flags);
+ __asm__ __volatile__(
+ ".set\tmips3\n\t"
+ ".set\tnoat\n\t"
+ "ld\t$1,(%0)\n\t"
+ "ld\t$2,(%1)\n\t"
+ "and\t$1,$1,$2\n\t"
+ "sd\t$1,(%0)\n\t"
+ ".set\tat\n\t"
+ ".set\tmips0"
+ :
+ :"r" (virt_addr),
+ "r" (&value)
+ :"$1","$2");
+ restore_flags(flags);
+}
+
+static __inline__ void vino_reg_or(unsigned long long value,
+ unsigned long addr)
+{
+ unsigned long virt_addr = KSEG1ADDR(addr + VINO_BASE);
+ unsigned long flags;
+
+ save_and_cli(flags);
+ __asm__ __volatile__(
+ ".set\tmips3\n\t"
+ ".set\tnoat\n\t"
+ "ld\t$1,(%0)\n\t"
+ "ld\t$2,(%1)\n\t"
+ "or\t$1,$1,$2\n\t"
+ "sd\t$1,(%0)\n\t"
+ ".set\tat\n\t"
+ ".set\tmips0"
+ :
+ :"r" (virt_addr),
+ "r" (&value)
+ :"$1","$2");
+ restore_flags(flags);
+}
+
+static int vino_dma_setup(void)
+{
+ return 0;
+}
+
+static void vino_dma_stop(void)
+{
+
+}
+
+static int vino_init(void)
+{
+ unsigned long ret;
+ unsigned short rev, id;
+ unsigned long long foo;
+ unsigned long *bar;
+
+ bar = (unsigned long *) &foo;
+
+ ret = vino_reg_read(VINO_REVID);
+
+ rev = (ret & VINO_REVID_REV_MASK);
+ id = (ret & VINO_REVID_ID_MASK) >> 4;
+
+ printk("Vino: ID:%02hx Rev:%02hx\n", id, rev);
+
+ foo = vino_reg_read(VINO_A_DESC_DATA0);
+ printk("0x%lx", bar[0]);
+ printk("%lx ", bar[1]);
+ foo = vino_reg_read(VINO_A_DESC_DATA1);
+ printk("0x%lx", bar[0]);
+ printk("%lx ", bar[1]);
+ foo = vino_reg_read(VINO_A_DESC_DATA2);
+ printk("0x%lx", bar[0]);
+ printk("%lx ", bar[1]);
+ foo = vino_reg_read(VINO_A_DESC_DATA3);
+ printk("0x%lx", bar[0]);
+ printk("%lx\n", bar[1]);
+ foo = vino_reg_read(VINO_B_DESC_DATA0);
+ printk("0x%lx", bar[0]);
+ printk("%lx ", bar[1]);
+ foo = vino_reg_read(VINO_B_DESC_DATA1);
+ printk("0x%lx", bar[0]);
+ printk("%lx ", bar[1]);
+ foo = vino_reg_read(VINO_B_DESC_DATA2);
+ printk("0x%lx", bar[0]);
+ printk("%lx ", bar[1]);
+ foo = vino_reg_read(VINO_B_DESC_DATA3);
+ printk("0x%lx", bar[0]);
+ printk("%lx\n", bar[1]);
+
+ return 0;
+}
+
+static void vino_dma_go(struct vino_device *v)
+{
+
+}
+
+/* Reset the vino back to default state */
+
+static void vino_setup(struct vino_device *v)
+{
+
+}
+
+static int vino_open(struct video_device *dev, int flags)
+{
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void vino_close(struct video_device *dev)
+{
+ MOD_DEC_USE_COUNT;
+}
+
+static int vino_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ return 0;
+}
+
+static int vino_mmap(struct video_device *dev, const char *adr,
+ unsigned long size)
+{
+ return 0;
+}
+
+static struct video_device vino_dev = {
+ "Vino IndyCam/TV",
+ VID_TYPE_CAPTURE,
+ VID_HARDWARE_VINO,
+ vino_open,
+ vino_close,
+ NULL, /* vino_read */
+ NULL, /* vino_write */
+ NULL, /* vino_poll */
+ vino_ioctl,
+ vino_mmap,
+ NULL, /* vino_init */
+ NULL,
+ 0,
+ 0
+};
+
+int __init init_vino(struct video_device *dev)
+{
+ int err;
+
+ err = vino_init();
+ if (err)
+ return err;
+
+#if 0
+ if (video_register_device(&vinodev, VFL_TYPE_GRABBER) == -1) {
+ return -ENODEV;
+ }
+#endif
+
+ return 0;
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+ int err;
+
+ err = vino_init();
+ if (err)
+ return err;
+
+ return 0;
+}
+
+void cleanup_module(void)
+{
+}
+#endif
diff --git a/drivers/media/video/vino.h b/drivers/media/video/vino.h
new file mode 100644
index 000000000..8caadc9aa
--- /dev/null
+++ b/drivers/media/video/vino.h
@@ -0,0 +1,118 @@
+/* $Id: vino.h,v 1.1 1999/02/08 18:29:32 ulfc Exp $
+ * drivers/sgi/vino.h
+ *
+ * Copyright (C) 1999 Ulf Carlsson (ulfc@bun.falkenberg.se)
+ */
+
+#define VINO_BASE 0x0008000
+
+#define VINO_REVID 0x0000
+#define VINO_CTRL 0x0008
+#define VINO_INTSTAT 0x0010 /* Interrupt status */
+#define VINO_I2C_CTRL 0x0018
+#define VINO_I2C_DATA 0x0020
+#define VINO_A_ALPHA 0x0028 /* Channel A ... */
+#define VINO_A_CLIPS 0x0030 /* Clipping start */
+#define VINO_A_CLIPE 0x0038 /* Clipping end */
+#define VINO_A_FRAMERT 0x0040 /* Framerate */
+#define VINO_A_FLDCNT 0x0048 /* Field counter */
+#define VINO_A_LNSZ 0x0050
+#define VINO_A_LNCNT 0x0058
+#define VINO_A_PGIX 0x0060 /* Page index */
+#define VINO_A_DESC_PTR 0x0068 /* Ptr to next four descriptors */
+#define VINO_A_DESC_TLB_PTR 0x0070 /* Ptr to start of descriptor table */
+#define VINO_A_DESC_DATA0 0x0078 /* Descriptor data 0 */
+#define VINO_A_DESC_DATA1 0x0080 /* ... */
+#define VINO_A_DESC_DATA2 0x0088
+#define VINO_A_DESC_DATA3 0x0090
+#define VINO_A_FIFO_THRESHOLD 0x0098 /* FIFO threshold */
+#define VINO_A_FIFO_RP 0x00a0
+#define VINO_A_FIFO_WP 0x00a8
+#define VINO_B_ALPHA 0x00b0 /* Channel B ... */
+#define VINO_B_CLIPS 0x00b8
+#define VINO_B_CLIPE 0x00c0
+#define VINO_B_FRAMERT 0x00c8
+#define VINO_B_FLDCNT 0x00d0
+#define VINO_B_LNSZ 0x00d8
+#define VINO_B_LNCNT 0x00e0
+#define VINO_B_PGIX 0x00e8
+#define VINO_B_DESC_PTR 0x00f0
+#define VINO_B_DESC_TLB_PTR 0x00f8
+#define VINO_B_DESC_DATA0 0x0100
+#define VINO_B_DESC_DATA1 0x0108
+#define VINO_B_DESC_DATA2 0x0110
+#define VINO_B_DESC_DATA3 0x0118
+#define VINO_B_FIFO_THRESHOLD 0x0120
+#define VINO_B_FIFO_RP 0x0128
+#define VINO_B_FIFO_WP 0x0130
+
+/* Bits in the VINO_REVID register */
+
+#define VINO_REVID_REV_MASK 0x000f /* bits 0:3 */
+#define VINO_REVID_ID_MASK 0x00f0 /* bits 4:7 */
+
+/* Bits in the VINO_CTRL register */
+
+#define VINO_CTRL_LITTLE_ENDIAN (1<<0)
+#define VINO_CTRL_A_FIELD_TRANS_INT (1<<1) /* Field transferred int */
+#define VINO_CTRL_A_FIFO_OF_INT (1<<2) /* FIFO overflow int */
+#define VINO_CTRL_A_END_DESC_TBL_INT (1<<3) /* End of desc table int */
+#define VINO_CTRL_B_FIELD_TRANS_INT (1<<4) /* Field transferred int */
+#define VINO_CTRL_B_FIFO_OF_INT (1<<5) /* FIFO overflow int */
+#define VINO_CTRL_B_END_DESC_TLB_INT (1<<6) /* End of desc table int */
+#define VINO_CTRL_A_DMA_ENBL (1<<7)
+#define VINO_CTRL_A_INTERLEAVE_ENBL (1<<8)
+#define VINO_CTRL_A_SYNC_ENBL (1<<9)
+#define VINO_CTRL_A_SELECT (1<<10) /* 1=D1 0=Philips */
+#define VINO_CTRL_A_RGB (1<<11) /* 1=RGB 0=YUV */
+#define VINO_CTRL_A_LUMA_ONLY (1<<12)
+#define VINO_CTRL_A_DEC_ENBL (1<<13) /* Decimation */
+#define VINO_CTRL_A_DEC_SCALE_MASK 0x1c000 /* bits 14:17 */
+#define VINO_CTRL_A_DEC_HOR_ONLY (1<<17) /* Horizontal only */
+#define VINO_CTRL_A_DITHER (1<<18) /* 24 -> 8 bit dither */
+#define VINO_CTRL_B_DMA_ENBL (1<<19)
+#define VINO_CTRL_B_INTERLEAVE_ENBL (1<<20)
+#define VINO_CTRL_B_SYNC_ENBL (1<<21)
+#define VINO_CTRL_B_SELECT (1<<22) /* 1=D1 0=Philips */
+#define VINO_CTRL_B_RGB (1<<22) /* 1=RGB 0=YUV */
+#define VINO_CTRL_B_LUMA_ONLY (1<<23)
+#define VINO_CTRL_B_DEC_ENBL (1<<24) /* Decimation */
+#define VINO_CTRL_B_DEC_SCALE_MASK 0x1c000000 /* bits 25:28 */
+#define VINO_CTRL_B_DEC_HOR_ONLY (1<<29) /* Decimation horizontal only */
+#define VINO_CTRL_B_DITHER (1<<30) /* ChanB 24 -> 8 bit dither */
+
+/* Bits in the Interrupt and Status register */
+
+#define VINO_INTSTAT_A_FIELD_TRANS (1<<0) /* Field transferred int */
+#define VINO_INTSTAT_A_FIFO_OF (1<<1) /* FIFO overflow int */
+#define VINO_INTSTAT_A_END_DESC_TBL (1<<2) /* End of desc table int */
+#define VINO_INTSTAT_B_FIELD_TRANS (1<<3) /* Field transferred int */
+#define VINO_INTSTAT_B_FIFO_OF (1<<4) /* FIFO overflow int */
+#define VINO_INTSTAT_B_END_DESC_TBL (1<<5) /* End of desc table int */
+
+/* Bits in the Clipping Start register */
+
+#define VINO_CLIPS_START 0x3ff /* bits 0:9 */
+#define VINO_CLIPS_ODD_MASK 0x7fc00 /* bits 10:18 */
+#define VINO_CLIPS_EVEN_MASK 0xff80000 /* bits 19:27 */
+
+/* Bits in the Clipping End register */
+
+#define VINO_CLIPE_END 0x3ff /* bits 0:9 */
+#define VINO_CLIPE_ODD_MASK 0x7fc00 /* bits 10:18 */
+#define VINO_CLIPE_EVEN_MASK 0xff80000 /* bits 19:27 */
+
+/* Bits in the Frame Rate register */
+
+#define VINO_FRAMERT_PAL (1<<0) /* 0=NTSC 1=PAL */
+#define VINO_FRAMERT_RT_MASK 0x1ffe /* bits 1:12 */
+
+/* Bits in the VINO_I2C_CTRL */
+
+#define VINO_CTRL_I2C_IDLE (1<<0) /* write: 0=force idle
+ * read: 0=idle 1=not idle */
+#define VINO_CTRL_I2C_DIR (1<<1) /* 0=read 1=write */
+#define VINO_CTRL_I2C_MORE_BYTES (1<<2) /* 0=last byte 1=more bytes */
+#define VINO_CTRL_I2C_TRANS_BUSY (1<<4) /* 0=trans done 1=trans busy */
+#define VINO_CTRL_I2C_ACK (1<<5) /* 0=ack received 1=ack not */
+#define VINO_CTRL_I2C_BUS_ERROR (1<<7) /* 0=no bus err 1=bus err */
diff --git a/drivers/media/video/zr36057.h b/drivers/media/video/zr36057.h
new file mode 100644
index 000000000..b672357bc
--- /dev/null
+++ b/drivers/media/video/zr36057.h
@@ -0,0 +1,168 @@
+/*
+ zr36057.h - zr36057 register offsets
+
+ Copyright (C) 1998 Dave Perks <dperks@ibm.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _ZR36057_H_
+#define _ZR36057_H_
+
+
+/* Zoran ZR36057 registers */
+
+#define ZR36057_VFEHCR 0x000 /* Video Front End, Horizontal Configuration Register */
+#define ZR36057_VFEHCR_HSPol (1<<30)
+#define ZR36057_VFEHCR_HStart 10
+#define ZR36057_VFEHCR_HEnd 0
+#define ZR36057_VFEHCR_Hmask 0x3ff
+
+#define ZR36057_VFEVCR 0x004 /* Video Front End, Vertical Configuration Register */
+#define ZR36057_VFEVCR_VSPol (1<<30)
+#define ZR36057_VFEVCR_VStart 10
+#define ZR36057_VFEVCR_VEnd 0
+#define ZR36057_VFEVCR_Vmask 0x3ff
+
+#define ZR36057_VFESPFR 0x008 /* Video Front End, Scaler and Pixel Format Register */
+#define ZR36057_VFESPFR_ExtFl (1<<26)
+#define ZR36057_VFESPFR_TopField (1<<25)
+#define ZR36057_VFESPFR_VCLKPol (1<<24)
+#define ZR36057_VFESPFR_HFilter 21
+#define ZR36057_VFESPFR_HorDcm 14
+#define ZR36057_VFESPFR_VerDcm 8
+#define ZR36057_VFESPFR_DispMode 6
+#define ZR36057_VFESPFR_YUV422 (0<<3)
+#define ZR36057_VFESPFR_RGB888 (1<<3)
+#define ZR36057_VFESPFR_RGB565 (2<<3)
+#define ZR36057_VFESPFR_RGB555 (3<<3)
+#define ZR36057_VFESPFR_ErrDif (1<<2)
+#define ZR36057_VFESPFR_Pack24 (1<<1)
+#define ZR36057_VFESPFR_LittleEndian (1<<0)
+
+#define ZR36057_VDTR 0x00c /* Video Display "Top" Register */
+
+#define ZR36057_VDBR 0x010 /* Video Display "Bottom" Register */
+
+#define ZR36057_VSSFGR 0x014 /* Video Stride, Status, and Frame Grab Register */
+#define ZR36057_VSSFGR_DispStride 16
+#define ZR36057_VSSFGR_VidOvf (1<<8)
+#define ZR36057_VSSFGR_SnapShot (1<<1)
+#define ZR36057_VSSFGR_FrameGrab (1<<0)
+
+#define ZR36057_VDCR 0x018 /* Video Display Configuration Register */
+#define ZR36057_VDCR_VidEn (1<<31)
+#define ZR36057_VDCR_MinPix 24
+#define ZR36057_VDCR_Triton (1<<24)
+#define ZR36057_VDCR_VidWinHt 12
+#define ZR36057_VDCR_VidWinWid 0
+
+#define ZR36057_MMTR 0x01c /* Masking Map "Top" Register */
+
+#define ZR36057_MMBR 0x020 /* Masking Map "Bottom" Register */
+
+#define ZR36057_OCR 0x024 /* Overlay Control Register */
+#define ZR36057_OCR_OvlEnable (1 << 15)
+#define ZR36057_OCR_MaskStride 0
+
+#define ZR36057_SPGPPCR 0x028 /* System, PCI, and General Purpose Pins Control Register */
+#define ZR36057_SPGPPCR_SoftReset (1<<24)
+
+#define ZR36057_GPPGCR1 0x02c /* General Purpose Pins and GuestBus Control Register (1) */
+
+#define ZR36057_MCSAR 0x030 /* MPEG Code Source Address Register */
+
+#define ZR36057_MCTCR 0x034 /* MPEG Code Transfer Control Register */
+#define ZR36057_MCTCR_CodTime (1 << 30)
+#define ZR36057_MCTCR_CEmpty (1 << 29)
+#define ZR36057_MCTCR_CFlush (1 << 28)
+#define ZR36057_MCTCR_CodGuestID 20
+#define ZR36057_MCTCR_CodGuestReg 16
+
+#define ZR36057_MCMPR 0x038 /* MPEG Code Memory Pointer Register */
+
+#define ZR36057_ISR 0x03c /* Interrupt Status Register */
+#define ZR36057_ISR_GIRQ1 (1<<30)
+#define ZR36057_ISR_GIRQ0 (1<<29)
+#define ZR36057_ISR_CodRepIRQ (1<<28)
+#define ZR36057_ISR_JPEGRepIRQ (1<<27)
+
+#define ZR36057_ICR 0x040 /* Interrupt Control Register */
+#define ZR36057_ICR_GIRQ1 (1<<30)
+#define ZR36057_ICR_GIRQ0 (1<<29)
+#define ZR36057_ICR_CodRepIRQ (1<<28)
+#define ZR36057_ICR_JPEGRepIRQ (1<<27)
+#define ZR36057_ICR_IntPinEn (1<<24)
+
+#define ZR36057_I2CBR 0x044 /* I2C Bus Register */
+#define ZR36057_I2CBR_SDA (1<<1)
+#define ZR36057_I2CBR_SCL (1<<0)
+
+#define ZR36057_JMC 0x100 /* JPEG Mode and Control */
+#define ZR36057_JMC_JPG (1 << 31)
+#define ZR36057_JMC_JPGExpMode (0 << 29)
+#define ZR36057_JMC_JPGCmpMode (1 << 29)
+#define ZR36057_JMC_MJPGExpMode (2 << 29)
+#define ZR36057_JMC_MJPGCmpMode (3 << 29)
+#define ZR36057_JMC_RTBUSY_FB (1 << 6)
+#define ZR36057_JMC_Go_en (1 << 5)
+#define ZR36057_JMC_SyncMstr (1 << 4)
+#define ZR36057_JMC_Fld_per_buff (1 << 3)
+#define ZR36057_JMC_VFIFO_FB (1 << 2)
+#define ZR36057_JMC_CFIFO_FB (1 << 1)
+#define ZR36057_JMC_Stll_LitEndian (1 << 0)
+
+#define ZR36057_JPC 0x104 /* JPEG Process Control */
+#define ZR36057_JPC_P_Reset (1 << 7)
+#define ZR36057_JPC_CodTrnsEn (1 << 5)
+#define ZR36057_JPC_Active (1 << 0)
+
+#define ZR36057_VSP 0x108 /* Vertical Sync Parameters */
+#define ZR36057_VSP_VsyncSize 16
+#define ZR36057_VSP_FrmTot 0
+
+#define ZR36057_HSP 0x10c /* Horizontal Sync Parameters */
+#define ZR36057_HSP_HsyncStart 16
+#define ZR36057_HSP_LineTot 0
+
+#define ZR36057_FHAP 0x110 /* Field Horizontal Active Portion */
+#define ZR36057_FHAP_NAX 16
+#define ZR36057_FHAP_PAX 0
+
+#define ZR36057_FVAP 0x114 /* Field Vertical Active Portion */
+#define ZR36057_FVAP_NAY 16
+#define ZR36057_FVAP_PAY 0
+
+#define ZR36057_FPP 0x118 /* Field Process Parameters */
+#define ZR36057_FPP_Odd_Even (1 << 0)
+
+#define ZR36057_JCBA 0x11c /* JPEG Code Base Address */
+
+#define ZR36057_JCFT 0x120 /* JPEG Code FIFO Threshold */
+
+#define ZR36057_JCGI 0x124 /* JPEG Codec Guest ID */
+#define ZR36057_JCGI_JPEGuestID 4
+#define ZR36057_JCGI_JPEGuestReg 0
+
+#define ZR36057_GCR2 0x12c /* GuestBus Control Register (2) */
+
+#define ZR36057_POR 0x200 /* Post Office Register */
+#define ZR36057_POR_POPen (1<<25)
+#define ZR36057_POR_POTime (1<<24)
+#define ZR36057_POR_PODir (1<<23)
+
+#define ZR36057_STR 0x300 /* "Still" Transfer Register */
+
+#endif
diff --git a/drivers/media/video/zr36060.h b/drivers/media/video/zr36060.h
new file mode 100644
index 000000000..d94f56b0b
--- /dev/null
+++ b/drivers/media/video/zr36060.h
@@ -0,0 +1,35 @@
+/*
+ zr36060.h - zr36060 register offsets
+
+ Copyright (C) 1998 Dave Perks <dperks@ibm.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _ZR36060_H_
+#define _ZR36060_H_
+
+
+/* Zoran ZR36060 registers */
+
+#define ZR36060_LoadParameters 0x000
+#define ZR36060_Load (1<<7)
+#define ZR36060_SyncRst (1<<0)
+
+#define ZR36060_CodeFifoStatus 0x001
+#define ZR36060_Load (1<<7)
+#define ZR36060_SyncRst (1<<0)
+
+#endif
diff --git a/drivers/media/video/zr36120.c b/drivers/media/video/zr36120.c
new file mode 100644
index 000000000..6e860021e
--- /dev/null
+++ b/drivers/media/video/zr36120.c
@@ -0,0 +1,2086 @@
+/*
+ zr36120.c - Zoran 36120/36125 based framegrabbers
+
+ Copyright (C) 1998-1999 Pauline Middelink <middelin@polyware.nl>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/malloc.h>
+#include <linux/vmalloc.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+#include <linux/signal.h>
+#include <asm/io.h>
+#include <asm/pgtable.h>
+#include <asm/page.h>
+#include <linux/sched.h>
+#include <linux/video_decoder.h>
+#include <asm/segment.h>
+
+#include <linux/version.h>
+#include <asm/uaccess.h>
+
+#include "tuner.h"
+#include "zr36120.h"
+#include "zr36120_mem.h"
+
+/* mark an required function argument unused - lintism */
+#define UNUSED(x) (void)(x)
+
+/* sensible default */
+#ifndef CARDTYPE
+#define CARDTYPE 0
+#endif
+
+/* Anybody who uses more than four? */
+#define ZORAN_MAX 4
+
+static unsigned int triton1=0; /* triton1 chipset? */
+static unsigned int cardtype[ZORAN_MAX]={ [ 0 ... ZORAN_MAX-1 ] = CARDTYPE };
+
+MODULE_AUTHOR("Pauline Middelink <middelin@polyware.nl>");
+MODULE_DESCRIPTION("Zoran ZR36120 based framegrabber");
+MODULE_PARM(triton1,"i");
+MODULE_PARM(cardtype,"1-" __MODULE_STRING(ZORAN_MAX) "i");
+
+static int zoran_cards;
+static struct zoran zorans[ZORAN_MAX];
+
+/*
+ * the meaning of each element can be found in zr36120.h
+ * Determining the value of gpdir/gpval can be tricky. The
+ * best way is to run the card under the original software
+ * and read the values from the general purpose registers
+ * 0x28 and 0x2C. How you do that is left as an exercise
+ * to the impatient reader :)
+ */
+#define T 1 /* to seperate the bools from the ints */
+#define F 0
+static struct tvcard tvcards[] = {
+ /* reported working by <middelin@polyware.nl> */
+/*0*/ { "Trust Victor II",
+ 2, 0, T, T, T, T, 0x7F, 0x80, { 1, SVHS(6) }, { 0 } },
+ /* reported working by <Michael.Paxton@aihw.gov.au> */
+/*1*/ { "Aitech WaveWatcher TV-PCI",
+ 3, 0, T, F, T, T, 0x7F, 0x80, { 1, TUNER(3), SVHS(6) }, { 0 } },
+ /* reported working by ? */
+/*2*/ { "Genius Video Wonder PCI Video Capture Card",
+ 2, 0, T, T, T, T, 0x7F, 0x80, { 1, SVHS(6) }, { 0 } },
+ /* reported working by <Pascal.Gabriel@wanadoo.fr> */
+/*3*/ { "Guillemot Maxi-TV PCI",
+ 2, 0, T, T, T, T, 0x7F, 0x80, { 1, SVHS(6) }, { 0 } },
+ /* reported working by "Craig Whitmore <lennon@igrin.co.nz> */
+/*4*/ { "Quadrant Buster",
+ 3, 3, T, F, T, T, 0x7F, 0x80, { SVHS(1), TUNER(2), 3 }, { 1, 2, 3 } },
+ /* a debug entry which has all inputs mapped */
+/*5*/ { "ZR36120 based framegrabber (all inputs enabled)",
+ 6, 0, T, T, T, T, 0x7F, 0x80, { 1, 2, 3, 4, 5, 6 }, { 0 } }
+};
+#undef T
+#undef F
+#define NRTVCARDS (sizeof(tvcards)/sizeof(tvcards[0]))
+
+#ifdef __sparc__
+#define ENDIANESS 0
+#else
+#define ENDIANESS ZORAN_VFEC_LE
+#endif
+
+static struct { const char name[8]; uint mode; uint bpp; } palette2fmt[] = {
+/* n/a */ { "n/a", 0, 0 },
+/* GREY */ { "GRAY", 0, 0 },
+/* HI240 */ { "HI240", 0, 0 },
+/* RGB565 */ { "RGB565", ZORAN_VFEC_RGB_RGB565|ENDIANESS, 2 },
+/* RGB24 */ { "RGB24", ZORAN_VFEC_RGB_RGB888|ENDIANESS|ZORAN_VFEC_PACK24, 3 },
+/* RGB32 */ { "RGB32", ZORAN_VFEC_RGB_RGB888|ENDIANESS, 4 },
+/* RGB555 */ { "RGB555", ZORAN_VFEC_RGB_RGB555|ENDIANESS, 2 },
+/* YUV422 */ { "YUV422", ZORAN_VFEC_RGB_YUV422|ENDIANESS, 2 },
+/* YUYV */ { "YUYV", 0, 0 },
+/* UYVY */ { "UYVY", 0, 0 },
+/* YUV420 */ { "YUV420", 0, 0 },
+/* YUV411 */ { "YUV411", 0, 0 },
+/* RAW */ { "RAW", 0, 0 },
+/* YUV422P */ { "YUV422P", 0, 0 },
+/* YUV411P */ { "YUV411P", 0, 0 }};
+#define NRPALETTES (sizeof(palette2fmt)/sizeof(palette2fmt[0]))
+#undef ENDIANESS
+
+/* ----------------------------------------------------------------------- */
+/* ZORAN chipset detector */
+/* shamelessly stolen from bttv.c */
+/* Reason for beeing here: we need to detect if we are running on a */
+/* Triton based chipset, and if so, enable a certain bit */
+/* ----------------------------------------------------------------------- */
+static
+void __init handle_chipset(void)
+{
+ struct pci_dev *dev = NULL;
+
+ /* Just in case some nut set this to something dangerous */
+ if (triton1)
+ triton1 = ZORAN_VDC_TRICOM;
+
+ while ((dev = pci_find_device(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82437, dev)))
+ {
+ printk(KERN_INFO "zoran: Host bridge 82437FX Triton PIIX\n");
+ triton1 = ZORAN_VDC_TRICOM;
+ }
+}
+
+/* ----------------------------------------------------------------------- */
+/* ZORAN functions */
+/* ----------------------------------------------------------------------- */
+
+static void zoran_set_geo(struct zoran* ztv, struct vidinfo* i);
+
+#if 0 /* unused */
+static
+void zoran_dump(struct zoran *ztv)
+{
+ char str[256];
+ char *p=str; /* shut up, gcc! */
+ int i;
+
+ for (i=0; i<0x60; i+=4) {
+ if ((i % 16) == 0) {
+ if (i) printk("%s\n",str);
+ p = str;
+ p+= sprintf(str, KERN_DEBUG " %04x: ",i);
+ }
+ p += sprintf(p, "%08x ",zrread(i));
+ }
+}
+#endif /* unused */
+
+static
+void reap_states(struct zoran* ztv)
+{
+ /* count frames */
+ ztv->fieldnr++;
+
+ /*
+ * Are we busy at all?
+ * This depends on if there is a workqueue AND the
+ * videotransfer is enabled on the chip...
+ */
+ if (ztv->workqueue && (zrread(ZORAN_VDC) & ZORAN_VDC_VIDEN))
+ {
+ struct vidinfo* newitem;
+
+ /* did we get a complete frame? */
+ if (zrread(ZORAN_VSTR) & ZORAN_VSTR_GRAB)
+ return;
+
+DEBUG(printk(CARD_DEBUG "completed %s at %p\n",CARD,ztv->workqueue->kindof==FBUFFER_GRAB?"grab":"read",ztv->workqueue));
+
+ /* we are done with this buffer, tell everyone */
+ ztv->workqueue->status = FBUFFER_DONE;
+ ztv->workqueue->fieldnr = ztv->fieldnr;
+ /* not good, here for BTTV_FIELDNR reasons */
+ ztv->lastfieldnr = ztv->fieldnr;
+
+ switch (ztv->workqueue->kindof) {
+ case FBUFFER_GRAB:
+ wake_up_interruptible(&ztv->grabq);
+ break;
+ case FBUFFER_VBI:
+ wake_up_interruptible(&ztv->vbiq);
+ break;
+ default:
+ printk(CARD_INFO "somebody killed the workqueue (kindof=%d)!\n",CARD,ztv->workqueue->kindof);
+ }
+
+ /* item completed, skip to next item in queue */
+ write_lock(&ztv->lock);
+ newitem = ztv->workqueue->next;
+ ztv->workqueue->next = 0; /* mark completed */
+ ztv->workqueue = newitem;
+ write_unlock(&ztv->lock);
+ }
+
+ /*
+ * ok, so it seems we have nothing in progress right now.
+ * Lets see if we can find some work.
+ */
+ if (ztv->workqueue)
+ {
+ struct vidinfo* newitem;
+again:
+
+DEBUG(printk(CARD_DEBUG "starting %s at %p\n",CARD,ztv->workqueue->kindof==FBUFFER_GRAB?"grab":"read",ztv->workqueue));
+
+ /* loadup the frame settings */
+ read_lock(&ztv->lock);
+ zoran_set_geo(ztv,ztv->workqueue);
+ read_unlock(&ztv->lock);
+
+ switch (ztv->workqueue->kindof) {
+ case FBUFFER_GRAB:
+ case FBUFFER_VBI:
+ zrand(~ZORAN_OCR_OVLEN, ZORAN_OCR);
+ zror(ZORAN_VSTR_SNAPSHOT,ZORAN_VSTR);
+ zror(ZORAN_VDC_VIDEN,ZORAN_VDC);
+
+ /* start single-shot grab */
+ zror(ZORAN_VSTR_GRAB, ZORAN_VSTR);
+ break;
+ default:
+ printk(CARD_INFO "what is this doing on the queue? (kindof=%d)\n",CARD,ztv->workqueue->kindof);
+ write_lock(&ztv->lock);
+ newitem = ztv->workqueue->next;
+ ztv->workqueue->next = 0;
+ ztv->workqueue = newitem;
+ write_unlock(&ztv->lock);
+ if (newitem)
+ goto again; /* yeah, sure.. */
+ }
+ /* bye for now */
+ return;
+ }
+DEBUG(printk(CARD_DEBUG "nothing in queue\n",CARD));
+
+ /*
+ * What? Even the workqueue is empty? Am i really here
+ * for nothing? Did i come all that way to... do nothing?
+ */
+
+ /* do we need to overlay? */
+ if (test_bit(STATE_OVERLAY, &ztv->state))
+ {
+ /* are we already overlaying? */
+ if (!(zrread(ZORAN_OCR) & ZORAN_OCR_OVLEN) ||
+ !(zrread(ZORAN_VDC) & ZORAN_VDC_VIDEN))
+ {
+DEBUG(printk(CARD_DEBUG "starting overlay\n",CARD));
+
+ read_lock(&ztv->lock);
+ zoran_set_geo(ztv,&ztv->overinfo);
+ read_unlock(&ztv->lock);
+
+ zror(ZORAN_OCR_OVLEN, ZORAN_OCR);
+ zrand(~ZORAN_VSTR_SNAPSHOT,ZORAN_VSTR);
+ zror(ZORAN_VDC_VIDEN,ZORAN_VDC);
+ }
+
+ /*
+ * leave overlaying on, but turn interrupts off.
+ */
+ zrand(~ZORAN_ICR_EN,ZORAN_ICR);
+ return;
+ }
+
+ /* do we have any VBI idle time processing? */
+ if (test_bit(STATE_VBI, &ztv->state))
+ {
+ struct vidinfo* item;
+ struct vidinfo* lastitem;
+
+ /* protect the workqueue */
+ write_lock(&ztv->lock);
+ lastitem = ztv->workqueue;
+ if (lastitem)
+ while (lastitem->next) lastitem = lastitem->next;
+ for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++)
+ if (item->next == 0 && item->status == FBUFFER_FREE)
+ {
+DEBUG(printk(CARD_DEBUG "%p added to queue\n",CARD,item));
+ item->status = FBUFFER_BUSY;
+ if (!lastitem)
+ ztv->workqueue = item;
+ else
+ lastitem->next = item;
+ lastitem = item;
+ }
+ write_unlock(&ztv->lock);
+ if (ztv->workqueue)
+ goto again; /* hey, _i_ graduated :) */
+ }
+
+ /*
+ * Then we must be realy IDLE
+ */
+DEBUG(printk(CARD_DEBUG "turning off\n",CARD));
+ /* nothing further to do, disable DMA and further IRQs */
+ zrand(~ZORAN_VDC_VIDEN,ZORAN_VDC);
+ zrand(~ZORAN_ICR_EN,ZORAN_ICR);
+}
+
+static
+void zoran_irq(int irq, void *dev_id, struct pt_regs * regs)
+{
+ u32 stat,estat;
+ int count = 0;
+ struct zoran *ztv = (struct zoran *)dev_id;
+
+ UNUSED(irq); UNUSED(regs);
+ for (;;) {
+ /* get/clear interrupt status bits */
+ stat=zrread(ZORAN_ISR);
+ estat=stat & zrread(ZORAN_ICR);
+ if (!estat)
+ return;
+ zrwrite(estat,ZORAN_ISR);
+ IDEBUG(printk(CARD_DEBUG "estat %08x\n",CARD,estat));
+ IDEBUG(printk(CARD_DEBUG " stat %08x\n",CARD,stat));
+
+ if (estat & ZORAN_ISR_CODE)
+ {
+ IDEBUG(printk(CARD_DEBUG "CodReplIRQ\n",CARD));
+ }
+ if (estat & ZORAN_ISR_GIRQ0)
+ {
+ IDEBUG(printk(CARD_DEBUG "GIRQ0\n",CARD));
+ if (!ztv->card->usegirq1)
+ reap_states(ztv);
+ }
+ if (estat & ZORAN_ISR_GIRQ1)
+ {
+ IDEBUG(printk(CARD_DEBUG "GIRQ1\n",CARD));
+ if (ztv->card->usegirq1)
+ reap_states(ztv);
+ }
+
+ count++;
+ if (count > 10)
+ printk(CARD_ERR "irq loop %d (%x)\n",CARD,count,estat);
+ if (count > 20)
+ {
+ zrwrite(0, ZORAN_ICR);
+ printk(CARD_ERR "IRQ lockup, cleared int mask\n",CARD);
+ }
+ }
+}
+
+static
+int zoran_muxsel(struct zoran* ztv, int channel, int norm)
+{
+ int rv;
+
+ /* set the new video norm */
+ rv = i2c_control_device(&(ztv->i2c), I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &norm);
+ if (rv)
+ return rv;
+ ztv->norm = norm;
+
+ /* map the given channel to the cards decoder's channel */
+ channel = ztv->card->video_mux[channel] & CHANNEL_MASK;
+
+ /* set the new channel */
+ rv = i2c_control_device(&(ztv->i2c), I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &channel);
+ return rv;
+}
+
+/* Tell the interrupt handler what to to. */
+static
+void zoran_cap(struct zoran* ztv, int on)
+{
+DEBUG(printk(CARD_DEBUG "zoran_cap(%d) state=%x\n",CARD,on,ztv->state));
+
+ if (on) {
+ ztv->running = 1;
+
+ /*
+ * turn interrupts (back) on. The DMA will be enabled
+ * inside the irq handler when it detects a restart.
+ */
+ zror(ZORAN_ICR_EN,ZORAN_ICR);
+ }
+ else {
+ /*
+ * turn both interrupts and DMA off
+ */
+ zrand(~ZORAN_VDC_VIDEN,ZORAN_VDC);
+ zrand(~ZORAN_ICR_EN,ZORAN_ICR);
+
+ ztv->running = 0;
+ }
+}
+
+static ulong dmask[] = {
+ 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFC, 0xFFFFFFF8,
+ 0xFFFFFFF0, 0xFFFFFFE0, 0xFFFFFFC0, 0xFFFFFF80,
+ 0xFFFFFF00, 0xFFFFFE00, 0xFFFFFC00, 0xFFFFF800,
+ 0xFFFFF000, 0xFFFFE000, 0xFFFFC000, 0xFFFF8000,
+ 0xFFFF0000, 0xFFFE0000, 0xFFFC0000, 0xFFF80000,
+ 0xFFF00000, 0xFFE00000, 0xFFC00000, 0xFF800000,
+ 0xFF000000, 0xFE000000, 0xFC000000, 0xF8000000,
+ 0xF0000000, 0xE0000000, 0xC0000000, 0x80000000
+};
+
+static
+void zoran_built_overlay(struct zoran* ztv, int count, struct video_clip *vcp)
+{
+ ulong* mtop;
+ int ystep = (ztv->vidXshift + ztv->vidWidth+31)/32; /* next DWORD */
+ int i;
+
+DEBUG(printk(KERN_DEBUG " overlay at %p, ystep=%d, clips=%d\n",ztv->overinfo.overlay,ystep,count));
+
+ for (i=0; i<count; i++) {
+ struct video_clip *vp = vcp+i;
+ UNUSED(vp);
+DEBUG(printk(KERN_DEBUG " %d: clip(%d,%d,%d,%d)\n", i,vp->x,vp->y,vp->width,vp->height));
+ }
+
+ /*
+ * activate the visible portion of the screen
+ * Note we take some shortcuts here, because we
+ * know the width can never be < 32. (I.e. a DWORD)
+ * We also assume the overlay starts somewhere in
+ * the FIRST dword.
+ */
+ {
+ int start = ztv->vidXshift;
+ ulong firstd = dmask[start];
+ ulong lastd = ~dmask[(start + ztv->overinfo.w) & 31];
+ mtop = ztv->overinfo.overlay;
+ for (i=0; i<ztv->overinfo.h; i++) {
+ int w = ztv->vidWidth;
+ ulong* line = mtop;
+ if (start & 31) {
+ *line++ = firstd;
+ w -= 32-(start&31);
+ }
+ memset(line, ~0, w/8);
+ if (w & 31)
+ line[w/32] = lastd;
+ mtop += ystep;
+ }
+ }
+
+ /* process clipping regions */
+ for (i=0; i<count; i++) {
+ int h;
+ if (vcp->x < 0 || (uint)vcp->x > ztv->overinfo.w ||
+ vcp->y < 0 || vcp->y > ztv->overinfo.h ||
+ vcp->width < 0 || (uint)(vcp->x+vcp->width) > ztv->overinfo.w ||
+ vcp->height < 0 || (vcp->y+vcp->height) > ztv->overinfo.h)
+ {
+ DEBUG(printk(CARD_DEBUG "illegal clipzone (%d,%d,%d,%d) not in (0,0,%d,%d), adapting\n",CARD,vcp->x,vcp->y,vcp->width,vcp->height,ztv->overinfo.w,ztv->overinfo.h));
+ if (vcp->x < 0) vcp->x = 0;
+ if ((uint)vcp->x > ztv->overinfo.w) vcp->x = ztv->overinfo.w;
+ if (vcp->y < 0) vcp->y = 0;
+ if (vcp->y > ztv->overinfo.h) vcp->y = ztv->overinfo.h;
+ if (vcp->width < 0) vcp->width = 0;
+ if ((uint)(vcp->x+vcp->width) > ztv->overinfo.w) vcp->width = ztv->overinfo.w - vcp->x;
+ if (vcp->height < 0) vcp->height = 0;
+ if (vcp->y+vcp->height > ztv->overinfo.h) vcp->height = ztv->overinfo.h - vcp->y;
+// continue;
+ }
+
+ mtop = &ztv->overinfo.overlay[vcp->y*ystep];
+ for (h=0; h<=vcp->height; h++) {
+ int w;
+ int x = ztv->vidXshift + vcp->x;
+ for (w=0; w<=vcp->width; w++) {
+ clear_bit(x&31, &mtop[x/32]);
+ x++;
+ }
+ mtop += ystep;
+ }
+ ++vcp;
+ }
+
+ mtop = ztv->overinfo.overlay;
+ zrwrite(virt_to_bus(mtop), ZORAN_MTOP);
+ zrwrite(virt_to_bus(mtop+ystep), ZORAN_MBOT);
+ zraor((ztv->vidInterlace*ystep)<<0,~ZORAN_OCR_MASKSTRIDE,ZORAN_OCR);
+}
+
+struct tvnorm
+{
+ u16 Wt, Wa, Ht, Ha, HStart, VStart;
+};
+
+static struct tvnorm tvnorms[] = {
+ /* PAL-BDGHI */
+/* { 864, 720, 625, 576, 131, 21 },*/
+/*00*/ { 864, 768, 625, 576, 81, 17 },
+ /* NTSC */
+/*01*/ { 858, 720, 525, 480, 121, 10 },
+ /* SECAM */
+/*02*/ { 864, 720, 625, 576, 131, 21 },
+ /* BW50 */
+/*03*/ { 864, 720, 625, 576, 131, 21 },
+ /* BW60 */
+/*04*/ { 858, 720, 525, 480, 121, 10 }
+};
+#define TVNORMS (sizeof(tvnorms)/sizeof(tvnorm))
+
+/*
+ * Program the chip for a setup as described in the vidinfo struct.
+ *
+ * Side-effects: calculates vidXshift, vidInterlace,
+ * vidHeight, vidWidth which are used in a later stage
+ * to calculate the overlay mask
+ *
+ * This is an internal function, as such it does not check the
+ * validity of the struct members... Spectaculair crashes will
+ * follow /very/ quick when you're wrong and the chip right :)
+ */
+static
+void zoran_set_geo(struct zoran* ztv, struct vidinfo* i)
+{
+ ulong top, bot;
+ int stride;
+ int winWidth, winHeight;
+ int maxWidth, maxHeight, maxXOffset, maxYOffset;
+ long vfec;
+
+DEBUG(printk(CARD_DEBUG "set_geo(rect=(%d,%d,%d,%d), norm=%d, format=%d, bpp=%d, bpl=%d, busadr=%lx, overlay=%p)\n",CARD,i->x,i->y,i->w,i->h,ztv->norm,i->format,i->bpp,i->bpl,i->busadr,i->overlay));
+
+ /*
+ * make sure the DMA transfers are inhibited during our
+ * reprogramming of the chip
+ */
+ zrand(~ZORAN_VDC_VIDEN,ZORAN_VDC);
+
+ maxWidth = tvnorms[ztv->norm].Wa;
+ maxHeight = tvnorms[ztv->norm].Ha/2;
+ maxXOffset = tvnorms[ztv->norm].HStart;
+ maxYOffset = tvnorms[ztv->norm].VStart;
+
+ /* setup vfec register (keep ExtFl,TopField and VCLKPol settings) */
+ vfec = (zrread(ZORAN_VFEC) & (ZORAN_VFEC_EXTFL|ZORAN_VFEC_TOPFIELD|ZORAN_VFEC_VCLKPOL)) |
+ (palette2fmt[i->format].mode & (ZORAN_VFEC_RGB|ZORAN_VFEC_ERRDIF|ZORAN_VFEC_LE|ZORAN_VFEC_PACK24));
+
+ /*
+ * Set top, bottom ptrs. Since these must be DWORD aligned,
+ * possible adjust the x and the width of the window.
+ * so the endposition stay the same. The vidXshift will make
+ * sure we are not writing pixels before the requested x.
+ */
+ ztv->vidXshift = 0;
+ winWidth = i->w;
+ if (winWidth < 0)
+ winWidth = -winWidth;
+ top = i->busadr + i->x*i->bpp + i->y*i->bpl;
+ if (top & 3) {
+ ztv->vidXshift = (top & 3) / i->bpp;
+ winWidth += ztv->vidXshift;
+ DEBUG(printk(KERN_DEBUG " window-x shifted %d pixels left\n",ztv->vidXshift));
+ top &= ~3;
+ }
+
+ /*
+ * bottom points to next frame but in interleaved mode we want
+ * to 'mix' the 2 frames to one capture, so 'bot' points to one
+ * (physical) line below the top line.
+ */
+ bot = top + i->bpl;
+ zrwrite(top,ZORAN_VTOP);
+ zrwrite(bot,ZORAN_VBOT);
+
+ /*
+ * Make sure the winWidth is DWORD aligned too,
+ * thereby automaticly making sure the stride to the
+ * next line is DWORD aligned too (as required by spec).
+ */
+ if ((winWidth*i->bpp) & 3) {
+DEBUG(printk(KERN_DEBUG " window-width enlarged by %d pixels\n",(winWidth*i->bpp) & 3));
+ winWidth += (winWidth*i->bpp) & 3;
+ }
+
+ /* determine the DispMode and stride */
+ if (i->h >= 0 && i->h <= maxHeight) {
+ /* single frame grab suffices for this height. */
+ vfec |= ZORAN_VFEC_DISPMOD;
+ ztv->vidInterlace = 0;
+ stride = i->bpl - (winWidth*i->bpp);
+ winHeight = i->h;
+ }
+ else {
+ /* interleaving needed for this height */
+ ztv->vidInterlace = 1;
+ stride = i->bpl*2 - (winWidth*i->bpp);
+ winHeight = i->h/2;
+ }
+ if (winHeight < 0) /* can happen for VBI! */
+ winHeight = -winHeight;
+
+ /* safety net, sometimes bpl is too short??? */
+ if (stride<0) {
+DEBUG(printk(CARD_DEBUG "WARNING stride = %d\n",CARD,stride));
+ stride = 0;
+ }
+
+ zraor((winHeight<<12)|(winWidth<<0),~(ZORAN_VDC_VIDWINHT|ZORAN_VDC_VIDWINWID), ZORAN_VDC);
+ zraor(stride<<16,~ZORAN_VSTR_DISPSTRIDE,ZORAN_VSTR);
+
+ /* remember vidWidth, vidHeight for overlay calculations */
+ ztv->vidWidth = winWidth;
+ ztv->vidHeight = winHeight;
+DEBUG(printk(KERN_DEBUG " top=%08lx, bottom=%08lx\n",top,bot));
+DEBUG(printk(KERN_DEBUG " winWidth=%d, winHeight=%d\n",winWidth,winHeight));
+DEBUG(printk(KERN_DEBUG " maxWidth=%d, maxHeight=%d\n",maxWidth,maxHeight));
+DEBUG(printk(KERN_DEBUG " stride=%d\n",stride));
+
+ /*
+ * determine horizontal scales and crops
+ */
+ if (i->w < 0) {
+ int Hstart = 1;
+ int Hend = Hstart + winWidth;
+DEBUG(printk(KERN_DEBUG " Y: scale=0, start=%d, end=%d\n", Hstart, Hend));
+ zraor((Hstart<<10)|(Hend<<0),~(ZORAN_VFEH_HSTART|ZORAN_VFEH_HEND),ZORAN_VFEH);
+ }
+ else {
+ int Wa = maxWidth;
+ int X = (winWidth*64+Wa-1)/Wa;
+ int We = winWidth*64/X;
+ int HorDcm = 64-X;
+ int hcrop1 = 2*(Wa-We)/4;
+ /*
+ * BUGFIX: Juha Nurmela <junki@qn-lpr2-165.quicknet.inet.fi>
+ * found the solution to the color phase shift.
+ * See ChangeLog for the full explanation)
+ */
+ int Hstart = (maxXOffset + hcrop1) | 1;
+ int Hend = Hstart + We - 1;
+
+DEBUG(printk(KERN_DEBUG " X: scale=%d, start=%d, end=%d\n", HorDcm, Hstart, Hend));
+
+ zraor((Hstart<<10)|(Hend<<0),~(ZORAN_VFEH_HSTART|ZORAN_VFEH_HEND),ZORAN_VFEH);
+ vfec |= HorDcm<<14;
+
+ if (HorDcm<16)
+ vfec |= ZORAN_VFEC_HFILTER_1; /* no filter */
+ else if (HorDcm<32)
+ vfec |= ZORAN_VFEC_HFILTER_3; /* 3 tap filter */
+ else if (HorDcm<48)
+ vfec |= ZORAN_VFEC_HFILTER_4; /* 4 tap filter */
+ else vfec |= ZORAN_VFEC_HFILTER_5; /* 5 tap filter */
+ }
+
+ /*
+ * Determine vertical scales and crops
+ *
+ * when height is negative, we want to read starting at line 0
+ * One day someone might need access to these lines...
+ */
+ if (i->h < 0) {
+ int Vstart = 0;
+ int Vend = Vstart + winHeight;
+DEBUG(printk(KERN_DEBUG " Y: scale=0, start=%d, end=%d\n", Vstart, Vend));
+ zraor((Vstart<<10)|(Vend<<0),~(ZORAN_VFEV_VSTART|ZORAN_VFEV_VEND),ZORAN_VFEV);
+ }
+ else {
+ int Ha = maxHeight;
+ int Y = (winHeight*64+Ha-1)/Ha;
+ int He = winHeight*64/Y;
+ int VerDcm = 64-Y;
+ int vcrop1 = 2*(Ha-He)/4;
+ int Vstart = maxYOffset + vcrop1;
+ int Vend = Vstart + He - 1;
+
+DEBUG(printk(KERN_DEBUG " Y: scale=%d, start=%d, end=%d\n", VerDcm, Vstart, Vend));
+ zraor((Vstart<<10)|(Vend<<0),~(ZORAN_VFEV_VSTART|ZORAN_VFEV_VEND),ZORAN_VFEV);
+ vfec |= VerDcm<<8;
+ }
+
+DEBUG(printk(KERN_DEBUG " F: format=%d(=%s)\n",i->format,palette2fmt[i->format].name));
+
+ /* setup the requested format */
+ zrwrite(vfec, ZORAN_VFEC);
+}
+
+static
+void zoran_common_open(struct zoran* ztv, int flags)
+{
+ UNUSED(flags);
+
+ /* already opened? */
+ if (ztv->users++ != 0)
+ return;
+
+ /* unmute audio */
+ /* /what/ audio? */
+
+ ztv->state = 0;
+
+ /* setup the encoder to the initial values */
+ ztv->picture.colour=254<<7;
+ ztv->picture.brightness=128<<8;
+ ztv->picture.hue=128<<8;
+ ztv->picture.contrast=216<<7;
+ i2c_control_device(&ztv->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_PICTURE, &ztv->picture);
+
+ /* default to the composite input since my camera is there */
+ zoran_muxsel(ztv, 0, VIDEO_MODE_PAL);
+}
+
+static
+void zoran_common_close(struct zoran* ztv)
+{
+ if (--ztv->users != 0)
+ return;
+
+ /* mute audio */
+ /* /what/ audio? */
+
+ /* stop the chip */
+ zoran_cap(ztv, 0);
+}
+
+/*
+ * Open a zoran card. Right now the flags are just a hack
+ */
+static int zoran_open(struct video_device *dev, int flags)
+{
+ struct zoran *ztv = (struct zoran*)dev;
+ struct vidinfo* item;
+ char* pos;
+
+ DEBUG(printk(CARD_DEBUG "open(dev,%d)\n",CARD,flags));
+
+ /*********************************************
+ * We really should be doing lazy allocing...
+ *********************************************/
+ /* allocate a frame buffer */
+ if (!ztv->fbuffer)
+ ztv->fbuffer = bmalloc(ZORAN_MAX_FBUFSIZE);
+ if (!ztv->fbuffer) {
+ /* could not get a buffer, bail out */
+ return -ENOBUFS;
+ }
+ /* at this time we _always_ have a framebuffer */
+ memset(ztv->fbuffer,0,ZORAN_MAX_FBUFSIZE);
+
+ if (!ztv->overinfo.overlay)
+ ztv->overinfo.overlay = (void*)kmalloc(1024*1024/8, GFP_KERNEL);
+ if (!ztv->overinfo.overlay) {
+ /* could not get an overlay buffer, bail out */
+ bfree(ztv->fbuffer, ZORAN_MAX_FBUFSIZE);
+ return -ENOBUFS;
+ }
+ /* at this time we _always_ have a overlay */
+
+ /* clear buffer status, and give them a DMAable address */
+ pos = ztv->fbuffer;
+ for (item=ztv->grabinfo; item!=ztv->grabinfo+ZORAN_MAX_FBUFFERS; item++)
+ {
+ item->status = FBUFFER_FREE;
+ item->memadr = pos;
+ item->busadr = virt_to_bus(pos);
+ pos += ZORAN_MAX_FBUFFER;
+ }
+
+ /* do the common part of all open's */
+ zoran_common_open(ztv, flags);
+
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static
+void zoran_close(struct video_device* dev)
+{
+ struct zoran *ztv = (struct zoran*)dev;
+
+ DEBUG(printk(CARD_DEBUG "close(dev)\n",CARD));
+
+ /* driver specific closure */
+ clear_bit(STATE_OVERLAY, &ztv->state);
+
+ zoran_common_close(ztv);
+
+ /*
+ * This is sucky but right now I can't find a good way to
+ * be sure its safe to free the buffer. We wait 5-6 fields
+ * which is more than sufficient to be sure.
+ */
+ current->state = TASK_UNINTERRUPTIBLE;
+ schedule_timeout(HZ/10); /* Wait 1/10th of a second */
+
+ /* free the allocated framebuffer */
+ if (ztv->fbuffer)
+ bfree( ztv->fbuffer, ZORAN_MAX_FBUFSIZE );
+ ztv->fbuffer = 0;
+ if (ztv->overinfo.overlay)
+ kfree( ztv->overinfo.overlay );
+ ztv->overinfo.overlay = 0;
+
+ MOD_DEC_USE_COUNT;
+}
+
+/*
+ * This read function could be used reentrant in a SMP situation.
+ *
+ * This is made possible by the spinlock which is kept till we
+ * found and marked a buffer for our own use. The lock must
+ * be released as soon as possible to prevent lock contention.
+ */
+static
+long zoran_read(struct video_device* dev, char* buf, unsigned long count, int nonblock)
+{
+ struct zoran *ztv = (struct zoran*)dev;
+ unsigned long max;
+ struct vidinfo* unused = 0;
+ struct vidinfo* done = 0;
+
+ DEBUG(printk(CARD_DEBUG "zoran_read(%p,%ld,%d)\n",CARD,buf,count,nonblock));
+
+ /* find ourself a free or completed buffer */
+ for (;;) {
+ struct vidinfo* item;
+
+ write_lock_irq(&ztv->lock);
+ for (item=ztv->grabinfo; item!=ztv->grabinfo+ZORAN_MAX_FBUFFERS; item++)
+ {
+ if (!unused && item->status == FBUFFER_FREE)
+ unused = item;
+ if (!done && item->status == FBUFFER_DONE)
+ done = item;
+ }
+ if (done || unused)
+ break;
+
+ /* no more free buffers, wait for them. */
+ write_unlock_irq(&ztv->lock);
+ if (nonblock)
+ return -EWOULDBLOCK;
+ interruptible_sleep_on(&ztv->grabq);
+ if (signal_pending(current))
+ return -EINTR;
+ }
+
+ /* Do we have 'ready' data? */
+ if (!done) {
+ /* no? than this will take a while... */
+ if (nonblock) {
+ write_unlock_irq(&ztv->lock);
+ return -EWOULDBLOCK;
+ }
+
+ /* mark the unused buffer as wanted */
+ unused->status = FBUFFER_BUSY;
+ unused->w = 320;
+ unused->h = 240;
+ unused->format = VIDEO_PALETTE_RGB24;
+ unused->bpp = palette2fmt[unused->format].bpp;
+ unused->bpl = unused->w * unused->bpp;
+ unused->next = 0;
+ { /* add to tail of queue */
+ struct vidinfo* oldframe = ztv->workqueue;
+ if (!oldframe) ztv->workqueue = unused;
+ else {
+ while (oldframe->next) oldframe = oldframe->next;
+ oldframe->next = unused;
+ }
+ }
+ write_unlock_irq(&ztv->lock);
+
+ /* tell the state machine we want it filled /NOW/ */
+ zoran_cap(ztv, 1);
+
+ /* wait till this buffer gets grabbed */
+ while (unused->status == FBUFFER_BUSY) {
+ interruptible_sleep_on(&ztv->grabq);
+ /* see if a signal did it */
+ if (signal_pending(current))
+ return -EINTR;
+ }
+ done = unused;
+ }
+ else
+ write_unlock_irq(&ztv->lock);
+
+ /* Yes! we got data! */
+ max = done->bpl * done->h;
+ if (count > max)
+ count = max;
+ if (copy_to_user((void*)buf, done->memadr, count))
+ count = -EFAULT;
+
+ /* keep the engine running */
+ done->status = FBUFFER_FREE;
+// zoran_cap(ztv,1);
+
+ /* tell listeners this buffer became free */
+ wake_up_interruptible(&ztv->grabq);
+
+ /* goodbye */
+ DEBUG(printk(CARD_DEBUG "zoran_read() returns %lu\n",CARD,count));
+ return count;
+}
+
+static
+long zoran_write(struct video_device* dev, const char* buf, unsigned long count, int nonblock)
+{
+ struct zoran *ztv = (struct zoran *)dev;
+ UNUSED(ztv); UNUSED(dev); UNUSED(buf); UNUSED(count); UNUSED(nonblock);
+ DEBUG(printk(CARD_DEBUG "zoran_write\n",CARD));
+ return -EINVAL;
+}
+
+#if LINUX_VERSION_CODE >= 0x020100
+static
+unsigned int zoran_poll(struct video_device *dev, struct file *file, poll_table *wait)
+{
+ struct zoran *ztv = (struct zoran *)dev;
+ struct vidinfo* item;
+ unsigned int mask = 0;
+
+ poll_wait(file, &ztv->grabq, wait);
+
+ for (item=ztv->grabinfo; item!=ztv->grabinfo+ZORAN_MAX_FBUFFERS; item++)
+ if (item->status == FBUFFER_DONE)
+ {
+ mask |= (POLLIN | POLLRDNORM);
+ break;
+ }
+
+ DEBUG(printk(CARD_DEBUG "zoran_poll()=%x\n",CARD,mask));
+
+ return mask;
+}
+#endif
+
+/* append a new clipregion to the vector of video_clips */
+static
+void new_clip(struct video_window* vw, struct video_clip* vcp, int x, int y, int w, int h)
+{
+ vcp[vw->clipcount].x = x;
+ vcp[vw->clipcount].y = y;
+ vcp[vw->clipcount].width = w;
+ vcp[vw->clipcount].height = h;
+ vw->clipcount++;
+}
+
+static
+int zoran_ioctl(struct video_device* dev, unsigned int cmd, void *arg)
+{
+ struct zoran* ztv = (struct zoran*)dev;
+
+ switch (cmd) {
+ case VIDIOCGCAP:
+ {
+ struct video_capability c;
+ DEBUG(printk(CARD_DEBUG "VIDIOCGCAP\n",CARD));
+
+ strcpy(c.name,ztv->video_dev.name);
+ c.type = VID_TYPE_CAPTURE|
+ VID_TYPE_OVERLAY|
+ VID_TYPE_CLIPPING|
+ VID_TYPE_FRAMERAM|
+ VID_TYPE_SCALES;
+ if (ztv->have_tuner)
+ c.type |= VID_TYPE_TUNER;
+ if (ztv->have_decoder) {
+ c.channels = ztv->card->video_inputs;
+ c.audios = ztv->card->audio_inputs;
+ } else
+ /* no decoder -> no channels */
+ c.channels = c.audios = 0;
+ c.maxwidth = 768;
+ c.maxheight = 576;
+ c.minwidth = 32;
+ c.minheight = 32;
+ if (copy_to_user(arg,&c,sizeof(c)))
+ return -EFAULT;
+ break;
+ }
+
+ case VIDIOCGCHAN:
+ {
+ struct video_channel v;
+ int mux;
+ if (copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "VIDIOCGCHAN(%d)\n",CARD,v.channel));
+ v.flags=VIDEO_VC_AUDIO
+#ifdef VIDEO_VC_NORM
+ |VIDEO_VC_NORM
+#endif
+ ;
+ v.tuners=0;
+ v.type=VIDEO_TYPE_CAMERA;
+#ifdef I_EXPECT_POSSIBLE_NORMS_IN_THE_API
+ v.norm=VIDEO_MODE_PAL|
+ VIDEO_MODE_NTSC|
+ VIDEO_MODE_SECAM;
+#else
+ v.norm=VIDEO_MODE_PAL;
+#endif
+ /* too many inputs? no decoder -> no channels */
+ if (!ztv->have_decoder || v.channel >= ztv->card->video_inputs)
+ return -EINVAL;
+
+ /* now determine the name of the channel */
+ mux = ztv->card->video_mux[v.channel];
+ if (mux & IS_TUNER) {
+ /* lets assume only one tuner, yes? */
+ strcpy(v.name,"Television");
+ v.type = VIDEO_TYPE_TV;
+ if (ztv->have_tuner) {
+ v.flags |= VIDEO_VC_TUNER;
+ v.tuners = 1;
+ }
+ }
+ else if (mux & IS_SVHS)
+ sprintf(v.name,"S-Video-%d",v.channel);
+ else
+ sprintf(v.name,"CVBS-%d",v.channel);
+
+ if (copy_to_user(arg,&v,sizeof(v)))
+ return -EFAULT;
+ break;
+ }
+ case VIDIOCSCHAN:
+ { /* set video channel */
+ struct video_channel v;
+ if (copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "VIDIOCSCHAN(%d,%d)\n",CARD,v.channel,v.norm));
+
+ /* too many inputs? no decoder -> no channels */
+ if (!ztv->have_decoder || v.channel >= ztv->card->video_inputs)
+ return -EINVAL;
+
+ if (v.norm != VIDEO_MODE_PAL &&
+ v.norm != VIDEO_MODE_NTSC &&
+ v.norm != VIDEO_MODE_SECAM &&
+ v.norm != VIDEO_MODE_AUTO)
+ return -EOPNOTSUPP;
+
+ /* make it happen, nr1! */
+ return zoran_muxsel(ztv,v.channel,v.norm);
+ }
+
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner v;
+ if (copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "VIDIOCGTUNER(%d)\n",CARD,v.tuner));
+
+ /* Only no or one tuner for now */
+ if (!ztv->have_tuner || v.tuner)
+ return -EINVAL;
+
+ strcpy(v.name,"Television");
+ v.rangelow = 0;
+ v.rangehigh = ~0;
+ v.flags = VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM;
+ v.mode = ztv->norm;
+ v.signal = 0xFFFF; /* unknown */
+
+ if (copy_to_user(arg,&v,sizeof(v)))
+ return -EFAULT;
+ break;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner v;
+ if (copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "VIDIOCSTUNER(%d,%d)\n",CARD,v.tuner,v.mode));
+
+ /* Only no or one tuner for now */
+ if (!ztv->have_tuner || v.tuner)
+ return -EINVAL;
+
+ /* and it only has certain valid modes */
+ if( v.mode != VIDEO_MODE_PAL &&
+ v.mode != VIDEO_MODE_NTSC &&
+ v.mode != VIDEO_MODE_SECAM)
+ return -EOPNOTSUPP;
+
+ /* engage! */
+ return zoran_muxsel(ztv,v.tuner,v.mode);
+ }
+
+ case VIDIOCGPICT:
+ {
+ struct video_picture p = ztv->picture;
+ DEBUG(printk(CARD_DEBUG "VIDIOCGPICT\n",CARD));
+ p.depth = ztv->depth;
+ switch (p.depth) {
+ case 8: p.palette=VIDEO_PALETTE_YUV422;
+ break;
+ case 15: p.palette=VIDEO_PALETTE_RGB555;
+ break;
+ case 16: p.palette=VIDEO_PALETTE_RGB565;
+ break;
+ case 24: p.palette=VIDEO_PALETTE_RGB24;
+ break;
+ case 32: p.palette=VIDEO_PALETTE_RGB32;
+ break;
+ }
+ if (copy_to_user(arg, &p, sizeof(p)))
+ return -EFAULT;
+ break;
+ }
+ case VIDIOCSPICT:
+ {
+ struct video_picture p;
+ if (copy_from_user(&p, arg,sizeof(p)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "VIDIOCSPICT(%d,%d,%d,%d,%d,%d,%d)\n",CARD,p.brightness,p.hue,p.colour,p.contrast,p.whiteness,p.depth,p.palette));
+
+ /* depth must match with framebuffer */
+ if (p.depth != ztv->depth)
+ return -EINVAL;
+
+ /* check if palette matches this bpp */
+ if (p.palette>NRPALETTES ||
+ palette2fmt[p.palette].bpp != ztv->overinfo.bpp)
+ return -EINVAL;
+
+ write_lock_irq(&ztv->lock);
+ ztv->overinfo.format = p.palette;
+ ztv->picture = p;
+ write_unlock_irq(&ztv->lock);
+
+ /* tell the decoder */
+ i2c_control_device(&ztv->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_PICTURE, &p);
+ break;
+ }
+
+ case VIDIOCGWIN:
+ {
+ struct video_window vw;
+ DEBUG(printk(CARD_DEBUG "VIDIOCGWIN\n",CARD));
+ read_lock(&ztv->lock);
+ vw.x = ztv->overinfo.x;
+ vw.y = ztv->overinfo.y;
+ vw.width = ztv->overinfo.w;
+ vw.height = ztv->overinfo.h;
+ vw.chromakey= 0;
+ vw.flags = 0;
+ if (ztv->vidInterlace)
+ vw.flags|=VIDEO_WINDOW_INTERLACE;
+ read_unlock(&ztv->lock);
+ if (copy_to_user(arg,&vw,sizeof(vw)))
+ return -EFAULT;
+ break;
+ }
+ case VIDIOCSWIN:
+ {
+ struct video_window vw;
+ struct video_clip *vcp;
+ int on;
+ if (copy_from_user(&vw,arg,sizeof(vw)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "VIDIOCSWIN(%d,%d,%d,%d,%x,%d)\n",CARD,vw.x,vw.y,vw.width,vw.height,vw.flags,vw.clipcount));
+
+ if (vw.flags)
+ return -EINVAL;
+
+ if (vw.clipcount>256)
+ return -EDOM; /* Too many! */
+
+ /*
+ * Do any clips.
+ */
+ vcp = vmalloc(sizeof(struct video_clip)*(vw.clipcount+4));
+ if (vcp==NULL)
+ return -ENOMEM;
+ if (vw.clipcount && copy_from_user(vcp,vw.clips,sizeof(struct video_clip)*vw.clipcount))
+ return -EFAULT;
+
+ on = ztv->running;
+ if (on)
+ zoran_cap(ztv, 0);
+
+ /*
+ * strange, it seems xawtv sometimes calls us with 0
+ * width and/or height. Ignore these values
+ */
+ if (vw.x == 0)
+ vw.x = ztv->overinfo.x;
+ if (vw.y == 0)
+ vw.y = ztv->overinfo.y;
+
+ /* by now we are committed to the new data... */
+ write_lock_irq(&ztv->lock);
+ ztv->overinfo.x = vw.x;
+ ztv->overinfo.y = vw.y;
+ ztv->overinfo.w = vw.width;
+ ztv->overinfo.h = vw.height;
+ write_unlock_irq(&ztv->lock);
+
+ /*
+ * Impose display clips
+ */
+ if (vw.x+vw.width > ztv->swidth)
+ new_clip(&vw, vcp, ztv->swidth-vw.x, 0, vw.width-1, vw.height-1);
+ if (vw.y+vw.height > ztv->sheight)
+ new_clip(&vw, vcp, 0, ztv->sheight-vw.y, vw.width-1, vw.height-1);
+
+ /* built the requested clipping zones */
+ zoran_set_geo(ztv, &ztv->overinfo);
+ zoran_built_overlay(ztv, vw.clipcount, vcp);
+ vfree(vcp);
+
+ /* if we were on, restart the video engine */
+ if (on)
+ zoran_cap(ztv, 1);
+ break;
+ }
+
+ case VIDIOCCAPTURE:
+ {
+ int v;
+ get_user_ret(v,(int*)arg, -EFAULT);
+ DEBUG(printk(CARD_DEBUG "VIDIOCCAPTURE(%d)\n",CARD,v));
+
+ if (v==0) {
+ clear_bit(STATE_OVERLAY, &ztv->state);
+ zoran_cap(ztv, 1);
+ }
+ else {
+ /* is VIDIOCSFBUF, VIDIOCSWIN done? */
+ if (ztv->overinfo.busadr==0 || ztv->overinfo.w==0 || ztv->overinfo.h==0)
+ return -EINVAL;
+
+ set_bit(STATE_OVERLAY, &ztv->state);
+ zoran_cap(ztv, 1);
+ }
+ break;
+ }
+
+ case VIDIOCGFBUF:
+ {
+ struct video_buffer v;
+ DEBUG(printk(CARD_DEBUG "VIDIOCGFBUF\n",CARD));
+ read_lock(&ztv->lock);
+ v.base = (void *)ztv->overinfo.busadr;
+ v.height = ztv->sheight;
+ v.width = ztv->swidth;
+ v.depth = ztv->depth;
+ v.bytesperline = ztv->overinfo.bpl;
+ read_unlock(&ztv->lock);
+ if(copy_to_user(arg, &v,sizeof(v)))
+ return -EFAULT;
+ break;
+ }
+ case VIDIOCSFBUF:
+ {
+ struct video_buffer v;
+#if LINUX_VERSION_CODE >= 0x020100
+ if(!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_ADMIN))
+#else
+ if(!suser())
+#endif
+ return -EPERM;
+ if (copy_from_user(&v, arg,sizeof(v)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "VIDIOCSFBUF(%p,%d,%d,%d,%d)\n",CARD,v.base, v.width,v.height,v.depth,v.bytesperline));
+
+ if (v.depth!=15 && v.depth!=16 && v.depth!=24 && v.depth!=32)
+ return -EINVAL;
+ if (v.bytesperline<1)
+ return -EINVAL;
+ if (ztv->running)
+ return -EBUSY;
+ write_lock_irq(&ztv->lock);
+ ztv->overinfo.busadr = (ulong)v.base;
+ ztv->sheight = v.height;
+ ztv->swidth = v.width;
+ ztv->depth = v.depth; /* bits per pixel */
+ ztv->overinfo.bpp = ((v.depth+1)&0x38)/8;/* bytes per pixel */
+ ztv->overinfo.bpl = v.bytesperline; /* bytes per line */
+ write_unlock_irq(&ztv->lock);
+ break;
+ }
+
+ case VIDIOCKEY:
+ {
+ /* Will be handled higher up .. */
+ break;
+ }
+
+ case VIDIOCSYNC:
+ {
+ int i;
+ get_user_ret(i,(int*)arg, -EFAULT);
+ DEBUG(printk(CARD_DEBUG "VIDEOCSYNC(%d)\n",CARD,i));
+ if (i<0 || i>ZORAN_MAX_FBUFFERS)
+ return -EINVAL;
+ switch (ztv->grabinfo[i].status) {
+ case FBUFFER_FREE:
+ return -EINVAL;
+ case FBUFFER_BUSY:
+ /* wait till this buffer gets grabbed */
+ while (ztv->grabinfo[i].status == FBUFFER_BUSY) {
+ interruptible_sleep_on(&ztv->grabq);
+ /* see if a signal did it */
+ if (signal_pending(current))
+ return -EINTR;
+ }
+ /* don't fall through; a DONE buffer is not UNUSED */
+ break;
+ case FBUFFER_DONE:
+ ztv->grabinfo[i].status = FBUFFER_FREE;
+ /* tell ppl we have a spare buffer */
+ wake_up_interruptible(&ztv->grabq);
+ break;
+ }
+ DEBUG(printk(CARD_DEBUG "VIDEOCSYNC(%d) returns\n",CARD,i));
+ break;
+ }
+
+ case VIDIOCMCAPTURE:
+ {
+ struct video_mmap vm;
+ struct vidinfo* frame;
+ if (copy_from_user(&vm,arg,sizeof(vm)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "VIDIOCMCAPTURE(%d,(%d,%d),%d)\n",CARD,vm.frame,vm.width,vm.height,vm.format));
+ if (vm.frame<0 || vm.frame>ZORAN_MAX_FBUFFERS ||
+ vm.width<32 || vm.width>768 ||
+ vm.height<32 || vm.height>576 ||
+ vm.format>NRPALETTES ||
+ palette2fmt[vm.format].mode == 0)
+ return -EINVAL;
+
+ /* we are allowed to take over UNUSED and DONE buffers */
+ frame = &ztv->grabinfo[vm.frame];
+ if (frame->status == FBUFFER_BUSY)
+ return -EBUSY;
+
+ /* setup the other parameters if they are given */
+ write_lock_irq(&ztv->lock);
+ frame->w = vm.width;
+ frame->h = vm.height;
+ frame->format = vm.format;
+ frame->bpp = palette2fmt[frame->format].bpp;
+ frame->bpl = frame->w*frame->bpp;
+ frame->status = FBUFFER_BUSY;
+ frame->next = 0;
+ { /* add to tail of queue */
+ struct vidinfo* oldframe = ztv->workqueue;
+ if (!oldframe) ztv->workqueue = frame;
+ else {
+ while (oldframe->next) oldframe = oldframe->next;
+ oldframe->next = frame;
+ }
+ }
+ write_unlock_irq(&ztv->lock);
+ zoran_cap(ztv, 1);
+ break;
+ }
+
+ case VIDIOCGMBUF:
+ {
+ struct video_mbuf mb;
+ int i;
+ DEBUG(printk(CARD_DEBUG "VIDIOCGMBUF\n",CARD));
+ mb.size = ZORAN_MAX_FBUFSIZE;
+ mb.frames = ZORAN_MAX_FBUFFERS;
+ for (i=0; i<ZORAN_MAX_FBUFFERS; i++)
+ mb.offsets[i] = i*ZORAN_MAX_FBUFFER;
+ if(copy_to_user(arg, &mb,sizeof(mb)))
+ return -EFAULT;
+ break;
+ }
+
+ case VIDIOCGUNIT:
+ {
+ struct video_unit vu;
+ DEBUG(printk(CARD_DEBUG "VIDIOCGUNIT\n",CARD));
+ vu.video = ztv->video_dev.minor;
+ vu.vbi = ztv->vbi_dev.minor;
+ vu.radio = VIDEO_NO_UNIT;
+ vu.audio = VIDEO_NO_UNIT;
+ vu.teletext = VIDEO_NO_UNIT;
+ if(copy_to_user(arg, &vu,sizeof(vu)))
+ return -EFAULT;
+ break;
+ }
+
+ case VIDIOCGFREQ:
+ {
+ unsigned long v = ztv->tuner_freq;
+ if (copy_to_user(arg,&v,sizeof(v)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "VIDIOCGFREQ\n",CARD));
+ break;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long v;
+ if (copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "VIDIOCSFREQ\n",CARD));
+
+ if (ztv->have_tuner) {
+ int fixme = v;
+ if (i2c_control_device(&(ztv->i2c), I2C_DRIVERID_TUNER, TUNER_SET_TVFREQ, &fixme) < 0)
+ return -EAGAIN;
+ }
+ ztv->tuner_freq = v;
+ break;
+ }
+
+ /* Why isn't this in the API?
+ * And why doesn't it take a buffer number?
+ case BTTV_FIELDNR:
+ {
+ unsigned long v = ztv->lastfieldnr;
+ if (copy_to_user(arg,&v,sizeof(v)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "BTTV_FIELDNR\n",CARD));
+ break;
+ }
+ */
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static
+int zoran_mmap(struct video_device* dev, const char* adr, unsigned long size)
+{
+ struct zoran* ztv = (struct zoran*)dev;
+ unsigned long start = (unsigned long)adr;
+ unsigned long pos;
+
+ DEBUG(printk(CARD_DEBUG "zoran_mmap(0x%p,%ld)\n",CARD,adr,size));
+
+ /* sanity checks */
+ if (size > ZORAN_MAX_FBUFSIZE || !ztv->fbuffer)
+ return -EINVAL;
+
+ /* start mapping the whole shabang to user memory */
+ pos = (unsigned long)ztv->fbuffer;
+ while (size>0) {
+ unsigned long page = virt_to_phys((void*)pos);
+ if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED))
+ return -EAGAIN;
+ start += PAGE_SIZE;
+ pos += PAGE_SIZE;
+ size -= PAGE_SIZE;
+ }
+ return 0;
+}
+
+static struct video_device zr36120_template=
+{
+ "UNSET",
+ VID_TYPE_TUNER|VID_TYPE_CAPTURE|VID_TYPE_OVERLAY,
+ VID_HARDWARE_ZR36120,
+
+ zoran_open,
+ zoran_close,
+ zoran_read,
+ zoran_write,
+#if LINUX_VERSION_CODE >= 0x020100
+ zoran_poll, /* poll */
+#endif
+ zoran_ioctl,
+ zoran_mmap,
+ NULL, /* initialize */
+ NULL,
+ 0,
+ -1
+};
+
+static
+int vbi_open(struct video_device *dev, int flags)
+{
+ struct zoran *ztv = (struct zoran*)dev->priv;
+ struct vidinfo* item;
+
+ DEBUG(printk(CARD_DEBUG "vbi_open(dev,%d)\n",CARD,flags));
+
+ /*
+ * During VBI device open, we continiously grab VBI-like
+ * data in the vbi buffer when we have nothing to do.
+ * Only when there is an explicit request for VBI data
+ * (read call) we /force/ a read.
+ */
+
+ /* allocate buffers */
+ for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++)
+ {
+ item->status = FBUFFER_FREE;
+
+ /* alloc */
+ if (!item->memadr) {
+ item->memadr = bmalloc(ZORAN_VBI_BUFSIZE);
+ if (!item->memadr) {
+ /* could not get a buffer, bail out */
+ while (item != ztv->readinfo) {
+ item--;
+ bfree(item->memadr, ZORAN_VBI_BUFSIZE);
+ item->memadr = 0;
+ item->busadr = 0;
+ }
+ return -ENOBUFS;
+ }
+ }
+
+ /* determine the DMAable address */
+ item->busadr = virt_to_bus(item->memadr);
+ }
+
+ /* do the common part of all open's */
+ zoran_common_open(ztv, flags);
+
+ set_bit(STATE_VBI, &ztv->state);
+ /* start read-ahead */
+ zoran_cap(ztv, 1);
+
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static
+void vbi_close(struct video_device *dev)
+{
+ struct zoran *ztv = (struct zoran*)dev->priv;
+ struct vidinfo* item;
+
+ DEBUG(printk(CARD_DEBUG "vbi_close(dev)\n",CARD));
+
+ /* driver specific closure */
+ clear_bit(STATE_VBI, &ztv->state);
+
+ zoran_common_close(ztv);
+
+ /*
+ * This is sucky but right now I can't find a good way to
+ * be sure its safe to free the buffer. We wait 5-6 fields
+ * which is more than sufficient to be sure.
+ */
+ current->state = TASK_UNINTERRUPTIBLE;
+ schedule_timeout(HZ/10); /* Wait 1/10th of a second */
+
+ for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++)
+ {
+ if (item->memadr)
+ bfree(item->memadr, ZORAN_VBI_BUFSIZE);
+ item->memadr = 0;
+ }
+
+ MOD_DEC_USE_COUNT;
+}
+
+/*
+ * This read function could be used reentrant in a SMP situation.
+ *
+ * This is made possible by the spinlock which is kept till we
+ * found and marked a buffer for our own use. The lock must
+ * be released as soon as possible to prevent lock contention.
+ */
+static
+long vbi_read(struct video_device* dev, char* buf, unsigned long count, int nonblock)
+{
+ struct zoran *ztv = (struct zoran*)dev->priv;
+ unsigned long max;
+ struct vidinfo* unused = 0;
+ struct vidinfo* done = 0;
+
+ DEBUG(printk(CARD_DEBUG "vbi_read(0x%p,%ld,%d)\n",CARD,buf,count,nonblock));
+
+ /* find ourself a free or completed buffer */
+ for (;;) {
+ struct vidinfo* item;
+
+ write_lock_irq(&ztv->lock);
+ for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++) {
+ if (!unused && item->status == FBUFFER_FREE)
+ unused = item;
+ if (!done && item->status == FBUFFER_DONE)
+ done = item;
+ }
+ if (done || unused)
+ break;
+
+ /* no more free buffers, wait for them. */
+ write_unlock_irq(&ztv->lock);
+ if (nonblock)
+ return -EWOULDBLOCK;
+ interruptible_sleep_on(&ztv->vbiq);
+ if (signal_pending(current))
+ return -EINTR;
+ }
+
+ /* Do we have 'ready' data? */
+ if (!done) {
+ /* no? than this will take a while... */
+ if (nonblock) {
+ write_unlock_irq(&ztv->lock);
+ return -EWOULDBLOCK;
+ }
+
+ /* mark the unused buffer as wanted */
+ unused->status = FBUFFER_BUSY;
+ unused->next = 0;
+ { /* add to tail of queue */
+ struct vidinfo* oldframe = ztv->workqueue;
+ if (!oldframe) ztv->workqueue = unused;
+ else {
+ while (oldframe->next) oldframe = oldframe->next;
+ oldframe->next = unused;
+ }
+ }
+ write_unlock_irq(&ztv->lock);
+
+ /* tell the state machine we want it filled /NOW/ */
+ zoran_cap(ztv, 1);
+
+ /* wait till this buffer gets grabbed */
+ while (unused->status == FBUFFER_BUSY) {
+ interruptible_sleep_on(&ztv->vbiq);
+ /* see if a signal did it */
+ if (signal_pending(current))
+ return -EINTR;
+ }
+ done = unused;
+ }
+ else
+ write_unlock_irq(&ztv->lock);
+
+ /* Yes! we got data! */
+ max = done->bpl * -done->h;
+ if (count > max)
+ count = max;
+
+ /* check if the user gave us enough room to write the data */
+ if (!access_ok(VERIFY_WRITE, buf, count)) {
+ count = -EFAULT;
+ goto out;
+ }
+
+ /*
+ * Now transform/strip the data from YUV to Y-only
+ * NB. Assume the Y is in the LSB of the YUV data.
+ */
+ {
+ unsigned char* optr = buf;
+ unsigned char* eptr = buf+count;
+
+ /* are we beeing accessed from an old driver? */
+ if (count == 2*19*2048) {
+ /*
+ * Extreme HACK, old VBI programs expect 2048 points
+ * of data, and we only got 864 orso. Double each
+ * datapoint and clear the rest of the line.
+ * This way we have appear to have a
+ * sample_frequency of 29.5 Mc.
+ */
+ int x,y;
+ unsigned char* iptr = done->memadr+1;
+ for (y=done->h; optr<eptr && y<0; y++)
+ {
+ /* copy to doubled data to userland */
+ for (x=0; optr+1<eptr && x<-done->w; x++)
+ {
+ unsigned char a = iptr[x*2];
+ *optr++ = a;
+ *optr++ = a;
+ }
+ /* and clear the rest of the line */
+ for (x*=2; optr<eptr && x<done->bpl; x++)
+ *optr++ = 0;
+ /* next line */
+ iptr += done->bpl;
+ }
+ }
+ else {
+ /*
+ * Other (probably newer) programs asked
+ * us what geometry we are using, and are
+ * reading the correct size.
+ */
+ int x,y;
+ unsigned char* iptr = done->memadr+1;
+ for (y=done->h; optr<eptr && y<0; y++)
+ {
+ /* copy to doubled data to userland */
+ for (x=0; optr<eptr && x<-done->w; x++)
+ *optr++ = iptr[x*2];
+ /* and clear the rest of the line */
+ for (;optr<eptr && x<done->bpl; x++)
+ *optr++ = 0;
+ /* next line */
+ iptr += done->bpl;
+ }
+ }
+
+ /* API compliance:
+ * place the framenumber (half fieldnr) in the last long
+ */
+ ((ulong*)eptr)[-1] = done->fieldnr/2;
+ }
+
+ /* keep the engine running */
+ done->status = FBUFFER_FREE;
+ zoran_cap(ztv, 1);
+
+ /* tell listeners this buffer just became free */
+ wake_up_interruptible(&ztv->vbiq);
+
+ /* goodbye */
+out:
+ DEBUG(printk(CARD_DEBUG "vbi_read() returns %lu\n",CARD,count));
+ return count;
+}
+
+#if LINUX_VERSION_CODE >= 0x020100
+static
+unsigned int vbi_poll(struct video_device *dev, struct file *file, poll_table *wait)
+{
+ struct zoran *ztv = (struct zoran*)dev->priv;
+ struct vidinfo* item;
+ unsigned int mask = 0;
+
+ poll_wait(file, &ztv->vbiq, wait);
+
+ for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++)
+ if (item->status == FBUFFER_DONE)
+ {
+ mask |= (POLLIN | POLLRDNORM);
+ break;
+ }
+
+ DEBUG(printk(CARD_DEBUG "vbi_poll()=%x\n",CARD,mask));
+
+ return mask;
+}
+#endif
+
+static
+int vbi_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
+{
+ struct zoran* ztv = (struct zoran*)dev->priv;
+
+ switch (cmd) {
+ case VIDIOCGVBIFMT:
+ {
+ struct vbi_format f;
+ DEBUG(printk(CARD_DEBUG "VIDIOCGVBIINFO\n",CARD));
+ f.sampling_rate = 14750000UL;
+ f.samples_per_line = -ztv->readinfo[0].w;
+ f.sample_format = VIDEO_PALETTE_RAW;
+ f.start[0] = f.start[1] = ztv->readinfo[0].y;
+ f.start[1] += 312;
+ f.count[0] = f.count[1] = -ztv->readinfo[0].h;
+ f.flags = VBI_INTERLACED;
+ if (copy_to_user(arg,&f,sizeof(f)))
+ return -EFAULT;
+ break;
+ }
+ case VIDIOCSVBIFMT:
+ {
+ struct vbi_format f;
+ int i;
+ if (copy_from_user(&f, arg,sizeof(f)))
+ return -EFAULT;
+ DEBUG(printk(CARD_DEBUG "VIDIOCSVBIINFO(%d,%d,%d,%d,%d,%d,%d,%x)\n",CARD,f.sampling_rate,f.samples_per_line,f.sample_format,f.start[0],f.start[1],f.count[0],f.count[1],f.flags));
+
+ /* lots of parameters are fixed... (PAL) */
+ if (f.sampling_rate != 14750000UL ||
+ f.samples_per_line > 864 ||
+ f.sample_format != VIDEO_PALETTE_RAW ||
+ f.start[0] < 0 ||
+ f.start[0] != f.start[1]-312 ||
+ f.count[0] != f.count[1] ||
+ f.start[0]+f.count[0] >= 288 ||
+ f.flags != VBI_INTERLACED)
+ return -EINVAL;
+
+ write_lock_irq(&ztv->lock);
+ ztv->readinfo[0].y = f.start[0];
+ ztv->readinfo[0].w = -f.samples_per_line;
+ ztv->readinfo[0].h = -f.count[0];
+ ztv->readinfo[0].bpl = f.samples_per_line*ztv->readinfo[0].bpp;
+ for (i=1; i<ZORAN_VBI_BUFFERS; i++)
+ ztv->readinfo[i] = ztv->readinfo[i];
+ write_unlock_irq(&ztv->lock);
+ break;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static struct video_device vbi_template=
+{
+ "UNSET",
+ VID_TYPE_CAPTURE|VID_TYPE_TELETEXT,
+ VID_HARDWARE_ZR36120,
+
+ vbi_open,
+ vbi_close,
+ vbi_read,
+ zoran_write,
+#if LINUX_VERSION_CODE >= 0x020100
+ vbi_poll, /* poll */
+#endif
+ vbi_ioctl,
+ NULL, /* no mmap */
+ NULL, /* no initialize */
+ NULL, /* priv */
+ 0, /* busy */
+ -1 /* minor */
+};
+
+/*
+ * Scan for a Zoran chip, request the irq and map the io memory
+ */
+static
+int __init find_zoran(void)
+{
+ int result;
+ struct zoran *ztv;
+ struct pci_dev *dev = NULL;
+ unsigned char revision;
+ int zoran_num=0;
+
+ while ((dev = pci_find_device(PCI_VENDOR_ID_ZORAN,PCI_DEVICE_ID_ZORAN_36120, dev)))
+ {
+ /* Ok, a ZR36120/ZR36125 found! */
+ ztv = &zorans[zoran_num];
+ ztv->dev = dev;
+
+ if (pci_enable_device(dev))
+ return -EIO;
+
+ pci_read_config_byte(dev, PCI_CLASS_REVISION, &revision);
+ printk(KERN_INFO "zoran: Zoran %x (rev %d) ",
+ dev->device, revision);
+ printk("bus: %d, devfn: %d, irq: %d, ",
+ dev->bus->number, dev->devfn, dev->irq);
+ printk("memory: 0x%08lx.\n", ztv->zoran_adr);
+
+ ztv->zoran_mem = ioremap(ztv->zoran_adr, 0x1000);
+ DEBUG(printk(KERN_DEBUG "zoran: mapped-memory at 0x%p\n",ztv->zoran_mem));
+
+ result = request_irq(dev->irq, zoran_irq,
+ SA_SHIRQ|SA_INTERRUPT,"zoran",(void *)ztv);
+ if (result==-EINVAL)
+ {
+ printk(KERN_ERR "zoran: Bad irq number or handler\n");
+ return -EINVAL;
+ }
+ if (result==-EBUSY)
+ printk(KERN_ERR "zoran: IRQ %d busy, change your PnP config in BIOS\n",dev->irq);
+ if (result < 0)
+ return result;
+
+ /* Enable bus-mastering */
+ pci_set_master(dev);
+
+ zoran_num++;
+ }
+ if(zoran_num)
+ printk(KERN_INFO "zoran: %d Zoran card(s) found.\n",zoran_num);
+ return zoran_num;
+}
+
+static
+int __init init_zoran(int card)
+{
+ struct zoran *ztv = &zorans[card];
+ int i;
+
+ /* if the given cardtype valid? */
+ if (cardtype[card]>=NRTVCARDS) {
+ printk(KERN_INFO "invalid cardtype(%d) detected\n",cardtype[card]);
+ return -1;
+ }
+
+ /* reset the zoran */
+ zrand(~ZORAN_PCI_SOFTRESET,ZORAN_PCI);
+ udelay(10);
+ zror(ZORAN_PCI_SOFTRESET,ZORAN_PCI);
+ udelay(10);
+
+ /* zoran chip specific details */
+ ztv->card = tvcards+cardtype[card]; /* point to the selected card */
+ ztv->norm = 0; /* PAL */
+ ztv->tuner_freq = 0;
+
+ /* videocard details */
+ ztv->swidth = 800;
+ ztv->sheight = 600;
+ ztv->depth = 16;
+
+ /* State details */
+ ztv->fbuffer = 0;
+ ztv->overinfo.kindof = FBUFFER_OVERLAY;
+ ztv->overinfo.status = FBUFFER_FREE;
+ ztv->overinfo.x = 0;
+ ztv->overinfo.y = 0;
+ ztv->overinfo.w = 768; /* 640 */
+ ztv->overinfo.h = 576; /* 480 */
+ ztv->overinfo.format = VIDEO_PALETTE_RGB565;
+ ztv->overinfo.bpp = palette2fmt[ztv->overinfo.format].bpp;
+ ztv->overinfo.bpl = ztv->overinfo.bpp*ztv->swidth;
+ ztv->overinfo.busadr = 0;
+ ztv->overinfo.memadr = 0;
+ ztv->overinfo.overlay = 0;
+ for (i=0; i<ZORAN_MAX_FBUFFERS; i++) {
+ ztv->grabinfo[i] = ztv->overinfo;
+ ztv->grabinfo[i].kindof = FBUFFER_GRAB;
+ }
+ init_waitqueue_head(&ztv->grabq);
+
+ /* VBI details */
+ ztv->readinfo[0] = ztv->overinfo;
+ ztv->readinfo[0].kindof = FBUFFER_VBI;
+ ztv->readinfo[0].w = -864;
+ ztv->readinfo[0].h = -38;
+ ztv->readinfo[0].format = VIDEO_PALETTE_YUV422;
+ ztv->readinfo[0].bpp = palette2fmt[ztv->readinfo[0].format].bpp;
+ ztv->readinfo[0].bpl = 1024*ztv->readinfo[0].bpp;
+ for (i=1; i<ZORAN_VBI_BUFFERS; i++)
+ ztv->readinfo[i] = ztv->readinfo[0];
+ init_waitqueue_head(&ztv->vbiq);
+
+ /* maintenance data */
+ ztv->have_decoder = 0;
+ ztv->have_tuner = 0;
+ ztv->tuner_type = 0;
+ ztv->running = 0;
+ ztv->users = 0;
+ ztv->lock = RW_LOCK_UNLOCKED;
+ ztv->workqueue = 0;
+ ztv->fieldnr = 0;
+ ztv->lastfieldnr = 0;
+
+ if (triton1)
+ zrand(~ZORAN_VDC_TRICOM, ZORAN_VDC);
+
+ /* external FL determines TOP frame */
+ zror(ZORAN_VFEC_EXTFL, ZORAN_VFEC);
+
+ /* set HSpol */
+ if (ztv->card->hsync_pos)
+ zrwrite(ZORAN_VFEH_HSPOL, ZORAN_VFEH);
+ /* set VSpol */
+ if (ztv->card->vsync_pos)
+ zrwrite(ZORAN_VFEV_VSPOL, ZORAN_VFEV);
+
+ /* Set the proper General Purpuse register bits */
+ /* implicit: no softreset, 0 waitstates */
+ zrwrite(ZORAN_PCI_SOFTRESET|(ztv->card->gpdir<<0),ZORAN_PCI);
+ /* implicit: 3 duration and recovery PCI clocks on guest 0-3 */
+ zrwrite(ztv->card->gpval<<24,ZORAN_GUEST);
+
+ /* clear interrupt status */
+ zrwrite(~0, ZORAN_ISR);
+
+ /*
+ * i2c template
+ */
+ ztv->i2c = zoran_i2c_bus_template;
+ sprintf(ztv->i2c.name,"zoran-%d",card);
+ ztv->i2c.data = ztv;
+
+ /*
+ * Now add the template and register the device unit
+ */
+ ztv->video_dev = zr36120_template;
+ strcpy(ztv->video_dev.name, ztv->i2c.name);
+ ztv->video_dev.priv = ztv;
+ if (video_register_device(&ztv->video_dev, VFL_TYPE_GRABBER) < 0)
+ return -1;
+
+ ztv->vbi_dev = vbi_template;
+ strcpy(ztv->vbi_dev.name, ztv->i2c.name);
+ ztv->vbi_dev.priv = ztv;
+ if (video_register_device(&ztv->vbi_dev, VFL_TYPE_VBI) < 0) {
+ video_unregister_device(&ztv->video_dev);
+ return -1;
+ }
+ i2c_register_bus(&ztv->i2c);
+
+ /* set interrupt mask - the PIN enable will be set later */
+ zrwrite(ZORAN_ICR_GIRQ0|ZORAN_ICR_GIRQ1|ZORAN_ICR_CODE, ZORAN_ICR);
+
+ printk(KERN_INFO "%s: installed %s\n",ztv->i2c.name,ztv->card->name);
+ return 0;
+}
+
+static
+void __exit release_zoran(int max)
+{
+ struct zoran *ztv;
+ int i;
+
+ for (i=0;i<max; i++)
+ {
+ ztv = &zorans[i];
+
+ /* turn off all capturing, DMA and IRQs */
+ /* reset the zoran */
+ zrand(~ZORAN_PCI_SOFTRESET,ZORAN_PCI);
+ udelay(10);
+ zror(ZORAN_PCI_SOFTRESET,ZORAN_PCI);
+ udelay(10);
+
+ /* first disable interrupts before unmapping the memory! */
+ zrwrite(0, ZORAN_ICR);
+ zrwrite(0xffffffffUL,ZORAN_ISR);
+
+ /* free it */
+ free_irq(ztv->dev->irq,ztv);
+
+ /* unregister i2c_bus */
+ i2c_unregister_bus((&ztv->i2c));
+
+ /* unmap and free memory */
+ if (ztv->zoran_mem)
+ iounmap(ztv->zoran_mem);
+
+ video_unregister_device(&ztv->video_dev);
+ video_unregister_device(&ztv->vbi_dev);
+ }
+}
+
+void __exit zr36120_exit(void)
+{
+ release_zoran(zoran_cards);
+}
+
+int __init zr36120_init(void)
+{
+ int card;
+
+ handle_chipset();
+ zoran_cards = find_zoran();
+ if (zoran_cards<0)
+ /* no cards found, no need for a driver */
+ return -EIO;
+
+ /* initialize Zorans */
+ for (card=0; card<zoran_cards; card++) {
+ if (init_zoran(card)<0) {
+ /* only release the zorans we have registered */
+ release_zoran(card);
+ return -EIO;
+ }
+ }
+ return 0;
+}
+
+module_init(zr36120_init);
+module_exit(zr36120_exit);
diff --git a/drivers/media/video/zr36120.h b/drivers/media/video/zr36120.h
new file mode 100644
index 000000000..e500de10d
--- /dev/null
+++ b/drivers/media/video/zr36120.h
@@ -0,0 +1,279 @@
+/*
+ zr36120.h - Zoran 36120/36125 based framegrabbers
+
+ Copyright (C) 1998-1999 Pauline Middelink (middelin@polyware.nl)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef _ZR36120_H
+#define _ZR36120_H
+
+#ifdef __KERNEL__
+
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include <linux/i2c-old.h>
+#include <linux/videodev.h>
+
+#include <asm/io.h>
+
+/*
+ * Debug macro's, place an x behind the ) for actual debug-compilation
+ * E.g. #define DEBUG(x...) x
+ */
+#define DEBUG(x...) /* Debug driver */
+#define IDEBUG(x...) /* Debug interrupt handler */
+#define PDEBUG 0 /* Debug PCI writes */
+
+/* defined in zr36120_i2c */
+extern struct i2c_bus zoran_i2c_bus_template;
+
+#define ZORAN_MAX_FBUFFERS 2
+#define ZORAN_MAX_FBUFFER (768*576*2)
+#define ZORAN_MAX_FBUFSIZE (ZORAN_MAX_FBUFFERS*ZORAN_MAX_FBUFFER)
+
+#define ZORAN_VBI_BUFFERS 2
+#define ZORAN_VBI_BUFSIZE (22*1024*2)
+
+struct tvcard {
+ char* name; /* name of the cardtype */
+ int video_inputs; /* number of channels defined in video_mux */
+ int audio_inputs; /* number of channels defined in audio_mux */
+ __u32 swapi2c:1, /* need to swap i2c wires SDA/SCL? */
+ usegirq1:1, /* VSYNC at GIRQ1 instead of GIRQ0? */
+ vsync_pos:1, /* positive VSYNC signal? */
+ hsync_pos:1, /* positive HSYNC signal? */
+ gpdir:8, /* General Purpose Direction register */
+ gpval:8; /* General Purpose Value register */
+ int video_mux[6]; /* mapping channel number to physical input */
+#define IS_TUNER 0x80
+#define IS_SVHS 0x40
+#define CHANNEL_MASK 0x3F
+ int audio_mux[6]; /* mapping channel number to physical input */
+};
+#define TUNER(x) ((x)|IS_TUNER)
+#define SVHS(x) ((x)|IS_SVHS)
+
+struct vidinfo {
+ struct vidinfo* next; /* next active buffer */
+ uint kindof;
+#define FBUFFER_OVERLAY 0
+#define FBUFFER_GRAB 1
+#define FBUFFER_VBI 2
+ uint status;
+#define FBUFFER_FREE 0
+#define FBUFFER_BUSY 1
+#define FBUFFER_DONE 2
+ ulong fieldnr; /* # of field, not framer! */
+ uint x,y;
+ int w,h; /* w,h can be negative! */
+ uint format; /* index in palette2fmt[] */
+ uint bpp; /* lookup from palette2fmt[] */
+ uint bpl; /* calc: width * bpp */
+ ulong busadr; /* bus addr for DMA engine */
+ char* memadr; /* kernel addr for making copies */
+ ulong* overlay; /* kernel addr of overlay mask */
+};
+
+struct zoran
+{
+ struct video_device video_dev;
+#define CARD_DEBUG KERN_DEBUG "%s(%lu): "
+#define CARD_INFO KERN_INFO "%s(%lu): "
+#define CARD_ERR KERN_ERR "%s(%lu): "
+#define CARD ztv->video_dev.name,ztv->fieldnr
+
+ /* zoran chip specific details */
+ struct i2c_bus i2c; /* i2c registration data */
+ struct pci_dev* dev; /* ptr to PCI device */
+ ulong zoran_adr; /* bus address of IO memory */
+ char* zoran_mem; /* kernel address of IO memory */
+ struct tvcard* card; /* the cardtype */
+ uint norm; /* 0=PAL, 1=NTSC, 2=SECAM */
+ uint tuner_freq; /* Current freq in kHz */
+ struct video_picture picture; /* Current picture params */
+
+ /* videocard details */
+ uint swidth; /* screen width */
+ uint sheight; /* screen height */
+ uint depth; /* depth in bits */
+
+ /* State details */
+ char* fbuffer; /* framebuffers for mmap */
+ struct vidinfo overinfo; /* overlay data */
+ struct vidinfo grabinfo[ZORAN_MAX_FBUFFERS]; /* grabbing data*/
+ wait_queue_head_t grabq; /* grabbers queue */
+
+ /* VBI details */
+ struct video_device vbi_dev;
+ struct vidinfo readinfo[2]; /* VBI data - flip buffers */
+ wait_queue_head_t vbiq; /* vbi queue */
+
+ /* maintenance data */
+ int have_decoder; /* did we detect a mux? */
+ int have_tuner; /* did we detect a tuner? */
+ int users; /* howmany video/vbi open? */
+ int tuner_type; /* tuner type, when found */
+ int running; /* are we rolling? */
+ rwlock_t lock;
+ int state; /* what is requested of us? */
+#define STATE_OVERLAY 0
+#define STATE_VBI 1
+ struct vidinfo* workqueue; /* buffers to grab, head is active */
+ ulong fieldnr; /* #field, ticked every VSYNC */
+ ulong lastfieldnr; /* #field, ticked every GRAB */
+
+ int vidInterlace; /* calculated */
+ int vidXshift; /* calculated */
+ uint vidWidth; /* calculated */
+ uint vidHeight; /* calculated */
+};
+
+#define zrwrite(dat,adr) writel((dat),(char *) (ztv->zoran_mem+(adr)))
+#define zrread(adr) readl(ztv->zoran_mem+(adr))
+
+#if PDEBUG == 0
+#define zrand(dat,adr) zrwrite((dat) & zrread(adr), adr)
+#define zror(dat,adr) zrwrite((dat) | zrread(adr), adr)
+#define zraor(dat,mask,adr) zrwrite( ((dat)&~(mask)) | ((mask)&zrread(adr)), adr)
+#else
+#define zrand(dat, adr) \
+do { \
+ ulong data = (dat) & zrread((adr)); \
+ zrwrite(data, (adr)); \
+ if (0 != (~(dat) & zrread((adr)))) \
+ printk(KERN_DEBUG "zoran: zrand at %d(%d) detected set bits(%x)\n", __LINE__, (adr), (dat)); \
+} while(0)
+
+#define zror(dat, adr) \
+do { \
+ ulong data = (dat) | zrread((adr)); \
+ zrwrite(data, (adr)); \
+ if ((dat) != ((dat) & zrread(adr))) \
+ printk(KERN_DEBUG "zoran: zror at %d(%d) detected unset bits(%x)\n", __LINE__, (adr), (dat)); \
+} while(0)
+
+#define zraor(dat, mask, adr) \
+do { \
+ ulong data; \
+ if ((dat) & (mask)) \
+ printk(KERN_DEBUG "zoran: zraor at %d(%d) detected bits(%x:%x)\n", __LINE__, (adr), (dat), (mask)); \
+ data = ((dat)&~(mask)) | ((mask) & zrread((adr))); \
+ zrwrite(data,(adr)); \
+ if ( (dat) != (~(mask) & zrread((adr))) ) \
+ printk(KERN_DEBUG "zoran: zraor at %d(%d) could not set all bits(%x:%x)\n", __LINE__, (adr), (dat), (mask)); \
+} while(0)
+#endif
+
+#endif
+
+/* zoran PCI address space */
+#define ZORAN_VFEH 0x000 /* Video Front End Horizontal Conf. */
+#define ZORAN_VFEH_HSPOL (1<<30)
+#define ZORAN_VFEH_HSTART (0x3FF<<10)
+#define ZORAN_VFEH_HEND (0x3FF<<0)
+
+#define ZORAN_VFEV 0x004 /* Video Front End Vertical Conf. */
+#define ZORAN_VFEV_VSPOL (1<<30)
+#define ZORAN_VFEV_VSTART (0x3FF<<10)
+#define ZORAN_VFEV_VEND (0x3FF<<0)
+
+#define ZORAN_VFEC 0x008 /* Video Front End Scaler and Pixel */
+#define ZORAN_VFEC_EXTFL (1<<26)
+#define ZORAN_VFEC_TOPFIELD (1<<25)
+#define ZORAN_VFEC_VCLKPOL (1<<24)
+#define ZORAN_VFEC_HFILTER (7<<21)
+#define ZORAN_VFEC_HFILTER_1 (0<<21) /* no lumi, 3-tap chromo */
+#define ZORAN_VFEC_HFILTER_2 (1<<21) /* 3-tap lumi, 3-tap chromo */
+#define ZORAN_VFEC_HFILTER_3 (2<<21) /* 4-tap lumi, 4-tap chromo */
+#define ZORAN_VFEC_HFILTER_4 (3<<21) /* 5-tap lumi, 4-tap chromo */
+#define ZORAN_VFEC_HFILTER_5 (4<<21) /* 4-tap lumi, 4-tap chromo */
+#define ZORAN_VFEC_DUPFLD (1<<20)
+#define ZORAN_VFEC_HORDCM (63<<14)
+#define ZORAN_VFEC_VERDCM (63<<8)
+#define ZORAN_VFEC_DISPMOD (1<<6)
+#define ZORAN_VFEC_RGB (3<<3)
+#define ZORAN_VFEC_RGB_YUV422 (0<<3)
+#define ZORAN_VFEC_RGB_RGB888 (1<<3)
+#define ZORAN_VFEC_RGB_RGB565 (2<<3)
+#define ZORAN_VFEC_RGB_RGB555 (3<<3)
+#define ZORAN_VFEC_ERRDIF (1<<2)
+#define ZORAN_VFEC_PACK24 (1<<1)
+#define ZORAN_VFEC_LE (1<<0)
+
+#define ZORAN_VTOP 0x00C /* Video Display "Top" */
+
+#define ZORAN_VBOT 0x010 /* Video Display "Bottom" */
+
+#define ZORAN_VSTR 0x014 /* Video Display Stride */
+#define ZORAN_VSTR_DISPSTRIDE (0xFFFF<<16)
+#define ZORAN_VSTR_VIDOVF (1<<8)
+#define ZORAN_VSTR_SNAPSHOT (1<<1)
+#define ZORAN_VSTR_GRAB (1<<0)
+
+#define ZORAN_VDC 0x018 /* Video Display Conf. */
+#define ZORAN_VDC_VIDEN (1<<31)
+#define ZORAN_VDC_MINPIX (0x1F<<25)
+#define ZORAN_VDC_TRICOM (1<<24)
+#define ZORAN_VDC_VIDWINHT (0x3FF<<12)
+#define ZORAN_VDC_VIDWINWID (0x3FF<<0)
+
+#define ZORAN_MTOP 0x01C /* Masking Map "Top" */
+
+#define ZORAN_MBOT 0x020 /* Masking Map "Bottom" */
+
+#define ZORAN_OCR 0x024 /* Overlay Control */
+#define ZORAN_OCR_OVLEN (1<<15)
+#define ZORAN_OCR_MASKSTRIDE (0xFF<<0)
+
+#define ZORAN_PCI 0x028 /* System, PCI and GPP Control */
+#define ZORAN_PCI_SOFTRESET (1<<24)
+#define ZORAN_PCI_WAITSTATE (3<<16)
+#define ZORAN_PCI_GENPURDIR (0xFF<<0)
+
+#define ZORAN_GUEST 0x02C /* GuestBus Control */
+
+#define ZORAN_CSOURCE 0x030 /* Code Source Address */
+
+#define ZORAN_CTRANS 0x034 /* Code Transfer Control */
+
+#define ZORAN_CMEM 0x038 /* Code Memory Pointer */
+
+#define ZORAN_ISR 0x03C /* Interrupt Status Register */
+#define ZORAN_ISR_CODE (1<<28)
+#define ZORAN_ISR_GIRQ0 (1<<29)
+#define ZORAN_ISR_GIRQ1 (1<<30)
+
+#define ZORAN_ICR 0x040 /* Interrupt Control Register */
+#define ZORAN_ICR_EN (1<<24)
+#define ZORAN_ICR_CODE (1<<28)
+#define ZORAN_ICR_GIRQ0 (1<<29)
+#define ZORAN_ICR_GIRQ1 (1<<30)
+
+#define ZORAN_I2C 0x044 /* I2C-Bus */
+#define ZORAN_I2C_SCL (1<<1)
+#define ZORAN_I2C_SDA (1<<0)
+
+#define ZORAN_POST 0x48 /* PostOffice */
+#define ZORAN_POST_PEN (1<<25)
+#define ZORAN_POST_TIME (1<<24)
+#define ZORAN_POST_DIR (1<<23)
+#define ZORAN_POST_GUESTID (3<<20)
+#define ZORAN_POST_GUEST (7<<16)
+#define ZORAN_POST_DATA (0xFF<<0)
+
+#endif
diff --git a/drivers/media/video/zr36120_i2c.c b/drivers/media/video/zr36120_i2c.c
new file mode 100644
index 000000000..0de59b9f1
--- /dev/null
+++ b/drivers/media/video/zr36120_i2c.c
@@ -0,0 +1,133 @@
+/*
+ zr36120_i2c.c - Zoran 36120/36125 based framegrabbers
+
+ Copyright (C) 1998-1999 Pauline Middelink <middelin@polyware.nl>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+
+#include <linux/version.h>
+#include <linux/video_decoder.h>
+#include <asm/uaccess.h>
+
+#include "tuner.h"
+#include "zr36120.h"
+
+/* ----------------------------------------------------------------------- */
+/* I2C functions */
+/* ----------------------------------------------------------------------- */
+
+/* software I2C functions */
+
+#define I2C_DELAY 10
+
+static void i2c_setlines(struct i2c_bus *bus,int ctrl,int data)
+{
+ struct zoran *ztv = (struct zoran*)bus->data;
+ unsigned int b = 0;
+ if (data) b |= ztv->card->swapi2c ? ZORAN_I2C_SCL : ZORAN_I2C_SDA;
+ if (ctrl) b |= ztv->card->swapi2c ? ZORAN_I2C_SDA : ZORAN_I2C_SCL;
+ zrwrite(b, ZORAN_I2C);
+ udelay(I2C_DELAY);
+}
+
+static int i2c_getdataline(struct i2c_bus *bus)
+{
+ struct zoran *ztv = (struct zoran*)bus->data;
+ if (ztv->card->swapi2c)
+ return zrread(ZORAN_I2C) & ZORAN_I2C_SCL;
+ return zrread(ZORAN_I2C) & ZORAN_I2C_SDA;
+}
+
+static
+void attach_inform(struct i2c_bus *bus, int id)
+{
+ struct zoran *ztv = (struct zoran*)bus->data;
+ struct video_decoder_capability dc;
+ int rv;
+
+ switch (id) {
+ case I2C_DRIVERID_VIDEODECODER:
+ DEBUG(printk(CARD_INFO "decoder attached\n",CARD));
+
+ /* fetch the capabilites of the decoder */
+ rv = i2c_control_device(&ztv->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_GET_CAPABILITIES, &dc);
+ if (rv) {
+ DEBUG(printk(CARD_DEBUG "decoder is not V4L aware!\n",CARD));
+ break;
+ }
+ DEBUG(printk(CARD_DEBUG "capabilities %d %d %d\n",CARD,dc.flags,dc.inputs,dc.outputs));
+
+ /* Test if the decoder can de VBI transfers */
+ if (dc.flags & 16 /*VIDEO_DECODER_VBI*/)
+ ztv->have_decoder = 2;
+ else
+ ztv->have_decoder = 1;
+ break;
+ case I2C_DRIVERID_TUNER:
+ ztv->have_tuner = 1;
+ DEBUG(printk(CARD_INFO "tuner attached\n",CARD));
+ if (ztv->tuner_type >= 0)
+ {
+ if (i2c_control_device(&ztv->i2c,I2C_DRIVERID_TUNER,TUNER_SET_TYPE,&ztv->tuner_type)<0)
+ DEBUG(printk(CARD_INFO "attach_inform; tuner wont be set to type %d\n",CARD,ztv->tuner_type));
+ }
+ break;
+ default:
+ DEBUG(printk(CARD_INFO "attach_inform; unknown device id=%d\n",CARD,id));
+ break;
+ }
+}
+
+static
+void detach_inform(struct i2c_bus *bus, int id)
+{
+ struct zoran *ztv = (struct zoran*)bus->data;
+
+ switch (id) {
+ case I2C_DRIVERID_VIDEODECODER:
+ ztv->have_decoder = 0;
+ DEBUG(printk(CARD_INFO "decoder detached\n",CARD));
+ break;
+ case I2C_DRIVERID_TUNER:
+ ztv->have_tuner = 0;
+ DEBUG(printk(CARD_INFO "tuner detached\n",CARD));
+ break;
+ default:
+ DEBUG(printk(CARD_INFO "detach_inform; unknown device id=%d\n",CARD,id));
+ break;
+ }
+}
+
+struct i2c_bus zoran_i2c_bus_template =
+{
+ "ZR36120",
+ I2C_BUSID_ZORAN,
+ NULL,
+
+ SPIN_LOCK_UNLOCKED,
+
+ attach_inform,
+ detach_inform,
+
+ i2c_setlines,
+ i2c_getdataline,
+ NULL,
+ NULL
+};
diff --git a/drivers/media/video/zr36120_mem.c b/drivers/media/video/zr36120_mem.c
new file mode 100644
index 000000000..b4c6078d3
--- /dev/null
+++ b/drivers/media/video/zr36120_mem.c
@@ -0,0 +1,77 @@
+/*
+ zr36120_mem.c - Zoran 36120/36125 based framegrabbers
+
+ Copyright (C) 1998-1999 Pauline Middelink <middelin@polyware.nl>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+#include <linux/wrapper.h>
+#include <linux/slab.h>
+#include <asm/io.h>
+#ifdef CONFIG_BIGPHYS_AREA
+#include <linux/bigphysarea.h>
+#endif
+
+#include "zr36120.h"
+#include "zr36120_mem.h"
+
+/*******************************/
+/* Memory management functions */
+/*******************************/
+
+void* bmalloc(unsigned long size)
+{
+ void* mem;
+#ifdef CONFIG_BIGPHYS_AREA
+ mem = bigphysarea_alloc_pages(size/PAGE_SIZE, 1, GFP_KERNEL);
+#else
+ /*
+ * The following function got a lot of memory at boottime,
+ * so we know its always there...
+ */
+ mem = (void*)__get_free_pages(GFP_USER|GFP_DMA,get_order(size));
+#endif
+ if (mem) {
+ unsigned long adr = (unsigned long)mem;
+ while (size > 0) {
+ mem_map_reserve(virt_to_page(phys_to_virt(adr)));
+ adr += PAGE_SIZE;
+ size -= PAGE_SIZE;
+ }
+ }
+ return mem;
+}
+
+void bfree(void* mem, unsigned long size)
+{
+ if (mem) {
+ unsigned long adr = (unsigned long)mem;
+ unsigned long siz = size;
+ while (siz > 0) {
+ mem_map_unreserve(virt_to_page(phys_to_virt(adr)));
+ adr += PAGE_SIZE;
+ siz -= PAGE_SIZE;
+ }
+#ifdef CONFIG_BIGPHYS_AREA
+ bigphysarea_free_pages(mem);
+#else
+ free_pages((unsigned long)mem,get_order(size));
+#endif
+ }
+}
diff --git a/drivers/media/video/zr36120_mem.h b/drivers/media/video/zr36120_mem.h
new file mode 100644
index 000000000..aad117acc
--- /dev/null
+++ b/drivers/media/video/zr36120_mem.h
@@ -0,0 +1,3 @@
+/* either kmalloc() or bigphysarea() alloced memory - continuous */
+void* bmalloc(unsigned long size);
+void bfree(void* mem, unsigned long size);