Subject: Accelerated LCSPX driver
To: None <port-vax@NetBSD.org>
From: Blaz Antonic <blaz.antonic@siol.net>
List: port-vax
Date: 08/16/2004 08:57:15
This is a multi-part message in MIME format.

--------------1E262080FE2
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Hello,

Attached is HW accelerated LCSPX driver (for Vaxstation 4000/9x class
machines). It is a drop-in replacement for existing dumb framebuffer
driver, offers the same functionality and much more (full palette
support, hardware cursor, HW acceleration, everything needed to get X
running in unaccelerated mode, user-defined font support, etc.). It has
been tested under NetBSD 2.0_beta.

While this driver is complete as far as console operation goes it still
has few chunks of debugging code inside (which is commented out) and it
hasn't been optimised yet. It is also missing HW acceleration support
for X which will be added before i finish working on it so there's at
least one more release coming up sometime in the future.

I would really appreciate it if people could try it out. Please try out
different font types to see which one suits you best (and to flush out
any bugs that might still be lurking in the driver, possibly with cursor
which is the least tested part). Despite being "beta" this driver can
cause no harm to your hardware, there's no risk involved in trying it
out, it's considerably faster than existing driver so there's no reason
for you not to try it if you have suitable hardware :-)

Note that since LCSPX doesn't seem to suypport any kind of mechanism
that would allow asynchronous requests to be issued to hardware (at
least the way i understand it, there are no docs avaliable on the
subject) the kernel must sit idle while HW operation is in progress -
this means that heavy screen output with lots of font rendering, the
slowest part of the driver that could use some hand optimisations, will
clog the machine somewhat, but much less than the existing driver would.
The slowdown is still noticable and unfortunately there is no way to
avoid it. Pre-rendering font glyphs with intention to copy them in as
needed doesn't seem like a good option since block copy operation
requires quite some setup and takes about as many I/O cycles as
rendering of one glyph does.

Last, but by no means least important: *many* thanks go to Michael Hitch
who's been weeding out the bugs i carefully planted inside :-) I don't
think i would ever make it this far without your help mate.

Blaz Antonic
-- 
Hi! I'm a signature virus!
Copy me into your signature to help me spread!

--------------1E262080FE2
Content-Type: text/plain; charset=us-ascii; name="LCSPX.C"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="LCSPX.C"

/*	$NetBSD: lcspx.c,v 1.2 2004/16/08 09:49:00 Blaz Antonic Exp $ */
/*
 * LCSPX accelerated framebuffer driver
 * Copyright (c) 2004 Blaz Antonic
 * All rights reserved.
 *
 * This software contains code written by Michael L. Hitch.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the abovementioned copyrights
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: lcspx.c,v 1.2 2004/16/08 09:49:00 Blaz Antonic Exp $");

#include <sys/param.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/time.h>
#include <sys/malloc.h>
#include <sys/conf.h>
#include <sys/kernel.h>

#include <uvm/uvm.h>

#include <machine/vsbus.h>
#include <machine/sid.h>
#include <machine/cpu.h>

#include <dev/cons.h>

#include <dev/dec/dzreg.h>
#include <dev/dec/dzvar.h>
#include <dev/dec/dzkbdvar.h>

#include <dev/wscons/wsdisplayvar.h>
#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wscons_callbacks.h>
#include <dev/wsfont/wsfont.h>

#include "machine/scb.h"

#include "dzkbd.h"

/* Screen hardware defs */

#define	LCSPX_FB_ADDR		0x38000000	/* Frame buffer */

/* register space defines */
#define LCSPX_REG_ADDR	0x39302000	/* 1st set of LCSPX registers */
#define LCSPX_REG_SIZE	0x00002000
#define LCSPX_REG(reg) regaddr[((reg - 0x2000) / 4)]

#define LCSPX_REG1_ADDR	0x39b00000	/* 2nd set of LCSPX registers */
#define LCSPX_REG1_SIZE	0x00001000
#define LCSPX_REG1(reg) regaddr1[(reg / 4)]

/* bt459 locations */
#define LCSPX_BT459_ADDRL	0x39b10000
#define LCSPX_BT459_ADDRH	0x39b14000
#define LCSPX_BT459_REG		0x39b18000
#define LCSPX_BT459_CMAP	0x39b1c000

#define	LCSPX_BG_COLOR	WSCOL_BLUE
#define	LCSPX_FG_COLOR	WSCOL_WHITE

/* these are NOT palette color but 4 pixels of cursor sprite, fg/bg respectively */
#define CURSOR_FG	0xff
#define CURSOR_BG	0x00
#define CUR_XBIAS	360
#define CUR_YBIAS	35

/* few BT459 indirect register defines */
#define BT459_REG_CCOLOR_1	0x181
#define BT459_REG_CCOLOR_2	0x182
#define BT459_REG_CCOLOR_3	0x183
#define BT459_REG_ID		0x200
#define BT459_REG_CMD0		0x201
#define BT459_REG_CMD1		0x202
#define BT459_REG_CMD2		0x203
#define BT459_REG_PRM		0x204
#define BT459_REG_CCR		0x300
#define BT459_REG_CXLO          0x301
#define BT459_REG_CXHI          0x302
#define BT459_REG_CYLO          0x303
#define BT459_REG_CYHI          0x304
#define BT459_REG_WXLO          0x305
#define BT459_REG_WXHI          0x306
#define BT459_REG_WYLO          0x307
#define BT459_REG_WYHI          0x308
#define BT459_REG_WWLO          0x309
#define BT459_REG_WWHI          0x30a
#define BT459_REG_WHLO          0x30b
#define BT459_REG_WHHI          0x30c
#define BT459_REG_CRAM_BASE	0x400

/* used to access top/bottom 8 bits of a 16-bit argument for BT459 split addressing */
#define HI(x)	(((x) >> 8) & 0xff)
#define LO(x)	((x) & 0xff)

/* implement sanity checks at certain points to ensure safer operation */
#define LCSPX_SAFE

static	int lcspx_match(struct device *, struct cfdata *, void *);
static	void lcspx_attach(struct device *, struct device *, void *);

struct	lcspx_softc {
	struct	device ss_dev;
};

CFATTACH_DECL(lcspx, sizeof(struct lcspx_softc),
    lcspx_match, lcspx_attach, NULL, NULL);

static void	lcspx_cursor(void *, int, int, int);
static int	lcspx_mapchar(void *, int, unsigned int *);
static void	lcspx_putchar(void *, int, int, u_int, long);
static void	lcspx_copycols(void *, int, int, int,int);
static void	lcspx_erasecols(void *, int, int, int, long);
static void	lcspx_copyrows(void *, int, int, int);
static void	lcspx_eraserows(void *, int, int, long);
static int	lcspx_allocattr(void *, int, int, int, long *);
static int	lcspx_get_cmap(struct wsdisplay_cmap *);
static int	lcspx_set_cmap(struct wsdisplay_cmap *);
static void	lcspx_init_common(struct device *, struct vsbus_attach_args *);
#if 0
static void	showmem(long addr, u_int xsize, u_int ysize);
#endif

const struct wsdisplay_emulops lcspx_emulops = {
	lcspx_cursor,
	lcspx_mapchar,
	lcspx_putchar,
	lcspx_copycols,
	lcspx_erasecols,
	lcspx_copyrows,
	lcspx_eraserows,
	lcspx_allocattr
};

static char lcspx_stdscreen_name[10] = "160x68";
struct wsscreen_descr lcspx_stdscreen = {
	lcspx_stdscreen_name, 160, 68,		/* dynamically set */
	&lcspx_emulops,
	8, 15,					/* dynamically set */
	WSSCREEN_UNDERLINE|WSSCREEN_REVERSE|WSSCREEN_WSCOLORS,
};

const struct wsscreen_descr *_lcspx_scrlist[] = {
	&lcspx_stdscreen,
};

const struct wsscreen_list lcspx_screenlist = {
	sizeof(_lcspx_scrlist) / sizeof(struct wsscreen_descr *),
	_lcspx_scrlist,
};

static	char *lcspxaddr;
static	volatile long *regaddr;
static	volatile long *regaddr1;

static char	*bt_addrl;
static char	*bt_addrh;
static char	*bt_reg;
static char	*bt_cmap;

static int	lcspx_xsize;
static int	lcspx_ysize;
static int	lcspx_depth;
static int	lcspx_cols;
static int	lcspx_rows;
static int	lcspx_onerow;
static long	lcspx_fb_size;

/* Our current hardware colormap */
static struct hwcmap256 {
#define	CMAP_SIZE	256	/* 256 R/G/B entries */
	u_int8_t r[CMAP_SIZE];
	u_int8_t g[CMAP_SIZE];
	u_int8_t b[CMAP_SIZE];
} lcspx_cmap;

/* The default colormap */
static struct {
	u_int8_t r[8];
	u_int8_t g[8];
	u_int8_t b[8];
} lcspx_default_cmap = {
	{ 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff },
	{ 0x00, 0x00, 0xff, 0x7f, 0x00, 0x00, 0xff, 0xff },
	{ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff }
};

static char cons_curmap[1024];

struct wsdisplay_font lcspx_font;
static u_char *qf;
static u_short *qf2;

#define QCHAR(c) (c < lcspx_font.firstchar ? 0 : \
	(c >= (lcspx_font.firstchar + lcspx_font.numchars) ? 0 : c - lcspx_font.firstchar))
#define QFONT(c,line)	((lcspx_font.stride == 2 ? \
	qf2[QCHAR(c) * lcspx_font.fontheight + line] : \
	qf[QCHAR(c) * lcspx_font.fontheight + line]))
#define	LCSPX_ADDR(row, col, line, dot) \
	lcspxaddr[((col) * lcspx_font.fontwidth) + ((row) * lcspx_font.fontheight * lcspx_xsize) + \
	    (line) * lcspx_xsize + dot]


static int	lcspx_ioctl(void *, u_long, caddr_t, int, struct proc *);
static paddr_t	lcspx_mmap(void *, off_t, int);
static int	lcspx_alloc_screen(void *, const struct wsscreen_descr *,
				      void **, int *, int *, long *);
static void	lcspx_free_screen(void *, void *);
static int	lcspx_show_screen(void *, void *, int,
				     void (*) (void *, int, int), void *);
static void	lcspx_crsr_blink(void *);

static void	lcspx_update_cmap(int entry, char red, char green, char blue);
static void	set_btreg(u_int addr, u_char value);
static u_char	get_btreg(u_int addr);

/* LCSPX HW accel functions */
static void	blkcpy(u_int sxpos, u_int sypos, u_int dxpos, u_int dypos, u_int xdim, u_int ydim);
static void	blkset(u_int xpos, u_int ypos, u_int xdim, u_int ydim, char color);

const struct wsdisplay_accessops lcspx_accessops = {
	lcspx_ioctl,
	lcspx_mmap,
	lcspx_alloc_screen,
	lcspx_free_screen,
	lcspx_show_screen,
	0 /* load_font */
};

/* TODO allocate ss_image dynamically for consoles beyond first one */
struct	lcspx_screen {
	int	ss_curx;
	int	ss_cury;
	int	ss_cur_fg;
	int	ss_cur_bg;
	struct {
		u_char	data;			/* Image character */
		u_char	attr;			/* Attribute: 80/70/08/07 */
	} ss_image[160 * 128];			/* allow for maximum possible cell matrix */
};
#define	LCSPX_ATTR_UNDERLINE	0x80
#define	LCSPX_BG_MASK		0x70
#define	LCSPX_ATTR_REVERSE	0x08
#define	LCSPX_FG_MASK		0x07

static	struct lcspx_screen lcspx_conscreen;
static	struct lcspx_screen *curscr;
static	struct lcspx_screen *savescr;

static	struct callout lcspx_cursor_ch = CALLOUT_INITIALIZER;

static	int cur_fg, cur_bg;

void lcspx_update_cmap(int entry, char red, char green, char blue)
{
	bt_addrl[0] = LO(entry);
	bt_addrh[0] = HI(entry);
	if ((entry >= 0x181) || (entry <= 0x183))
	{
		bt_reg[0] = red;
		bt_reg[0] = green;
		bt_reg[0] = blue;
	} else {
		bt_cmap[0] = red;
		bt_cmap[0] = green;
		bt_cmap[0] = blue;
	}
}

void set_btreg(u_int addr, u_char value)
{
	bt_addrl[0] = LO(addr);
	bt_addrh[0] = HI(addr);
	bt_reg[0] = value;
}

u_char get_btreg(u_int addr)
{
	bt_addrl[0] = LO(addr);
	bt_addrh[0] = HI(addr);
	return bt_reg[0] & 0xff;
}

void blkcpy(u_int sxpos, u_int sypos, u_int dxpos, u_int dypos, u_int xdim, u_int ydim)
{
	u_int counter = 0xfffe;
	long temp = 0;
	
	LCSPX_REG(0x21ec) = 0x84884648;
//	LCSPX_REG(0x2260) = 0xffffffff;
//	LCSPX_REG(0x219c) = 0xffaa5501;
	
	LCSPX_REG(0x21d0) = dxpos << 16;
	LCSPX_REG(0x21d4) = dypos << 16;
	LCSPX_REG(0x21d8) = (dxpos + xdim) << 16;
	LCSPX_REG(0x21dc) = (dypos + ydim) << 16;

	temp = ((LCSPX_REG1(0x10) & 0xc0) << (30 - 6)) | ((LCSPX_REG1(0x10) & 0x3f) << 24);

	LCSPX_REG(0x21e0) = (temp + (dypos * lcspx_xsize) + dxpos);
//	LCSPX_REG(0x20e0) = 0xff000000;
	LCSPX_REG(0x21e4) = (temp + (sypos * lcspx_xsize) + sxpos);
	LCSPX_REG(0x20e4) = 0;
//	LCSPX_REG(0x21e8) = (lcspx_xsize << 16) | lcspx_xsize;
	LCSPX_REG(0x21f0) = 0xff;
	LCSPX_REG(0x21f4) = 0xff;

	if ((LCSPX_REG(0x2100) >> 24) == 0) {
		printf("blkcpy: going the long way !!!\n");
		LCSPX_REG(0x2268) = 0x002223b4;
		LCSPX_REG(0x226c) = 0x00b620ac;
		LCSPX_REG(0x2270) = 0x00272003;
		LCSPX_REG(0x2274) = 0x601020af;
		LCSPX_REG(0x227c) = 0x2010e010;
		LCSPX_REG(0x23c8) = 0x012c0000;		
		LCSPX_REG(0x21fc) = 0xa9;
	} else {
		LCSPX_REG(0x2270) = 0x00112003; // CHECKME !!!
//		LCSPX_REG(0x2270) = ((0x2014 << 16) + 0x2003);
		LCSPX_REG(0x21fc) = 0x201a;
	}

	LCSPX_REG1(0x18) = 0xffffffff;
	while ((counter) && ((LCSPX_REG1(0x18) & 2) == 0))
		counter--;

}

void blkset(u_int xpos, u_int ypos, u_int xdim, u_int ydim, char color)
{
	u_int counter = 0xfffe;
	long temp = 0;

	LCSPX_REG(0x21ec) = 0x84884608;
	LCSPX_REG(0x21d0) = xpos << 16;
	LCSPX_REG(0x21d4) = ypos << 16;
	LCSPX_REG(0x21d8) = (xpos + xdim) << 16;
	LCSPX_REG(0x21dc) = (ypos + ydim) << 16;
//	LCSPX_REG(0x21e8) = (lcspx_xsize << 16) | lcspx_xsize;
	LCSPX_REG(0x2270) = 0x20102003;
	
	temp = ((LCSPX_REG1(0x10) & 0xc0) << (30 - 6)) | ((LCSPX_REG1(0x10) & 0x3f) << 24);
	temp += (xpos + (ypos * lcspx_xsize));
	LCSPX_REG(0x21e0) = temp;
//	LCSPX_REG(0x20e0) = 0xff000000;
	
	/* CHECKME: do we need datapath width case at all ? */
	LCSPX_REG(0x2260) = (color << 24) | (color << 16) | (color << 8) | color;
	
	LCSPX_REG(0x21fc) = 0x00002019;

	LCSPX_REG1(0x18) = 0xffffffff;
	while ((counter) && ((LCSPX_REG1(0x18) & 2) == 0))
		counter--;

}

int
lcspx_match(struct device *parent, struct cfdata *match, void *aux)
{
	struct vsbus_softc *sc = (void *)parent;
#if 0
	struct vsbus_attach_args *va = aux;
	char *ch = (char *)va->va_addr;
#endif
	if (vax_boardtype != VAX_BTYP_49)
		return 0;

#if 0 /* FIXME are memory addresses besides va->va_addr off limits at this time ? */
	/* BT459 ID register test, should always read 0x4a */
	if (get_btreg(BT459_REG_ID) != 0x4a)
		return 0;
#endif

#if 0
	*ch = 1;
	if ((*ch & 1) == 0)
		return 0;
	*ch = 0;
	if ((*ch & 1) != 0)
		return 0;
#endif
	sc->sc_mask = 0x04; /* XXX - should be generated */
	scb_fake(0x120, 0x15);	

	return 20;
}

#if 0
void showmem(long addr, u_int xsize, u_int ysize)
{
	u_int i, j;
	for (i = 0; i < ysize; i++) {
		printf("0x%x: ", (u_int)(LCSPX_FB_ADDR + addr + i * lcspx_xsize));
		for (j = 0; j < xsize; j++)
			printf("%02x ", (lcspxaddr[addr + (i * lcspx_xsize) + j] & 0xff));
		printf("\n");
	}
}
#endif

void
lcspx_attach(struct device *parent, struct device *self, void *aux)
{
	struct vsbus_attach_args *va = aux;
	struct wsemuldisplaydev_attach_args aa;

#if 0
	u_int i, j, k;
#endif

	printf("\n");
		aa.console = lcspxaddr != NULL;

	lcspx_init_common(self, va);

	curscr = &lcspx_conscreen;

	aa.scrdata = &lcspx_screenlist;
	aa.accessops = &lcspx_accessops;

	/* enable cursor */
	callout_reset(&lcspx_cursor_ch, hz / 2, lcspx_crsr_blink, NULL);

	config_found(self, &aa, wsemuldisplaydevprint);

#if 0
	/* display colorbar */
	for (i = 0; i <256; i++)
		for (j = 400; j < 600; j++)
			for (k = 0; k < 5; k++)
				if (k != 4)
					lcspxaddr[(j * lcspx_xsize) + (i * 5) + k] = i;

//	delay(3 * 1000 * 1000);

	for (i = 1; i < 256; i++)
		lcspx_update_cmap(i, 255 - i, 255 - i, 255 - i);
	
#endif
#if 0
//	LCSPX_REG(0x316c) = LCSPX_REG(0x316c) | 0x20000;
	for (i = 0; i < 16; i++)
		for (j = 0; j < 16; j++)
			lcspxaddr[((i + 50) * lcspx_xsize) + j + 100] = (i << 4) | j;

	showmem((50 * lcspx_xsize) + 100, 16, 16);
	k = 0;

//	delay(3 * 1000 * 1000);
#endif
//	LCSPX_REG(0x316c) = LCSPX_REG(0x316c) | 0xfffdffff;

#if 0
	showmem(0, 10, 10);
	blkcpy(1, 1, 5, 5, 4, 4);
	printf("\n");
	showmem(0, 10, 10);
#endif			
}

static	char *cursor;

static	int cur_on;
static	int cur_status = 0;

static void
lcspx_crsr_blink(void *arg)
{
#ifdef SOFTCURSOR
	int dot;

		for (dot = 0; dot < lcspx_font.fontwidth; dot++)
			cursor[dot] = ((cursor[dot] & 0x0f) == cur_fg) ? cur_bg : cur_fg;
#else
	if (cur_on && curscr != NULL) {
		set_btreg(BT459_REG_CCR, (cur_status == 0) ? 0xc0 : 0x00);
		cur_status = !cur_status;
	}
#endif
	callout_reset(&lcspx_cursor_ch, hz / 2, lcspx_crsr_blink, NULL);
}

void
lcspx_cursor(void *id, int on, int row, int col)
{
	struct lcspx_screen *ss = id;
	int attr;
#ifdef SOFTCURSOR
	int dot;
#endif

	attr = ss->ss_image[row * lcspx_cols + col].attr;
	if (ss == curscr) {
		if (cursor != NULL) {
#ifdef SOFTCURSOR
			int ch = QFONT(ss->ss_image[ss->ss_cury * lcspx_cols +
			    ss->ss_curx].data, lcspx_font.fontheight - 1);
#endif
			int attr = ss->ss_image[ss->ss_cury * lcspx_cols +
			    ss->ss_curx].attr;

			if (attr & LCSPX_ATTR_REVERSE) {
				cur_bg = attr & LCSPX_FG_MASK;
				cur_fg = (attr & LCSPX_BG_MASK) >> 4;
			} else {
				cur_fg = attr & LCSPX_FG_MASK;
				cur_bg = (attr & LCSPX_BG_MASK) >> 4;
			}

#ifdef SOFTCURSOR
			for (dot = 0; dot < lcspx_font.fontwidth; dot++)
				cursor[dot] =  (ch & (1 << dot)) ?
				    cur_fg : cur_bg;
#else
			/* change cursor foreground color colormap entry */
			lcspx_update_cmap(BT459_REG_CCOLOR_3, 
				lcspx_cmap.r[cur_fg],
				lcspx_cmap.g[cur_fg],
				lcspx_cmap.b[cur_fg]);
#if 0
			set_btreg(BT459_REG_CCOLOR_3, lcspx_cmap.r[cur_fg]);
			bt_reg[0] = lcspx_cmap.g[cur_fg];
			bt_reg[0] = lcspx_cmap.b[cur_fg];
#endif
			/* update cursor position registers */
			set_btreg(BT459_REG_CXLO, LO(col * lcspx_font.fontwidth + CUR_XBIAS));
			set_btreg(BT459_REG_CXHI, HI(col * lcspx_font.fontwidth + CUR_XBIAS));
			set_btreg(BT459_REG_CYLO, LO(row * lcspx_font.fontheight + CUR_YBIAS));
			set_btreg(BT459_REG_CYHI, HI(row * lcspx_font.fontheight + CUR_YBIAS));
#endif
		}


		cursor = &LCSPX_ADDR(row, col, lcspx_font.fontheight - 1, 0);
		cur_on = on;
		if (attr & LCSPX_ATTR_REVERSE) {
			cur_bg = attr & LCSPX_FG_MASK;
			cur_fg = (attr & LCSPX_BG_MASK) >> 4;
		} else {
			cur_fg = attr & LCSPX_FG_MASK;
			cur_bg = (attr & LCSPX_BG_MASK) >> 4;
		}
	}
	ss->ss_curx = col;
	ss->ss_cury = row;
	if (attr & LCSPX_ATTR_REVERSE) {
		ss->ss_cur_bg = attr & LCSPX_FG_MASK;
		ss->ss_cur_fg = (attr & LCSPX_BG_MASK) >> 4;
	} else {
		ss->ss_cur_fg = attr & LCSPX_FG_MASK;
		ss->ss_cur_bg = (attr & LCSPX_BG_MASK) >> 4;
	}
}

int
lcspx_mapchar(void *id, int uni, unsigned int *index)
{
	if (uni < 256) {
		*index = uni;
		return (5);
	}
	*index = ' ';
	return (0);
}

static void
lcspx_putchar(void *id, int row, int col, u_int c, long attr)
{
	struct lcspx_screen *ss = id;
	u_int i, j;
	char dot_fg, dot_bg;

	c &= 0xff;

	ss->ss_image[row * lcspx_cols + col].data = c;
	ss->ss_image[row * lcspx_cols + col].attr = attr;
	if (ss != curscr)
		return;

	dot_fg = attr & LCSPX_FG_MASK;
	dot_bg = (attr & LCSPX_BG_MASK) >> 4;
	if (attr & LCSPX_ATTR_REVERSE) {
		dot_fg = (attr & LCSPX_BG_MASK) >> 4;
		dot_bg = attr & LCSPX_FG_MASK;
	}

	for (i = 0; i < lcspx_font.fontheight; i++) {
		int ch = QFONT(c, i);
		char *p = &LCSPX_ADDR(row, col, i, 0);

		for (j = 0; j < lcspx_font.fontwidth; ++j) {
			*p++ = ch & (1 << j) ? dot_fg : dot_bg;
		}
	}

	if (attr & LCSPX_ATTR_UNDERLINE) {
		char *p = &LCSPX_ADDR(row, col, lcspx_font.fontheight - 1, 0);
		for (i = 0; i < lcspx_font.fontwidth; i++)
			p[i] = ~p[i];
	}
}

/*
 * copies columns inside a row.
 */
static void
lcspx_copycols(void *id, int row, int srccol, int dstcol, int ncols)
{
	struct lcspx_screen *ss = id;

	bcopy(&ss->ss_image[row * lcspx_cols + srccol], &ss->ss_image[row *
	    lcspx_cols + dstcol], ncols * sizeof(ss->ss_image[0]));
	if (ss != curscr)
		return;
	blkcpy((srccol * lcspx_font.fontwidth), (row * lcspx_font.fontheight),
		(dstcol * lcspx_font.fontwidth), (row * lcspx_font.fontheight),
		(ncols * lcspx_font.fontwidth), lcspx_font.fontheight);
}

/*
 * Erases a bunch of chars inside one row.
 */
static void
lcspx_erasecols(void *id, int row, int startcol, int ncols, long fillattr)
{
	struct lcspx_screen *ss = id;
	int i;

	bzero(&ss->ss_image[row * lcspx_cols + startcol], ncols * sizeof(ss->ss_image[0]));
	for (i = row * lcspx_cols + startcol; i < row * lcspx_cols + startcol + ncols; ++i)
		ss->ss_image[i].attr = fillattr;
	if (ss != curscr)
		return;
	blkset((startcol * lcspx_font.fontwidth), (row * lcspx_font.fontheight),
		(ncols * lcspx_font.fontwidth), lcspx_font.fontheight, ((fillattr & LCSPX_BG_MASK) >> 4));
}

static void
lcspx_copyrows(void *id, int srcrow, int dstrow, int nrows)
{
	struct lcspx_screen *ss = id;

	bcopy(&ss->ss_image[srcrow * lcspx_cols], &ss->ss_image[dstrow * lcspx_cols],
	    nrows * lcspx_cols * sizeof(ss->ss_image[0]));
	if (ss != curscr)
		return;
	blkcpy(0, (srcrow * lcspx_font.fontheight),
		0, (dstrow * lcspx_font.fontheight),
		lcspx_xsize, (nrows * lcspx_font.fontheight));
}

static void
lcspx_eraserows(void *id, int startrow, int nrows, long fillattr)
{
	struct lcspx_screen *ss = id;
	int i;

	bzero(&ss->ss_image[startrow * lcspx_cols], nrows * lcspx_cols *
	    sizeof(ss->ss_image[0]));
	for (i = startrow * lcspx_cols; i < (startrow + nrows) * lcspx_cols; ++i)
		ss->ss_image[i].attr = fillattr;
	if (ss != curscr)
		return;
	blkset(0, (startrow * lcspx_font.fontheight), lcspx_xsize,
		(nrows * lcspx_font.fontheight), ((fillattr & LCSPX_BG_MASK) >> 4));
}

static int
lcspx_allocattr(void *id, int fg, int bg, int flags, long *attrp)
{
	long myattr;

	if (flags & WSATTR_WSCOLORS)
		myattr = (fg & LCSPX_FG_MASK) | ((bg << 4) & LCSPX_BG_MASK);
	else
		myattr = WSCOL_WHITE << 4;	/* XXXX */
	if (flags & WSATTR_REVERSE)
		myattr |= LCSPX_ATTR_REVERSE;
	if (flags & WSATTR_UNDERLINE)
		myattr |= LCSPX_ATTR_UNDERLINE;
	*attrp = myattr;
	return 0;
}

int
lcspx_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
{
	struct wsdisplay_fbinfo *fb = (void *)data;
	int i;

	switch (cmd) {
	case WSDISPLAYIO_GTYPE:
		*(u_int *)data = WSDISPLAY_TYPE_SPX;
		break;

	case WSDISPLAYIO_GINFO:
		fb->height = lcspx_ysize;
		fb->width = lcspx_xsize;
		fb->depth = lcspx_depth;
		fb->cmsize = 1 << lcspx_depth;
		break;

	case WSDISPLAYIO_GETCMAP:
		return lcspx_get_cmap((struct wsdisplay_cmap *)data);

	case WSDISPLAYIO_PUTCMAP:
		return lcspx_set_cmap((struct wsdisplay_cmap *)data);

	case WSDISPLAYIO_GMODE:
		return EPASSTHROUGH;

	case WSDISPLAYIO_SMODE:
		/* if setting WSDISPLAYIO_MODE_EMUL, restore console cmap, current screen */
		if (*(u_int *)data == WSDISPLAYIO_MODE_EMUL) {
			for (i = 0; i < 8; i++) {
				lcspx_cmap.r[i] = lcspx_default_cmap.r[i];
				lcspx_cmap.g[i] = lcspx_default_cmap.g[i];
				lcspx_cmap.b[i] = lcspx_default_cmap.b[i];
				lcspx_update_cmap(i, 
					lcspx_cmap.r[i],
					lcspx_cmap.g[i],
					lcspx_cmap.b[i]);
			}
			if (savescr != NULL)
				lcspx_show_screen(NULL, savescr, 0, NULL, NULL);
			savescr = NULL;
		} else {		/* WSDISPLAYIO_MODE_MAPPED */
			savescr = curscr;
			curscr = NULL;
			/* clear screen? */
		}

		return EPASSTHROUGH;
#if 0
	/* FIXME use bt459 to disable video ? */
	case WSDISPLAYIO_SVIDEO:
		if (*(u_int *)data == WSDISPLAYIO_VIDEO_ON) {
			LCSPX_REG(LCSPX_REG_VIDEO_CONFIG) = LCSPX_REG(LCSPX_REG_VIDEO_CONFIG) | 0x1000;
		} else {
			LCSPX_REG(LCSPX_REG_VIDEO_CONFIG) = LCSPX_REG(LCSPX_REG_VIDEO_CONFIG) & ~0x1000;
		}
		break;

	/* FIXME ??? */
	case WSDISPLAYIO_GVIDEO:
		*(u_int *)data = (LCSPX_REG(LCSPX_REG_VIDEO_CONFIG) & 0x1000) ?
		    WSDISPLAYIO_VIDEO_ON : WSDISPLAYIO_VIDEO_OFF;
		break;
#endif
	default:
		return EPASSTHROUGH;
	}
	return 0;
}

static paddr_t
lcspx_mmap(void *v, off_t offset, int prot)
{
	if (offset >= lcspx_fb_size || offset < 0)
		return -1;
	return (LCSPX_FB_ADDR + offset) >> PGSHIFT;
}

int
lcspx_alloc_screen(void *v, const struct wsscreen_descr *type, void **cookiep,
    int *curxp, int *curyp, long *defattrp)
{
	int i;
	struct lcspx_screen *ss;

	*cookiep = malloc(sizeof(struct lcspx_screen), M_DEVBUF, M_WAITOK);
	bzero(*cookiep, sizeof(struct lcspx_screen));
	*curxp = *curyp = 0;
	*defattrp = (LCSPX_BG_COLOR << 4) | LCSPX_FG_COLOR;
	ss = *cookiep;
	for (i = 0; i < lcspx_cols * lcspx_rows; ++i)
		ss->ss_image[i].attr = (LCSPX_BG_COLOR << 4) | LCSPX_FG_COLOR;
	return 0;
}

void
lcspx_free_screen(void *v, void *cookie)
{
/* FIXME add something to actually free malloc()ed screen ? */
}

int
lcspx_show_screen(void *v, void *cookie, int waitok,
    void (*cb)(void *, int, int), void *cbarg)
{
	struct lcspx_screen *ss = cookie;
	u_int row, col, dot_fg, dot_bg, i, j, attr;
	u_char c;

	if (ss == curscr)
		return (0);

	for (row = 0; row < lcspx_rows; row++)
		for (col = 0; col < lcspx_cols; col++) {
			attr = ss->ss_image[row * lcspx_cols + col].attr;
			if (attr & LCSPX_ATTR_REVERSE) {
				dot_fg = (attr & LCSPX_BG_MASK) >> 4;
				dot_bg = attr & LCSPX_FG_MASK;
			} else {
				dot_fg = attr & LCSPX_FG_MASK;
				dot_bg = (attr & LCSPX_BG_MASK) >> 4;
			}
			c = ss->ss_image[row * lcspx_cols + col].data;

			if (c < 32)
				c = 32;

			for (i = 0; i < lcspx_font.fontheight; i++) {
				int ch = QFONT(c, i);
				char *p = &LCSPX_ADDR(row, col, i, 0);

				for (j = 0; j < lcspx_font.fontwidth; ++j) {
					*p++ = ch & (1 << j) ? dot_fg : dot_bg;
				}
			}

			if (attr & LCSPX_ATTR_UNDERLINE)
				for (j = 0; j < lcspx_font.fontwidth; j++)
					LCSPX_ADDR(row, col,
					    lcspx_font.fontheight - 1, j) = dot_fg;
		}

	cursor = &lcspxaddr[(ss->ss_cury * lcspx_onerow) +
		((lcspx_font.fontheight - 1) * lcspx_xsize) +
			(ss->ss_curx * lcspx_font.fontwidth)];
	cur_fg = ss->ss_cur_fg;
	cur_bg = ss->ss_cur_bg;

	curscr = ss;
	return (0);
}

static int
lcspx_get_cmap(p)
	struct wsdisplay_cmap *p;
{
	u_int index = p->index, count = p->count;
	int error;

	if (index >= (1 << lcspx_depth) || count > (1 << lcspx_depth) - index)
		return (EINVAL);

	error = copyout(&lcspx_cmap.r[index], p->red, count);
	if (error)
		return error;
	error = copyout(&lcspx_cmap.g[index], p->green, count);
	if (error)
		return error;
	error = copyout(&lcspx_cmap.b[index], p->blue, count);
	return error;
}

static int
lcspx_set_cmap(p)
	struct wsdisplay_cmap *p;
{
	u_int index = p->index, count = p->count;
	int error, s;

	if (index >= (1 << lcspx_depth) || count > (1 << lcspx_depth) - index)
		return (EINVAL);

	error = copyin(p->red, &lcspx_cmap.r[index], count);
	if (error)
		return error;
	error = copyin(p->green, &lcspx_cmap.g[index], count);
	if (error)
		return error;
	error = copyin(p->blue, &lcspx_cmap.b[index], count);

	if (error)
		return error;

	s = spltty();
	while (count-- > 0) {
		lcspx_update_cmap(index, 
			lcspx_cmap.r[index],
			lcspx_cmap.g[index],
			lcspx_cmap.b[index]);
		index++;
	}
	splx(s);
	return (0);
}

cons_decl(lcspx);

void
lcspxcninit(struct consdev *cndev)
{
	int i;
	/* Clear screen */
	printf("Clear screen\n");
	memset(lcspxaddr, LCSPX_BG_COLOR, lcspx_xsize * lcspx_ysize);

	curscr = &lcspx_conscreen;
	for (i = 0; i < lcspx_cols * lcspx_rows; i++)
		lcspx_conscreen.ss_image[i].attr =
		    (LCSPX_BG_COLOR << 4) | LCSPX_FG_COLOR;
	wsdisplay_cnattach(&lcspx_stdscreen, &lcspx_conscreen, 0, 0,
	    (LCSPX_BG_COLOR << 4) | LCSPX_FG_COLOR);
	cn_tab->cn_pri = CN_INTERNAL;

#if NDZKBD > 0
	dzkbd_cnattach(0); /* Connect keyboard and screen together */
#endif
}

/*
 * Called very early to setup the glass tty as console.
 * Because it's called before the VM system is inited, virtual memory
 * for the framebuffer can be stolen directly without disturbing anything.
 */
void
lcspxcnprobe(struct consdev *cndev)
{
	extern const struct cdevsw wsdisplay_cdevsw;

	if (vax_boardtype != VAX_BTYP_49)
		return; /* Only for VS 4000/90, 90A and 96 */

	if (vax_confdata & 0x100)
		return; /* Diagnostic console */

	lcspx_init_common(NULL, NULL);

	cndev->cn_pri = CN_INTERNAL;
	cndev->cn_dev = makedev(cdevsw_lookup_major(&wsdisplay_cdevsw), 0);
}

void
lcspx_init_common(struct device *self, struct vsbus_attach_args *va)
{
	int i, pixel;
	extern vaddr_t virtual_avail;
	int cookie;
	struct wsdisplay_font *wf;

#if 0
	if (regaddr != NULL)
		return;
#endif

	/* map LCSPX registers first */
	if (self != NULL) {
		regaddr = (long*)vax_map_physmem(LCSPX_REG_ADDR, (LCSPX_REG_SIZE/VAX_NBPG));
		if (regaddr == 0) {
			printf("%s: unable to allocate register memory (1).\n", self->dv_xname);
			return;
		}
		regaddr1 = (long*)vax_map_physmem(LCSPX_REG1_ADDR, (LCSPX_REG1_SIZE/VAX_NBPG));
		if (regaddr1 == 0) {
			printf("%s: unable to allocate register memory (2).\n", self->dv_xname);
			return;
		}
	} else {
		regaddr = (long*)virtual_avail;
		virtual_avail += LCSPX_REG_SIZE;
		ioaccess((vaddr_t)regaddr, LCSPX_REG_ADDR, (LCSPX_REG_SIZE/VAX_NBPG));

		regaddr1 = (long*)virtual_avail;
		virtual_avail += LCSPX_REG1_SIZE;
		ioaccess((vaddr_t)regaddr1, LCSPX_REG1_ADDR, (LCSPX_REG1_SIZE/VAX_NBPG));
	}

	/* any autodetection stuff comes here */
	lcspx_depth = 8;
	lcspx_xsize = 1280;
	lcspx_ysize = 1024;

	wsfont_init();
	cookie = wsfont_find(NULL, 12, 22, 0, WSDISPLAY_FONTORDER_R2L,
	    WSDISPLAY_FONTORDER_L2R);
	if (cookie == -1)
		cookie = wsfont_find(NULL, 8, 0, 0, WSDISPLAY_FONTORDER_R2L, 0);
	if (cookie == -1)
		cookie = wsfont_find(NULL, 0, 0, 0, WSDISPLAY_FONTORDER_R2L,
		    WSDISPLAY_FONTORDER_L2R);
	if (cookie == -1 || wsfont_lock(cookie, &wf))
		panic("lcspx_common_init: can't load console font");
	lcspx_font = *wf;
#if 0
	if (self != NULL) {
		printf("%s: Using font %s\n", self->dv_xname, wf->name);
	}
#endif
	lcspx_cols = lcspx_xsize / lcspx_font.fontwidth;
	lcspx_rows = lcspx_ysize / lcspx_font.fontheight;
	lcspx_onerow = lcspx_xsize * lcspx_font.fontheight;
	lcspx_fb_size = lcspx_xsize * lcspx_ysize;
	lcspx_stdscreen.ncols = lcspx_cols;
	lcspx_stdscreen.nrows = lcspx_rows;
	lcspx_stdscreen.fontwidth = lcspx_font.fontwidth;
	lcspx_stdscreen.fontheight = lcspx_font.fontheight;
	sprintf(lcspx_stdscreen_name, "%dx%d", lcspx_cols, lcspx_rows);

	qf = lcspx_font.data;
	qf2 = (u_short *)lcspx_font.data;

	if (self != NULL) {
		lcspxaddr = (caddr_t)vax_map_physmem(va->va_paddr,
					(lcspx_fb_size/VAX_NBPG));
		if (lcspxaddr == 0) {
			printf("%s: unable to map framebuffer memory.\n", self->dv_xname);
			return;
		}

		/* map 4 BT459 registers */
		bt_addrl = (caddr_t)vax_map_physmem(LCSPX_BT459_ADDRL, 1);
		if (bt_addrl == 0) {
			printf("%s: unable to map BT459 Address register (Low).\n", self->dv_xname);
			return;
		}
		bt_addrh = (caddr_t)vax_map_physmem(LCSPX_BT459_ADDRH, 1);
		if (bt_addrh == 0) {
			printf("%s: unable to map BT459 Address reister (High).\n", self->dv_xname);
			return;
		}
		bt_reg = (caddr_t)vax_map_physmem(LCSPX_BT459_REG, 1);
		if (bt_reg == 0) {
			printf("%s: unable to map BT459 I/O Register.\n", self->dv_xname);
			return;
		}
		bt_cmap = (caddr_t)vax_map_physmem(LCSPX_BT459_CMAP, 1);
		if (bt_cmap == 0) {
			printf("%s: unable to map BT459 Color Map register.\n", self->dv_xname);
			return;
		}
	} else {
		lcspxaddr = (caddr_t)virtual_avail;
		virtual_avail += lcspx_fb_size;
		ioaccess((vaddr_t)lcspxaddr, LCSPX_FB_ADDR, (lcspx_fb_size/VAX_NBPG));

		bt_addrl = (caddr_t)virtual_avail;
		virtual_avail += VAX_NBPG;
		ioaccess((vaddr_t)bt_addrl, LCSPX_BT459_ADDRL, 1);

		bt_addrh = (caddr_t)virtual_avail;
		virtual_avail += VAX_NBPG;
		ioaccess((vaddr_t)bt_addrh, LCSPX_BT459_ADDRH, 1);

		bt_reg = (caddr_t)virtual_avail;
		virtual_avail += VAX_NBPG;
		ioaccess((vaddr_t)bt_reg, LCSPX_BT459_REG, 1);

		bt_cmap = (caddr_t)virtual_avail;
		virtual_avail += VAX_NBPG;
		ioaccess((vaddr_t)bt_cmap, LCSPX_BT459_CMAP, 1);
	}

	/* BT459 ID register test, should always read 0x4a */
	if ((get_btreg(BT459_REG_ID) & 0xff) != 0x4a)
		printf("%s: BT459 test register read error, value = %x.\n", 
			self->dv_xname, get_btreg(BT459_REG_ID) & 0xff);

	for (i = 0; i < 8; i++) {
		lcspx_cmap.r[i] = lcspx_default_cmap.r[i];
		lcspx_cmap.g[i] = lcspx_default_cmap.g[i];
		lcspx_cmap.b[i] = lcspx_default_cmap.b[i];
		lcspx_update_cmap(i, 
			lcspx_cmap.r[i],
			lcspx_cmap.g[i], 
			lcspx_cmap.b[i]);
	}
	
	/* build console cursor bitmap based on font dimensions */
	for (i = 0; i < 1024; i++) {
		if ((i / 16) != (lcspx_font.fontheight - 1)) {
			/* this is not the cursor line */
			cons_curmap[i] = CURSOR_BG;
		} else {
			/* only enable lcspx_font.fontwidth of pixels */
			if ((i % 16) < (lcspx_font.fontwidth / 4))
				cons_curmap[i] = CURSOR_FG;

			if ((i % 16) == (lcspx_font.fontwidth / 4)) {
				cons_curmap[i] = 0;
				for (pixel = 0; pixel < 4; pixel++)
					if (pixel < (lcspx_font.fontwidth % 4))
						cons_curmap[i] += ((CURSOR_FG % 4) << (pixel * 2));
					else
						cons_curmap[i] += ((CURSOR_BG % 4) << (pixel * 2));
			}					

			if ((i % 16) > (lcspx_font.fontwidth / 4))
				cons_curmap[i] = CURSOR_BG;
		}
#if 0
		/* REMOVE verify rendering of cursor sprite */
		printf("%x ", cons_curmap[i] & 0xff);
		if ((i > 0) && (((i + 1) % 16) == 0))
			printf("\n");
#endif
		/* set BT459 cursor sprite */
		set_btreg(BT459_REG_CRAM_BASE + i, cons_curmap[i]);

#if 0
		/* REMOVE verify cursor sprite */
		printf("%x ", get_btreg(BT459_REG_CRAM_BASE + i) & 0xff);
		if ((i > 0) && (((i + 1) % 16) == 0))
			printf("\n");
#endif
	}

#if 0
/* for testing only, should be removed/replaced */
	lcspx_update_cmap(BT459_REG_CCOLOR_1,
				lcspx_cmap.r[7],
				lcspx_cmap.g[7],
				lcspx_cmap.b[7]);
	lcspx_update_cmap(BT459_REG_CCOLOR_2,
				lcspx_cmap.r[5],
				lcspx_cmap.g[5],
				lcspx_cmap.b[5]);
	lcspx_update_cmap(BT459_REG_CCOLOR_3,
				lcspx_cmap.r[6],
				lcspx_cmap.g[6],
				lcspx_cmap.b[6]);
#endif

	/* set LCSPX write/read plane masks */
	LCSPX_REG(0x3170) = 0xffffffff;
	LCSPX_REG(0x3174) = 0xffffffff;

	/* set various BT459 registers */
	set_btreg(BT459_REG_CMD0, 0x40);
	set_btreg(BT459_REG_CMD1, 0x00);
	set_btreg(BT459_REG_CMD2, 0xc0);
	set_btreg(BT459_REG_PRM, 0xff);

	set_btreg(BT459_REG_CXLO, 0);
	set_btreg(BT459_REG_CXHI, 0);
	set_btreg(BT459_REG_CYLO, 0);
	set_btreg(BT459_REG_CYHI, 0);

	set_btreg(BT459_REG_WXLO, 0);
	set_btreg(BT459_REG_WXHI, 0);
	set_btreg(BT459_REG_WYLO, 0);
	set_btreg(BT459_REG_WYHI, 0);

#if 1
	set_btreg(BT459_REG_WWLO, LO(lcspx_xsize));
	set_btreg(BT459_REG_WWHI, HI(lcspx_xsize));
	set_btreg(BT459_REG_WHLO, LO(lcspx_ysize));
	set_btreg(BT459_REG_WHHI, HI(lcspx_ysize));
#else
	set_btreg(BT459_REG_WWLO, 0);
	set_btreg(BT459_REG_WWHI, 0);
	set_btreg(BT459_REG_WHLO, 0);
	set_btreg(BT459_REG_WHHI, 0);
#endif
}
--------------1E262080FE2--