/*
 * Copyright (c) 1989-1994, Sun Microsystems, Inc.
 */

#pragma ident	"@(#)cgsix.c	1.11	94/06/22 SMI"

/*
 * Note: this driver code does not support Sun-4 workstations.  It
 * does support SPARCstation 1 and newer platforms.
 */

/*
 * Cgsix theory of operation:
 *
 * Most cg6 operations are done by mapping the cg6 components into
 * user process memory.  User processes that share mappings must
 * cooperate among themselves to prevent damaging the state of the
 * cg6.  User processes may also acquire private mappings (MAP_PRIVATE
 * flag to mmap(2)), in which case the cg6 driver will preserve device
 * state for each mapping.
 *
 * cg6_mmap interprets the device offset as follows:
 *
 *	CG6_VBASE	0x70000000
 *	CG6_VADDR_FBC	0x70000000	fbc mapping
 *	CG6_VADDR_TEC	0x70001000	tec mapping
 *	CG6_VADDR_CMAP	0x70002000	colormap dacs
 *	CG6_VADDR_FHC	0x70004000	fhc mapping
 *	CG6_VADDR_THC	0x70005000	thc mapping
 *	CG6_VADDR_ROM	0x70006000	eprom mapping
 *	CG6_VADDR_COLOR	0x70016000	framebuffer mapping
 *	CG6_VADDR_DHC	0x78000000	dac hardware
 *	CG6_VADDR_ALT	0x78002000	alternate registers
 *	CG6_VADDR_UART	0x78004000	uart, if any
 *	CG6_VADDR_VRT	0x78006000	vertical retrace counter page
 *
 * The lengths of these mappings should be:
 *
 *	CG6_CMAP_SZ	0x2000
 *	CG6_FBCTEC_SZ	0x2000
 *	CG6_FHCTHC_SZ	0x2000
 *	CG6_ROM_SZ	0x10000
 *	CG6_FB_SZ	0x100000
 *	CG6_DHC_SZ	0x2000
 *	CG6_ALT_SZ	0x2000
 *
 * Mappings to the fbc and tec registers may be MAP_PRIVATE, in which case
 * the driver keeps a per-context copy of the fbc and tec registers in
 * local memory.  Only one context at a time may have valid mappings.
 * If a process tries to access the registers through an invalid
 * mapping, the driver in invoked to swap register state and validate
 * and validate the mappings.
 *
 * In the case of the buggy LSC revision 2. chip, the framebuffer mapping
 * is also considered part of a context.  This is to ensure that the
 * registers are idle before the framebuffer is touched.
 *
 * Mappings to FBC, TEC and framebuffer may be made separately, in which
 * case the driver uses heuristics to bind separate mappings into a single
 * context.  These heuristics may break down if mappings are done in a
 * funny order or in a multi-threaded environment, so separate mappings
 * are not recommended.
 *
 * Processes have the option of mapping the "vertical retrace page".
 * This is a page in shared memory containing a 32-bit integer that is
 * incremented each time a vertical retrace interrupt occurs.  It is
 * used so that programs may synchronize themselves with vertical retrace.
 */

/*
 * SBus accelerated 8 bit color frame buffer driver
 */
#ifdef	_DDICT
#define	NBBY	8
#endif

#include <sys/debug.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/buf.h>
#include <sys/errno.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/map.h>
#include <sys/cred.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/devops.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/visual_io.h>

#include "cg6io.h"
#include "cg6reg.h"
#include "cg6var.h"


/* configuration options */

#ifndef lint
char _depends_on[] = "misc/seg_mapdev";
#endif /* lint */

#define	CG6DEBUG	0
#if CG6DEBUG >= 1
#define	ASSERT2(e) \
	{\
	    if (!(e)) \
		cmn_err(CE_NOTE, \
		"cgsix: assertion failed \"%s\", line %d\n", #e, __LINE__); }
#else
#define	ASSERT2(e)		/* nothing */
#endif

#define	CG6DELAY(c, n)    \
{ \
	register int N = n; \
	while (--N > 0) { \
		if (c) \
			break; \
		drv_usecwait(1); \
	} \
}

#if CG6DEBUG >= 2
int	cg6_debug = 1;

#define	DEBUGF(level, args) \
		{ if (cg6_debug == (level)) cmn_err args; }
#else
#define	DEBUGF(level, args)	/* nothing */
#define	DUMP_SEGS(level, s, c)	/* nothing */
#endif

#define	cg6mpr_linebytes(x, depth) \
	(((((short)x * (short)depth) + 15) >> 3) &~ 1)

#define	getprop(devi, name, def)	\
		ddi_getprop(DDI_DEV_T_ANY, (devi), \
		DDI_PROP_DONTPASS, (name), (def))


/* config info */

static int cg6_open(dev_t *, int, int, cred_t *);
static int cg6_close(dev_t, int, int, cred_t *);
static int cg6_ioctl(dev_t, int, int, int, cred_t *, int *);
static int cg6_mmap(dev_t, off_t, int);
static int cg6_segmap(dev_t, off_t, struct as *, caddr_t *, off_t,
    u_int, u_int, u_int, cred_t *);

static struct vis_identifier cg6_ident = {"SUNWcg6"};

static struct cb_ops cg6_cb_ops = {
	cg6_open,		/* open */
	cg6_close,		/* close */
	nodev,			/* strategy */
	nodev,			/* print */
	nodev,			/* dump */
	nodev,			/* read */
	nodev,			/* write */
	cg6_ioctl,		/* ioctl */
	nodev,			/* devmap */
	cg6_mmap,		/* mmap */
	cg6_segmap,		/* segmap */
	nochpoll,		/* poll */
	ddi_prop_op,		/* cb_prop_op */
	0,			/* streamtab  */
	D_NEW | D_MP		/* Driver compatibility flag */
};

static int
cg6_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result);
static int cg6_identify(dev_info_t *);
static int cg6_attach(dev_info_t *, ddi_attach_cmd_t);
static int cg6_detach(dev_info_t *, ddi_detach_cmd_t);

static struct dev_ops cgsix_ops = {
	DEVO_REV,		/* devo_rev, */
	0,			/* refcnt  */
	cg6_info,		/* info */
	cg6_identify,		/* identify */
	nulldev,		/* probe */
	cg6_attach,		/* attach */
	nodev,			/* detach */
	nodev,			/* reset */
	&cg6_cb_ops,		/* driver operations */
	(struct bus_ops *)0	/* bus operations */
};

static struct cg6map_pvt {
	struct cg6_softc *softc;
	ddi_mapdev_handle_t handle;	/* handle of mapdev object	*/
	u_int	offset;			/* starting offset of this map	*/
	u_int	len;			/* length of this map		*/
	struct cg6_cntxt *context;	/* associated context		*/
	struct cg6map_pvt *next;	/* List of associated pvt's for */
					/* this context			*/
};

/* how much to map */
#define	CG6MAPSIZE	MMAPSIZE(0)

/* maximum the prom can map */
#define	MAX_PROM_MAP	0x180000

/* vertical retrace counter page */

#ifndef	CG6_VRT_SZ
#define	CG6_VRT_SZ	8192
#endif

/*
 * Per-context info:
 *	many registers in the tec and fbc do
 *	not need to be saved/restored.
 */
struct cg6_cntxt {
	struct cg6map_pvt *pvt;	/* List of associated pvt's for this context */
	int	flag;

	struct {
	    u_int   mv;
	    u_int   clip;
	    u_int   vdc;
	    u_int   data[64][2];
	}    tec;

	struct {
	    u_int   status;
	    u_int   clipcheck;
	    struct l_fbc_misc misc;
	    u_int   x0, y0, x1, y1, x2, y2, x3, y3;
	    u_int   rasteroffx, rasteroffy;
	    u_int   autoincx, autoincy;
	    u_int   clipminx, clipminy, clipmaxx, clipmaxy;
	    u_int   fcolor, bcolor;
	    struct l_fbc_rasterop rasterop;
	    u_int   planemask, pixelmask;
	    union l_fbc_pattalign pattalign;
	    u_int   pattern0, pattern1, pattern2, pattern3, pattern4, pattern5,
		pattern6, pattern7;
	}    fbc;
};

/* per-unit data */
struct cg6_softc {

	short		*fb;
	int		w;
	int		h;
	int		linebytes;
	int		size;		/* total size of frame buffer	*/
	int		dummysize;	/* total size of overlay plane	*/
	kmutex_t	interlock;	/* interrupt locking		*/
	off_t		addr_rom;
	caddr_t		fbctec;		/* fbc&tec kernel map addr.	*/
	caddr_t		cmap;		/* colormap kernel map addr.	*/
	caddr_t		fhcthc;		/* fhc&thc kernel map addr.	*/
	caddr_t		rom;		/* rom kernel map addr.		*/
	caddr_t		dhc;		/* dac hardware			*/
	caddr_t		alt;		/* alt registers		*/
	caddr_t		uart;		/* uart registers		*/
	u_int		fbpfnum;	/* pfn of fb for mmap()		*/

	struct softcur {
	    short   enable;		/* cursor enable	*/
	    short   pad1;
	    struct cg6curpos pos;	/* cursor position	*/
	    struct cg6curpos hot;	/* cursor hot spot	*/
	    struct cg6curpos size;	/* cursor bitmap size	*/
	    u_long  image[32];		/* cursor image bitmap	*/
	    u_long  mask[32];		/* cursor mask bitmap	*/
	}    cur;

	union {				/* shadow overlay color map	*/
	    u_long  omap_long[2];	/* cheating here to save space	*/
	    u_char  omap_char[3][2];
	} omap_image;
#define	omap_rgb	omap_image.omap_char[0]

	u_short omap_update;		/* overlay colormap update flag	*/

	u_short cmap_index;		/* colormap update index	*/
	u_short cmap_count;		/* colormap update count	*/

	union {				/* shadow color map		*/
	    u_long  cmap_long[CG6_CMAP_ENTRIES * 3 / sizeof (u_long)];
	    u_char  cmap_char[3][CG6_CMAP_ENTRIES];
	} cmap_image;
#define	cmap_rgb	cmap_image.cmap_char[0]

#define	CG6VRTIOCTL	1		/* FBIOVERTICAL in effect	*/
#define	CG6VRTCTR	2		/* OWGX vertical retrace counter */

	unsigned long	fbmappable;	/* bytes mappable		*/
	int		*vrtpage;	/* pointer to VRT page		*/
	int		vrtmaps;	/* number of VRT page maps	*/
	int		vrtflag;	/* vrt interrupt flag		*/
	struct cg6_inform cg6info;	/* info about this cg6		*/
	struct cg6_mon_info moninfo;	/* info about this monitor	*/
	struct cg6_cntxt *curctx;	/* context switching		*/
	struct cg6_cntxt shared_ctx;	/* shared context		*/
	int		chiprev;	/* fbc chip revision #		*/
	int		emulation;	/* emulation type, normally cgsix */
	dev_info_t	*devi;		/* back pointer			*/
	ddi_iblock_cookie_t iblock_cookie; /* block interrupts		*/
	int		busy;		/* unit is open			*/
	kmutex_t	mutex;		/* mutex locking		*/
	kcondvar_t	vrtsleep;	/* for sleeping			*/
	int		mapped_by_prom;
	int		mapped_by_driver;
	int		waiting;
};

static int cg6map_access(ddi_mapdev_handle_t, void *, off_t);
static void cg6map_free(ddi_mapdev_handle_t, void *);
static int cg6map_dup(ddi_mapdev_handle_t, void *,
    ddi_mapdev_handle_t, void **);

static struct ddi_mapdev_ctl cg6map_ops = {
	MAPDEV_REV,		/* mapdev_ops version number	*/
	cg6map_access,		/* mapdev access routine	*/
	cg6map_free,		/* mapdev free routine		*/
	cg6map_dup		/* mapdev dup routine		*/
};

/* boardrev bits */
#define	BRDRV_SETRES	0x01	/* board supports set resolution */
#define	BRDRV_TYPE	0x78	/* board type:			*/
#define	BRDRV_GX	0x00
#define	BRDRV_LSC	0x08
#define	BRDRV_DUPLO	0x10
#define	BRDRV_LEGOHR	0x18
#define	BRDRV_QAUDRO	0x20
#define	BRDRV_HIRES	0x80	/* hires dacs (junior vs. senior) */

#ifdef	COMMENT
static int unit = 0;
#endif				/* COMMENT */

static void *cg6_softc_head;
static kmutex_t *cg6_mutex = NULL;	/* driver-wide mutex */
static u_int pagesize, pageoffset;

/* default structure for CG6BIOGATTR ioctl */
static struct cg6_gattr cg6_attr = {
/*	real_type	 owner */
	CG6_IDENTIFIER, 0,
/* fbtype: type		 h  w  depth    cms  size */
	{CG6_IDENTIFIER, 0, 0, CG6_DEPTH, CG6_CMAP_ENTRIES, 0},
/* fbsattr: flags emu_type    dev_specific */
	{0, 0, {0}},
/*	emu_types */
	{CG6_IDENTIFIER, 0, 0, -1}
};


/*
 * handy macros
 */

#define	getsoftc(unit) \
	((struct cg6_softc *)ddi_get_soft_state(cg6_softc_head, (unit)))


#define	LOOP_DECR(var)		(--(var) >= 0)
#define	_CG6STMT(op)		do { op } while (0)
#define	CG6PR_LOOPV(var, op)	_CG6STMT(while (LOOP_DECR(var)) { op; })
#define	CG6PR_LOOPVP(var, op)	do { op; } while (LOOP_DECR(var))

#define	btob(n)			ptob(btopr(n))

#define	BZERO(d, c)		bzero((caddr_t)(d), (u_int)(c))
#define	CG6BCOPY(s, d, c)	bcopy((caddr_t)(s), (caddr_t)(d), (u_int)(c))
#define	CG6_COPYIN(s, d, c, f)	ddi_copyin((caddr_t)(s), (caddr_t)(d), \
				    (size_t)(c), (int)(f))
#define	CG6_COPYOUT(s, d, c, f)	ddi_copyout((caddr_t)(s), (caddr_t)(d), \
				    (size_t)(c), (int)(f))

#define	CG6_FBC_WAIT	500000	/* .5 seconds */


/* enable/disable interrupt */
#define	TEC_EN_VBLANK_IRQ	0x20
#define	TEC_HCMISC_IRQBIT	0x10

/* convert softc to data pointers */

#define	S_FBC(softc)	((struct fbc *)(softc)->fbctec)
#define	S_TEC(softc)	((struct tec *)((softc)->fbctec + CG6_TEC_POFF))
#define	S_FHC(softc)	((u_int *)(softc)->fhcthc)
#define	S_THC(softc)	((struct thc *)((softc)->fhcthc + CG6_TEC_POFF))
#define	S_CMAP(softc)	((struct cg6_cmap *)(softc)->cmap)


/* position value to use to disable HW cursor */
#define	CG6_CURSOR_OFFPOS	(0xffe0ffe0)

#define	cg6_set_video(softc, on)	thc_set_video(S_THC(softc), (on))
#define	cg6_get_video(softc)		thc_get_video(S_THC(softc))

#define	cg6_int_disable(softc) \
	{\
	    mutex_enter(&(softc)->interlock); \
	    thc_int_disable(S_THC(softc)); \
	    mutex_exit(&(softc)->interlock); }

#define	cg6_int_pending(softc)		thc_int_pending(S_THC(softc))

/* check if color map update is pending */
#define	cg6_update_pending(softc) \
	((softc)->cmap_count || (softc)->omap_update)

/*
 * forward references
 */
static u_int cg6_intr(caddr_t intr_arg);
static void cg6_reset_cmap(volatile u_char *, register u_int);
static void cg6_update_cmap(struct cg6_softc *, u_int, u_int);
static void cg6_cmap_bcopy(register u_char *, register u_char *, u_int);
static caddr_t cg6_getpages(int n, int flag);
static void cg6_freepages(void *ptr, int size);
static void cg6_setcurpos(struct cg6_softc *);
static void cg6_setcurshape(struct cg6_softc *);
static void cg6_reset(struct cg6_softc *);
static int cg6_cntxsave(volatile struct fbc *fbc, volatile struct tec *tec,
    struct cg6_cntxt *saved);
static int cg6_cntxrestore(volatile struct fbc *fbc, volatile struct tec *tec,
    struct cg6_cntxt *saved);

/* Loadable Driver stuff */

extern struct mod_ops mod_driverops;

static struct modldrv modldrv = {
	&mod_driverops,		/* Type of module: driver		*/
	"cgsix driver v1.32",	/* Name of the module.			*/
	&cgsix_ops,		/* driver ops				*/
};

static struct modlinkage modlinkage = {
	MODREV_1, (void *) &modldrv, NULL
};


int
_init(void)
{
	register int e;

	DEBUGF(1, (CE_CONT, "cgsix: compiled %s, %s\n", __TIME__, __DATE__));

	if ((e = ddi_soft_state_init(&cg6_softc_head,
	    sizeof (struct cg6_softc), 1)) != 0) {
		return (e);
	}

	cg6_mutex = (kmutex_t *)kmem_alloc(sizeof (kmutex_t), KM_SLEEP);
	mutex_init(cg6_mutex, "cgsix_drv", MUTEX_DRIVER, NULL);

	e = mod_install(&modlinkage);

	if (e) {
		DEBUGF(1, (CE_CONT, "destroy cg6_mutex\n"));
		mutex_destroy(cg6_mutex);
		kmem_free((caddr_t)cg6_mutex, sizeof (kmutex_t));
		DEBUGF(1, (CE_CONT, "done\n"));
	}
	DEBUGF(1, (CE_CONT, "cgsix: _init done rtn=%d\n", e));
	return (e);
}

int
_fini(void)
{
	register int e;

	ASSERT2(cg6_mutex != NULL);

	if ((e = mod_remove(&modlinkage)) != 0)
		return (e);

	mutex_destroy(cg6_mutex);
	kmem_free(cg6_mutex, sizeof (*cg6_mutex));
	ddi_soft_state_fini(&cg6_softc_head);

	return (0);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

static
cg6_identify(dev_info_t *devi)
{
	char   *name = ddi_get_name(devi);

	if (strncmp(name, "cgsix", 5) == 0 ||
	    strncmp(name, "SUNW,cgsix", 10) == 0)
		return (DDI_IDENTIFIED);
	else
		return (DDI_NOT_IDENTIFIED);
}

static int
cg6_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	register struct cg6_softc *softc;
	int	reg;
	int	w, h, bytes;
	char	name[16];
	int	unit = ddi_get_instance(devi);

	DEBUGF(1, (CE_CONT, "cg6_attach unit=%d cmd=%d\n", unit, (int)cmd));

	if (cmd != DDI_ATTACH)
		return (DDI_FAILURE);

	ASSERT2(cg6_mutex != NULL);
	mutex_enter(cg6_mutex);

	DEBUGF(1, (CE_CONT, "cg6_attach unit=%d\n", unit));

	pagesize = ddi_ptob(devi, 1);
	pageoffset = pagesize - 1;

	/* Allocate softc struct */
	if (ddi_soft_state_zalloc(cg6_softc_head, unit) != 0)
		return (DDI_FAILURE);

	softc = getsoftc(unit);

	/* link it in */
	softc->devi = devi;
	DEBUGF(1, (CE_CONT, "cg6_attach devi=0x%x unit=%d\n", devi, unit));
	ddi_set_driver_private(devi, (caddr_t)softc);

	softc->emulation = CG6_IDENTIFIER;

	softc->w = w = getprop(devi, "width", 1152);
	softc->h = h = getprop(devi, "height", 900);
	bytes = getprop(devi, "linebytes", cg6mpr_linebytes(w, 8));

	softc->linebytes = bytes;

	/* Compute size of color frame buffer */
	bytes = btob(bytes * h);
	softc->size = ddi_ptob(devi, ddi_btopr(devi, bytes));

	softc->cg6info.vmsize = getprop(devi, "vmsize", 1);
	if (softc->cg6info.vmsize > 1) {
		softc->size = ddi_ptob(devi, ddi_btopr(devi, 8 * 1024 * 1024));
		softc->fbmappable = 8 * 1024 * 1024;
	} else
		softc->fbmappable = 1024 * 1024;

	/* Compute size of dummy overlay/enable planes */
	softc->dummysize = btob(cg6mpr_linebytes(w, 1) * h) * 2;

	/*
	 * only use address property if we are console fb NOTE: if the prom has
	 * already mapped the fb *and* it has mapped all of fbmappable, then we
	 * don't need a new mapping
	 */
	if (reg = getprop(devi, "address", 0)) {
		softc->fb = (short *)reg;
		softc->mapped_by_prom = 1;
		if (ddi_ptob(devi, ddi_btopr(devi, w * h)) <=
		    getprop(devi, "fbmapped", w * h))
			bytes = 0;
		DEBUGF(2, (CE_CONT, "cg6 mapped by PROM\n"));
	}

	softc->cg6info.line_bytes = softc->linebytes;
	softc->cg6info.accessible_width	= getprop(devi, "awidth", 1152);

	softc->cg6info.accessible_height = (u_int)
	    (softc->cg6info.vmsize * 1024 * 1024) /
	    softc->cg6info.accessible_width;

	softc->cg6info.hdb_capable = getprop(devi, "dblbuf", 0);
	softc->cg6info.boardrev    = getprop(devi, "boardrev", 0);
	softc->vrtpage = NULL;
	softc->vrtmaps = 0;
	softc->vrtflag = 0;

#ifdef DEBUG
	softc->cg6info.pad1 = CG6_VADDR_COLOR + CG6_FB_SZ;
#endif

#ifdef	COMMENT
	softc->cg6info.slot =
	    ((int)devi->devi_reg[0].reg_addr - SBUS_BASE) / SBUS_SIZE;
#endif	/* COMMENT */

	/*
	* get monitor attributes
	*/
	softc->moninfo.mon_type = getprop(devi, "montype", 0);
	softc->moninfo.pixfreq  = getprop(devi, "pixfreq", 929405);
	softc->moninfo.hfreq    = getprop(devi, "hfreq", 61795);
	softc->moninfo.vfreq    = getprop(devi, "vfreq", 66);
	softc->moninfo.hfporch  = getprop(devi, "hfporch", 32);
	softc->moninfo.vfporch  = getprop(devi, "vfporch", 2);
	softc->moninfo.hbporch  = getprop(devi, "hbporch", 192);
	softc->moninfo.vbporch  = getprop(devi, "vbporch", 31);
	softc->moninfo.hsync    = getprop(devi, "hsync", 128);
	softc->moninfo.vsync    = getprop(devi, "vsync", 4);


	/*
	 * map in the registers.  Map fbc&tec together.  Likewise for fhc&thc.
	 */
	softc->addr_rom = ddi_dev_is_sid(devi) == DDI_SUCCESS ?
	    CG6_ADDR_ROM_SBUS : CG6_ADDR_ROM_P4;

	if (ddi_map_regs(devi, 0, &softc->fbctec, CG6_ADDR_FBC,
	    (off_t)CG6_FBCTEC_SZ) != 0) {
		mutex_exit(cg6_mutex);
		(void) cg6_detach(devi, DDI_DETACH);
		return (DDI_FAILURE);
	}

	if (ddi_map_regs(devi, 0, &softc->cmap, CG6_ADDR_CMAP,
	    (off_t)CG6_CMAP_SZ) != 0) {
		mutex_exit(cg6_mutex);
		(void) cg6_detach(devi, DDI_DETACH);
		return (DDI_FAILURE);
	}
	if (ddi_map_regs(devi, 0, &softc->fhcthc, CG6_ADDR_FHC,
		    (off_t)CG6_FHCTHC_SZ) != 0) {
	    mutex_exit(cg6_mutex);
	    (void) cg6_detach(devi, DDI_DETACH);
	    return (DDI_FAILURE);
	}
	if (ddi_map_regs(devi, 0, &softc->rom, softc->addr_rom,
	    (off_t)CG6_ROM_SZ) != 0) {
		mutex_exit(cg6_mutex);
		(void) cg6_detach(devi, DDI_DETACH);
		return (DDI_FAILURE);
	}
	if (ddi_map_regs(devi, 0, &softc->dhc, CG6_ADDR_DHC,
	    (off_t)CG6_DHC_SZ) != 0) {
		mutex_exit(cg6_mutex);
		(void) cg6_detach(devi, DDI_DETACH);
		return (DDI_FAILURE);
	}
	if (ddi_map_regs(devi, 0, &softc->alt, CG6_ADDR_ALT,
	    (off_t)CG6_ALT_SZ) != 0) {
		mutex_exit(cg6_mutex);
		(void) cg6_detach(devi, DDI_DETACH);
		return (DDI_FAILURE);
	}


	/* map frame buffer if necessary */
	if (bytes) {
		if (ddi_map_regs(devi, 0, (caddr_t *)&softc->fb,
		    CG6_ADDR_COLOR, (off_t)ddi_ptob(devi,
		    ddi_btopr(devi, w * h))) == -1) {
			mutex_exit(cg6_mutex);
			(void) cg6_detach(devi, DDI_DETACH);
			return (DDI_FAILURE);
		}
		softc->mapped_by_driver = 1;
	}

	softc->fbpfnum = hat_getkpfnum((caddr_t)softc->fb);

	softc->chiprev =
	    *S_FHC(softc) >> FHC_CONFIG_REV_SHIFT & FHC_CONFIG_REV_MASK;

	cg6_reset(softc);

	/* attach interrupt */
	if (ddi_add_intr(devi, 0, &softc->iblock_cookie, 0,
	    cg6_intr, (caddr_t)softc) != DDI_SUCCESS) {
		DEBUGF(2, (CE_CONT,
		    "cg6_attach%d add_intr failed\n", unit));
		mutex_exit(cg6_mutex);
		(void) cg6_detach(devi, DDI_DETACH);
		return (DDI_FAILURE);
	}
	mutex_init(&softc->interlock, "cgsix_interlock", MUTEX_DRIVER,
	    softc->iblock_cookie);
	mutex_init(&softc->mutex, "cgsix", MUTEX_DRIVER, softc->iblock_cookie);
	cv_init(&softc->vrtsleep, "cgsix", CV_DRIVER, softc->iblock_cookie);

	/*
	 * Initialize hardware colormap and software colormap images. It might
	 * make sense to read the hardware colormap here.
	 */
	cg6_reset_cmap(softc->cmap_rgb, CG6_CMAP_ENTRIES);
	cg6_reset_cmap(softc->omap_rgb, 2);
	cg6_update_cmap(softc, (u_int)0, CG6_CMAP_ENTRIES);
	cg6_update_cmap(softc, (u_int)0, (u_int)0);

	DEBUGF(2, (CE_CONT,
	    "cg6_attach%d just before create_minor node\n", unit));
	(void) sprintf(name, "cgsix%d", unit);
	if (ddi_create_minor_node(devi, name, S_IFCHR,
	    unit, DDI_NT_DISPLAY, 0) == DDI_FAILURE) {
		ddi_remove_minor_node(devi, NULL);
		mutex_exit(cg6_mutex);
		DEBUGF(2, (CE_CONT,
		    "cg6_attach%d create_minor node failed\n", unit));
		return (DDI_FAILURE);
	}
	ddi_report_dev(devi);

	if (softc->chiprev == 0)
		cmn_err(CE_CONT, "?Revision 0 FBC\n");

	cmn_err(CE_CONT,
	    "?cgsix%d: screen %dx%d, %s buffered, %dM mappable, rev %d\n",
	    unit, w, h, softc->cg6info.hdb_capable ? "double" : "single",
	    softc->cg6info.vmsize, softc->chiprev);

	/*
	 * Initialize the shared context for this unit
	 */
	softc->shared_ctx.pvt = NULL;

	mutex_exit(cg6_mutex);
	return (DDI_SUCCESS);
}

static int
cg6_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
	register struct cg6_softc *softc;

	int unit = ddi_get_instance(devi);

	softc = getsoftc(unit);

	DEBUGF(1, (CE_CONT, "cg6_detach softc=%x, devi=0x%x\n", softc, devi));

	if (cmd != DDI_DETACH)
		return (DDI_FAILURE);

	/* shut off video if not console */

	if (!softc->mapped_by_prom)
		cg6_set_video(softc, 0);

	mutex_enter(&softc->mutex);
	cg6_int_disable(softc);
	mutex_exit(&softc->mutex);

	ddi_remove_intr(devi, 0, softc->iblock_cookie);

	if (softc->fbctec)
		ddi_unmap_regs(devi, 0,
		    &softc->fbctec, CG6_ADDR_FBC, CG6_FBCTEC_SZ);

	if (softc->cmap)
		ddi_unmap_regs(devi, 0, &softc->cmap, CG6_ADDR_CMAP,
		    CG6_CMAP_SZ);

	if (softc->fhcthc)
		ddi_unmap_regs(devi, 0,
		    &softc->fhcthc, CG6_ADDR_FHC, CG6_FHCTHC_SZ);

	if (softc->rom)
		ddi_unmap_regs(devi, 0, &softc->rom, softc->addr_rom,
		    CG6_ROM_SZ);

	if (softc->dhc)
		ddi_unmap_regs(devi, 0, &softc->dhc, CG6_ADDR_DHC, CG6_DHC_SZ);

	if (softc->alt)
		ddi_unmap_regs(devi, 0, &softc->alt, CG6_ADDR_ALT, CG6_ALT_SZ);

	if (softc->fb && softc->mapped_by_driver)
		ddi_unmap_regs(devi, 0, (caddr_t *)&softc->fb,
		    CG6_ADDR_COLOR, softc->fbmappable);

	if (softc->vrtpage != NULL)
		cg6_freepages(softc->vrtpage, 1);

	mutex_destroy(&softc->mutex);

	cv_destroy(&softc->vrtsleep);

	ASSERT2(softc->curctx == NULL);

	/* free softc struct */
	kmem_free((caddr_t)softc, (u_int)sizeof (struct cg6_softc));

	ddi_soft_state_free(cg6_softc_head, unit);

	return (0);
}

/* ARGSUSED */
static int
cg6_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
	register int error;
	register struct cg6_softc *softc;

	switch (infocmd) {

	case DDI_INFO_DEVT2DEVINFO:
		if ((softc = getsoftc(getminor((dev_t)arg))) == NULL) {
			*result = NULL;
			error = DDI_FAILURE;
		} else {
			mutex_enter(&softc->mutex);
			*result = softc->devi;
			mutex_exit(&softc->mutex);
			error = DDI_SUCCESS;
		}
		break;

	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *) getminor((dev_t)arg);
		error = DDI_SUCCESS;
		break;

	default:
		error = DDI_FAILURE;
	}

	return (error);
}

/*ARGSUSED*/
static
cg6_open(dev_t *devp, int flag, int otyp, cred_t *cred)
{
	struct cg6_softc *softc;
	int    unit = getminor(*devp);


	/* make sure we don't get called during _fini */
	mutex_enter(cg6_mutex);

	if ((softc = getsoftc(unit)) == NULL) {
		mutex_exit(cg6_mutex);
		return (ENXIO);
	}
	mutex_enter(&softc->mutex);
	++softc->busy;
	mutex_exit(&softc->mutex);
	mutex_exit(cg6_mutex);

	return (0);
}


/*ARGSUSED*/
static
cg6_close(dev_t dev, int flag, int otyp, cred_t *cred)
{
	int    unit = getminor(dev);
	struct cg6_softc *softc;

	DEBUGF(2, (CE_CONT, "cg6_close(%d, %d, %d)\n",
	    unit, flag, otyp));

	if ((softc = getsoftc(unit)) == NULL)
		return (ENXIO);

	mutex_enter(&softc->mutex);
	softc->cur.enable = 0;
	softc->curctx = NULL;
	softc->busy = 0;
	cg6_reset(softc);

	mutex_exit(&softc->mutex);

	return (0);
}

/*ARGSUSED*/
static
cg6_mmap(dev_t dev, register off_t off, int prot)
{
	register struct cg6_softc *softc = getsoftc(getminor(dev));
	register int diff;
	register caddr_t page;
	register int rval = 0;

	DEBUGF(off ? 5 : 1, (CE_CONT, "cg6_mmap(%d, 0x%x)\n",
	    getminor(dev), (u_int)off));

	if ((diff = off - CG6_VADDR_COLOR) >= 0 && diff < softc->fbmappable)
		rval = softc->fbpfnum + diff / pagesize;

	else if ((diff = off - CG6_VADDR_FBC) >= 0 && diff < CG6_FBCTEC_SZ)
		page = softc->fbctec + diff;

	else if ((diff = off - CG6_VADDR_CMAP) >= 0 && diff < CG6_CMAP_SZ)
		page = softc->cmap + diff;

	else if ((diff = off - CG6_VADDR_FHC) >= 0 && diff < CG6_FHCTHC_SZ)
		page = softc->fhcthc + diff;

	else if ((diff = off - CG6_VADDR_ROM) >= 0 && diff < CG6_ROM_SZ)
		page = softc->rom + diff;

	else if ((diff = off - CG6_VADDR_DHC) >= 0 && diff < CG6_DHC_SZ)
		page = softc->dhc + diff;

	else if ((diff = off - CG6_VADDR_ALT) >= 0 && diff < CG6_ALT_SZ)
		page = softc->alt + diff;

	else if ((diff = off - CG6_VADDR_VRT) >= 0 && diff < CG6_VRT_SZ)
		page = softc->vrtpage ? (caddr_t)softc->vrtpage + diff :
		    (caddr_t)-1;
	else
		page = (caddr_t)-1;

	if (rval == 0)
		if (page != (caddr_t)-1)
			rval = hat_getkpfnum(page);
		else
			rval = -1;

	DEBUGF(5, (CE_CONT, "cg6_mmap returning 0x%x\n", rval));

	return (rval);
}


/*ARGSUSED*/
static
cg6_ioctl(dev_t dev, int cmd, int data, int mode, cred_t *cred, int *rval)
{
	register struct cg6_softc *softc = getsoftc(getminor(dev));
	int    cursor_cmap;
	int    i;
	static union {
		u_char  cmap[CG6_CMAP_ENTRIES];
		u_long  cur[32];
	} iobuf;

	DEBUGF(3, (CE_CONT, "cg6_ioctl(%d, 0x%x)\n", getminor(dev), cmd));

	/* default to updating normal colormap */
	cursor_cmap = 0;

	switch (cmd) {

	case VIS_GETIDENTIFIER:
		if (CG6_COPYOUT(&cg6_ident, data,
		    sizeof (struct vis_identifier), mode))
			return (EFAULT);
		break;

	case CG6IOPUTCMAP:
	case CG6IOGETCMAP: {
		struct cg6cmap *cmap, cma;
		u_int   index;
		u_int   count;
		u_char *map;
		u_int   entries;

	cmap_ioctl:
		mutex_enter(&softc->mutex);

		if (CG6_COPYIN(data, &cma, sizeof (struct cg6cmap),
		    mode) != 0) {
			mutex_exit(&softc->mutex);
			return (EFAULT);
		}

		cmap = &cma;
		index = (u_int)cmap->index;
		count = (u_int)cmap->count;

		if (cursor_cmap == 0) {
			switch ((unsigned)index >> 25) {

			case 0:
			case 2: /* XXXX PIXPG_8BIT_COLOR */
				map = softc->cmap_rgb;
				entries = CG6_CMAP_ENTRIES;
				break;

			default:
				mutex_exit(&softc->mutex);
				return (EINVAL);
			}
		} else {
			map = softc->omap_rgb;
			entries = 2;
		}

		if ((index &= 0x00FFFFFF /* XXXX PIX_ALL_PLANES*/) >= entries ||
		    index + count > entries) {
			mutex_exit(&softc->mutex);
			return (EINVAL);
		}

		if (count == 0) {
			mutex_exit(&softc->mutex);
			return (0);
		}

		if (cmd == CG6IOPUTCMAP) {
			register int error;

			DEBUGF(3, (CE_CONT, "CG6IOPUTCMAP\n"));

			if (cg6_update_pending(softc))
				cg6_int_disable(softc);

			map += index * 3;


			if ((error = CG6_COPYIN(cmap->red, iobuf.cmap,
			    count, mode)) == 0) {
				cg6_cmap_bcopy(iobuf.cmap, map++, count);

				if ((error = CG6_COPYIN(cmap->green,
				    iobuf.cmap, count, mode)) == 0) {

					cg6_cmap_bcopy(iobuf.cmap, map++,
					    count);

					if ((error = CG6_COPYIN(cmap->blue,
					    iobuf.cmap, count, mode)) == 0) {

						cg6_cmap_bcopy(iobuf.cmap,
						    map, count);
					}
				}
			}

			if (error) {
				if (cg6_update_pending(softc))
					thc_int_enable(S_THC(softc));
				mutex_exit(&softc->mutex);
				return (EFAULT);
			}

			/* cursor colormap update */
			if (entries < CG6_CMAP_ENTRIES)
				count = 0;

			cg6_update_cmap(softc, index, count);
			thc_int_enable(S_THC(softc));
		} else {

			/* CG6IOGETCMAP */
			DEBUGF(3, (CE_CONT, "CG6IOGETCMAP\n"));
			map += index * 3;

			cg6_cmap_bcopy(iobuf.cmap, map++, -count);
			if (CG6_COPYOUT(iobuf.cmap, cmap->red, count,
			    mode) != 0) {
				mutex_exit(&softc->mutex);
				return (EFAULT);
			}

			cg6_cmap_bcopy(iobuf.cmap, map++, -count);
			if (CG6_COPYOUT(iobuf.cmap, cmap->green, count,
			    mode) != 0) {
				mutex_exit(&softc->mutex);
				return (EFAULT);
			}

			cg6_cmap_bcopy(iobuf.cmap, map, -count);
			if (CG6_COPYOUT(iobuf.cmap, cmap->blue, count,
			    mode) != 0) {
				mutex_exit(&softc->mutex);
				return (EFAULT);
			}
		}
	}
	mutex_exit(&softc->mutex);
	break;

	case CG6IOSATTR: {
		struct cg6_sattr attr;

		if (CG6_COPYIN(data, &attr, sizeof (attr), mode) != 0)
			return (EFAULT);

		DEBUGF(3, (CE_CONT, "CG6IOSATTR, type=%d\n", attr.emu_type));

		mutex_enter(&softc->mutex);
		softc->emulation = attr.emu_type;
		mutex_exit(&softc->mutex);
	}
	break;

	case CG6IOGATTR: {
		struct cg6_gattr attr;

		DEBUGF(3, (CE_CONT, "CG6IOGATTR, emu_type=%d\n",
		    softc->emulation));

		CG6BCOPY(&cg6_attr, &attr, sizeof (attr));
		mutex_enter(&softc->mutex);
		attr.fbtype.fb_type = softc->emulation;
		attr.fbtype.fb_width = softc->w;
		attr.fbtype.fb_height = softc->h;

		/* XXX not quite like a cg4 */
		attr.fbtype.fb_size = softc->size;
		attr.sattr.emu_type = softc->emulation;

		mutex_exit(&softc->mutex);

		if (CG6_COPYOUT(&attr, data, sizeof (struct cg6_gattr),
		    mode) != 0)
			return (EFAULT);
	}
	break;

	case CG6IOSVIDEO:
		DEBUGF(3, (CE_CONT, "CG6IOSVIDEO\n"));

		if (CG6_COPYIN(data, &i, sizeof (int), mode) != 0)
			return (EFAULT);

		mutex_enter(&softc->mutex);

		cg6_set_video(softc, i & CG6VIDEO_ON);

		mutex_exit(&softc->mutex);
		break;

	case CG6IOGVIDEO:
		DEBUGF(3, (CE_CONT, "CG6IOGVIDEO\n"));

		mutex_enter(&softc->mutex);

		i = cg6_get_video(softc) ? CG6VIDEO_ON : CG6VIDEO_OFF;
		mutex_exit(&softc->mutex);

		if (CG6_COPYOUT(&i, data, sizeof (int), mode) != 0)
			return (EFAULT);
		break;

	/*
	 * informational ioctls
	 */
	case CG6IOGXINFO:
		mutex_enter(&softc->mutex);
		if (CG6_COPYOUT(&softc->cg6info, data,
		    sizeof (struct cg6_inform), mode) != 0) {
			mutex_exit(&softc->mutex);
			return (EFAULT);
		}

		mutex_exit(&softc->mutex);
		return (0);

	case CG6IOMONINFO:
		mutex_enter(&softc->mutex);

		if (CG6_COPYOUT(&softc->moninfo, data,
		    sizeof (struct cg6_mon_info), mode) != 0) {

			mutex_exit(&softc->mutex);
			return (EFAULT);
		}

		mutex_exit(&softc->mutex);
		return (0);

	/*
	 * vertical retrace interrupt
	 */
	case CG6IOVERTICAL:
		mutex_enter(&softc->mutex);
		softc->vrtflag |= CG6VRTIOCTL;

		thc_int_enable(S_THC(softc));
		cv_wait(&softc->vrtsleep, &softc->mutex);

		mutex_exit(&softc->mutex);

		return (0);

	case CG6IOVRTOFFSET:
		i = CG6_VADDR_VRT;

		if (CG6_COPYOUT(&i, data, sizeof (int), mode) != 0)
			return (EFAULT);

		return (0);

	/* HW cursor control */
	case CG6IOSCURSOR: {
		struct cg6cursor cp;
		int    set;
		int    cbytes;

		if (CG6_COPYIN(data, &cp, sizeof (struct cg6cursor), mode) != 0)
			return (EFAULT);

		mutex_enter(&softc->mutex);

		set = cp.set;
		if (set & CG6_CUR_SETCUR)
			softc->cur.enable = cp.enable;

		if (set & CG6_CUR_SETPOS)
			softc->cur.pos = cp.pos;

		if (set & CG6_CUR_SETHOT)
			softc->cur.hot = cp.hot;

		/* update hardware */
		cg6_setcurpos(softc);

		if (set & CG6_CUR_SETSHAPE) {
			if ((u_int)cp.size.x > 32 || (u_int)cp.size.y > 32) {
				mutex_exit(&softc->mutex);
				return (EINVAL);
			}

			softc->cur.size = cp.size;

			/* compute cursor bitmap bytes */
			cbytes = softc->cur.size.y *
			    sizeof (softc->cur.image[0]);

			/* copy cursor image into softc */
			if (cp.image != NULL) {
				BZERO(softc->cur.image,
				    sizeof (softc->cur.image));

				if (CG6_COPYIN(cp.image, softc->cur.image,
				    cbytes, mode) != 0) {
					mutex_exit(&softc->mutex);
					return (EFAULT);
				}
			}

			if (cp.mask != NULL) {
				BZERO(softc->cur.mask,
				    sizeof (softc->cur.mask));
				if (CG6_COPYIN(cp.mask, softc->cur.mask,
				    cbytes, mode) != 0) {
					mutex_exit(&softc->mutex);
					return (EFAULT);
				}
			}

			/* load into hardware */
			cg6_setcurshape(softc);
		}

		mutex_exit(&softc->mutex);

		/* load colormap */
		if (set & CG6_CUR_SETCMAP) {
			cursor_cmap = 1;
			cmd = CG6IOPUTCMAP;
			data = (int)&cp.cmap;
			goto cmap_ioctl;
		    }
		break;
	}

	case CG6IOGCURSOR: {
		struct cg6cursor cp;
		int    cbytes;

		if (CG6_COPYIN(data, &cp, sizeof (struct cg6cursor), mode) != 0)
			return (EFAULT);

		mutex_enter(&softc->mutex);
		cp.set = 0;
		cp.enable = softc->cur.enable;
		cp.pos = softc->cur.pos;
		cp.hot = softc->cur.hot;
		cp.size = softc->cur.size;
		cp.cmap.index = 0;
		cp.cmap.count = 2;

		if (CG6_COPYOUT(&cp, data, sizeof (struct cg6cursor),
		    mode) != 0) {
			mutex_exit(&softc->mutex);
			return (EFAULT);
		}

		/* compute cursor bitmap bytes */
		cbytes = softc->cur.size.y * sizeof (softc->cur.image[0]);

		/* if image pointer is non-null copy both bitmaps */
		if (cp.image && ((CG6_COPYOUT(softc->cur.image, cp.image,
		    cbytes, mode) != 0) || (CG6_COPYOUT(softc->cur.mask,
		    cp.mask, cbytes, mode) != 0))) {
			mutex_exit(&softc->mutex);
			return (EFAULT);
		}

		mutex_exit(&softc->mutex);

		/* if red pointer is non-null copy colormap */
		if (cp.cmap.red) {
			cursor_cmap = 1;
			cmd = CG6IOGETCMAP;
			data = (int)&cp.cmap;
			goto cmap_ioctl;
		}
		break;
	}

	case CG6IOSCURPOS:
		mutex_enter(&softc->mutex);
		if (CG6_COPYIN(data, &softc->cur.pos, sizeof (struct cg6curpos),
		    mode) != 0) {
			mutex_exit(&softc->mutex);
			return (EFAULT);
		}

		cg6_setcurpos(softc);
		mutex_exit(&softc->mutex);
		break;

	case CG6IOGCURPOS:
		mutex_enter(&softc->mutex);
		if (CG6_COPYOUT(&softc->cur.pos, data,
		    sizeof (struct cg6curpos), mode) != 0) {
			mutex_exit(&softc->mutex);
			return (EFAULT);
		}
		mutex_exit(&softc->mutex);
		break;

	case CG6IOGCURMAX: {
		static struct cg6curpos curmax = {32, 32};

		if (CG6_COPYOUT(&curmax, data, sizeof (struct cg6curpos),
		    mode) != 0)
			return (EFAULT);
		break;
		}

#if	CG6DEBUG >= 3
	case 253:
		break;

	case 255:
		cg6_debug = (int)data;
		if (cg6_debug == -1)
			cg6_debug = CG6DEBUG;
		cmn_err(CE_CONT, "cg6_debug is now %d\n", cg6_debug);
		break;
#endif

	default:
		return (ENOTTY);
	}				/* switch (cmd) */

	return (0);
}


static  u_int
cg6_intr(caddr_t arg)
{
	register struct cg6_softc *softc = (struct cg6_softc *)arg;
	volatile u_long *in;
	volatile u_long *out;
	volatile u_long  tmp;

	DEBUGF(7, (CE_CONT,
	    "cg6_intr: softc=%x, vrtflag=%x\n", softc, softc->vrtflag));

	if (!cg6_int_pending(softc)) {
		return (0);		/* nope, not my interrupt */
	}

	mutex_enter(&softc->mutex);
	mutex_enter(&softc->interlock);

	if (!(cg6_update_pending(softc) || (softc)->vrtflag)) {
		thc_int_disable(S_THC(softc));
		mutex_exit(&softc->interlock);
		mutex_exit(&softc->mutex);
		return (1);
	}

	if (softc->vrtflag & CG6VRTCTR) {
		if (softc->vrtmaps == 0) {
			softc->vrtflag &= ~CG6VRTCTR;
		} else
			*softc->vrtpage += 1;
	}

	if (softc->vrtflag & CG6VRTIOCTL) {
		softc->vrtflag &= ~CG6VRTIOCTL;
		cv_broadcast(&softc->vrtsleep);
	}

	if (cg6_update_pending(softc)) {
		volatile struct cg6_cmap *cmap = S_CMAP(softc);
		int  count = softc->cmap_count;


		/* load cursor color map */
		if (softc->omap_update) {
			in = &softc->omap_image.omap_long[0];
			out = (u_long *)& cmap->omap;

			/* background color */
			cmap->addr = 1 << 24;
			tmp = in[0];
			*out = tmp;
			*out = tmp <<= 8;
			*out = tmp <<= 8;

			/* foreground color */
			cmap->addr = 3 << 24;
			*out = tmp <<= 8;
			tmp = in[1];
			*out = tmp;
			*out = tmp <<= 8;
		}

		/* load main color map */
		if (count) {
			int  index = softc->cmap_index;

			in = &softc->cmap_image.cmap_long[0];
			out = (u_long *)& cmap->cmap;

			/* count multiples of 4 RGB entries */
			count = (count + (index & 3) + 3) >> 2;

			/* round index to 4 entry boundary */
			index &= ~3;

			cmap->addr = index << 24;
			in = (unsigned long *)((char *)(in) + (index*3));

			/* copy 4 bytes (4/3 RGB entries) per loop iteration */
			count *= 3;

			while (--count >= 0) {
				tmp = *in++;
				*out = tmp;
				*out = tmp <<= 8;
				*out = tmp <<= 8;
				*out = tmp <<= 8;
			}

			softc->cmap_count = 0;
		}

		softc->omap_update = 0;
	}

	thc_int_disable(S_THC(softc));

	if (softc->vrtflag)
		thc_int_enable(S_THC(softc));

#ifdef	COMMENT
	if (!softc->vrtflag)
	    thc_int_disable(S_THC(softc));
#endif				/* COMMENT */

	mutex_exit(&softc->interlock);
	mutex_exit(&softc->mutex);
	return (DDI_INTR_CLAIMED);
}

/*
 * Initialize a colormap: background = white, all others = black
 */
static void
cg6_reset_cmap(volatile u_char *cmap, register u_int entries)
{
	bzero((char *)cmap, entries * 3);
	cmap[0] = 255;
	cmap[1] = 255;
	cmap[2] = 255;
}

/*
 * Compute color map update parameters: starting index and count.
 * If count is already nonzero, adjust values as necessary.
 * Zero count argument indicates cursor color map update desired.
 */
static void
cg6_update_cmap(struct cg6_softc *softc, u_int index, u_int count)
{
	u_int   high, low;

	if (count == 0) {
		softc->omap_update = 1;
		return;
	}

	high = softc->cmap_count;
	if (high != 0) {
		high += (low = softc->cmap_index);

		if (index < low)
			softc->cmap_index = low = index;

		if (index + count > high)
			high = index + count;

		softc->cmap_count = high - low;
	} else {
		softc->cmap_index = index;
		softc->cmap_count = count;
	}
}

/*
 * Copy colormap entries between red, green, or blue array and
 * interspersed rgb array.
 *
 * count > 0 : copy count bytes from buf to rgb
 * count < 0 : copy -count bytes from rgb to buf
 */
static void
cg6_cmap_bcopy(register u_char *bufp, register u_char *rgb, u_int count)
{
	register int rcount = count;

	if (--rcount >= 0)
		CG6PR_LOOPVP(rcount,
		    *rgb = *bufp++;
		    rgb += 3);
	else {
		rcount = -rcount - 2;
		CG6PR_LOOPVP(rcount,
		    *bufp++ = *rgb;
		    rgb += 3);
	}
}

/*
 * enable/disable/update HW cursor
 */
static void
cg6_setcurpos(struct cg6_softc *softc)
{
	volatile struct thc *thc = S_THC(softc);

	thc->l_thc_cursor = softc->cur.enable ?
	    ((softc->cur.pos.x - softc->cur.hot.x) << 16) |
	    ((softc->cur.pos.y - softc->cur.hot.y) & 0xffff) :
	    CG6_CURSOR_OFFPOS;
}

/*
 * load HW cursor bitmaps
 */
static void
cg6_setcurshape(struct cg6_softc *softc)
{
	u_long  tmp, edge = 0;
	volatile u_long *image, *mask, *hw;
	volatile struct thc *thc = S_THC(softc);
	int    i;

	/* compute right edge mask */
	if (softc->cur.size.x)
		edge = (u_long)~0 << (32 - softc->cur.size.x);

	image = softc->cur.image;
	mask = softc->cur.mask;
	hw = (u_long *)&thc->l_thc_cursora00;

	for (i = 0; i < 32; i++) {
		hw[i] = (tmp = mask[i] & edge);
		hw[i + 32] = tmp & image[i];
	}
}

static void
cg6_reset(struct cg6_softc *softc)
{
	volatile struct thc *thc = S_THC(softc);

	/* disable HW cursor */
	thc->l_thc_cursor = CG6_CURSOR_OFFPOS;

	/* re-initialize TEC */
	{
		volatile struct tec *tec = S_TEC(softc);

		tec->l_tec_mv = 0;
		tec->l_tec_clip = 0;
		tec->l_tec_vdc = 0;
	}

	/* re-initialize FBC config register */
	{
		volatile u_int  *fhc = S_FHC(softc);
		u_int rev, conf;

		rev = *fhc >> FHC_CONFIG_REV_SHIFT & FHC_CONFIG_REV_MASK;
		if (rev <= 4) {

			/* PROM knows how to deal with LSC and above */
			/* rev == 0 : FBC 0 (not available to customers) */
			/* rev == 1 : FBC 1 */
			/* rev == 2 : FBC 2 */
			/* rev == 3 : Toshiba (never built) */
			/* rev == 4 : Standard Cell (not built yet) */
			/* rev == 5 : LSC rev 2 (buggy) */
			/* rev == 6 : LSC rev 3 */
			conf = *fhc & FHC_CONFIG_RES_MASK |
			    FHC_CONFIG_CPU_68020;

#if FBC_REV0
			/* FBC0: test window = 0, disable fast rops */
			if (rev == 0)
				conf |= FHC_CONFIG_TEST |
				    FHC_CONFIG_FROP_DISABLE;
			else
#endif	/* FBC_REV0 */
				/* test window = 1K x 1K */
				conf |= FHC_CONFIG_TEST |
				    (10 + 1) << FHC_CONFIG_TESTX_SHIFT |
				    (10 + 1) << FHC_CONFIG_TESTY_SHIFT;

			/* FBC[01]: disable destination cache */
			if (rev <= 1)
				conf |= FHC_CONFIG_DST_DISABLE;

			*fhc = conf;
		}
	}

	/* reprogram DAC to enable HW cursor use */
	{
		volatile struct cg6_cmap *cmap = S_CMAP(softc);

		/* command register */
		cmap->addr = 6 << 24;

		/* turn on CR1:0, overlay enable */
		cmap->ctrl = cmap->ctrl | (0x3 << 24);
	}
}


/*
 * CG6 Context Management
 *
 * This virtualizes the cg6 register file by associating a register
 * save area with each mapping of the device and using the ddi_mapdev
 * mechanism to allow us to detect mapping changes.
 * Only one of these contexts is valid at any time; an access to one
 * of the invalid contexts saves the current cg6 context, invalidates
 * the current valid mapping, restores the former register contents
 * appropriate to the faulting mapping, and then validates it.
 *
 * This implements a graphical context switch that is transparent to
 * the user.
 *
 * the TEC and FBC contain the interesting context registers.
 *
 * Per-mapping info:
 *	Some, but not all, mappings are part of a context.
 *	Any mapping that is MAP_PRIVATE to the TEC or FBC will be
 *	part of a unique context.  MAP_SHARED mappings are part
 *	of the shared context and all such programs must arbitrate among
 *	themselves to keep from stepping on each other's register settings.
 *	Mappings to the framebuffer may or may not be part of a context,
 *	depending on exact hardware type.
 */

#define	CG6MAP_SHARED	0x02	/* shared context */
#define	CG6MAP_LOCK	0x04	/* lock page */
#define	CG6MAP_VRT	0x08	/* vrt page */
#define	CG6MAP_FBCTEC	0X10	/* mapping includes fbc and/or tec */
#define	CG6MAP_FB	0X40	/* mapping includes framebuffer */

#define	CG6MAP_CTX	(CG6MAP_FBCTEC | CG6MAP_FB)	/* needs context */

static struct cg6map_pvt *
cg6_pvt_alloc(struct cg6_cntxt *ctx, u_int off, u_int len,
    struct cg6_softc *softc)
{
struct cg6map_pvt *pvt;

	/*
	 * create the private data portion of the mapdev object
	 */
	pvt = (struct cg6map_pvt *)kmem_zalloc(sizeof (struct cg6map_pvt),
	    KM_SLEEP);
	pvt->offset  = off;
	pvt->len = len;
	pvt->context = ctx;
	pvt->softc = softc;

	/*
	 * Link this pvt into the list of associated pvt's for this
	 * context
	 */
	pvt->next = ctx->pvt;
	ctx->pvt = pvt;

	return (pvt);
}

/*
 * This routine is called through the cb_ops table to handle
 * cg6 mmap requests.
 */
/*ARGSUSED*/
static int
cg6_segmap(dev_t dev, off_t off, struct as *as, caddr_t	*addrp, off_t len,
    u_int prot, u_int maxprot, u_int flags, cred_t *cred)
{
	struct cg6_softc *softc		= getsoftc(getminor(dev));
	struct cg6_cntxt *ctx		= (struct cg6_cntxt *)NULL;
	struct cg6_cntxt *shared_ctx	= &softc->shared_ctx;
	struct cg6map_pvt *pvt;
	u_int	maptype = 0;
	u_int	ctxmap;
	int	error;
	int	i;

	DEBUGF(3, (CE_CONT, "segmap: off=%x, len=%x\n", off, len));

	mutex_enter(&softc->mutex);

	/*
	 * Validate and categorize the map request.  Valid mmaps to
	 * the cgsix driver are to device hardware or to the vertical
	 * retrace counter page
	 *
	 * VRT page mapping?  If so, be sure to count VRT events
	 */
	if (off == CG6_VADDR_VRT) {
		if (len != pagesize) {
			mutex_exit(&softc->mutex);
			DEBUGF(3, (CE_CONT,
			    "rejecting because off=vrt and len=%x\n", len));
			return (EINVAL);
		}

		maptype = CG6MAP_VRT;

		if (softc->vrtmaps++ == 0) {
			if (softc->vrtpage == NULL)
				softc->vrtpage = (int *)cg6_getpages(1,
				    KM_SLEEP);
			*softc->vrtpage = 0;
			softc->vrtflag |= CG6VRTCTR;
			thc_int_enable(S_THC(softc));
		}
	}

	else {

		/*
		 * Check to insure that the entire range is legal and we are
		 * not trying to map more than defined by the device.
		 */
		for (i = 0; i < len; i += pagesize) {
			if (cg6_mmap(dev, (off_t)off + i,
			    (int)maxprot) == -1) {
				mutex_exit(&softc->mutex);
				DEBUGF(3, (CE_CONT,
				    "rejecting: mmap returns -1, off=%x\n",
				    off+i));
				return (ENXIO);
			}
		}

		/* classify it */
		if (off + len > CG6_VADDR_FBC &&
		    off < CG6_VADDR_FBC + CG6_FBCTEC_SZ) {

			maptype |= CG6MAP_FBCTEC;

		} else if (off + len > CG6_VADDR_COLOR &&
		    off < CG6_VADDR_COLOR + CG6_FB_SZ) {

			maptype |= CG6MAP_FB;
		}
	}

	/*
	 * LSC DFB BUG KLUDGE:  DFB must always be mapped private on the buggy
	 * (chip rev. 5) LSC chip.  This is done to ensure that nobody ever
	 * touches the framebuffer without the context switching getting
	 * involved to make sure the registers are idle.
	 *
	 * Under libraries such as XGL, which maps the chips private and the
	 * FB shared, the FB becomes part of the context.  Programs which only
	 * map the FB will also become contexts, but since they don't map the
	 * chips, there's no context to switch.
	 */
	if (softc->chiprev == 5 && (maptype & CG6MAP_FB))
		flags |= MAP_PRIVATE;

	/*
	 * Is this mapping for part of the cgsix context?
	 * If so, splice it into the softc context list.
	 */
	ctxmap = softc->chiprev == 5 ? (CG6MAP_FBCTEC|CG6MAP_FB):CG6MAP_FBCTEC;

	if (maptype & ctxmap) {

		/*
		 * Is this a shared mapping.  If so use the shared_ctx.
		 */
		if (flags & MAP_SHARED) {
			ctx = shared_ctx;
			ctx->flag = CG6MAP_CTX;

		} else {
			ctx = (struct cg6_cntxt *)
			    kmem_zalloc(sizeof (struct cg6_cntxt), KM_SLEEP);
			ctx->flag  = maptype;
		}

		pvt = cg6_pvt_alloc(ctx, 0, len, softc);

		/*
		 * create the mapdev object
		 */
		error = ddi_mapdev(dev, off, as, addrp, len, prot, maxprot,
		    flags, cred, &cg6map_ops, &pvt->handle, (void *)pvt);

		if (error != 0) {
			cmn_err(CE_WARN, "ddi_mapdev failure\n");
		}

	} else {
		error = ddi_segmap(dev, off, as, addrp, len, prot, maxprot,
		    flags, cred);
	}

	mutex_exit(&softc->mutex);

	return (error);
}

/*
 * An access has been made to a context other than the current one
 */
/* ARGSUSED */
static int
cg6map_access(ddi_mapdev_handle_t handle, void *pvt, off_t offset)
{
	struct cg6map_pvt *p   = (struct cg6map_pvt *)pvt;
	struct cg6map_pvt *pvts;
	struct cg6_softc *softc = p->softc;
	volatile struct fbc *fbc;
	int err = 0;

	ASSERT(pvt);

	mutex_enter(&softc->mutex);

	/*
	 * Do we need to switch contexts?
	 */
	if (softc->curctx != p->context) {

		fbc = S_FBC(softc);

		/*
		 * If there's a current context, save it
		 */
		if (softc->curctx != (struct cg6_cntxt *)NULL) {
			/*
			 * Set mapdev for current context and all associated
			 * handles to intercept references to their addresses
			 */
			ASSERT(softc->curctx->pvt);
			for (pvts = softc->curctx->pvt; pvts != NULL;
			    pvts = pvts->next) {
				(void) ddi_mapdev_intercept(pvts->handle,
				    pvts->offset, pvts->len);
			}

			if (!cg6_cntxsave(fbc, S_TEC(softc), softc->curctx)) {
				DEBUGF(1, (CE_CONT,
				    "cgsix: context save failed\n"));
				/*
				 * At this point we have no current context.
				 */
				softc->curctx = NULL;
				mutex_exit(&softc->mutex);
				return (-1);
			}
		}

		/*
		 * Idle the chips
		 */
		CG6DELAY(!(fbc->l_fbc_status & L_FBC_BUSY), CG6_FBC_WAIT);
		if (fbc->l_fbc_status & L_FBC_BUSY) {
			DEBUGF(1, (CE_CONT, "cgsix: idle_cg6: status = %x\n",
			fbc->l_fbc_status));
			/*
			 * At this point we have no current context.
			 */
			softc->curctx = NULL;
			mutex_exit(&softc->mutex);
			return (-1);
		}

		DEBUGF(4, (CE_CONT, "loading context %x\n", p->context));

		if (p->context->flag & CG6MAP_FBCTEC)
			if (!cg6_cntxrestore(fbc, S_TEC(softc), p->context)) {
				DEBUGF(1, (CE_CONT,
				    "cgsix: context restore failed\n"));
				/*
				 * At this point we have no current context.
				 */
				softc->curctx = NULL;
				mutex_exit(&softc->mutex);
				return (-1);
			}

		/*
		 * switch software "context"
		 */
		softc->curctx = p->context;
	}

	ASSERT(p->context->pvt);
	for (pvts = p->context->pvt; pvts != NULL; pvts = pvts->next) {
		if ((err = ddi_mapdev_nointercept(pvts->handle,
		    pvts->offset, pvts->len)) != 0) {
			mutex_exit(&softc->mutex);
			return (err);
		}
	}

	mutex_exit(&softc->mutex);

	return (err);
}


/* ARGSUSED */
static void
cg6map_free(ddi_mapdev_handle_t handle, void *pvt)
{
	struct cg6map_pvt *p   = (struct cg6map_pvt *)pvt;
	struct cg6map_pvt *pvts;
	struct cg6map_pvt *ppvts;
	struct cg6_cntxt *ctx   = p->context;
	struct cg6_softc *softc = p->softc;
	struct cg6_cntxt *shared_ctx	= &softc->shared_ctx;

	mutex_enter(&softc->mutex);

	/*
	 * Remove the pvt data
	 */
	ppvts = NULL;
	for (pvts = ctx->pvt; pvts != NULL; pvts = pvts->next) {
		if (pvts == pvt) {
			if (ppvts == NULL) {
				ctx->pvt = pvts->next;
			} else {
				ppvts->next = pvts->next;
			}
			kmem_free(pvt, sizeof (struct cg6map_pvt));
			break;
		}
		ppvts = pvt;
	}

	/*
	 * Remove the context if this is not the shared context and there are
	 * no more associated pvt's
	 */
	if ((ctx != shared_ctx) && (ctx->pvt == NULL)) {
		if (ctx == softc->curctx)
			softc->curctx = NULL;
		kmem_free(ctx, sizeof (struct cg6_cntxt));
	}

	/*
	 * If the curctx is the shared context, and there are no
	 * more pvt's for the shared context, set the curctx to
	 * NULL to force a context switch on the next device access.
	 */
	if ((softc->curctx == shared_ctx) && (softc->curctx->pvt == NULL)) {
		softc->curctx = NULL;
	}

	mutex_exit(&softc->mutex);
}


/* ARGSUSED */
static int
cg6map_dup(ddi_mapdev_handle_t old_handle, void *oldpvt,
    ddi_mapdev_handle_t new_handle, void **newpvt)
{
	volatile struct fbc *fbc;
	struct cg6map_pvt *p   = (struct cg6map_pvt *)oldpvt;
	struct cg6_softc *softc = p->softc;
	struct cg6map_pvt *pvt;
	struct cg6_cntxt *ctx;

	fbc = S_FBC(softc);

	mutex_enter(&softc->mutex);
	if (p->context != &softc->shared_ctx) {
		ctx = (struct cg6_cntxt *)
		    kmem_zalloc(sizeof (struct cg6_cntxt), KM_SLEEP);

		if (!cg6_cntxsave(fbc, S_TEC(softc), softc->curctx)) {
			DEBUGF(1, (CE_CONT, "cgsix: context save failed\n"));
			mutex_exit(&softc->mutex);
			return (ENOMEM);
		}
		ctx->pvt = NULL;
	} else
		ctx = &softc->shared_ctx;

	pvt = cg6_pvt_alloc(ctx, 0, p->len, softc);

	pvt->handle = new_handle;
	*newpvt = pvt;

	mutex_exit(&softc->mutex);
	return (0);
}

static  caddr_t
cg6_getpages(int npages, int flag)
{
	register caddr_t p1;

	p1 = kmem_zalloc(ptob(npages), flag);
	ASSERT(p1 != NULL && !((u_int) p1 & pageoffset));
	return (p1);
}


static void
cg6_freepages(void *ptr, int size)
{
	ASSERT2(ptr != NULL && !((u_int) ptr & pageoffset));
	kmem_free(ptr, ptob(size));
}



#undef	L_TEC_VDC_INTRNL0
#define	L_TEC_VDC_INTRNL0	0x8000
#undef	L_TEC_VDC_INTRNL1
#define	L_TEC_VDC_INTRNL1	0xa000


static int
cg6_cntxsave(volatile struct fbc *fbc, volatile struct tec *tec,
    struct cg6_cntxt *saved)
{
	int    dreg;		/* counts through the data registers */
	u_int  *dp;		/* points to a tec data register */

	DEBUGF(5, (CE_CONT, "saving registers\n"));

	CG6DELAY(!(fbc->l_fbc_status & L_FBC_BUSY), CG6_FBC_WAIT);

	if (fbc->l_fbc_status & L_FBC_BUSY) {
		DEBUGF(1, (CE_CONT, "cgsix: idle_cg6: status = %x\n",
		    fbc->l_fbc_status));
		return (0);
	}

	/*
	 * start dumping stuff out.
	 */
	saved->fbc.status	= fbc->l_fbc_status;
	saved->fbc.clipcheck	= fbc->l_fbc_clipcheck;
	saved->fbc.misc		= fbc->l_fbc_misc;
	saved->fbc.x0		= fbc->l_fbc_x0;
	saved->fbc.y0		= fbc->l_fbc_y0;
	saved->fbc.x1		= fbc->l_fbc_x1;
	saved->fbc.y1		= fbc->l_fbc_y1;
	saved->fbc.x2		= fbc->l_fbc_x2;
	saved->fbc.y2		= fbc->l_fbc_y2;
	saved->fbc.x3		= fbc->l_fbc_x3;
	saved->fbc.y3		= fbc->l_fbc_y3;
	saved->fbc.rasteroffx	= fbc->l_fbc_rasteroffx;
	saved->fbc.rasteroffy	= fbc->l_fbc_rasteroffy;
	saved->fbc.autoincx	= fbc->l_fbc_autoincx;
	saved->fbc.autoincy	= fbc->l_fbc_autoincy;
	saved->fbc.clipminx	= fbc->l_fbc_clipminx;
	saved->fbc.clipminy	= fbc->l_fbc_clipminy;
	saved->fbc.clipmaxx	= fbc->l_fbc_clipmaxx;
	saved->fbc.clipmaxy	= fbc->l_fbc_clipmaxy;
	saved->fbc.fcolor	= fbc->l_fbc_fcolor;
	saved->fbc.bcolor	= fbc->l_fbc_bcolor;
	saved->fbc.rasterop	= fbc->l_fbc_rasterop;
	saved->fbc.planemask	= fbc->l_fbc_planemask;
	saved->fbc.pixelmask	= fbc->l_fbc_pixelmask;
	saved->fbc.pattalign	= fbc->l_fbc_pattalign;
	saved->fbc.pattern0	= fbc->l_fbc_pattern0;
	saved->fbc.pattern1	= fbc->l_fbc_pattern1;
	saved->fbc.pattern2	= fbc->l_fbc_pattern2;
	saved->fbc.pattern3	= fbc->l_fbc_pattern3;
	saved->fbc.pattern4	= fbc->l_fbc_pattern4;
	saved->fbc.pattern5	= fbc->l_fbc_pattern5;
	saved->fbc.pattern6	= fbc->l_fbc_pattern6;
	saved->fbc.pattern7	= fbc->l_fbc_pattern7;

	/*
	 * The TEC matrix and clipping registers are easy.
	 */
	saved->tec.mv	= tec->l_tec_mv;
	saved->tec.clip	= tec->l_tec_clip;
	saved->tec.vdc	= tec->l_tec_vdc;

	/*
	 * The TEC data registers are a little less obvious. Internally,
	 * they are 36 bits.  What we see in the register file is a
	 * 32-bit window onto the underlying data register.  Changing the
	 * data-type in the VDC gets us either of two parts of the data
	 * register.  The internal format is opaque to us.
	 */
	tec->l_tec_vdc = (u_int) L_TEC_VDC_INTRNL0;
	for (dreg = 0, dp = (u_int *)&tec->l_tec_data00; dreg < 64;
	    dreg++, dp++) {
		saved->tec.data[dreg][0] = *dp;
	}

	tec->l_tec_vdc = (u_int) L_TEC_VDC_INTRNL1;
	for (dreg = 0, dp = (u_int *)&tec->l_tec_data00; dreg < 64;
	    dreg++, dp++) {
		saved->tec.data[dreg][1] = *dp;
	}
	return (1);
}

static int
cg6_cntxrestore(volatile struct fbc *fbc, volatile struct tec *tec,
    struct cg6_cntxt *saved)
{
	int    dreg;
	u_int  *dp;

	DEBUGF(5, (CE_CONT, "restoring registers\n"));

	/*
	 * Reload the TEC data registers.  See above for "how do they get
	 * 36 bits in that itty-bitty int"
	 */
	tec->l_tec_vdc = (u_int) L_TEC_VDC_INTRNL0;
	for (dreg = 0, dp = (u_int *)&tec->l_tec_data00;
	    dreg < 64; dreg++, dp++) {
		*dp = saved->tec.data[dreg][0];
	}

	tec->l_tec_vdc = (u_int) L_TEC_VDC_INTRNL1;
	for (dreg = 0, dp = (u_int *)&tec->l_tec_data00;
	    dreg < 64; dreg++, dp++) {
		*dp = saved->tec.data[dreg][1];
	}

	/*
	 * The TEC matrix and clipping registers are next.
	 */
	tec->l_tec_mv = saved->tec.mv;
	tec->l_tec_clip = saved->tec.clip;
	tec->l_tec_vdc = saved->tec.vdc;

	/*
	 * now the FBC vertex and address registers
	 */
	fbc->l_fbc_x0		= saved->fbc.x0;
	fbc->l_fbc_y0		= saved->fbc.y0;
	fbc->l_fbc_x1		= saved->fbc.x1;
	fbc->l_fbc_y1		= saved->fbc.y1;
	fbc->l_fbc_x2		= saved->fbc.x2;
	fbc->l_fbc_y2		= saved->fbc.y2;
	fbc->l_fbc_x3		= saved->fbc.x3;
	fbc->l_fbc_y3		= saved->fbc.y3;
	fbc->l_fbc_rasteroffx	= saved->fbc.rasteroffx;
	fbc->l_fbc_rasteroffy	= saved->fbc.rasteroffy;
	fbc->l_fbc_autoincx	= saved->fbc.autoincx;
	fbc->l_fbc_autoincy	= saved->fbc.autoincy;
	fbc->l_fbc_clipminx	= saved->fbc.clipminx;
	fbc->l_fbc_clipminy	= saved->fbc.clipminy;
	fbc->l_fbc_clipmaxx	= saved->fbc.clipmaxx;
	fbc->l_fbc_clipmaxy	= saved->fbc.clipmaxy;

	/*
	 * restoring the attribute registers
	 */
	fbc->l_fbc_fcolor	= saved->fbc.fcolor;
	fbc->l_fbc_bcolor	= saved->fbc.bcolor;
	fbc->l_fbc_rasterop	= saved->fbc.rasterop;
	fbc->l_fbc_planemask	= saved->fbc.planemask;
	fbc->l_fbc_pixelmask	= saved->fbc.pixelmask;
	fbc->l_fbc_pattalign	= saved->fbc.pattalign;
	fbc->l_fbc_pattern0	= saved->fbc.pattern0;
	fbc->l_fbc_pattern1	= saved->fbc.pattern1;
	fbc->l_fbc_pattern2	= saved->fbc.pattern2;
	fbc->l_fbc_pattern3	= saved->fbc.pattern3;
	fbc->l_fbc_pattern4	= saved->fbc.pattern4;
	fbc->l_fbc_pattern5	= saved->fbc.pattern5;
	fbc->l_fbc_pattern6	= saved->fbc.pattern6;
	fbc->l_fbc_pattern7	= saved->fbc.pattern7;

	fbc->l_fbc_clipcheck	= saved->fbc.clipcheck;
	fbc->l_fbc_misc		= saved->fbc.misc;

	/*
	 * lastly, let's restore the status
	 */
	fbc->l_fbc_status = saved->fbc.status;
	(void) fbc->l_fbc_status;
	return (1);
}
