summaryrefslogtreecommitdiff
path: root/os/pc/devtv.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/pc/devtv.c')
-rw-r--r--os/pc/devtv.c1826
1 files changed, 1826 insertions, 0 deletions
diff --git a/os/pc/devtv.c b/os/pc/devtv.c
new file mode 100644
index 00000000..5e45fa37
--- /dev/null
+++ b/os/pc/devtv.c
@@ -0,0 +1,1826 @@
+/*
+ * Driver for Hauppage TV board
+ *
+ * Control commands:
+ *
+ * init
+ * window %d %d %d %d
+ * colorkey %d %d %d %d %d %d
+ * capture %d %d %d %d
+ * capbrightness %d
+ * capcontrast %d
+ * capsaturation %d
+ * caphue %d
+ * capbw %d
+ * brightness %d
+ * contrast %d
+ * saturation %d
+ * source %d
+ * svideo %d
+ * format %d
+ * channel %d %d
+ * signal
+ * volume %d [ %d ]
+ * bass %d
+ * treble %d
+ * freeze %d
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "tv.h"
+
+#include <draw.h>
+
+enum {
+ MemSize= 1,
+ MemAddr= 0xB8000,
+
+ CompressReg= -14,
+
+ /* smart lock registers */
+ SLReg1= -2,
+ SLReg2= -1,
+
+ /* the Bt812 registers */
+ Bt812Index= -5,
+ Bt812Data= -6,
+
+ Bt2VideoPresent= 0x40,
+ Bt4ColorBars= 0x40,
+ Bt5YCFormat= 0x80,
+ Bt7TriState= 0x0C,
+
+ /* VxP 500 registers */
+ Vxp500Index= 0,
+ Vxp500Data= 1,
+
+ /* video controller registers */
+ MemoryWindowBaseAddrA= 0x14,
+ MemoryWindowBaseAddrB= 0x15,
+ MemoryPageReg= 0x16,
+ MemoryConfReg= 0x18,
+ ISAControl= 0x30,
+ I2CControl= 0x34,
+ InputVideoConfA= 0x38,
+ InputVideoConfB= 0x39,
+ ISASourceWindowWidthA= 0x3A,
+ ISASourceWindowWidthB= 0x3B,
+ ISASourceWindowHeightA= 0x3C,
+ ISASourceWindowHeightB= 0x3D,
+ InputHorzCropLeftA= 0x40,
+ InputHorzCropLeftB= 0x41,
+ InputHorzCropRightA= 0x44,
+ InputHorzCropRightB= 0x45,
+ InputHorzCropTopA= 0x48,
+ InputHorzCropTopB= 0x49,
+ InputHorzCropBottomA= 0x4C,
+ InputHorzCropBottomB= 0x4D,
+ InputHorzFilter= 0x50,
+ InputHorzScaleControlA= 0x54,
+ InputHorzScaleControlB= 0x55,
+ InputVertInterpolControl= 0x58,
+ InputVertScaleControlA= 0x5C,
+ InputVertScaleControlB= 0x5D,
+ InputFieldPixelBufStatus= 0x64,
+ VideoInputFrameBufDepthA= 0x68,
+ VideoInputFrameBufDepthB= 0x69,
+ AcquisitionControl= 0x6C,
+ AcquisitionAddrA= 0x70,
+ AcquisitionAddrB= 0x71,
+ AcquisitionAddrC= 0x72,
+ VideoBufferLayoutControl= 0x73,
+ CaptureControl= 0x80,
+ CaptureViewPortAddrA= 0x81,
+ CaptureViewPortAddrB= 0x82,
+ CaptureViewPortAddrC= 0x83,
+ CaptureViewPortWidthA= 0x84,
+ CaptureViewPortWidthB= 0x85,
+ CaptureViewPortHeightA= 0x86,
+ CaptureViewPortHeightB= 0x87,
+ CapturePixelBufLow= 0x88,
+ CapturePixelBufHigh= 0x89,
+ CaptureMultiBufDepthA= 0x8A,
+ CaptureMultiBufDepthB= 0x8B,
+ DisplayControl= 0x92,
+ VGAControl= 0x94,
+ OutputProcControlA= 0x96,
+ OutputProcControlB= 0x97,
+ DisplayViewPortStartAddrA= 0xA0,
+ DisplayViewPortStartAddrB= 0xA1,
+ DisplayViewPortStartAddrC= 0xA2,
+ DisplayViewPortWidthA= 0xA4,
+ DisplayViewPortWidthB= 0xA5,
+ DisplayViewPortHeightA= 0xA6,
+ DisplayViewPortHeightB= 0xA7,
+ DisplayViewPortOrigTopA= 0xA8,
+ DisplayViewPortOrigTopB= 0xA9,
+ DisplayViewPortOrigLeftA= 0xAA,
+ DisplayViewPortOrigLeftB= 0xAB,
+ DisplayWindowLeftA= 0xB0,
+ DisplayWindowLeftB= 0xB1,
+ DisplayWindowRightA= 0xB4,
+ DisplayWindowRightB= 0xB5,
+ DisplayWindowTopA= 0xB8,
+ DisplayWindowTopB= 0xB9,
+ DisplayWindowBottomA= 0xBC,
+ DisplayWindowBottomB= 0xBD,
+ OutputVertZoomControlA= 0xC0,
+ OutputVertZoomControlB= 0xC1,
+ OutputHorzZoomControlA= 0xC4,
+ OutputHorzZoomControlB= 0xC5,
+ BrightnessControl= 0xC8,
+ ContrastControl= 0xC9,
+ SaturationControl= 0xCA,
+ VideoOutIntrStatus= 0xD3,
+
+ /* smart lock bits */
+ PixelClk= 0x03,
+ SmartLock= 0x00,
+ FeatureConnector= 0x01,
+ Divider= 0x02,
+ Window= 0x08,
+ KeyWindow= 0x0C,
+ HSyncLow= 0x20,
+ VSyncLow= 0x40,
+
+ ClkBit= 0x01,
+ DataBit= 0x02,
+ HoldBit= 0x04,
+ SelBit= 0x08,
+ DivControl= 0x40,
+
+ /* i2c bus control bits */
+ I2C_Clock= 0x02,
+ I2C_Data= 0x08,
+ I2C_RdClock= 0x10,
+ I2C_RdData= 0x20,
+ I2C_RdData_D= 0x40,
+
+ /* I2C bus addresses */
+ Adr5249= 0x22, /* teletext decoder */
+ Adr8444= 0x48, /* 6-bit DAC (TDA 8444) */
+ Adr6300= 0x80, /* sound fader (TEA 6300) */
+ Adr6320= 0x80, /* sound fader (TEA 6320T) */
+ AdrTuner= 0xC0,
+
+ /* Philips audio chips */
+ TEA6300= 0,
+ TEA6320T= 1,
+
+ /* input formats */
+ NTSC_M = 0,
+ NTSC_443 = 1,
+ External = 2,
+
+ NTSCCropLeft= 36, /* NTSC 3.6 usec */
+ NTSCCropRight= 558, /* NTSC 55.8 usec */
+
+ /* color control indices */
+ Vxp500Brightness= 1,
+ Vxp500Contrast= 2,
+ Vxp500Saturation= 3,
+ Bt812Brightness= 4,
+ Bt812Contrast= 5,
+ Bt812Saturation= 6,
+ Bt812Hue= 7,
+ Bt812BW= 8,
+
+ /* board revision numbers */
+ RevisionPP= 0,
+ RevisionA= 1,
+ HighQ= 2,
+
+ /* VGA controller registers */
+ VGAMiscOut= 0x3CC,
+ VGAIndex= 0x3D4,
+ VGAData= 0x3D5,
+ VGAHorzTotal= 0x00,
+};
+
+enum {
+ Qdir,
+ Qdata,
+ Qctl,
+};
+
+static
+Dirtab tvtab[]={
+ ".", {Qdir, 0, QTDIR}, 0, 0555,
+ "tv", {Qdata, 0}, 0, 0666,
+ "tvctl", {Qctl, 0}, 0, 0666,
+};
+
+static
+int ports[] = { /* board addresses */
+ 0x51C, 0x53C, 0x55C, 0x57C,
+ 0x59C, 0x5BC, 0x5DC, 0x5FC
+};
+
+/*
+ * Default settings, settings between 0..100
+ */
+static
+int defaults[] = {
+ Vxp500Brightness, 0,
+ Vxp500Contrast, 54,
+ Vxp500Saturation, 54,
+ Bt812Brightness, 13,
+ Bt812Contrast, 57,
+ Bt812Saturation, 51,
+ Bt812Hue, 0,
+ Bt812BW, 0,
+};
+
+static int port;
+static int soundchip;
+static int boardrev;
+static int left, right;
+static int vsync, hsync;
+static ulong xtalfreq;
+static ushort cropleft, cropright;
+static ushort cropbottom, croptop;
+static Rectangle window, capwindow;
+
+static void setreg(int, int);
+static void setbt812reg(int, int);
+static void videoinit(void);
+static void createwindow(Rectangle);
+static void setcontrols(int, uchar);
+static void setcolorkey(int, int, int, int, int, int);
+static void soundinit(void);
+static void setvolume(int, int);
+static void setbass(int);
+static void settreble(int);
+static void setsoundsource(int);
+static void tunerinit(void);
+static void settuner(int, int);
+static void setvideosource(int);
+static int waitvideosignal(void);
+static void freeze(int);
+static void setsvideo(int);
+static void setinputformat(int);
+static void enablevideo(void);
+static void *saveframe(int *);
+
+static int
+min(int a, int b)
+{
+ return a < b ? a : b;
+}
+
+static int
+max(int a, int b)
+{
+ return a < b ? b : a;
+}
+
+static int
+present(int port)
+{
+ outb(port+Vxp500Index, 0xAA);
+ if (inb(port+Vxp500Index) != 0xAA)
+ return 0;
+ outb(port+Vxp500Index, 0x55);
+ outb(port+Vxp500Data, 0xAA);
+ if (inb(port+Vxp500Index) != 0x55)
+ return 0;
+ if (inb(port+Vxp500Data) != 0xAA)
+ return 0;
+ outb(port+Vxp500Data, 0x55);
+ if (inb(port+Vxp500Index) != 0x55)
+ return 0;
+ if (inb(port+Vxp500Data) != 0x55)
+ return 0;
+ return 1;
+}
+
+static int
+getvsync(void)
+{
+ int vslow, vshigh, s;
+ ushort timo;
+
+ s = splhi();
+
+ outb(port+Vxp500Index, VideoOutIntrStatus);
+
+ /* wait for VSync to go high then low */
+ for (timo = ~0; timo; timo--)
+ if (inb(port+Vxp500Data) & 2) break;
+ for (timo = ~0; timo; timo--)
+ if ((inb(port+Vxp500Data) & 2) == 0) break;
+
+ /* count how long it stays low and how long it stays high */
+ for (vslow = 0, timo = ~0; timo; timo--, vslow++)
+ if (inb(port+Vxp500Data) & 2) break;
+ for (vshigh = 0, timo = ~0; timo; timo--, vshigh++)
+ if ((inb(port+Vxp500Data) & 2) == 0) break;
+ splx(s);
+
+ return vslow < vshigh;
+}
+
+static int
+gethsync(void)
+{
+ int hslow, hshigh, s;
+ ushort timo;
+
+ s = splhi();
+
+ outb(port+Vxp500Index, VideoOutIntrStatus);
+
+ /* wait for HSync to go high then low */
+ for (timo = ~0; timo; timo--)
+ if (inb(port+Vxp500Data) & 1) break;
+ for (timo = ~0; timo; timo--)
+ if ((inb(port+Vxp500Data) & 1) == 0) break;
+
+ /* count how long it stays low and how long it stays high */
+ for (hslow = 0, timo = ~0; timo; timo--, hslow++)
+ if (inb(port+Vxp500Data) & 1) break;
+ for (hshigh = 0, timo = ~0; timo; timo--, hshigh++)
+ if ((inb(port+Vxp500Data) & 1) == 0) break;
+ splx(s);
+
+ return hslow < hshigh;
+}
+
+static void
+tvinit(void)
+{
+ int i;
+
+ for (i = 0, port = 0; i < nelem(ports); i++) {
+ if (present(ports[i])) {
+ port = ports[i];
+ break;
+ }
+ }
+ if (i == nelem(ports))
+ return;
+
+ /*
+ * the following routines are the prefered way to
+ * find out the sync polarities. Unfortunately, it
+ * doesn't always work.
+ */
+#ifndef VSync
+ vsync = getvsync();
+ hsync = gethsync();
+#else
+ vsync = VSync;
+ hsync = HSync;
+#endif
+ left = right = 80;
+ soundinit();
+ tunerinit();
+ videoinit();
+}
+
+static Chan*
+tvattach(char *spec)
+{
+ if (port == 0)
+ error(Enonexist);
+ return devattach('V', spec);
+}
+
+static Walkqid*
+tvwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, tvtab, nelem(tvtab), devgen);
+}
+
+static int
+tvstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, tvtab, nelem(tvtab), devgen);
+}
+
+static Chan*
+tvopen(Chan *c, int omode)
+{
+ return devopen(c, omode, tvtab, nelem(tvtab), devgen);
+}
+
+static void
+tvclose(Chan *)
+{
+}
+
+static long
+tvread(Chan *c, void *a, long n, vlong offset)
+{
+ static void *frame;
+ static int size;
+
+ USED(offset);
+
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, a, n, tvtab, nelem(tvtab), devgen);
+ case Qdata:
+ if (eqrect(capwindow, Rect(0, 0, 0, 0)))
+ error(Ebadarg);
+ if (offset == 0)
+ frame = saveframe(&size);
+ if (frame) {
+ if (n > size - offset)
+ n = size - offset;
+ memmove(a, (char *)frame + offset, n);
+ } else
+ error(Enovmem);
+ break;
+ default:
+ n=0;
+ break;
+ }
+ return n;
+}
+
+static long
+tvwrite(Chan *c, void *vp, long n, vlong offset)
+{
+ char buf[128], *field[10], *a;
+ int i, nf, source;
+ static Rectangle win;
+ static int hsize, size = 0;
+ static void *frame;
+
+ USED(offset);
+
+ a = vp;
+ switch((ulong)c->qid.path){
+ case Qctl:
+ if (n > sizeof(buf)-1)
+ n = sizeof(buf)-1;
+ memmove(buf, a, n);
+ buf[n] = '\0';
+
+ nf = getfields(buf, field, nelem(field), 1, " \t");
+ if (nf < 1) error(Ebadarg);
+
+ if (strcmp(field[0], "init") == 0) {
+ window = Rect(0, 0, 0, 0);
+ capwindow = Rect(0, 0, 0, 0);
+ source = 0; /* video 0 input */
+ setvideosource(source);
+ left = right = 80;
+ setsoundsource(source);
+ for (i = 0; i < nelem(defaults); i += 2)
+ setcontrols(defaults[i], defaults[i+1]);
+ } else if (strcmp(field[0], "colorkey") == 0) {
+ if (nf < 7) error(Ebadarg);
+ setcolorkey(strtoul(field[1], 0, 0), strtoul(field[2], 0, 0),
+ strtoul(field[3], 0, 0), strtoul(field[4], 0, 0),
+ strtoul(field[5], 0, 0), strtoul(field[6], 0, 0));
+ } else if (strcmp(field[0], "window") == 0) {
+ if (nf < 5) error(Ebadarg);
+ createwindow(Rect(strtoul(field[1], 0, 0), strtoul(field[2], 0, 0),
+ strtoul(field[3], 0, 0), strtoul(field[4], 0, 0)));
+ setvolume(left, right);
+ } else if (strcmp(field[0], "capture") == 0) {
+ if (nf < 5) error(Ebadarg);
+ capwindow = Rect(strtoul(field[1], 0, 0), strtoul(field[2], 0, 0),
+ strtoul(field[3], 0, 0), strtoul(field[4], 0, 0));
+ } else if (strcmp(field[0], "freeze") == 0) {
+ if (nf < 2) error(Ebadarg);
+ freeze(strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "capbrightness") == 0) {
+ if (nf < 2) error(Ebadarg);
+ setcontrols(Bt812Brightness, strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "capcontrast") == 0) {
+ if (nf < 2) error(Ebadarg);
+ setcontrols(Bt812Contrast, strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "capsaturation") == 0) {
+ if (nf < 2) error(Ebadarg);
+ setcontrols(Bt812Saturation, strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "caphue") == 0) {
+ if (nf < 2) error(Ebadarg);
+ setcontrols(Bt812Hue, strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "capbw") == 0) {
+ if (nf < 2) error(Ebadarg);
+ setcontrols(Bt812BW, strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "brightness") == 0) {
+ if (nf < 2) error(Ebadarg);
+ setcontrols(Vxp500Brightness, strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "contrast") == 0) {
+ if (nf < 2) error(Ebadarg);
+ setcontrols(Vxp500Contrast, strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "saturation") == 0) {
+ if (nf < 2) error(Ebadarg);
+ setcontrols(Vxp500Saturation, strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "source") == 0) {
+ if (nf < 2) error(Ebadarg);
+ source = strtoul(field[1], 0, 0);
+ setvideosource(source);
+ setsoundsource(source);
+ } else if (strcmp(field[0], "svideo") == 0) {
+ if (nf < 2) error(Ebadarg);
+ setsvideo(strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "format") == 0) {
+ if (nf < 2) error(Ebadarg);
+ setinputformat(strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "channel") == 0) {
+ if (nf < 3) error(Ebadarg);
+ setvolume(0, 0);
+ settuner(strtoul(field[1], 0, 0), strtoul(field[2], 0, 0));
+ tsleep(&up->sleep, return0, 0, 300);
+ setvolume(left, right);
+ } else if (strcmp(field[0], "signal") == 0) {
+ if (!waitvideosignal())
+ error(Etimedout);
+ } else if (strcmp(field[0], "volume") == 0) {
+ if (nf < 2) error(Ebadarg);
+ left = strtoul(field[1], 0, 0);
+ if (nf < 3)
+ right = left;
+ else
+ right = strtoul(field[2], 0, 0);
+ setvolume(left, right);
+ } else if (strcmp(field[0], "bass") == 0) {
+ if (nf < 2) error(Ebadarg);
+ setbass(strtoul(field[1], 0, 0));
+ } else if (strcmp(field[0], "treble") == 0) {
+ if (nf < 2) error(Ebadarg);
+ settreble(strtoul(field[1], 0, 0));
+ } else
+ error(Ebadctl);
+ break;
+ default:
+ error(Ebadusefd);
+ }
+ return n;
+}
+
+
+Dev tvdevtab = {
+ 'V',
+ "tv",
+
+ devreset,
+ tvinit,
+ devshutdown,
+ tvattach,
+ tvwalk,
+ tvstat,
+ tvopen,
+ devcreate,
+ tvclose,
+ tvread,
+ devbread,
+ tvwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
+
+static void
+setreg(int index, int data)
+{
+ outb(port+Vxp500Index, index);
+ outb(port+Vxp500Data, data);
+}
+
+static unsigned int
+getreg(int index)
+{
+ outb(port+Vxp500Index, index);
+ return inb(port+Vxp500Data);
+}
+
+/*
+ * I2C routines
+ */
+static void
+delayi2c(void)
+{
+ int i, val;
+
+ /* delay for 4.5 usec to guarantee clock time */
+ for (i = 0; i < 75; i++) { /* was 50 */
+ val = inb(port+Vxp500Data);
+ USED(val);
+ }
+}
+
+static int
+waitSDA(void)
+{
+ ushort timo;
+
+ /* wait for i2c clock to float high */
+ for (timo = ~0; timo; timo--)
+ if (inb(port+Vxp500Data) & I2C_RdData)
+ break;
+ if (!timo) print("devtv: waitSDA fell out of loop\n");
+ return !timo;
+}
+
+static int
+waitSCL(void)
+{
+ ushort timo;
+
+ /* wait for i2c clock to float high */
+ for (timo = ~0; timo; timo--)
+ if (inb(port+Vxp500Data) & I2C_RdClock)
+ break;
+ delayi2c();
+ if (!timo) print("devtv: waitSCL fell out of loop\n");
+ return !timo;
+}
+
+static int
+seti2cdata(int data)
+{
+ int b, reg, val;
+ int error;
+
+ error = 0;
+ reg = inb(port+Vxp500Data);
+ for (b = 0x80; b; b >>= 1) {
+ if (data & b)
+ reg |= I2C_Data;
+ else
+ reg &= ~I2C_Data;
+ outb(port+Vxp500Data, reg);
+ reg |= I2C_Clock;
+ outb(port+Vxp500Data, reg);
+ error |= waitSCL();
+ reg &= ~I2C_Clock;
+ outb(port+Vxp500Data, reg);
+ delayi2c();
+ }
+ reg |= I2C_Data;
+ outb(port+Vxp500Data, reg);
+ reg |= I2C_Clock;
+ outb(port+Vxp500Data, reg);
+ error |= waitSCL();
+ val = inb(port+Vxp500Data);
+ USED(val);
+ reg &= ~I2C_Clock;
+ outb(port+Vxp500Data, reg);
+ delayi2c();
+ return error;
+}
+
+static int
+seti2creg(int id, int index, int data)
+{
+ int reg, error;
+
+ error = 0;
+ /* set i2c control register to enable i2c clock and data lines */
+ setreg(I2CControl, I2C_Data|I2C_Clock);
+ error |= waitSDA();
+ error |= waitSCL();
+ outb(port+Vxp500Data, I2C_Clock);
+ delayi2c();
+ outb(port+Vxp500Data, 0);
+ delayi2c();
+
+ error |= seti2cdata(id);
+ error |= seti2cdata(index);
+ error |= seti2cdata(data);
+
+ reg = inb(port+Vxp500Data);
+ reg &= ~I2C_Data;
+ outb(port+Vxp500Data, reg);
+ reg |= I2C_Clock;
+ outb(port+Vxp500Data, reg);
+ error |= waitSCL();
+ reg |= I2C_Data;
+ outb(port+Vxp500Data, reg);
+ error |= waitSDA();
+ return error;
+}
+
+static int
+seti2cregs(int id, int index, int n, uchar *data)
+{
+ int reg, error;
+
+ error = 0;
+ /* set i2c control register to enable i2c clock and data lines */
+ setreg(I2CControl, I2C_Data|I2C_Clock);
+ error |= waitSDA();
+ error |= waitSCL();
+ outb(port+Vxp500Data, I2C_Clock);
+ delayi2c();
+ outb(port+Vxp500Data, 0);
+ delayi2c();
+
+ /* send data */
+ error |= seti2cdata(id);
+ error |= seti2cdata(index);
+ while (n--)
+ error |= seti2cdata(*data++);
+
+ /* send stop */
+ reg = inb(port+Vxp500Data);
+ reg &= ~I2C_Data;
+ outb(port+Vxp500Data, reg);
+ reg |= I2C_Clock;
+ outb(port+Vxp500Data, reg);
+ error |= waitSCL();
+ reg |= I2C_Data;
+ outb(port+Vxp500Data, reg);
+ error |= waitSDA();
+ return error;
+}
+
+/*
+ * Audio routines
+ */
+static void
+setvolume(int left, int right)
+{
+ int vol, loudness = 0;
+
+ if (soundchip == TEA6300) {
+ seti2creg(Adr6300, 0, (63L * left) / 100);
+ seti2creg(Adr6300, 1, (63L * right) / 100);
+ vol = (15L * max(left, right)) / 100;
+ seti2creg(Adr6300, 4, 0x30 | vol);
+ } else {
+ vol = (63L * max(left, right)) / 100;
+ seti2creg(Adr6320, 0, vol | (loudness << 6));
+ seti2creg(Adr6320, 1, (63L * right) / 100);
+ seti2creg(Adr6320, 2, (63L * left) / 100);
+ }
+}
+
+static void
+setbass(int bass)
+{
+ if (soundchip == TEA6300)
+ seti2creg(Adr6300, 2, (15L * bass) / 100);
+ else
+ seti2creg(Adr6320, 5, max((31L * bass) / 100, 4));
+}
+
+static void
+settreble(int treble)
+{
+ if (soundchip == TEA6300)
+ seti2creg(Adr6300, 3, (15L * treble) / 100);
+ else
+ seti2creg(Adr6320, 6, max((31L * treble) / 100, 7));
+
+}
+
+static void
+setsoundsource(int source)
+{
+ if (soundchip == TEA6300)
+ seti2creg(Adr6300, 5, 1 << source);
+ else
+ seti2creg(Adr6320, 7, source);
+ setbass(50);
+ settreble(50);
+ setvolume(left, right);
+}
+
+static void
+soundinit(void)
+{
+ if (seti2creg(Adr6320, 7, 0) && seti2creg(Adr6300, 4, 0))
+ print("devtv: Audio init failed\n");
+
+ soundchip = AudioChip;
+ setvolume(0, 0);
+}
+
+/*
+ * Tuner routines
+ */
+static
+long hrcfreq[] = { /* HRC CATV frequencies */
+ 0, 7200, 5400, 6000, 6600, 7800, 8400, 17400,
+ 18000, 18600, 19200, 19800, 20400, 21000, 12000, 12600,
+ 13200, 13800, 14400, 15000, 15600, 16200, 16800, 21600,
+ 22200, 22800, 23400, 24000, 24600, 25200, 25800, 26400,
+ 27000, 27600, 28200, 28800, 29400, 30000, 30600, 31200,
+ 31800, 32400, 33000, 33600, 34200, 34800, 35400, 36000,
+ 36600, 37200, 37800, 38400, 39000, 39600, 40200, 40800,
+ 41400, 42000, 42600, 43200, 43800, 44400, 45000, 45600,
+ 46200, 46800, 47400, 48000, 48600, 49200, 49800, 50400,
+ 51000, 51600, 52200, 52800, 53400, 54000, 54600, 55200,
+ 55800, 56400, 57000, 57600, 58200, 58800, 59400, 60000,
+ 60600, 61200, 61800, 62400, 63000, 63600, 64200, 9000,
+ 9600, 10200, 10800, 11400, 64800, 65400, 66000, 66600,
+ 67200, 67800, 68400, 69000, 69600, 70200, 70800, 71400,
+ 72000, 72600, 73200, 73800, 74400, 75000, 75600, 76200,
+ 76800, 77400, 78000, 78600, 79200, 79800,
+};
+
+static void
+settuner(int channel, int finetune)
+{
+ static long lastfreq;
+ uchar data[3];
+ long freq;
+ int cw2, n, sa;
+
+ if (channel < 0 || channel > nelem(hrcfreq))
+ error(Ebadarg);
+
+ freq = hrcfreq[channel];
+
+ /* these settings are all for (FS936E) USA Tuners */
+ if (freq < 16025) /* low band */
+ cw2 = 0xA4;
+ else if (freq < 45425) /* mid band */
+ cw2 = 0x94;
+ else
+ cw2 = 0x34;
+
+ /*
+ * Channels are stored are 1/100 MHz resolutions, but
+ * the tuner wants stuff in MHZ, so divide by 100, we
+ * then have to shift by 4 to get the prog. div. value
+ */
+ n = ((freq + 4575L) * 16) / 100L + finetune;
+
+ if (freq > lastfreq) {
+ sa = (n >> 8) & 0xFF;
+ data[0] = n & 0xFF;
+ data[1] = 0x8E;
+ data[2] = cw2;
+ } else {
+ sa = 0x8E;
+ data[0] = cw2;
+ data[1] = (n >> 8) & 0xFF;
+ data[2] = n & 0xFF;
+ }
+ lastfreq = freq;
+ seti2cregs(AdrTuner, sa, 3, data);
+}
+
+static void
+tunerinit(void)
+{
+ if (seti2creg(AdrTuner, 0, 0))
+ print("devtv: Tuner init failed\n");
+}
+
+/*
+ * Video routines
+ */
+static int slreg1 = 0;
+static int slreg2 = 0;
+static int vcogain = 0;
+static int phdetgain = 2;
+static int plln1 = 2;
+static int pllp2 = 1;
+
+static void
+waitforretrace(void)
+{
+ ushort timo;
+
+ for (timo = ~0; (getreg(VideoOutIntrStatus) & 2) == 0 && timo; timo--)
+ /* wait for VSync inactive */;
+ for (timo = ~0; (getreg(VideoOutIntrStatus) & 2) && timo; timo--)
+ /* wait for VSync active */;
+}
+
+static void
+updateshadowregs(void)
+{
+ int val;
+
+ setreg(InputVideoConfA, getreg(InputVideoConfA) | 0x40);
+ val = getreg(OutputProcControlB);
+ setreg(OutputProcControlB, val & 0x7F);
+ setreg(OutputProcControlB, val | 0x80);
+}
+
+static void
+setvgareg(int data)
+{
+ /* set HSync & VSync first, to make sure VSync works properly */
+ setreg(VGAControl, (getreg(VGAControl) & ~0x06) | (data & 0x06));
+
+ /* wait for VSync and set the whole register */
+ waitforretrace();
+ setreg(VGAControl, data);
+}
+
+static void
+setbt812reg(int index, int data)
+{
+ outb(port+Bt812Index, index);
+ outb(port+Bt812Data, data);
+}
+
+static int
+getbt812reg(int index)
+{
+ outb(port+Bt812Index, index);
+ return inb(port+Bt812Data);
+}
+
+static void
+setbt812regpair(int index, ushort data)
+{
+ outb(port+Bt812Index, index);
+ outb(port+Bt812Data, data);
+ outb(port+Bt812Data, data >> 8);
+}
+
+static void
+setvideosource(int source)
+{
+ int s;
+
+ source &= 7;
+ s = source & 3;
+ setbt812reg(0, ((s << 2) | s) << 3);
+ s = (source & 4) << 4;
+ setbt812reg(4, (getbt812reg(4) & ~Bt4ColorBars) | s);
+}
+
+static void
+setsvideo(int enable)
+{
+ if (enable)
+ setbt812reg(5, getbt812reg(5) | Bt5YCFormat);
+ else
+ setbt812reg(5, getbt812reg(5) & ~Bt5YCFormat);
+}
+
+static int
+waitvideosignal(void)
+{
+ ushort timo;
+
+ for (timo = ~0; timo; timo--)
+ if (getbt812reg(2) & Bt2VideoPresent)
+ return 1;
+ return 0;
+}
+
+/*
+ * ICS1572 Programming Configuration
+ *
+ * R = 1
+ * M = x
+ * A = x
+ * N1 = 4
+ * N2 = internal divide ratio
+ */
+static
+uchar ICSbits[7] = {
+ 0x01, /* bits 8 - 1 00000001 */
+ 0x05, /* bits 16 - 9 00000101 */
+ 0xFF, /* bits 24 - 17 11111111 */
+ 0x8C, /* bits 32 - 25 10001100 */
+ 0xBF, /* bits 40 - 33 10111111 */
+ 0x00, /* bits 48 - 41 00000000 */
+ 0x00, /* bits 56 - 49 00000000 */
+};
+
+static void
+sendbit(int val, int hold)
+{
+ slreg2 &= ~(HoldBit|DataBit|ClkBit);
+ if (val) slreg2 |= DataBit;
+ if (hold) slreg2 |= HoldBit;
+ outb(port+SLReg2, slreg2);
+ outb(port+SLReg2, slreg2|ClkBit);
+ outb(port+SLReg2, slreg2);
+}
+
+static void
+load1572(int select)
+{
+ int reg;
+ uchar mask;
+
+ if (select)
+ slreg2 |= SelBit;
+ else
+ slreg2 &= ~SelBit;
+ outb(port+SLReg2, slreg2);
+
+ for (reg = 0; reg < sizeof(ICSbits); reg++) {
+ for (mask = 1; mask != 0; mask <<= 1) {
+ if (reg == sizeof(ICSbits)-1 && mask == 0x80) {
+ sendbit(ICSbits[reg] & mask, 1);
+ } else
+ sendbit(ICSbits[reg] & mask, 0);
+ }
+ }
+}
+
+static void
+smartlockdiv(int count, int vcogain, int phdetgain, int n1, int p2)
+{
+ int extdiv, intdiv;
+ int nslreg2, external;
+
+ nslreg2 = slreg2;
+ extdiv = ((count - 1) / 512) + 1;
+ intdiv = (count / extdiv);
+ nslreg2 &= ~0xC0;
+ switch (extdiv) {
+ case 1: external = 0; break;
+ case 2: external = 1; break;
+ case 3: external = 1; nslreg2 |= 0x40; break;
+ case 4: external = 1; nslreg2 |= 0x80; break;
+ default: return;
+ }
+ if ((slreg1 & PixelClk) == 0) {
+ slreg2 = nslreg2;
+ outb(port+SLReg2, slreg2);
+ }
+
+ /* set PLL divider */
+ ICSbits[0] &= ~0x07;
+ ICSbits[0] |= n1 & 0x07;
+ ICSbits[3] &= ~0xB7;
+ ICSbits[3] |= vcogain & 0x07;
+ ICSbits[3] |= (phdetgain & 0x03) << 4;
+ ICSbits[3] |= p2 << 7;
+ if (external)
+ ICSbits[1] |= 0x04; /* set EXTFBKEN */
+ else
+ ICSbits[1] &= ~0x04; /* clear EXTFBKEN */
+ intdiv--;
+ ICSbits[2] = intdiv; /* set N2 */
+ ICSbits[3] &= ~ 0x08;
+ ICSbits[3] |= (intdiv >> 5) & 0x08;
+ load1572(1);
+}
+
+static void
+disablecolorkey(void)
+{
+ setreg(DisplayControl, getreg(DisplayControl) & 0xFE);
+ updateshadowregs();
+}
+
+static
+uchar colorkeylimit[6] = {
+ 15, /* upper limit green */
+ 255, /* lower limit green */
+ 63, /* upper limit red */
+ 63, /* upper limit blue */
+ 15, /* lower limit red */
+ 15, /* lower limit blue */
+};
+
+static void
+enablecolorkey(int enable)
+{
+ int i;
+
+ if (enable) {
+ for (i = 0; i < 6; i++)
+ seti2creg(Adr8444, 0xF0 | i, colorkeylimit[i]);
+ slreg1 &= ~0x1C;
+ if (colorkeylimit[4] == 255)
+ slreg1 |= 0x04; /* disable red lower limit */
+ if (colorkeylimit[1] == 255)
+ slreg1 |= 0x08; /* disable green lower limit */
+ if (colorkeylimit[5] == 255)
+ slreg1 |= 0x10; /* disable blue lower limit */
+ } else {
+ for (i = 0; i < 6; i++)
+ seti2creg(Adr8444, 0xF0 | i, 63);
+ slreg1 |= 0x1C;
+ }
+ outb(port+SLReg1, slreg1);
+ disablecolorkey();
+}
+
+static void
+setcolorkey(int rl, int rh, int gl, int gh, int bl, int bh)
+{
+ colorkeylimit[0] = gh;
+ colorkeylimit[1] = gl;
+ colorkeylimit[2] = rh;
+ colorkeylimit[3] = bh;
+ colorkeylimit[4] = rl;
+ colorkeylimit[5] = bl;
+ enablecolorkey(1);
+}
+
+static void
+waitvideoframe(void)
+{
+ ushort timo;
+ int val;
+
+ /* clear status bits and wait for start of an even field */
+ val = getreg(InputFieldPixelBufStatus);
+ USED(val);
+ for (timo = ~0; timo; timo--)
+ if ((getreg(InputFieldPixelBufStatus) & 2) == 0)
+ break;
+ if (!timo) print("devtv: Wait for video frame failed\n");
+}
+
+static void
+freeze(int enable)
+{
+ ushort timo;
+ int reg;
+
+ if (enable) {
+ waitvideoframe();
+ waitvideoframe();
+
+ setreg(InputVideoConfB, getreg(InputVideoConfB) | 0x08);
+ updateshadowregs();
+
+ for (timo = ~0; timo; timo--)
+ if (getreg(InputVideoConfB) & 0x80) break;
+ waitvideoframe();
+
+ reg = getreg(OutputProcControlB);
+ if ((reg & 0x20) == 0) {
+ setreg(ISAControl, 0x80);
+ setreg(OutputProcControlB, getreg(OutputProcControlB) | 0x20);
+ setreg(ISAControl, 0x42);
+
+ reg = getreg(OutputProcControlB);
+ setreg(OutputProcControlB, reg & 0x7F);
+ setreg(OutputProcControlB, reg | 0x80);
+ }
+ } else {
+ setreg(InputVideoConfB, getreg(InputVideoConfB) & ~0x08);
+ updateshadowregs();
+
+ for (timo = ~0; timo; timo--)
+ if (getreg(InputVideoConfB) & 0x40) break;
+ waitvideoframe();
+ reg = getreg(InputFieldPixelBufStatus);
+ USED(reg);
+ }
+}
+
+static void
+enablevideo(void)
+{
+ setreg(DisplayControl, 0x04);
+ updateshadowregs();
+}
+
+static void
+disablevideo(void)
+{
+ setreg(DisplayControl, 0x18);
+ updateshadowregs();
+}
+
+static
+uchar vxp500init[] = { /* video register initialization in (index,data) hex pairs */
+ 0x30, 0x82, 0x39, 0x40, 0x58, 0x0C, 0x73, 0x02, 0x80, 0x00, 0x25, 0x0F,
+ 0x26, 0x0F, 0x38, 0x46, 0x30, 0x03, 0x12, 0x3B, 0x97, 0x20, 0x13, 0x00,
+ 0x14, 0x34, 0x15, 0x04, 0x16, 0x00, 0x17, 0x53, 0x18, 0x04, 0x19, 0x62,
+ 0x1C, 0x00, 0x1D, 0x00, 0x34, 0x3A, 0x38, 0x06, 0x3A, 0x00, 0x3B, 0x00,
+ 0x3C, 0x00, 0x3D, 0x00, 0x40, 0x40, 0x41, 0x40, 0x44, 0xFF, 0x45, 0xFF,
+ 0x48, 0x40, 0x49, 0x40, 0x4C, 0xFF, 0x4D, 0xFF, 0x50, 0xF0, 0x54, 0x30,
+ 0x55, 0x00, 0x5C, 0x04, 0x5D, 0x00, 0x60, 0x00, 0x68, 0x00, 0x69, 0x00,
+ 0x6C, 0x06, 0x6D, 0x00, 0x70, 0x00, 0x71, 0x00, 0x72, 0x00, 0x78, 0x01,
+ 0x79, 0x0C, 0x80, 0x10, 0x81, 0x00, 0x82, 0x00, 0x83, 0x00, 0x84, 0x00,
+ 0x85, 0x00, 0x86, 0x00, 0x87, 0x00, 0x88, 0x04, 0x89, 0x10, 0x8A, 0x00,
+ 0x8B, 0x00, 0x90, 0x05, 0x91, 0x0C, 0x92, 0x18, 0x93, 0x00, 0x96, 0x18,
+ 0x9A, 0x30, 0x9C, 0x2D, 0x9D, 0x00, 0xA0, 0x00, 0xA1, 0x00, 0xA2, 0x00,
+ 0xA4, 0x50, 0xA5, 0x50, 0xA6, 0xF0, 0xA7, 0xF0, 0xA8, 0x19, 0xA9, 0x18,
+ 0xAA, 0x64, 0xAB, 0x64, 0xB0, 0x64, 0xB1, 0x64, 0xB4, 0xA4, 0xB5, 0xA5,
+ 0xB8, 0x19, 0xB9, 0x18, 0xBC, 0x09, 0xBD, 0x09, 0xC0, 0x00, 0xC1, 0x02,
+ 0xC4, 0x00, 0xC5, 0x00, 0xC8, 0x00, 0xC9, 0x08, 0xCA, 0x08, 0xCE, 0x00,
+ 0xCF, 0x00, 0xD0, 0x00, 0xD1, 0x00, 0xD2, 0x00, 0xD8, 0x00, 0xD9, 0x00,
+ 0xDA, 0x00, 0xDB, 0x00, 0xDC, 0x00, 0xDD, 0x00, 0x38, 0x46, 0x97, 0xA0,
+ 0x97, 0x20, 0x97, 0xA0,
+};
+
+static
+uchar bt812init[] = { /* bt812 initializations */
+ 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0xC0,
+ 0x04, 0x08, 0x05, 0x00, 0x06, 0x40, 0x07, 0x00, 0x08, 0x10,
+ 0x09, 0x90, 0x0A, 0x80, 0x0B, 0x00, 0x0C, 0x0C, 0x0D, 0x03,
+ 0x0E, 0x66, 0x0F, 0x00, 0x10, 0x80, 0x11, 0x02, 0x12, 0x16,
+ 0x13, 0x00, 0x14, 0xE5, 0x15, 0x01, 0x16, 0xAB, 0x17, 0xAA,
+ 0x18, 0x12, 0x19, 0x51, 0x1A, 0x46, 0x1B, 0x00, 0x1C, 0x00,
+ 0x1D, 0x37,
+};
+
+static ushort actpixs = 720;
+static ulong Hdesired = 13500000L;
+
+/* NTSC-M NTSC-443 EXTERNAL */
+static ushort horzfreq[] = { 15734, 15625, 0 };
+static ushort Vdelay[] = { 22, 25, 25 };
+static ushort s2b[] = { 90, 90, 0 };
+static ushort actlines[] = { 485, 485, 575 };
+static ulong subcarfreq[] = { 3579545, 4433619, 4433619 };
+
+static
+unsigned int framewidth[5][4] = {
+ 1024, 512, 512, 512, /* mode 0 - single, double, single, quad */
+ 1536, 768, 768, 384, /* mode 1 - single, double, single, quad */
+ 2048, 1024, 1024, 512, /* mode 2 - single, double, single, quad */
+ 1024, 512, 512, 512, /* mode 3 - single, double, single, quad */
+ 1536, 768, 768, 384 /* mode 4 - single, double, single, quad */
+};
+
+static
+unsigned int frameheight[5][4] = {
+ 512, 512, 1024, 512, /* mode 0 - single, double, single, quad */
+ 512, 512, 1024, 512, /* mode 1 - single, double, single, quad */
+ 512, 512, 1024, 512, /* mode 2 - single, double, single, quad */
+ 512, 512, 1024, 512, /* mode 3 - single, double, single, quad */
+ 512, 512, 1024, 256 /* mode 4 - single, double, single, quad */
+};
+
+static
+uchar horzfilter[] = { 3, 3, 2, 2, 1, 1, 0, 0 };
+
+static
+uchar interleave[] = { 2, 3, 4, 2, 3 };
+
+#define ADJUST(n) (((n) * hrsmult + hrsdiv - 1) / hrsdiv)
+
+static int q = 100;
+static int ilv = 2;
+static int hrsmult = 1;
+static int hrsdiv = 2;
+
+static ushort panmask[] = { 0xFFFE, 0xFFFC, 0xFFFF, 0xFFFF, 0xFFFE };
+
+
+static void
+cropwindow(int left, int right, int top, int bottom)
+{
+ top &= 0x3FE;
+ bottom &= 0x3FE;
+ setreg(InputHorzCropLeftA, left);
+ setreg(InputHorzCropLeftB, left >> 8);
+ setreg(InputHorzCropRightA, right);
+ setreg(InputHorzCropRightB, right >> 8);
+ setreg(InputHorzCropTopA, top);
+ setreg(InputHorzCropTopB, top >> 8);
+ setreg(InputHorzCropBottomA, bottom);
+ setreg(InputHorzCropBottomB, bottom >> 8);
+}
+
+static void
+setinputformat(int format)
+{
+ ushort hclock, hclockdesired;
+ ulong subcarrier;
+ int cr7;
+
+ cr7 = getbt812reg(7) & ~Bt7TriState;
+ if (format == External)
+ cr7 |= Bt7TriState;
+ setbt812reg(7, cr7);
+ setbt812reg(5, getbt812reg(5) & 2);
+
+ hclock = (xtalfreq >> 1) / horzfreq[format];
+ setbt812regpair(0x0C, hclock);
+ setbt812regpair(0x0E,
+ (ushort)(s2b[format] * (Hdesired / 10) / 1000000L) | 1);
+ setbt812regpair(0x10, actpixs);
+ setbt812regpair(0x12, Vdelay[format]);
+ setbt812regpair(0x14, actlines[format]);
+
+ subcarrier = (ulong)
+ ((((long long)subcarfreq[format] * 0x1000000) / xtalfreq + 1) / 2);
+ setbt812regpair(0x16, (int)(subcarrier & 0xFFFF)); /* subcarrier */
+ setbt812reg(0x18, (int)(subcarrier >> 16));
+
+ setbt812reg(0x19, (uchar)(((xtalfreq / 200) * 675) / 1000000L + 8));
+ setbt812reg(0x1A, (uchar)((xtalfreq * 65) / 20000000L - 10));
+ hclockdesired = (ushort) (Hdesired / horzfreq[format]);
+ setbt812regpair(0x1B,
+ (ushort)(((hclock - hclockdesired) * 65536L) / hclockdesired));
+}
+
+static ushort
+vgadivider(void)
+{
+ ushort horztotal;
+
+ outb(VGAIndex, VGAHorzTotal);
+ horztotal = (inb(VGAData) << 3) + 40;
+ if (horztotal > ScreenWidth && horztotal < ((ScreenWidth * 3 ) / 2))
+ return horztotal;
+ else
+ return (ScreenWidth * 5) / 4;
+}
+
+static void
+videoinit(void)
+{
+ int i, reg, width, tuner;
+
+ /* early PLL smart lock initialization */
+ if (ScreenWidth == 640) {
+ slreg1 = Window|HSyncLow|VSyncLow;
+ slreg2 = 0x0D;
+ } else {
+ slreg1 = Window;
+ slreg2 = 0x0C;
+ }
+ outb(port+CompressReg, 2);
+ outb(port+SLReg1, slreg1);
+ outb(port+SLReg2, slreg2);
+ smartlockdiv((vgadivider() * hrsmult)/hrsdiv, vcogain, phdetgain, 2, 1);
+
+ /* program the VxP-500 chip (disables video) */
+ waitforretrace();
+ for (i = 0; i < sizeof(vxp500init); i += 2)
+ setreg(vxp500init[i], vxp500init[i+1]);
+
+ /* set memory base for frame capture */
+ setreg(MemoryWindowBaseAddrA, MemAddr >> 14);
+ setreg(MemoryWindowBaseAddrB, ((MemAddr >> 22) & 3) | (MemSize << 2));
+ setreg(MemoryPageReg, 0);
+
+ /* generic 422 decoder, mode 3 and 4 */
+ setreg(MemoryConfReg, ilv+1);
+
+ setreg(AcquisitionAddrA, 0);
+ setreg(AcquisitionAddrB, 0);
+ setreg(AcquisitionAddrC, 0);
+
+ /* program VxP-500 for correct sync polarity */
+ reg = ScreenWidth > 1023 ? 0x01 : 0x00;
+ reg |= (vsync << 1) | (hsync << 2);
+ setvgareg(reg);
+ setreg(VGAControl, reg);
+
+ setreg(VideoBufferLayoutControl, 0); /* for ilv = 2 */
+
+ /* set sync polarities to get proper blanking */
+ if (vsync)
+ slreg1 |= VSyncLow;
+ if (!hsync) {
+ slreg1 ^= HSyncLow;
+ setreg(VGAControl, reg | 4);
+ }
+ outb(port+SLReg1, slreg1);
+
+ if ((slreg1 & PixelClk) == 0) { /* smart lock active */
+ enablecolorkey(1);
+ setreg(VGAControl, getreg(VGAControl) & 6);
+ } else
+ enablecolorkey(0);
+
+ /* color key initializations */
+ if ((slreg1 & PixelClk) == 0)
+ setreg(VGAControl, getreg(VGAControl) & 7);
+
+ /* initialize Bt812 */
+ for (i = 0; i < sizeof(bt812init); i += 2)
+ setbt812reg(bt812init[i], bt812init[i+1]);
+
+ /* figure out clock source (Xtal or Oscillator) and revision */
+ setbt812reg(6, 0x40);
+ reg = getreg(InputFieldPixelBufStatus) & 3;
+ if ((getreg(InputFieldPixelBufStatus) & 3) == reg) {
+ /* crystal - could be revision PP if R34 is installed */
+ setbt812reg(6, 0x00);
+ reg = inb(port+SLReg1);
+ if (reg & 0x20) {
+ if ((reg & 0xE0) == 0xE0)
+ boardrev = HighQ;
+ else
+ boardrev = RevisionA;
+ } else
+ boardrev = RevisionPP;
+ } else /* revision A or newer with 27 MHz oscillator */
+ boardrev = RevisionA;
+
+ /* figure out xtal frequency */
+ if (xtalfreq == 0) {
+ if (boardrev == RevisionPP) {
+ tuner = (inb(port+SLReg1) >> 6) & 3;
+ if (tuner == 0) /* NTSC */
+ xtalfreq = 24545400L;
+ else
+ xtalfreq = 29500000L;
+ } else if (boardrev == HighQ)
+ xtalfreq = 29500000L;
+ else
+ xtalfreq = 27000000L;
+ }
+
+// print("Hauppage revision %d (xtalfreq %ld)\n", boardrev, xtalfreq);
+
+ /* on RevPP boards set early sync, on rev A and newer clear it */
+ if (boardrev == RevisionPP)
+ setreg(InputVideoConfA, getreg(InputVideoConfA) | 4);
+ else
+ setreg(InputVideoConfA, getreg(InputVideoConfA) & ~4);
+
+ switch (xtalfreq) {
+ case 24545400L:
+ actpixs = 640;
+ break;
+ case 29500000L:
+ actpixs = 768;
+ break;
+ default:
+ actpixs = 720;
+ break;
+ }
+
+ /* set crop window (these values are for NTSC!) */
+ if (boardrev == RevisionPP) {
+ Hdesired = xtalfreq / 2;
+ cropleft = (NTSCCropLeft * ((Hdesired / 10))) / 1000000L;
+ cropright = (NTSCCropRight * ((Hdesired / 10))) / 1000000L;
+ } else {
+ cropleft = actpixs / 100;
+ cropright = actpixs - cropleft;
+ }
+ width = ((cropright - cropleft + ilv) / ilv) * ilv;
+ cropright = cropleft + width + 1;
+ croptop = 26;
+ cropbottom = 505;
+ cropwindow(cropleft, cropright, croptop, cropbottom);
+
+ /* set input format */
+ setinputformat(NTSC_M);
+ setsvideo(0);
+}
+
+static void
+panwindow(Point p)
+{
+ int memmode, ilv, frw;
+ ulong pos;
+
+ memmode = getreg(MemoryConfReg) & 7;
+ ilv = interleave[memmode];
+ frw = framewidth[memmode][getreg(VideoBufferLayoutControl) & 3];
+
+ pos = (p.y * (frw/ilv)) + ((p.x/ilv) & panmask[memmode]);
+ setreg(DisplayViewPortStartAddrA, (uchar) pos);
+ setreg(DisplayViewPortStartAddrB, (uchar) (pos >> 8));
+ setreg(DisplayViewPortStartAddrC, (uchar) (pos >> 16) & 0x03);
+ updateshadowregs();
+}
+
+static int
+testqfactor(void)
+{
+ ulong timo;
+ int reg;
+
+ waitvideoframe();
+ for (reg = 0, timo = ~0; timo; timo--) {
+ reg |= getreg(InputFieldPixelBufStatus);
+ if (reg & 0xE) break;
+ }
+ if (reg & 0xC) return 0;
+
+ waitvideoframe();
+ for (reg = 0, timo = ~0; timo; timo--) {
+ reg |= getreg(InputFieldPixelBufStatus);
+ if (reg & 0xE) break;
+ }
+ return (reg & 0xC) == 0;
+}
+
+static void
+newwindow(Rectangle r)
+{
+ unsigned ww, wh, dx, dy, xs, ys, xe, ye;
+ unsigned scalex, scaley;
+ int frwidth, frheight, vidwidth, vidheight;
+ int memmode, layout;
+ int width, height;
+ int filter, changed, val;
+
+ changed = r.min.x != window.min.x || r.min.y != window.min.y ||
+ r.max.x != window.max.x || r.max.y != window.max.y;
+ if (changed) window = r;
+
+ if (r.min.x < 0) r.min.x = 0;
+ if (r.max.x > ScreenWidth) r.max.x = ScreenWidth;
+ if (r.min.y < 0) r.min.y = 0;
+ if (r.max.y > ScreenHeight) r.max.y = ScreenHeight;
+
+ if ((dx = r.max.x - r.min.x) <= 0) dx = 1;
+ if ((dy = r.max.y - r.min.y) <= 0) dy = 1;
+
+ wh = dy;
+ ww = dx = ADJUST(dx);
+ r.min.x = (r.min.x * hrsmult) / hrsdiv;
+
+ memmode = getreg(MemoryConfReg) & 7;
+ layout = getreg(VideoBufferLayoutControl) & 3;
+ vidwidth = cropright - cropleft + 1;
+ vidheight = (cropbottom & 0x3FE) - (croptop & 0x3FE) + 1;
+ frwidth = min(framewidth[memmode][layout], vidwidth);
+ frheight = min(frameheight[memmode][layout], vidheight);
+
+ /* round up scale width to nearest multiple of interleave factor */
+ dx = ((ulong)dx * q) / 100;
+ dx = ilv * ((dx + ilv - 1) / ilv);
+
+ scalex = (((ulong)dx * 1024L) + vidwidth - 2) / (vidwidth - 1);
+ if (dy > frheight) dy = frheight - 1;
+ scaley = (((ulong)dy * 1024L) + vidheight - 2) / (vidheight - 1);
+
+ setreg(InputHorzScaleControlA, (scalex << 6) & 0xC0);
+ setreg(InputHorzScaleControlB, (scalex >> 2) & 0xFF);
+ setreg(InputVertScaleControlA, (scaley << 6) & 0xC0);
+ setreg(InputVertScaleControlB, (scaley >> 2) & 0xFF);
+
+ /* turn on horizontal filtering if we are scaling down */
+ setreg(InputHorzFilter, horzfilter[((scalex - 1) >> 7) & 7]);
+
+ /* set vertical interpolation */
+ filter = scaley > 512 ? (ScreenWidth == 640 ? 0x44 : 0xC5) : 0x46; /* magic */
+ if ((getreg(InputVertInterpolControl) & 0x1F) != (filter & 0x1F)) {
+ setreg(ISAControl, 0x80);
+ setreg(InputVertInterpolControl, filter & 0x1F);
+ setreg(ISAControl, 0x42);
+ }
+ setreg(AcquisitionControl, ((filter >> 6) ^ 3) | 0x04);
+
+ /* set viewport position and size */
+ width = ((ulong)ww * q) / 100;
+ if (width >= frwidth - ilv)
+ width = frwidth - ilv;
+ width = ((width + ilv - 1) / ilv) + 2;
+
+ height = ((ulong)wh * dy + wh - 1) / wh;
+ if (height >= frheight)
+ height = frheight - 3;
+ height += 2;
+
+ xs = r.min.x + XCorrection;
+ if (xs < 0) xs = 2;
+ ys = r.min.y + YCorrection;
+ if (ys < 0) ys = 2;
+ if (ScreenWidth > 1023) ys |= 1;
+
+ setreg(DisplayViewPortWidthA, width);
+ setreg(DisplayViewPortWidthB, width >> 8);
+ setreg(DisplayViewPortHeightA, height);
+ setreg(DisplayViewPortHeightB, height >> 8);
+ setreg(DisplayViewPortOrigTopA, ys);
+ setreg(DisplayViewPortOrigTopB, ys >> 8);
+ setreg(DisplayViewPortOrigLeftA, xs);
+ setreg(DisplayViewPortOrigLeftB, xs >> 8);
+
+ xe = r.min.x + ww - 1 + XCorrection;
+ if (xe < 0) xe = 2;
+ ye = r.min.y + wh - 1 + YCorrection;
+ if (ye < 0) ye = 2;
+
+ setreg(DisplayWindowLeftA, xs);
+ setreg(DisplayWindowLeftB, xs >> 8);
+ setreg(DisplayWindowRightA, xe);
+ setreg(DisplayWindowRightB, xe >> 8);
+ setreg(DisplayWindowTopA, ys);
+ setreg(DisplayWindowTopB, ys >> 8);
+ setreg(DisplayWindowBottomA, ye);
+ setreg(DisplayWindowBottomB, ye >> 8);
+
+ if (dx < ww) { /* horizontal zoom */
+ int zoom = ((ulong) (dx - 1) * 2048) / ww;
+ setreg(OutputProcControlA, getreg(OutputProcControlA) | 6);
+ setreg(OutputHorzZoomControlA, zoom);
+ setreg(OutputHorzZoomControlB, zoom >> 8);
+ } else
+ setreg(OutputProcControlA, getreg(OutputProcControlA) & 0xF9);
+
+ if (dy < wh) { /* vertical zoom */
+ int zoom = ((ulong) (dy - 1) * 2048) / wh;
+ setreg(OutputProcControlB, getreg(OutputProcControlB) | 1);
+ setreg(OutputVertZoomControlA, zoom);
+ setreg(OutputVertZoomControlB, zoom >> 8);
+ } else
+ setreg(OutputProcControlB, getreg(OutputProcControlB) & 0xFE);
+
+ setreg(OutputProcControlB, getreg(OutputProcControlB) | 0x20);
+ updateshadowregs();
+
+ if (changed) {
+ setreg(OutputProcControlA, getreg(OutputProcControlA) & 0xDF);
+ } else {
+ val = getreg(InputFieldPixelBufStatus);
+ USED(val);
+ }
+
+ panwindow(Pt(0, 0));
+}
+
+static void
+createwindow(Rectangle r)
+{
+ for (q = 100; q >= 30; q -= 10) {
+ newwindow(r);
+ if (testqfactor())
+ break;
+ }
+ enablevideo();
+}
+
+static void
+setcontrols(int index, uchar val)
+{
+ switch (index) {
+ case Vxp500Brightness:
+ setreg(BrightnessControl, (127L * val) / 100);
+ updateshadowregs();
+ break;
+ case Vxp500Contrast:
+ setreg(ContrastControl, (15L * val) / 100);
+ updateshadowregs();
+ break;
+ case Vxp500Saturation:
+ setreg(SaturationControl, (15L * val) / 100);
+ updateshadowregs();
+ break;
+ case Bt812Brightness:
+ setbt812reg(0x08, ((126L * val) / 100) & 0xFE);
+ break;
+ case Bt812Contrast:
+ setbt812reg(0x09, ((254L * val) / 100) & 0xFE);
+ break;
+ case Bt812Saturation:
+ setbt812reg(0x0A, ((254L * val) / 100) & 0xFE);
+ break;
+ case Bt812Hue:
+ setbt812reg(0x0B, ((254L * val) / 100) & 0xFE);
+ break;
+ case Bt812BW:
+ setbt812reg(0x05, (getbt812reg(0x5) & ~2) | ((val << 1) & 2));
+ break;
+ }
+}
+
+static void
+enablememwindow(void)
+{
+ setreg(MemoryWindowBaseAddrB, getreg(MemoryWindowBaseAddrB) | 0x20);
+}
+
+static void
+disablememwindow(void)
+{
+ setreg(MemoryWindowBaseAddrB, getreg(MemoryWindowBaseAddrB) & ~0x20);
+}
+
+volatile static ushort *fb = (ushort *)EISA(MemAddr);
+static uchar yuvpadbound[] = { 4, 12, 4, 2, 6 };
+
+/*
+ * Capture a frame in UY0, VY1 format
+ */
+static void *
+saveframe(int *nb)
+{
+ int memmode, layout, ilv;
+ int frwidth, frheight;
+ int bound, n, val, toggle;
+ unsigned save58, save6C;
+ int x, y, w, h, width, height;
+ ulong pos, size;
+ char *p;
+ static void *frame = 0;
+ static ulong framesize = 0;
+
+ width = capwindow.max.x - capwindow.min.x;
+ height = capwindow.max.y - capwindow.min.y;
+
+ memmode = getreg(MemoryConfReg) & 7;
+ if (memmode <= 2) {
+ print("devtv: cannot handle YUV411\n");
+ error(Egreg); /* actually, Eleendert */
+ }
+ layout = getreg(VideoBufferLayoutControl) & 3;
+ ilv = interleave[memmode];
+ frwidth = framewidth[memmode][layout];
+ frheight = frameheight[memmode][layout];
+
+ pos = getreg(AcquisitionAddrA) +
+ (getreg(AcquisitionAddrB) << 8) + (getreg(AcquisitionAddrC) & 3) << 16;
+
+ x = capwindow.min.x + (pos % frwidth);
+ y = capwindow.min.y + (pos / frwidth);
+ if (x > frwidth || y > frheight)
+ return 0;
+ if (x + width > frwidth)
+ width = frwidth - x;
+ if (y + height > frheight)
+ height = frheight - y;
+
+ pos = y * (frwidth / ilv) + (x / ilv);
+
+ /* compute padding for each scan line */
+ bound = yuvpadbound[memmode];
+ switch (bound) {
+ case 2:
+ width = (width + 1) & ~1;
+ break;
+ case 4:
+ width = (width + 3) & ~3;
+ break;
+ default:
+ width = (width + (bound - 1)) / bound;
+ break;
+ }
+
+ size = width * height * sizeof(ushort);
+ if (size != framesize) {
+ framesize = 0;
+ if (frame)
+ free(frame);
+ frame = malloc(size + 256);
+ }
+ if (frame == 0)
+ return 0;
+
+ memset(frame, 0, size + 256);
+
+ framesize = size;
+ p = (char *) frame + snprint(frame, 256,
+ "TYPE=ccir601\nWINDOW=%d %d %d %d\n\n",
+ capwindow.min.x, capwindow.min.y,
+ capwindow.min.x+width, capwindow.min.y+height);
+
+ freeze(1);
+
+ save58 = getreg(InputVertInterpolControl);
+ save6C = getreg(AcquisitionControl);
+
+ waitforretrace();
+ setreg(ISAControl, 0xC0); /* global reset */
+ setreg(InputVertInterpolControl, 0x0D);
+ setreg(AcquisitionControl, 0x04);
+ setreg(CaptureControl, 0x80); /* set capture mode */
+ setreg(VideoInputFrameBufDepthA, 0xFF);
+ setreg(VideoInputFrameBufDepthB, 0x03);
+ setreg(InputVideoConfA, getreg(InputVideoConfA) | 0x40);
+ setreg(ISAControl, 0x44); /* tight decode, global reset off */
+
+ setreg(CaptureViewPortAddrA, (int) pos & 0xFF);
+ setreg(CaptureViewPortAddrB, (int) (pos >> 8) & 0xFF);
+ setreg(CaptureViewPortAddrC, (int) (pos >> 16) & 0x03);
+ n = (width / ilv) - 1;
+ setreg(CaptureViewPortWidthA, n & 0xFF);
+ setreg(CaptureViewPortWidthB, n >> 8);
+ setreg(CaptureViewPortHeightA, (height-1) & 0xFF);
+ setreg(CaptureViewPortHeightB, (height-1) >> 8);
+ setreg(CapturePixelBufLow, 0x04); /* pix buffer low */
+ setreg(CapturePixelBufHigh, 0x0E); /* pix buffer high */
+ setreg(CaptureMultiBufDepthA, 0xFF); /* multi buffer depth maximum */
+ setreg(CaptureMultiBufDepthB, 0x03);
+ updateshadowregs();
+
+ setreg(CaptureControl, 0x90); /* capture reset */
+ val = getreg(InputFieldPixelBufStatus); /* clear read status */
+ USED(val);
+
+ toggle = !(getreg(OutputProcControlA) & 0x01) ? 0x8000 : 0x0000;
+ setreg(CaptureControl, 0xC0); /* capture enable, active */
+
+ while ((getreg(InputFieldPixelBufStatus) & 0x10) == 0)
+ /* wait for capture FIFO to become ready */;
+
+ enablememwindow();
+ for (h = height; h > 0; h--) {
+ for (w = width; w > 0; w -= 2) {
+ ushort uy0 = swab16(fb[0]) ^ toggle;
+ ushort vy1 = swab16(fb[1]) ^ toggle;
+ /* unfortunately p may not be properly aligned */
+ *p++ = uy0 >> 8;
+ *p++ = uy0;
+ *p++ = vy1 >> 8;
+ *p++ = vy1;
+ }
+ }
+ disablememwindow();
+
+ waitforretrace();
+ setreg(ISAControl, 0xC0); /* global reset */
+ setreg(CaptureControl, 0); /* clear capture mode */
+ setreg(InputVertInterpolControl, save58);
+ setreg(AcquisitionControl, save6C);
+ setreg(InputVideoConfA, getreg(InputVideoConfA) | 0x40);
+ setreg(ISAControl, 0x40); /* clear global reset */
+ updateshadowregs();
+
+ freeze(0);
+
+ *nb = p - (char *) frame;
+ return frame;
+}