patch-2.3.48 linux/drivers/video/matrox/matroxfb_crtc2.c

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

diff -u --recursive --new-file v2.3.47/linux/drivers/video/matrox/matroxfb_crtc2.c linux/drivers/video/matrox/matroxfb_crtc2.c
@@ -0,0 +1,788 @@
+#include "matroxfb_maven.h"
+#include "matroxfb_crtc2.h"
+#include "matroxfb_misc.h"
+#include "matroxfb_DAC1064.h"
+#include <linux/matroxfb.h>
+#include <asm/uaccess.h>
+
+/* **************************************************** */
+
+static int mem = 8192;
+
+MODULE_PARM(mem, "i");
+MODULE_PARM_DESC(mem, "Memory size reserved for dualhead (default=8MB)");
+
+/* **************************************************** */
+
+static int matroxfb_dh_getcolreg(unsigned regno, unsigned *red, unsigned *green,
+		unsigned *blue, unsigned *transp, struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	if (regno > 16)
+		return 1;
+	*red = m2info->palette[regno].red;
+	*blue = m2info->palette[regno].blue;
+	*green = m2info->palette[regno].green;
+	*transp = m2info->palette[regno].transp;
+	return 0;
+#undef m2info
+}
+
+static int matroxfb_dh_setcolreg(unsigned regno, unsigned red, unsigned green,
+		unsigned blue, unsigned transp, struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	struct display* p;
+
+	if (regno > 16)
+		return 1;
+	m2info->palette[regno].red = red;
+	m2info->palette[regno].blue = blue;
+	m2info->palette[regno].green = green;
+	m2info->palette[regno].transp = transp;
+	p = m2info->currcon_display;
+	if (p->var.grayscale) {
+		/* gray = 0.30*R + 0.59*G + 0.11*B */
+		red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8;
+	}
+	red = CNVT_TOHW(red, p->var.red.length);
+	green = CNVT_TOHW(green, p->var.green.length);
+	blue = CNVT_TOHW(blue, p->var.blue.length);
+	transp = CNVT_TOHW(transp, p->var.transp.length);
+
+	switch (p->var.bits_per_pixel) {
+#ifdef FBCON_HAS_CFB16
+		case 16:
+			m2info->cmap.cfb16[regno] =
+				(red << p->var.red.offset)     |
+				(green << p->var.green.offset) |
+				(blue << p->var.blue.offset)   |
+				(transp << p->var.transp.offset);
+			break;
+#endif
+#ifdef FBCON_HAS_CFB32
+		case 32:
+			m2info->cmap.cfb32[regno] =
+				(red << p->var.red.offset)     |
+				(green << p->var.green.offset) |
+				(blue << p->var.blue.offset)   |
+				(transp << p->var.transp.offset);
+			break;
+#endif
+	}
+	return 0;
+#undef m2info
+}
+
+static void do_install_cmap(struct matroxfb_dh_fb_info* m2info, struct display* p) {
+	if (p->cmap.len)
+		fb_set_cmap(&p->cmap, 1, matroxfb_dh_setcolreg, &m2info->fbcon);
+	else
+		fb_set_cmap(fb_default_cmap(16), 1, matroxfb_dh_setcolreg, &m2info->fbcon);
+}
+
+static void matroxfb_dh_restore(struct matroxfb_dh_fb_info* m2info,
+		struct my_timming* mt,
+		struct display* p,
+		int mode,
+		unsigned int pos) {
+	u_int32_t tmp;
+	struct matrox_fb_info* minfo = m2info->primary_dev;
+
+	switch (mode) {
+		case 15:
+			tmp = 0x00200000;
+			break;
+		case 16:
+			tmp = 0x00400000;
+			break;
+/*		case 32: */
+		default:
+			tmp = 0x00800000;
+			break;
+	}
+
+	if (ACCESS_FBINFO(output.sh)) {
+		tmp |= 0x00000001;	/* enable CRTC2 */
+
+		if (ACCESS_FBINFO(output.sh) & MATROXFB_OUTPUT_CONN_SECONDARY) {
+			tmp |= 0x00000002; /* source from VDOCLK */
+			tmp |= 0xC0000000; /* enable vvidrst & hvidrst */
+			/* MGA TVO is our clock source */
+		} else if (ACCESS_FBINFO(output.sh) & MATROXFB_OUTPUT_CONN_PRIMARY) {
+			tmp |= 0x00000004; /* source from pixclock */
+			/* PIXPLL is our clock source */
+		}
+
+		if (ACCESS_FBINFO(output.sh) & MATROXFB_OUTPUT_CONN_PRIMARY)
+			tmp |= 0x00100000;	/* connect CRTC2 to DAC */
+	}
+	mga_outl(0x3C10, tmp | 0x10000000);	/* depth and so on... 0x10000000 is VIDRST polarity */
+	mga_outl(0x3C14, ((mt->HDisplay - 8) << 16) | (mt->HTotal - 8));
+	mga_outl(0x3C18, ((mt->HSyncEnd - 8) << 16) | (mt->HSyncStart - 8));
+	mga_outl(0x3C1C, ((mt->VDisplay - 1) << 16) | (mt->VTotal - 1));
+	mga_outl(0x3C20, ((mt->VSyncEnd - 1) << 16) | (mt->VSyncStart - 1));
+	mga_outl(0x3C24, ((mt->VSyncStart) << 16) | (mt->HSyncStart));	/* preload */
+	mga_outl(0x3C28, pos);		/* vmemory start */
+	mga_outl(0x3C40, p->var.xres_virtual * (p->var.bits_per_pixel >> 3));
+	tmp = 0x0FFF0000;		/* line compare */
+	if (mt->sync & FB_SYNC_HOR_HIGH_ACT)
+		tmp |= 0x00000100;
+	if (mt->sync & FB_SYNC_VERT_HIGH_ACT)
+		tmp |= 0x00000200;
+	mga_outl(0x3C44, tmp);
+	mga_outl(0x3C4C, 0);		/* data control */
+}
+
+static void matroxfb_dh_cfbX_init(struct matroxfb_dh_fb_info* m2info,
+		struct display* p) {
+	/* no acceleration for secondary head... */
+}
+
+static void matroxfb_dh_pan_var(struct matroxfb_dh_fb_info* m2info,
+		struct fb_var_screeninfo* var) {
+	unsigned int pos;
+
+#define minfo (m2info->primary_dev)
+	pos = (var->yoffset * var->xres_virtual + var->xoffset) * var->bits_per_pixel >> 3;
+	pos += m2info->video.offbase;
+	mga_outl(0x3C28, pos);
+#undef minfo
+}
+
+static int matroxfb_dh_decode_var(struct matroxfb_dh_fb_info* m2info,
+		struct display* p,
+		struct fb_var_screeninfo* var,
+		int *visual,
+		int *video_cmap_len,
+		int *mode) {
+	unsigned int mask;
+	unsigned int memlen;
+	unsigned int vramlen;
+
+	switch (var->bits_per_pixel) {
+#ifdef FBCON_HAS_CFB16
+		case 16:	mask = 0x1F;
+				break;
+#endif
+#ifdef FBCON_HAS_CFB32
+		case 32:	mask = 0x0F;
+				break;
+#endif
+		default:	return -EINVAL;
+	}
+	vramlen = m2info->video.len_usable;
+	if (var->yres_virtual < var->yres)
+		var->yres_virtual = var->yres;
+	if (var->xres_virtual < var->xres)
+		var->xres_virtual = var->xres;
+	var->xres_virtual = (var->xres_virtual + mask) & ~mask;
+	if (var->yres_virtual > 32767)
+		return -EINVAL;
+	memlen = var->xres_virtual * var->yres_virtual * (var->bits_per_pixel >> 3);
+	if (memlen > vramlen)
+		return -EINVAL;
+	if (var->xoffset + var->xres > var->xres_virtual)
+		var->xoffset = var->xres_virtual - var->xres;
+	if (var->yoffset + var->yres > var->yres_virtual)
+		var->yoffset = var->yres_virtual - var->yres;
+
+	var->xres &= ~7;
+	var->left_margin &= ~7;
+	var->right_margin &= ~7;
+	var->hsync_len &= ~7;
+
+	*mode = var->bits_per_pixel;
+	if (var->bits_per_pixel == 16) {
+		if (var->green.length == 5) {
+			var->red.offset = 10;
+			var->red.length = 5;
+			var->green.offset = 5;
+			var->green.length = 5;
+			var->blue.offset = 0;
+			var->blue.length = 5;
+			var->transp.offset = 15;
+			var->transp.length = 1;
+			*mode = 15;
+		} else {
+			var->red.offset = 11;
+			var->red.length = 5;
+			var->green.offset = 5;
+			var->green.length = 6;
+			var->blue.offset = 0;
+			var->blue.length = 5;
+			var->transp.offset = 0;
+			var->transp.length = 0;
+		}
+	} else {
+			var->red.offset = 16;
+			var->red.length = 8;
+			var->green.offset = 8;
+			var->green.length = 8;
+			var->blue.offset = 0;
+			var->blue.length = 8;
+			var->transp.offset = 24;
+			var->transp.length = 8;
+	}
+	*visual = FB_VISUAL_TRUECOLOR;
+	*video_cmap_len = 16;
+	return 0;
+}
+
+static void initMatroxDH(struct matroxfb_dh_fb_info* m2info, struct display* p) {
+	switch (p->var.bits_per_pixel) {
+#ifdef FBCON_HAS_CFB16
+		case 16:
+			p->dispsw_data = m2info->cmap.cfb16;
+			p->dispsw = &fbcon_cfb16;
+			break;
+#endif
+#ifdef FBCON_HAS_CFB32
+		case 32:
+			p->dispsw_data = m2info->cmap.cfb32;
+			p->dispsw = &fbcon_cfb32;
+			break;
+#endif
+		default:
+			p->dispsw_data = NULL;
+			p->dispsw = &fbcon_dummy;
+			break;
+	}
+}
+
+static int matroxfb_dh_open(struct fb_info* info, int user) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	struct matrox_fb_info* minfo = m2info->primary_dev;
+	MOD_INC_USE_COUNT;
+
+	if (minfo) {
+		if (ACCESS_FBINFO(dead)) {
+			return -ENXIO;
+		}
+	}
+	return 0;
+#undef m2info
+}
+
+static int matroxfb_dh_release(struct fb_info* info, int user) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	struct matrox_fb_info* minfo = m2info->primary_dev;
+
+	if (minfo) {
+	}
+	MOD_DEC_USE_COUNT;
+	return 0;
+#undef m2info
+}
+
+static int matroxfb_dh_get_fix(struct fb_fix_screeninfo* fix, int con,
+		struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	struct display* p;
+
+	if (con >= 0)
+		p = fb_display + con;
+	else
+		p = m2info->fbcon.disp;
+
+	memset(fix, 0, sizeof(*fix));
+	strcpy(fix->id, "MATROX DH");
+
+	fix->smem_start = m2info->video.base;
+	fix->smem_len = m2info->video.len_usable;
+	fix->type = p->type;
+	fix->type_aux = p->type_aux;
+	fix->visual = p->visual;
+	fix->xpanstep = 8;	/* TBD */
+	fix->ypanstep = 1;
+	fix->ywrapstep = 0;
+	fix->line_length = p->line_length;
+	fix->mmio_start = m2info->mmio.base;
+	fix->mmio_len = m2info->mmio.len;
+	fix->accel = 0;		/* no accel... */
+	return 0;
+#undef m2info
+}
+
+static int matroxfb_dh_get_var(struct fb_var_screeninfo* var, int con,
+		struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	if (con < 0)
+		*var = m2info->fbcon.disp->var;
+	else
+		*var = fb_display[con].var;
+	return 0;
+#undef m2info
+}
+
+static int matroxfb_dh_set_var(struct fb_var_screeninfo* var, int con,
+		struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	struct display* p;
+	int chgvar;
+	int visual;
+	int cmap_len;
+	int mode;
+	int err;
+	struct matrox_fb_info* minfo = m2info->primary_dev;
+
+	if (con < 0)
+		p = m2info->fbcon.disp;
+	else
+		p = fb_display + con;
+	if ((err = matroxfb_dh_decode_var(m2info, p, var, &visual, &cmap_len, &mode)) != 0)
+		return err;
+	switch (var->activate & FB_ACTIVATE_MASK) {
+		case FB_ACTIVATE_TEST:	return 0;
+		case FB_ACTIVATE_NXTOPEN:
+		case FB_ACTIVATE_NOW:	break;
+		default:		return -EINVAL;
+	}
+	if (con >= 0) {
+		chgvar = (p->var.xres != var->xres) ||
+			(p->var.yres != var->yres) ||
+			(p->var.xres_virtual != var->xres_virtual) ||
+			(p->var.yres_virtual != var->yres_virtual) ||
+			(p->var.bits_per_pixel != var->bits_per_pixel) ||
+			memcmp(&p->var.red, &var->red, sizeof(var->red)) ||
+			memcmp(&p->var.green, &var->green, sizeof(var->green)) ||
+			memcmp(&p->var.blue, &var->blue, sizeof(var->blue));
+	} else
+		chgvar = 0;
+	p->var = *var;
+	/* cmap */
+	p->screen_base = vaddr_va(m2info->video.vbase);
+	p->visual = visual;
+	p->ypanstep = 1;
+	p->ywrapstep = 0;
+	p->type = FB_TYPE_PACKED_PIXELS;
+	p->type_aux = 0;
+	p->next_line = p->line_length = (var->xres_virtual * var->bits_per_pixel) >> 3;
+	p->can_soft_blank = 0;
+	p->inverse = 0;	/* TBD */
+	initMatroxDH(m2info, p);
+	if (chgvar && info && info->changevar)
+		info->changevar(con);
+	if (con == m2info->currcon) {
+		struct my_timming mt;
+		struct matrox_hw_state* hw;
+		struct matrox_hw_state* ohw;
+		unsigned int pos;
+
+		matroxfb_var2my(var, &mt);
+		/* CRTC2 delay */
+		mt.delay = 34;
+
+		hw = ACCESS_FBINFO(newhw);
+		ohw = ACCESS_FBINFO(currenthw);
+
+		/* copy last setting... */
+		memcpy(hw, ohw, sizeof(*hw));
+
+		pos = (var->yoffset * var->xres_virtual + var->xoffset) * var->bits_per_pixel >> 3;
+		pos += m2info->video.offbase;
+		DAC1064_global_init(PMINFO hw);
+		if (ACCESS_FBINFO(output.sh) & MATROXFB_OUTPUT_CONN_PRIMARY) {
+			if (ACCESS_FBINFO(primout))
+				ACCESS_FBINFO(primout)->compute(MINFO, &mt, hw);
+		}
+		if (ACCESS_FBINFO(output.sh) & MATROXFB_OUTPUT_CONN_SECONDARY) {
+			down_read(&ACCESS_FBINFO(altout.lock));
+			if (ACCESS_FBINFO(altout.output))
+				ACCESS_FBINFO(altout.output)->compute(ACCESS_FBINFO(altout.device), &mt, hw);
+			up_read(&ACCESS_FBINFO(altout.lock));
+		}
+		matroxfb_dh_restore(m2info, &mt, p, mode, pos);
+		DAC1064_global_restore(PMINFO hw);
+		if (ACCESS_FBINFO(output.sh) & MATROXFB_OUTPUT_CONN_PRIMARY) {
+			if (ACCESS_FBINFO(primout))
+				ACCESS_FBINFO(primout)->program(MINFO, hw);
+		}
+		if (ACCESS_FBINFO(output.sh) & MATROXFB_OUTPUT_CONN_SECONDARY) {
+			down_read(&ACCESS_FBINFO(altout.lock));
+			if (ACCESS_FBINFO(altout.output))
+				ACCESS_FBINFO(altout.output)->program(ACCESS_FBINFO(altout.device), hw);
+			up_read(&ACCESS_FBINFO(altout.lock));
+		}
+		if (ACCESS_FBINFO(output.sh) & MATROXFB_OUTPUT_CONN_PRIMARY) {
+			if (ACCESS_FBINFO(primout))
+				ACCESS_FBINFO(primout)->start(MINFO);
+		}
+		if (ACCESS_FBINFO(output.sh) & MATROXFB_OUTPUT_CONN_SECONDARY) {
+			down_read(&ACCESS_FBINFO(altout.lock));
+			if (ACCESS_FBINFO(altout.output))
+				ACCESS_FBINFO(altout.output)->start(ACCESS_FBINFO(altout.device));
+			up_read(&ACCESS_FBINFO(altout.lock));
+		}
+		matroxfb_dh_cfbX_init(m2info, p);
+		do_install_cmap(m2info, p);
+	}
+	return 0;
+#undef m2info
+}
+
+static int matroxfb_dh_get_cmap(struct fb_cmap* cmap, int kspc, int con,
+		struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	struct display* dsp;
+
+	if (con < 0)
+		dsp = m2info->fbcon.disp;
+	else
+		dsp = fb_display + con;
+	if (con == m2info->currcon)
+		return fb_get_cmap(cmap, kspc, matroxfb_dh_getcolreg, info);
+	else if (dsp->cmap.len)
+		fb_copy_cmap(&dsp->cmap, cmap, kspc ? 0 : 2);
+	else
+		fb_copy_cmap(fb_default_cmap(16), cmap, kspc ? 0 : 2);
+	return 0;
+#undef m2info
+}
+
+static int matroxfb_dh_set_cmap(struct fb_cmap* cmap, int kspc, int con,
+		struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	struct display* dsp;
+
+	if (con < 0)
+		dsp = m2info->fbcon.disp;
+	else
+		dsp = fb_display + con;
+	if (dsp->cmap.len != 16) {
+		int err;
+
+		err = fb_alloc_cmap(&dsp->cmap, 16, 0);
+		if (err)
+			return err;
+	}
+	if (con == m2info->currcon)
+		return fb_set_cmap(cmap, kspc, matroxfb_dh_setcolreg, info);
+	else
+		fb_copy_cmap(cmap, &dsp->cmap, kspc ? 0 : 1);
+	return 0;
+#undef m2info
+}
+
+static int matroxfb_dh_pan_display(struct fb_var_screeninfo* var, int con,
+		struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	if (var->xoffset + fb_display[con].var.xres > fb_display[con].var.xres_virtual ||
+	    var->yoffset + fb_display[con].var.yres > fb_display[con].var.yres_virtual)
+		return -EINVAL;
+	if (con == m2info->currcon)
+		matroxfb_dh_pan_var(m2info, var);
+	fb_display[con].var.xoffset = var->xoffset;
+	fb_display[con].var.yoffset = var->yoffset;
+	return 0;
+#undef m2info
+}
+
+static int matroxfb_dh_switch(int con, struct fb_info* info);
+
+static int matroxfb_dh_get_vblank(const struct matroxfb_dh_fb_info* m2info, struct fb_vblank* vblank) {
+	struct matrox_fb_info* minfo = m2info->primary_dev;
+
+	memset(vblank, 0, sizeof(*vblank));
+	vblank->flags = FB_VBLANK_HAVE_VCOUNT | FB_VBLANK_HAVE_VBLANK;
+	/* mask out reserved bits + field number (odd/even) */
+	vblank->vcount = mga_inl(0x3C48) & 0x000007FF;
+	/* compatibility stuff */
+	if (vblank->vcount >= m2info->currcon_display->var.yres)
+		vblank->flags |= FB_VBLANK_VBLANKING;
+	return 0;
+}
+
+static int matroxfb_dh_ioctl(struct inode* inode,
+		struct file* file,
+		unsigned int cmd,
+		unsigned long arg,
+		int con,
+		struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	struct matrox_fb_info* minfo = m2info->primary_dev;
+
+	DBG("matroxfb_crtc2_ioctl")
+
+	switch (cmd) {
+		case FBIOGET_VBLANK:
+			{
+				struct fb_vblank vblank;
+				int err;
+
+				err = matroxfb_dh_get_vblank(m2info, &vblank);
+				if (err)
+					return err;
+				copy_to_user_ret((struct fb_vblank*)arg, &vblank, sizeof(vblank), -EFAULT);
+				return 0;
+			}
+		case MATROXFB_SET_OUTPUT_MODE:
+		case MATROXFB_GET_OUTPUT_MODE:
+		case MATROXFB_GET_ALL_OUTPUTS:
+			{
+				return ACCESS_FBINFO(fbcon.fbops)->fb_ioctl(inode, file, cmd, arg, con, &minfo->fbcon);
+			}
+		case MATROXFB_SET_OUTPUT_CONNECTION:
+			{
+				u_int32_t tmp;
+
+				get_user_ret(tmp, (u_int32_t*)arg, -EFAULT);
+				if (tmp & ~ACCESS_FBINFO(output.all))
+					return -EINVAL;
+				if (tmp & ACCESS_FBINFO(output.ph))
+					return -EINVAL;
+				if (tmp == ACCESS_FBINFO(output.sh))
+					return 0;
+				ACCESS_FBINFO(output.sh) = tmp;
+				matroxfb_dh_switch(m2info->currcon, info);
+				return 0;
+			}
+		case MATROXFB_GET_OUTPUT_CONNECTION:
+			{
+				put_user_ret(ACCESS_FBINFO(output.sh), (u_int32_t*)arg, -EFAULT);
+				return 0;
+			}
+		case MATROXFB_GET_AVAILABLE_OUTPUTS:
+			{
+				u_int32_t tmp;
+
+				tmp = ACCESS_FBINFO(output.all) & ~ACCESS_FBINFO(output.ph);
+				put_user_ret(tmp, (u_int32_t*)arg, -EFAULT);
+				return 0;
+			}
+	}
+	return -EINVAL;
+#undef m2info
+}
+
+static struct fb_ops matroxfb_dh_ops = {
+	matroxfb_dh_open,
+	matroxfb_dh_release,
+	matroxfb_dh_get_fix,
+	matroxfb_dh_get_var,
+	matroxfb_dh_set_var,
+	matroxfb_dh_get_cmap,
+	matroxfb_dh_set_cmap,
+	matroxfb_dh_pan_display,
+	matroxfb_dh_ioctl,
+	NULL			/* mmap */
+};
+
+static int matroxfb_dh_switch(int con, struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	struct fb_cmap* cmap;
+	struct display* p;
+
+	if (m2info->currcon >= 0) {
+		cmap = &m2info->currcon_display->cmap;
+		if (cmap->len) {
+			fb_get_cmap(cmap, 1, matroxfb_dh_getcolreg, info);
+		}
+	}
+	m2info->currcon = con;
+	if (con < 0)
+		p = m2info->fbcon.disp;
+	else
+		p = fb_display + con;
+	m2info->currcon_display = p;
+	p->var.activate = FB_ACTIVATE_NOW;
+	matroxfb_dh_set_var(&p->var, con, info);
+	return 0;
+#undef m2info
+}
+
+static int matroxfb_dh_updatevar(int con, struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	matroxfb_dh_pan_var(m2info, &fb_display[con].var);
+	return 0;
+#undef m2info
+}
+
+static void matroxfb_dh_blank(int blank, struct fb_info* info) {
+#define m2info ((struct matroxfb_dh_fb_info*)info)
+	switch (blank) {
+		case 1:
+		case 2:
+		case 3:
+		case 4:
+		default:;
+	}
+	/* do something... */
+#undef m2info
+}
+
+static struct fb_var_screeninfo matroxfb_dh_defined = {
+		640,480,640,480,/* W,H, virtual W,H */
+		0,0,		/* offset */
+		32,		/* depth */
+		0,		/* gray */
+		{0,0,0},	/* R */
+		{0,0,0},	/* G */
+		{0,0,0},	/* B */
+		{0,0,0},	/* alpha */
+		0,		/* nonstd */
+		FB_ACTIVATE_NOW,
+		-1,-1,		/* display size */
+		0,		/* accel flags */
+		39721L,48L,16L,33L,10L,
+		96L,2,0,	/* no sync info */
+		FB_VMODE_NONINTERLACED,
+		{0,0,0,0,0,0}
+};
+
+static int matroxfb_dh_regit(CPMINFO struct matroxfb_dh_fb_info* m2info) {
+#define minfo (m2info->primary_dev)
+	struct display* d;
+	void* oldcrtc2;
+
+	d = kmalloc(sizeof(*d), GFP_KERNEL);
+
+	memset(d, 0, sizeof(*d));
+
+	strcpy(m2info->fbcon.modename, "MATROX CRTC2");
+	m2info->fbcon.changevar = NULL;
+	m2info->fbcon.node = -1;
+	m2info->fbcon.fbops = &matroxfb_dh_ops;
+	m2info->fbcon.disp = d;
+	m2info->fbcon.switch_con = &matroxfb_dh_switch;
+	m2info->fbcon.updatevar = &matroxfb_dh_updatevar;
+	m2info->fbcon.blank = &matroxfb_dh_blank;
+	m2info->fbcon.flags = FBINFO_FLAG_DEFAULT;
+	m2info->currcon = -1;
+	m2info->currcon_display = d;
+
+	if (mem < 64)
+		mem *= 1024;
+	if (mem < 64*1024)
+		mem *= 1024;
+	mem &= ~0x00000FFF;	/* PAGE_MASK? */
+	if (minfo->video.len_usable + mem <= minfo->video.len)
+		m2info->video.offbase = minfo->video.len - mem;
+	else if (minfo->video.len < mem) {
+		kfree(d);
+		return -ENOMEM;
+	} else { /* check yres on first head... */
+		m2info->video.borrowed = mem;
+		minfo->video.len_usable -= mem;
+		m2info->video.offbase = minfo->video.len_usable;
+	}
+	m2info->video.base = ACCESS_FBINFO(video.base) + m2info->video.offbase;
+	m2info->video.len = m2info->video.len_usable = m2info->video.len_maximum = mem;
+	m2info->video.vbase.vaddr = vaddr_va(ACCESS_FBINFO(video.vbase)) + m2info->video.offbase;
+	m2info->mmio.base = ACCESS_FBINFO(mmio.base);
+	m2info->mmio.vbase = ACCESS_FBINFO(mmio.vbase);
+	m2info->mmio.len = ACCESS_FBINFO(mmio.len);
+
+	/*
+	 *  If we have two outputs, connect CRTC2 to it...
+	 */
+	if (ACCESS_FBINFO(output.all) & MATROXFB_OUTPUT_CONN_SECONDARY) {
+		ACCESS_FBINFO(output.sh) |= MATROXFB_OUTPUT_CONN_SECONDARY;
+		ACCESS_FBINFO(output.ph) &= ~MATROXFB_OUTPUT_CONN_SECONDARY;
+	}
+
+	matroxfb_dh_set_var(&matroxfb_dh_defined, -2, &m2info->fbcon);
+	if (register_framebuffer(&m2info->fbcon)) {
+		kfree(d);
+		return -ENXIO;
+	}
+	if (m2info->currcon < 0) {
+		matroxfb_dh_set_var(&matroxfb_dh_defined, -1, &m2info->fbcon);
+	}
+	down_write(&ACCESS_FBINFO(crtc2.lock));
+	oldcrtc2 = minfo->crtc2.info;
+	minfo->crtc2.info = &m2info->fbcon;
+	up_write(&ACCESS_FBINFO(crtc2.lock));
+	if (oldcrtc2) {
+		printk(KERN_ERR "matroxfb_crtc2: Internal consistency check failed: crtc2 already present: %p\n",
+			oldcrtc2);
+	}
+	return 0;
+#undef minfo
+}
+
+/* ************************** */
+
+static int matroxfb_dh_registerfb(struct matroxfb_dh_fb_info* m2info) {
+#define minfo (m2info->primary_dev)
+	if (matroxfb_dh_regit(PMINFO m2info)) {
+		printk(KERN_ERR "matroxfb_crtc2: secondary head failed to register\n");
+		return -1;
+	}
+	printk(KERN_INFO "matroxfb_crtc2: secondary head of fb%u was registered as fb%u\n",
+		GET_FB_IDX(ACCESS_FBINFO(fbcon.node)), GET_FB_IDX(m2info->fbcon.node));
+	m2info->fbcon_registered = 1;
+	return 0;
+#undef minfo
+}
+
+static void matroxfb_dh_deregisterfb(struct matroxfb_dh_fb_info* m2info) {
+#define minfo (m2info->primary_dev)
+	if (m2info->fbcon_registered) {
+		int id;
+		struct fb_info* crtc2;
+
+		down_write(&ACCESS_FBINFO(crtc2.lock));
+		crtc2 = minfo->crtc2.info;
+		if (crtc2 == &m2info->fbcon)
+			minfo->crtc2.info = NULL;
+		up_write(&ACCESS_FBINFO(crtc2.lock));
+		if (crtc2 != &m2info->fbcon) {
+			printk(KERN_ERR "matroxfb_crtc2: Internal consistency check failed: crtc2 mismatch at unload: %p != %p\n",
+				crtc2, &m2info->fbcon);
+			printk(KERN_ERR "matroxfb_crtc2: Expect kernel crash after module unload.\n");
+			return;
+		}
+		id = GET_FB_IDX(m2info->fbcon.node);
+		unregister_framebuffer(&m2info->fbcon);
+		kfree(m2info->fbcon.disp);
+		/* return memory back to primary head */
+		ACCESS_FBINFO(video.len_usable) += m2info->video.borrowed;
+		printk(KERN_INFO "matroxfb_crtc2: fb%u unregistered\n", id);
+		m2info->fbcon_registered = 0;
+	}
+#undef minfo
+}
+
+static void* matroxfb_crtc2_probe(struct matrox_fb_info* minfo) {
+	struct matroxfb_dh_fb_info* m2info;
+
+	/* hardware is CRTC2 incapable... */
+	if (!minfo->devflags.crtc2)
+		return NULL;
+	m2info = (struct matroxfb_dh_fb_info*)kmalloc(sizeof(*m2info), GFP_KERNEL);
+	if (!m2info) {
+		printk(KERN_ERR "matroxfb_crtc2: Not enough memory for CRTC2 control structs\n");
+		return NULL;
+	}
+	memset(m2info, 0, sizeof(*m2info));
+	m2info->primary_dev = minfo;
+	if (matroxfb_dh_registerfb(m2info)) {
+		kfree(m2info);
+		printk(KERN_ERR "matroxfb_crtc2: CRTC2 framebuffer failed to register\n");
+		return NULL;
+	}
+	return m2info;
+}
+
+static void matroxfb_crtc2_remove(struct matrox_fb_info* minfo, void* crtc2) {
+	matroxfb_dh_deregisterfb(crtc2);
+}
+
+static struct matroxfb_driver crtc2 = {
+		name:	"Matrox G400 CRTC2",
+		probe:	matroxfb_crtc2_probe,
+		remove:	matroxfb_crtc2_remove };
+
+static int matroxfb_crtc2_init(void) {
+	matroxfb_register_driver(&crtc2);
+	return 0;
+}
+
+static void matroxfb_crtc2_exit(void) {
+	matroxfb_unregister_driver(&crtc2);
+}
+
+MODULE_AUTHOR("(c) 1999,2000 Petr Vandrovec <vandrove@vc.cvut.cz>");
+MODULE_DESCRIPTION("Matrox G400 CRTC2 driver");
+module_init(matroxfb_crtc2_init);
+module_exit(matroxfb_crtc2_exit);
+/* we do not have __setup() yet */

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