patch-2.4.21 linux-2.4.21/drivers/video/matrox/matroxfb_g450.c

Next file: linux-2.4.21/drivers/video/matrox/matroxfb_g450.h
Previous file: linux-2.4.21/drivers/video/matrox/matroxfb_crtc2.h
Back to the patch index
Back to the overall index

diff -urN linux-2.4.20/drivers/video/matrox/matroxfb_g450.c linux-2.4.21/drivers/video/matrox/matroxfb_g450.c
@@ -2,145 +2,634 @@
  *
  * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450.
  *
- * (c) 1998-2001 Petr Vandrovec <vandrove@vc.cvut.cz>
+ * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz>
  *
  * Portions Copyright (c) 2001 Matrox Graphics Inc.
  *
- * Version: 1.62 2001/11/29
+ * Version: 1.64 2002/06/02
  *
  * See matroxfb_base.c for contributors.
  *
  */
 
-#include "matroxfb_g450.h"
+#include "matroxfb_base.h"
 #include "matroxfb_misc.h"
 #include "matroxfb_DAC1064.h"
 #include "g450_pll.h"
 #include <linux/matroxfb.h>
 #include <asm/uaccess.h>
+#include <asm/div64.h>
 
-static int matroxfb_g450_compute(void* md, struct my_timming* mt) {
-#define m2info ((struct matroxfb_g450_info*)md)
-#define minfo (m2info->primary_dev)
-	ACCESS_FBINFO(hw).vidclk = mt->pixclock;
-#undef minfo
-#undef m2info
-	return 0;
+/* Definition of the various controls */
+struct mctl {
+	struct matroxfb_queryctrl desc;
+	size_t control;
+};
+
+#define BLMIN	0xF3
+#define WLMAX	0x3FF
+
+static const struct mctl g450_controls[] =
+{	{ { MATROXFB_CID_BRIGHTNESS,
+	  "brightness",
+	  0, WLMAX-BLMIN, 1, 370-BLMIN, 
+	  MATROXFB_CTRL_TYPE_INTEGER, 0, 0,
+	  "picture"
+	}, offsetof(struct matrox_fb_info, altout.tvo_params.brightness) },
+	{ { MATROXFB_CID_CONTRAST,
+	  "contrast",
+	  0, 1023, 1, 127, 
+	  MATROXFB_CTRL_TYPE_INTEGER, 0, 0,
+	  "picture"
+	}, offsetof(struct matrox_fb_info, altout.tvo_params.contrast) },
+	{ { MATROXFB_CID_SATURATION,
+	  "saturation",
+	  0, 255, 1, 165, 
+	  MATROXFB_CTRL_TYPE_INTEGER, 0, 0,
+	  "picture"
+	}, offsetof(struct matrox_fb_info, altout.tvo_params.saturation) },
+	{ { MATROXFB_CID_HUE,
+	  "hue",
+	  0, 255, 1, 0, 
+	  MATROXFB_CTRL_TYPE_INTEGER, 0, 0,
+	  "picture"
+	}, offsetof(struct matrox_fb_info, altout.tvo_params.hue) },
+	{ { MATROXFB_CID_TESTOUT,
+	  "test output",
+	  0, 1, 1, 0, 
+	  MATROXFB_CTRL_TYPE_BOOLEAN, 0, 0,
+	  "picture"
+	}, offsetof(struct matrox_fb_info, altout.tvo_params.testout) },
+};
+
+#define G450CTRLS (sizeof(g450_controls)/sizeof(g450_controls[0]))
+
+/* Return: positive number: id found
+           -EINVAL:         id not found, return failure
+	   -ENOENT:         id not found, create fake disabled control */
+static int get_ctrl_id(__u32 v4l2_id) {
+	int i;
+
+	for (i = 0; i < G450CTRLS; i++) {
+		if (v4l2_id < g450_controls[i].desc.id) {
+			if (g450_controls[i].desc.id == 0x08000000) {
+				return -EINVAL;
+			}
+			return -ENOENT;
+		}
+		if (v4l2_id == g450_controls[i].desc.id) {
+			return i;
+		}
+	}
+	return -EINVAL;
 }
 
-static int matroxfb_g450_program(void* md) {
-#define m2info ((struct matroxfb_g450_info*)md)
-#define minfo (m2info->primary_dev)
-	matroxfb_g450_setclk(PMINFO ACCESS_FBINFO(hw).vidclk, M_VIDEO_PLL);
-#undef minfo
-#undef m2info	
-	return 0;
+static inline int* get_ctrl_ptr(WPMINFO unsigned int idx) {
+	return (int*)((char*)MINFO + g450_controls[idx].control);
 }
 
-static int matroxfb_g450_start(void* md) {
-	return 0;
+static void tvo_fill_defaults(WPMINFO2) {
+	unsigned int i;
+	
+	for (i = 0; i < G450CTRLS; i++) {
+		*get_ctrl_ptr(PMINFO i) = g450_controls[i].desc.default_value;
+	}
 }
 
-static void matroxfb_g450_incuse(void* md) {
-	MOD_INC_USE_COUNT;
+static int cve2_get_reg(WPMINFO int reg) {
+	unsigned long flags;
+	int val;
+	
+	matroxfb_DAC_lock_irqsave(flags);
+	matroxfb_DAC_out(PMINFO 0x87, reg);
+	val = matroxfb_DAC_in(PMINFO 0x88);
+	matroxfb_DAC_unlock_irqrestore(flags);
+	return val;
 }
 
-static void matroxfb_g450_decuse(void* md) {
-	MOD_DEC_USE_COUNT;
+static void cve2_set_reg(WPMINFO int reg, int val) {
+	unsigned long flags;
+
+	matroxfb_DAC_lock_irqsave(flags);
+	matroxfb_DAC_out(PMINFO 0x87, reg);
+	matroxfb_DAC_out(PMINFO 0x88, val);
+	matroxfb_DAC_unlock_irqrestore(flags);
+}
+
+static void cve2_set_reg10(WPMINFO int reg, int val) {
+	unsigned long flags;
+
+	matroxfb_DAC_lock_irqsave(flags);
+	matroxfb_DAC_out(PMINFO 0x87, reg);
+	matroxfb_DAC_out(PMINFO 0x88, val >> 2);
+	matroxfb_DAC_out(PMINFO 0x87, reg + 1);
+	matroxfb_DAC_out(PMINFO 0x88, val & 3);
+	matroxfb_DAC_unlock_irqrestore(flags);
+}
+
+static void g450_compute_bwlevel(CPMINFO int *bl, int *wl) {
+	const int b = ACCESS_FBINFO(altout.tvo_params.brightness) + BLMIN;
+	const int c = ACCESS_FBINFO(altout.tvo_params.contrast);
+
+	*bl = max(b - c, BLMIN);
+	*wl = min(b + c, WLMAX);
 }
 
-static int matroxfb_g450_set_mode(void* md, u_int32_t arg) {
-	if (arg == MATROXFB_OUTPUT_MODE_MONITOR) {
-		return 1;
+static int g450_query_ctrl(void* md, struct matroxfb_queryctrl *p) {
+	int i;
+	
+	i = get_ctrl_id(p->id);
+	if (i >= 0) {
+		*p = g450_controls[i].desc;
+		return 0;
+	}
+	if (i == -ENOENT) {
+		static const struct matroxfb_queryctrl disctrl = 
+			{ 0, "", 0, 0, 0, 0, 0, 1, 1, "Disabled" };
+			
+		i = p->id;
+		*p = disctrl;
+		p->id = i;
+		sprintf(p->name, "Ctrl #%08X", i);
+		return 0;
 	}
 	return -EINVAL;
 }
 
-static int matroxfb_g450_get_mode(void* md, u_int32_t* arg) {
-	*arg = MATROXFB_OUTPUT_MODE_MONITOR;
+static int g450_set_ctrl(void* md, struct matroxfb_control *p) {
+	int i;
+	MINFO_FROM(md);
+	
+	i = get_ctrl_id(p->id);
+	if (i < 0) return -EINVAL;
+
+	/*
+	 * Check if changed.
+	 */
+	if (p->value == *get_ctrl_ptr(PMINFO i)) return 0;
+
+	/*
+	 * Check limits.
+	 */
+	if (p->value > g450_controls[i].desc.maximum) return -EINVAL;
+	if (p->value < g450_controls[i].desc.minimum) return -EINVAL;
+
+	/*
+	 * Store new value.
+	 */
+	*get_ctrl_ptr(PMINFO i) = p->value;
+
+	switch (p->id) {
+		case MATROXFB_CID_BRIGHTNESS:
+		case MATROXFB_CID_CONTRAST:
+			{
+				int blacklevel, whitelevel;
+				g450_compute_bwlevel(PMINFO &blacklevel, &whitelevel);
+				cve2_set_reg10(PMINFO 0x0e, blacklevel);
+				cve2_set_reg10(PMINFO 0x1e, whitelevel);
+			}
+			break;
+		case MATROXFB_CID_SATURATION:
+			cve2_set_reg(PMINFO 0x20, p->value);
+			cve2_set_reg(PMINFO 0x22, p->value);
+			break;
+		case MATROXFB_CID_HUE:
+			cve2_set_reg(PMINFO 0x25, p->value);
+			break;
+		case MATROXFB_CID_TESTOUT:
+			{
+				unsigned char val = cve2_get_reg (PMINFO 0x05);
+				if (p->value) val |=  0x02;
+				else          val &= ~0x02;
+				cve2_set_reg(PMINFO 0x05, val);
+			}
+			break;
+	}
+	
+
+	return 0;
+}
+
+static int g450_get_ctrl(void* md, struct matroxfb_control *p) {
+	int i;
+	MINFO_FROM(md);
+	
+	i = get_ctrl_id(p->id);
+	if (i < 0) return -EINVAL;
+	p->value = *get_ctrl_ptr(PMINFO i);
 	return 0;
 }
 
-static struct matrox_altout matroxfb_g450_altout = {
-	matroxfb_g450_compute,
-	matroxfb_g450_program,
-	matroxfb_g450_start,
-	matroxfb_g450_incuse,
-	matroxfb_g450_decuse,
-	matroxfb_g450_set_mode,
-	matroxfb_g450_get_mode
+struct output_desc {
+	unsigned int	h_vis;
+	unsigned int	h_f_porch;
+	unsigned int	h_sync;
+	unsigned int	h_b_porch;
+	unsigned long long int	chromasc;
+	unsigned int	burst;
+	unsigned int	v_total;
 };
 
-static int matroxfb_g450_connect(struct matroxfb_g450_info* m2info) {
-	MINFO_FROM(m2info->primary_dev);
+static void computeRegs(WPMINFO struct mavenregs* r, struct my_timming* mt, const struct output_desc* outd) {
+	u_int32_t chromasc;
+	u_int32_t hlen;
+	u_int32_t hsl;
+	u_int32_t hbp;
+	u_int32_t hfp;
+	u_int32_t hvis;
+	unsigned int pixclock;
+	unsigned long long piic;
+	int mnp;
+	int over;
+	
+	r->regs[0x80] = 0x03;	/* | 0x40 for SCART */
 
-	down_write(&ACCESS_FBINFO(altout.lock));
-	ACCESS_FBINFO(altout.device) = m2info;
-	ACCESS_FBINFO(altout.output) = &matroxfb_g450_altout;
-	up_write(&ACCESS_FBINFO(altout.lock));
-	ACCESS_FBINFO(output.all) |= MATROXFB_OUTPUT_CONN_SECONDARY;
-	matroxfb_switch(ACCESS_FBINFO(currcon), (struct fb_info*)MINFO);	
-	return 0;
+	hvis = ((mt->HDisplay << 1) + 3) & ~3;
+	
+	if (hvis >= 2048) {
+		hvis = 2044;
+	}
+	
+	piic = 1000000000ULL * hvis;
+	do_div(piic, outd->h_vis);
+
+	dprintk(KERN_DEBUG "Want %u kHz pixclock\n", (unsigned int)piic);
+	
+	mnp = matroxfb_g450_setclk(PMINFO piic, M_VIDEO_PLL);
+	
+	mt->mnp = mnp;
+	mt->pixclock = g450_mnp2f(PMINFO mnp);
+
+	dprintk(KERN_DEBUG "MNP=%08X\n", mnp);
+
+	pixclock = 1000000000U / mt->pixclock;
+
+	dprintk(KERN_DEBUG "Got %u ps pixclock\n", pixclock);
+
+	piic = outd->chromasc;
+	do_div(piic, mt->pixclock);
+	chromasc = piic;
+	
+	dprintk(KERN_DEBUG "Chroma is %08X\n", chromasc);
+
+	r->regs[0] = piic >> 24;
+	r->regs[1] = piic >> 16;
+	r->regs[2] = piic >>  8;
+	r->regs[3] = piic >>  0;
+	hbp = (((outd->h_b_porch + pixclock) / pixclock)) & ~1;
+	hfp = (((outd->h_f_porch + pixclock) / pixclock)) & ~1;
+	hsl = (((outd->h_sync + pixclock) / pixclock)) & ~1;
+	hlen = hvis + hfp + hsl + hbp;
+	over = hlen & 0x0F;
+	
+	dprintk(KERN_DEBUG "WL: vis=%u, hf=%u, hs=%u, hb=%u, total=%u\n", hvis, hfp, hsl, hbp, hlen);
+
+	if (over) {
+		hfp -= over;
+		hlen -= over;
+		if (over <= 2) {
+		} else if (over < 10) {
+			hfp += 4;
+			hlen += 4;
+		} else {
+			hfp += 16;
+			hlen += 16;
+		}
+	}
+
+	/* maybe cve2 has requirement 800 < hlen < 1184 */
+	r->regs[0x08] = hsl;
+	r->regs[0x09] = (outd->burst + pixclock - 1) / pixclock;	/* burst length */
+	r->regs[0x0A] = hbp;
+	r->regs[0x2C] = hfp;
+	r->regs[0x31] = hvis / 8;
+	r->regs[0x32] = hvis & 7;
+	
+	dprintk(KERN_DEBUG "PG: vis=%04X, hf=%02X, hs=%02X, hb=%02X, total=%04X\n", hvis, hfp, hsl, hbp, hlen);
+
+	r->regs[0x84] = 1;	/* x sync point */
+	r->regs[0x85] = 0;
+	hvis = hvis >> 1;
+	hlen = hlen >> 1;
+	
+	dprintk(KERN_DEBUG "hlen=%u hvis=%u\n", hlen, hvis);
+
+	mt->interlaced = 1;
+
+	mt->HDisplay = hvis & ~7;
+	mt->HSyncStart = mt->HDisplay + 8;
+	mt->HSyncEnd = (hlen & ~7) - 8;
+	mt->HTotal = hlen;
+
+	{
+		int upper;
+		unsigned int vtotal;
+		unsigned int vsyncend;
+		unsigned int vdisplay;
+		
+		vtotal = mt->VTotal;
+		vsyncend = mt->VSyncEnd;
+		vdisplay = mt->VDisplay;
+		if (vtotal < outd->v_total) {
+			unsigned int yovr = outd->v_total - vtotal;
+			
+			vsyncend += yovr >> 1;
+		} else if (vtotal > outd->v_total) {
+			vdisplay = outd->v_total - 4;
+			vsyncend = outd->v_total;
+		}
+		upper = (outd->v_total - vsyncend) >> 1;	/* in field lines */
+		r->regs[0x17] = outd->v_total / 4;
+		r->regs[0x18] = outd->v_total & 3;
+		r->regs[0x33] = upper - 1;	/* upper blanking */
+		r->regs[0x82] = upper;		/* y sync point */
+		r->regs[0x83] = upper >> 8;
+		
+		mt->VDisplay = vdisplay;
+		mt->VSyncStart = outd->v_total - 2;
+		mt->VSyncEnd = outd->v_total;
+		mt->VTotal = outd->v_total;
+	}
+}
+
+static void cve2_init_TVdata(int norm, struct mavenregs* data, const struct output_desc** outd) {
+	static const struct output_desc paloutd = {
+		.h_vis	   = 52148148,	// ps
+		.h_f_porch =  1407407,	// ps
+		.h_sync    =  4666667,	// ps
+		.h_b_porch =  5777778,	// ps
+		.chromasc  = 19042247534182ULL,	// 4433618.750 Hz
+		.burst     =  2518518,	// ps
+		.v_total   =      625,
+	};
+	static const struct output_desc ntscoutd = {
+		.h_vis     = 52888889,	// ps
+		.h_f_porch =  1333333,	// ps
+		.h_sync    =  4666667,	// ps
+		.h_b_porch =  4666667,	// ps
+		.chromasc  = 15374030659475ULL,	// 3579545.454 Hz
+		.burst     =  2418418,	// ps
+		.v_total   =      525,	// lines
+	};
+
+	static const struct mavenregs palregs = { {
+		0x2A, 0x09, 0x8A, 0xCB,	/* 00: chroma subcarrier */
+		0x00,
+		0x00,	/* test */
+		0xF9,	/* modified by code (F9 written...) */
+		0x00,	/* ? not written */
+		0x7E,	/* 08 */
+		0x44,	/* 09 */
+		0x9C,	/* 0A */
+		0x2E,	/* 0B */
+		0x21,	/* 0C */
+		0x00,	/* ? not written */
+//		0x3F, 0x03, /* 0E-0F */
+		0x3C, 0x03,
+		0x3C, 0x03, /* 10-11 */
+		0x1A,	/* 12 */
+		0x2A,	/* 13 */
+		0x1C, 0x3D, 0x14, /* 14-16 */
+		0x9C, 0x01, /* 17-18 */
+		0x00,	/* 19 */
+		0xFE,	/* 1A */
+		0x7E,	/* 1B */
+		0x60,	/* 1C */
+		0x05,	/* 1D */
+//		0x89, 0x03, /* 1E-1F */
+		0xAD, 0x03,
+//		0x72,	/* 20 */
+		0xA5,
+		0x07,	/* 21 */
+//		0x72,	/* 22 */
+		0xA5,
+		0x00,	/* 23 */
+		0x00,	/* 24 */
+		0x00,	/* 25 */
+		0x08,	/* 26 */
+		0x04,	/* 27 */
+		0x00,	/* 28 */
+		0x1A,	/* 29 */
+		0x55, 0x01, /* 2A-2B */
+		0x26,	/* 2C */
+		0x07, 0x7E, /* 2D-2E */
+		0x02, 0x54, /* 2F-30 */
+		0xB0, 0x00, /* 31-32 */
+		0x14,	/* 33 */
+		0x49,	/* 34 */
+		0x00,	/* 35 written multiple times */
+		0x00,	/* 36 not written */
+		0xA3,	/* 37 */
+		0xC8,	/* 38 */
+		0x22,	/* 39 */
+		0x02,	/* 3A */
+		0x22,	/* 3B */
+		0x3F, 0x03, /* 3C-3D */
+		0x00,	/* 3E written multiple times */
+		0x00,	/* 3F not written */
+	} };
+	static struct mavenregs ntscregs = { {
+		0x21, 0xF0, 0x7C, 0x1F,	/* 00: chroma subcarrier */
+		0x00,
+		0x00,	/* test */
+		0xF9,	/* modified by code (F9 written...) */
+		0x00,	/* ? not written */
+		0x7E,	/* 08 */
+		0x43,	/* 09 */
+		0x7E,	/* 0A */
+		0x3D,	/* 0B */
+		0x00,	/* 0C */
+		0x00,	/* ? not written */
+		0x41, 0x00, /* 0E-0F */
+		0x3C, 0x00, /* 10-11 */
+		0x17,	/* 12 */
+		0x21,	/* 13 */
+		0x1B, 0x1B, 0x24, /* 14-16 */
+		0x83, 0x01, /* 17-18 */
+		0x00,	/* 19 */
+		0x0F,	/* 1A */
+		0x0F,	/* 1B */
+		0x60,	/* 1C */
+		0x05,	/* 1D */
+		//0x89, 0x02, /* 1E-1F */
+		0xC0, 0x02, /* 1E-1F */
+		//0x5F,	/* 20 */
+		0x9C,	/* 20 */
+		0x04,	/* 21 */
+		//0x5F,	/* 22 */
+		0x9C,	/* 22 */
+		0x01,	/* 23 */
+		0x02,	/* 24 */
+		0x00,	/* 25 */
+		0x0A,	/* 26 */
+		0x05,	/* 27 */
+		0x00,	/* 28 */
+		0x10,	/* 29 */
+		0xFF, 0x03, /* 2A-2B */
+		0x24,	/* 2C */
+		0x0F, 0x78, /* 2D-2E */
+		0x00, 0x00, /* 2F-30 */
+		0xB2, 0x04, /* 31-32 */
+		0x14,	/* 33 */
+		0x02,	/* 34 */
+		0x00,	/* 35 written multiple times */
+		0x00,	/* 36 not written */
+		0xA3,	/* 37 */
+		0xC8,	/* 38 */
+		0x15,	/* 39 */
+		0x05,	/* 3A */
+		0x3B,	/* 3B */
+		0x3C, 0x00, /* 3C-3D */
+		0x00,	/* 3E written multiple times */
+		0x00,	/* never written */
+	} };
+
+	if (norm == MATROXFB_OUTPUT_MODE_PAL) {
+		*data = palregs;
+		*outd = &paloutd;
+	} else {
+  		*data = ntscregs;
+		*outd = &ntscoutd;
+	}
+ 	return;
 }
 
-static void matroxfb_g450_shutdown(struct matroxfb_g450_info* m2info) {
-	MINFO_FROM(m2info->primary_dev);
+#define LR(x) cve2_set_reg(PMINFO (x), m->regs[(x)])
+static void cve2_init_TV(WPMINFO const struct mavenregs* m) {
+	int i;
+
+	LR(0x80);
+	LR(0x82); LR(0x83);
+	LR(0x84); LR(0x85);
 	
-	if (MINFO) {
-		ACCESS_FBINFO(output.all) &= ~MATROXFB_OUTPUT_CONN_SECONDARY;
-		ACCESS_FBINFO(output.ph) &= ~MATROXFB_OUTPUT_CONN_SECONDARY;
-		ACCESS_FBINFO(output.sh) &= ~MATROXFB_OUTPUT_CONN_SECONDARY;
-		down_write(&ACCESS_FBINFO(altout.lock));
-		ACCESS_FBINFO(altout.device) = NULL;
-		ACCESS_FBINFO(altout.output) = NULL;
-		up_write(&ACCESS_FBINFO(altout.lock));
-		m2info->primary_dev = NULL;
+	cve2_set_reg(PMINFO 0x3E, 0x01);
+	
+	for (i = 0; i < 0x3E; i++) {
+		LR(i);
 	}
+	cve2_set_reg(PMINFO 0x3E, 0x00);
 }
 
-/* we do not have __setup() yet */
-static void* matroxfb_g450_probe(struct matrox_fb_info* minfo) {
-	struct matroxfb_g450_info* m2info;
+static int matroxfb_g450_compute(void* md, struct my_timming* mt) {
+	MINFO_FROM(md);
 
-	/* hardware is not G450... */
-	if (!ACCESS_FBINFO(devflags.g450dac))
-		return NULL;
-	m2info = (struct matroxfb_g450_info*)kmalloc(sizeof(*m2info), GFP_KERNEL);
-	if (!m2info) {
-		printk(KERN_ERR "matroxfb_g450: Not enough memory for G450 DAC control structs\n");
-		return NULL;
+	dprintk(KERN_DEBUG "Computing, mode=%u\n", ACCESS_FBINFO(outputs[1]).mode);
+
+	if (mt->crtc == MATROXFB_SRC_CRTC2 &&
+	    ACCESS_FBINFO(outputs[1]).mode != MATROXFB_OUTPUT_MODE_MONITOR) {
+		const struct output_desc* outd;
+
+		cve2_init_TVdata(ACCESS_FBINFO(outputs[1]).mode, &ACCESS_FBINFO(hw).maven, &outd);
+		{
+			int blacklevel, whitelevel;
+			g450_compute_bwlevel(PMINFO &blacklevel, &whitelevel);
+			ACCESS_FBINFO(hw).maven.regs[0x0E] = blacklevel >> 2;
+			ACCESS_FBINFO(hw).maven.regs[0x0F] = blacklevel & 3;
+			ACCESS_FBINFO(hw).maven.regs[0x1E] = whitelevel >> 2;
+			ACCESS_FBINFO(hw).maven.regs[0x1F] = whitelevel & 3;
+
+			ACCESS_FBINFO(hw).maven.regs[0x20] =
+			ACCESS_FBINFO(hw).maven.regs[0x22] = ACCESS_FBINFO(altout.tvo_params.saturation);
+
+			ACCESS_FBINFO(hw).maven.regs[0x25] = ACCESS_FBINFO(altout.tvo_params.hue);
+
+			if (ACCESS_FBINFO(altout.tvo_params.testout)) {
+				ACCESS_FBINFO(hw).maven.regs[0x05] |= 0x02;
+			}
+		}
+		computeRegs(PMINFO &ACCESS_FBINFO(hw).maven, mt, outd);
+	} else if (mt->mnp < 0) {
+		/* We must program clocks before CRTC2, otherwise interlaced mode
+		   startup may fail */
+		mt->mnp = matroxfb_g450_setclk(PMINFO mt->pixclock, (mt->crtc == MATROXFB_SRC_CRTC1) ? M_PIXEL_PLL_C : M_VIDEO_PLL);
+		mt->pixclock = g450_mnp2f(PMINFO mt->mnp);
 	}
-	memset(m2info, 0, sizeof(*m2info));
-	m2info->primary_dev = MINFO;
-	if (matroxfb_g450_connect(m2info)) {
-		kfree(m2info);
-		printk(KERN_ERR "matroxfb_g450: G450 DAC failed to initialize\n");
-		return NULL;
+	dprintk(KERN_DEBUG "Pixclock = %u\n", mt->pixclock);
+	return 0;
+}
+
+static int matroxfb_g450_program(void* md) {
+	MINFO_FROM(md);
+	
+	if (ACCESS_FBINFO(outputs[1]).mode != MATROXFB_OUTPUT_MODE_MONITOR) {
+		cve2_init_TV(PMINFO &ACCESS_FBINFO(hw).maven);
 	}
-	return m2info;
+	return 0;
 }
 
-static void matroxfb_g450_remove(struct matrox_fb_info* minfo, void* g450) {
-	matroxfb_g450_shutdown(g450);
-	kfree(g450);
+static int matroxfb_g450_verify_mode(void* md, u_int32_t arg) {
+	MINFO_FROM(md);
+	
+	switch (arg) {
+		case MATROXFB_OUTPUT_MODE_PAL:
+		case MATROXFB_OUTPUT_MODE_NTSC:
+		case MATROXFB_OUTPUT_MODE_MONITOR:
+			return 0;
+	}
+	return -EINVAL;
 }
 
-static struct matroxfb_driver g450 = {
-		name:	"Matrox G450 output #2",
-		probe:	matroxfb_g450_probe,
-		remove:	matroxfb_g450_remove };
+static int g450_dvi_compute(void* md, struct my_timming* mt) {
+	MINFO_FROM(md);
 
-static int matroxfb_g450_init(void) {
-	matroxfb_register_driver(&g450);
+	if (mt->mnp < 0) {
+		mt->mnp = matroxfb_g450_setclk(PMINFO mt->pixclock, (mt->crtc == MATROXFB_SRC_CRTC1) ? M_PIXEL_PLL_C : M_VIDEO_PLL);
+		mt->pixclock = g450_mnp2f(PMINFO mt->mnp);
+	}
 	return 0;
 }
 
-static void matroxfb_g450_exit(void) {
-	matroxfb_unregister_driver(&g450);
+static struct matrox_altout matroxfb_g450_altout = {
+	.owner   = THIS_MODULE,
+	.name	 = "Secondary output",
+	.compute = matroxfb_g450_compute,
+	.program = matroxfb_g450_program,
+	.verifymode = matroxfb_g450_verify_mode,
+	.getqueryctrl = g450_query_ctrl,
+	.getctrl = g450_get_ctrl,
+	.setctrl = g450_set_ctrl,
+};
+
+static struct matrox_altout matroxfb_g450_dvi = {
+	.owner   = THIS_MODULE,
+	.name	 = "DVI output",
+	.compute = g450_dvi_compute,
+};
+
+void matroxfb_g450_connect(WPMINFO2) {
+	if (ACCESS_FBINFO(devflags.g450dac)) {
+		down_write(&ACCESS_FBINFO(altout.lock));
+		tvo_fill_defaults(PMINFO2);
+		ACCESS_FBINFO(outputs[1]).src = MATROXFB_SRC_CRTC1;
+		ACCESS_FBINFO(outputs[1]).data = MINFO;
+		ACCESS_FBINFO(outputs[1]).output = &matroxfb_g450_altout;
+		ACCESS_FBINFO(outputs[1]).mode = MATROXFB_OUTPUT_MODE_MONITOR;
+		ACCESS_FBINFO(outputs[2]).src = MATROXFB_SRC_CRTC1;
+		ACCESS_FBINFO(outputs[2]).data = MINFO;
+		ACCESS_FBINFO(outputs[2]).output = &matroxfb_g450_dvi;
+		ACCESS_FBINFO(outputs[2]).mode = MATROXFB_OUTPUT_MODE_MONITOR;
+		up_write(&ACCESS_FBINFO(altout.lock));
+	}
 }
 
-MODULE_AUTHOR("(c) 2000-2001 Petr Vandrovec <vandrove@vc.cvut.cz>");
-MODULE_DESCRIPTION("Matrox G450 secondary output driver");
+void matroxfb_g450_shutdown(WPMINFO2) {
+	if (ACCESS_FBINFO(devflags.g450dac)) {
+		down_write(&ACCESS_FBINFO(altout.lock));
+		ACCESS_FBINFO(outputs[1]).src = MATROXFB_SRC_NONE;
+		ACCESS_FBINFO(outputs[1]).output = NULL;
+		ACCESS_FBINFO(outputs[1]).data = NULL;
+		ACCESS_FBINFO(outputs[1]).mode = MATROXFB_OUTPUT_MODE_MONITOR;
+		ACCESS_FBINFO(outputs[2]).src = MATROXFB_SRC_NONE;
+		ACCESS_FBINFO(outputs[2]).output = NULL;
+		ACCESS_FBINFO(outputs[2]).data = NULL;
+		ACCESS_FBINFO(outputs[2]).mode = MATROXFB_OUTPUT_MODE_MONITOR;
+		up_write(&ACCESS_FBINFO(altout.lock));
+	}
+}
+
+EXPORT_SYMBOL(matroxfb_g450_connect);
+EXPORT_SYMBOL(matroxfb_g450_shutdown);
+
+MODULE_AUTHOR("(c) 2000-2002 Petr Vandrovec <vandrove@vc.cvut.cz>");
+MODULE_DESCRIPTION("Matrox G450/G550 output driver");
 MODULE_LICENSE("GPL");
-module_init(matroxfb_g450_init);
-module_exit(matroxfb_g450_exit);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)