/*
 * Copyright (c) 1989-1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)cgsix.c	1.112	99/11/15 SMI"

#pragma	weak	setintrenable
/*
 * Cgsix theory of operation:
 *
 * Most cg6 operations are done by mapping the cg6 components into
 * user process memory.  User processes that share mappings (typically
 * pixrect programs) 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 segment
 * driver will preserve device state for each mapping.
 *
 * Note that the segment driver may go away in the future.
 *
 * 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 segment 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 segment driver in invoked to swap register state
 * 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 seperately, in which
 * case the driver uses heuristics to bind seperate 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 seperate mappings
 * are not recommended.
 *
 * Finally, 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
 */

#include <sys/debug.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/buf.h>
#include <sys/errno.h>
#include <sys/systm.h>
#include <sys/conf.h>

#include <sys/model.h>

#include <sys/file.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/sysmacros.h>
#include <sys/mman.h>
#include <sys/cred.h>
#include <sys/open.h>
#include <sys/stat.h>

#include <sys/visual_io.h>
#include <sys/fbio.h>

#include <sys/cg6reg.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/vnode.h>

#include <vm/page.h>
#include <vm/as.h>
#include <vm/hat.h>
#include <vm/seg.h>

#include <sys/pixrect.h>
#include <sys/pr_impl_util.h>
#include <sys/pr_planegroups.h>
#include <sys/memvar.h>
#include <sys/cg3var.h>		/* for CG3_MMAP_OFFSET */
#include <sys/cg6var.h>
#include <sys/kstat.h>

#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/devops.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/fs/snode.h>

#include <sys/modctl.h>

#include <sys/machsystm.h>

#define	KIOIP		KSTAT_INTR_PTR(softc->intrstats)

#define	CG6DEBUG	0

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

#if CG6DEBUG >= 2
int	cg6_debug = 0;

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

#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, intptr_t, int, cred_t *, int *);
static int	cg6_mmap(dev_t, off_t, int);
static int	cg6_devmap(dev_t, devmap_cookie_t, offset_t, size_t,
			size_t *, uint_t);
static int	cg6_segmap(dev_t, off_t,
			struct as *, caddr_t *, off_t, uint_t,
			uint_t, uint_t, 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 */
	cg6_devmap,		/* devmap */
	cg6_mmap,		/* mmap */
	cg6_segmap,		/* segmap */
	nochpoll,		/* poll */
	ddi_prop_op,		/* cb_prop_op */
	0,			/* streamtab  */
	D_NEW|D_MP|D_DEVMAP|D_HOTPLUG,	/* Driver compatibility flag */
	CB_REV,			/* rev */
	nodev,			/* int (*cb_aread)() */
	nodev			/* int (*cb_awrite)() */
};

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

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

/*
 * This stucture is used to contain the driver
 * private mapping data (one for each requested
 * device mapping).  A pointer to this data is
 * passed into each mapping callback routine.
 */
struct cg6map_pvt {
	struct	cg6_softc *softc;
	devmap_cookie_t dhp;	/* handle of devmap object	*/
	uint_t	type;			/* mapping type */
	off_t   offset;			/* starting offset of this map	*/
	size_t	len;			/* length of this map		*/
	struct cg6_cntxt *context;	/* associated context		*/
	struct cg6map_pvt *next;	/* List of associated pvt's for */
					/* this context			*/
};

static struct ddi_device_acc_attr endian_attr = {
	DDI_DEVICE_ATTR_V0,
	DDI_NEVERSWAP_ACC,
	DDI_STRICTORDER_ACC
};

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

/* vertical retrace counter page */
#ifndef	CG6_VRT_SZ
#define	CG6_VRT_SZ	8192
#endif

/* 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) */

#define	CG6_FBC_WAIT	500000	/* .5 seconds */

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

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

/*
 * Per-context info:
 *	many registers in the tec and fbc do
 *	not need to be saved/restored.
 */

struct cg6_cntxt {
	struct cg6_cntxt *link;	/* link to next (private) context if any */
	struct cg6map_pvt *pvt; /* List of associated pvt's for this context */
	pid_t	pid;		/* "owner" of this context */
	int	flag;

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

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

/* per-unit data */
struct cg6_softc {
	Pixrect pr;			/* kernel pixrect */
	struct mprp_data prd;	/* pixrect private data */
#define	_w		pr.pr_size.x
#define	_h		pr.pr_size.y
#define	_fb		prd.mpr.md_image
#define	_linebytes	prd.mpr.md_linebytes
	size_t size;		/* total size of frame buffer */
	size_t ndvramsz;	/* size of non-display Video RAM */
	caddr_t ndvram;		/* Storage for nd-VRAM, while suspended */
	size_t	dummysize;	/* total size of overlay plane */
	kmutex_t interlock;	/* interrupt locking */
	off_t   addr_rom;	/* varies between p4 & sbus */
	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 */
	pfn_t   fbpfnum;	/* pfn of fb for mmap() */

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

	union {			/* shadow overlay color map */
	    uint32_t	omap_int[2];	/* cheating here to save space */
	    uchar_t  omap_char[3][2];
	}    omap_image;
#define	omap_rgb	omap_image.omap_char[0]
	ushort_t	omap_update;	/* overlay colormap update flag */
	uint32_t	cmap_index;	/*	colormap update index	*/
	uint32_t	cmap_count;	/*	colormap update count	*/
	union {			/* shadow color map */
	uint32_t	cmap_int[CG6_CMAP_ENTRIES * 3 / sizeof (uint32_t)];
	    uchar_t  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 */
	size_t	fbmappable;	/* bytes mappable */
	int		*vrtpage;	/* pointer to VRT page */
	ddi_umem_cookie_t	vrtcookie;	/* pointer to VRT allocation */
	int		vrtmaps;	/* number of VRT page maps */
	int		vrtflag;	/* vrt interrupt flag */
	struct cg6_info cg6info;	/* info about this cg6 */
	struct mon_info moninfo;	/* info about this monitor */
	struct cg6_cntxt *curctx;	/* context switching */
	struct cg6_cntxt shared_ctx;	/* shared context */
	struct cg6_cntxt *pvt_ctx;	/* list of non-shared contexts */
	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 */
	kmutex_t	mutex;		/* mutex locking */
	kcondvar_t	vrtsleep;	/* for waiting on vertical retrace */
	int		mapped_by_prom;	/* $#!@ SVr4 */
	off_t		mapped_by_driver;
	int		waiting;
	int		cg6_suspended;	/* true if driver is suspended */
	int		vidon;		/* video enable state */
	int		intr_flag;
	kstat_t		*intrstats;	/* interrupt statistics */
};

static int cg6map_map(devmap_cookie_t, dev_t, uint_t, offset_t, size_t,
		void **);
static int cg6map_contextmgt(devmap_cookie_t, void *, offset_t, size_t,
	uint_t, uint_t);
static int cg6map_dup(devmap_cookie_t, void *, devmap_cookie_t, void **);
static void cg6map_unmap(devmap_cookie_t, void *, offset_t, size_t,
			devmap_cookie_t, void **, devmap_cookie_t, void **);
static int cg6map_access(devmap_cookie_t, void *,  offset_t, size_t,
			uint_t, uint_t);
static
struct devmap_callback_ctl cg6map_ops = {
	DEVMAP_OPS_REV,	/* devmap_ops version number	*/
	cg6map_map,	/* devmap_ops map routine */
	cg6map_access,	/* devmap_ops access routine */
	cg6map_dup,		/* devmap_ops dup routine		*/
	cg6map_unmap,	/* devmap_ops unmap routine */
};

static size_t	pagesize;
static void	*cg6_softc_head;
clock_t	cg6_ctxholdval = 1;

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


/*
 * handy macros
 */
#define	getsoftc(instance)	\
	((struct cg6_softc *)ddi_get_soft_state(cg6_softc_head, (instance)))

#define	btob(n)		ptob(btopr(n))	/* TODO, change this? */



/* 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)	((uint_t *)(softc)->fhcthc)
#define	S_THC(softc)	((struct thc *)((softc)->fhcthc + CG6_TEC_POFF))
#define	S_CMAP(softc)	((struct cg6_cmap *)(softc)->cmap)

#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_enable(softc) \
	{\
	    thc_int_enable(S_THC(softc)); }

#define	cg6_int_disable_intr(softc) \
	{\
	    thc_int_disable(S_THC(softc)); }

#define	cg6_int_disable(softc) \
	{\
	    mutex_enter(&(softc)->interlock); \
	    softc->intr_flag = 1; \
	    cg6_int_disable_intr(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 uint_t	cg6_intr(caddr_t);
static void	cg6_reset_cmap(volatile uchar_t *, uint_t);
static void	cg6_update_cmap(struct cg6_softc *, uint_t, uint_t);
static void	cg6_cmap_bcopy(uchar_t *, uchar_t *, uint_t);
static void	cg6_restore_prom_cmap(struct cg6_softc *, volatile uchar_t *,
			uint_t);

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 *, volatile struct tec *,
			struct cg6_cntxt *);
static int	cg6_cntxrestore(volatile struct fbc *, volatile struct tec *,
			struct cg6_cntxt *);
static struct cg6_cntxt *ctx_map_insert(struct cg6_softc *, int);
static pid_t	getpid(void);

/* Loadable Driver stuff */

static struct modldrv modldrv = {
	&mod_driverops,		/* Type of module.  This one is a driver */
	"cgsix driver v1.112",	/* 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) {
	    DEBUGF(1, (CE_CONT, "done\n"));
	    return (e);
	}

	e = mod_install(&modlinkage);

	if (e) {
	    ddi_soft_state_fini(&cg6_softc_head);
	    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;

	DEBUGF(1, (CE_CONT, "cgsix: _fini, mem used=%d\n", total_memory));

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

	ddi_soft_state_fini(&cg6_softc_head);

	return (0);
}

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

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

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

	switch (cmd) {
	case DDI_ATTACH:
	    break;

	case DDI_RESUME:
	    if (!(softc = (struct cg6_softc *)ddi_get_driver_private(devi)))
		    return (DDI_FAILURE);
	    if (!softc->cg6_suspended)
		    return (DDI_SUCCESS);
	    mutex_enter(&softc->mutex);
	    cg6_reset(softc);
	    if (softc->curctx) {
		    /* Restore the video state */
		    cg6_set_video(softc, softc->vidon);

		    /* Restore non display RAM */
		    if (ddi_map_regs(devi, 0, (caddr_t *)&fb_ndvram,
			CG6_ADDR_COLOR + softc->_w * softc->_h,
			softc->ndvramsz) == -1) {
			    mutex_exit(&softc->mutex);
			    return (DDI_FAILURE);
		    }
		    bcopy(softc->ndvram, fb_ndvram, softc->ndvramsz);
		    ddi_unmap_regs(devi, 0, (caddr_t *)&fb_ndvram,
			CG6_ADDR_COLOR + softc->_w * softc->_h,
			softc->ndvramsz);
		    kmem_free(softc->ndvram, softc->ndvramsz);

		    /* Restore other frame buffer state */
		    (void) cg6_cntxrestore(S_FBC(softc), S_TEC(softc),
			softc->curctx);
		    cg6_setcurpos(softc);
		    cg6_setcurshape(softc);
		    cg6_update_cmap(softc, (uint_t)_ZERO_, CG6_CMAP_ENTRIES);
		    cg6_int_enable(softc);	/* Schedule the update */
	    }
	    softc->cg6_suspended = 0;
	    mutex_exit(&softc->mutex);
	    return (DDI_SUCCESS);

	default:
	    return (DDI_FAILURE);
	}

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

	pagesize = (size_t)ddi_ptob(devi, 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);

	/* Grab properties from PROM */
	/* TODO don't really want default w, h */
	if (ddi_prop_op(DDI_DEV_T_ANY, devi, PROP_LEN_AND_VAL_ALLOC,
		DDI_PROP_DONTPASS, "emulation", (caddr_t)&tmp, &proplen) ==
		    DDI_PROP_SUCCESS) {
	    if (strcmp(tmp, "cgthree+") == 0)
		softc->emulation = FBTYPE_SUN3COLOR;
	    else if (strcmp(tmp, "cgfour+") == 0)
		softc->emulation = FBTYPE_SUN4COLOR;
	    else if (strcmp(tmp, "bwtwo+") == 0)
		softc->emulation = FBTYPE_SUN2BW;
	    else
		softc->emulation = FBTYPE_SUNFAST_COLOR;
	    kmem_free(tmp, proplen);
	} else
	    softc->emulation = FBTYPE_SUNFAST_COLOR;

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

	softc->_linebytes = bytes;

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

	softc->cg6info.vmsize = getprop(devi, "vmsize", 1);
	if (softc->cg6info.vmsize > 1) {
	    softc->size = (size_t)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(mpr_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 = (caddr_t)(uintptr_t)getprop(devi, "address", 0)) {
	    softc->_fb = (MPR_T *) 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 = (uint_t)
	(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

	/*
	 * 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 = CG6_ADDR_ROM_SBUS;

	if (ddi_map_regs(devi, 0, &softc->fbctec, CG6_ADDR_FBC,
		    (off_t)CG6_FBCTEC_SZ) != 0) {
	    (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) {
	    (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) {
	    (void) cg6_detach(devi, DDI_DETACH);
	    return (DDI_FAILURE);
	}
#if 0
	if (ddi_map_regs(devi, 0, &softc->rom, softc->addr_rom,
		    (off_t)CG6_ROM_SZ) != 0) {
	    (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) {
	    (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) {
	    (void) cg6_detach(devi, DDI_DETACH);
	    return (DDI_FAILURE);
	}
	/* TODO: uart (future) */

	/* 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) {
		(void) cg6_detach(devi, DDI_DETACH);
		return (DDI_FAILURE);
	    }
	    softc->mapped_by_driver =
		(off_t)ddi_ptob(devi, ddi_btopr(devi, w * h));
	}

	softc->fbpfnum = hat_getkpfnum((caddr_t)softc->_fb);
#endif

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

	cg6_reset(softc);

	if (ddi_get_iblock_cookie(devi, 0, &softc->iblock_cookie)
	    != DDI_SUCCESS) {
		DEBUGF(2, (CE_CONT,
		    "cg6_attach%d ddi_get_iblock_cookie failed\n", unit));
		(void) cg6_detach(devi, DDI_DETACH);
		return (DDI_FAILURE);
	}

	mutex_init(&softc->interlock, NULL, MUTEX_DRIVER, softc->iblock_cookie);
	mutex_init(&softc->mutex, NULL, MUTEX_DRIVER, softc->iblock_cookie);
	cv_init(&softc->vrtsleep, NULL, CV_DRIVER, NULL);

	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));
	    (void) cg6_detach(devi, DDI_DETACH);
	    return (DDI_FAILURE);
	}

	/*
	 * 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, (uint_t)_ZERO_, CG6_CMAP_ENTRIES);
	cg6_update_cmap(softc, (uint_t)_ZERO_, (uint_t)_ZERO_);

	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, NULL) == DDI_FAILURE) {
		ddi_remove_minor_node(devi, NULL);
		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);

	softc->pvt_ctx = NULL;

	/*
	 * Initialize power management bookkeeping; components are created idle
	 */
	if (pm_create_components(devi, 2) == DDI_SUCCESS) {
		(void) pm_busy_component(devi, 0);
		pm_set_normal_power(devi, 0, 1);
		pm_set_normal_power(devi, 1, 1);

		(void) sprintf(name, "cgsixc%d", unit);
		softc->intrstats = kstat_create("cgsix", unit, name,
		    "controller", KSTAT_TYPE_INTR,
		    1, KSTAT_FLAG_PERSISTENT);
		if (softc->intrstats) {
			kstat_install(softc->intrstats);
		}

		return (DDI_SUCCESS);
	} else {
		return (DDI_FAILURE);
	}
}

static int
cg6_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
	int instance = ddi_get_instance(devi);
	register struct cg6_softc *softc = getsoftc(instance);
	caddr_t fb_ndvram;

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

	switch (cmd) {
	case DDI_DETACH:
	    break;

	case DDI_SUSPEND:
	    if (softc == NULL)
		    return (DDI_FAILURE);
	    if (softc->cg6_suspended)
		    return (DDI_FAILURE);

	    mutex_enter(&softc->mutex);

	    if (softc->curctx) {
		struct fbc *fbc0 = S_FBC(softc);

		/* Save the video state */
		softc->vidon = cg6_get_video(softc);

		/* Save non display RAM */
		softc->ndvramsz = (softc->cg6info.vmsize * 1024 * 1024)
			- (softc->_w * softc->_h);
		if ((softc->ndvram = kmem_alloc(softc->ndvramsz,
			KM_NOSLEEP)) == NULL) {
			mutex_exit(&softc->mutex);
			return (DDI_FAILURE);
		}

		/*
		 * If FBC is busy, wait for maximum of 2 seconds for it
		 * to be idle
		 */
		    CG6DELAY(!(fbc0->l_fbc_status & L_FBC_BUSY),
			4*CG6_FBC_WAIT);

		    if (fbc0->l_fbc_status & L_FBC_BUSY) {

			/*
			 * if still busy, try another 2 seconds before
			 * giving up
			 */
			CG6DELAY(!(fbc0->l_fbc_status & L_FBC_BUSY),
				4*CG6_FBC_WAIT);
			if (fbc0->l_fbc_status & L_FBC_BUSY)
			    cmn_err(CE_WARN, "cg6_detach: FBC still busy");
		    }

		    if (ddi_map_regs(devi, 0, &fb_ndvram, CG6_ADDR_COLOR +
			softc->_w * softc->_h, softc->ndvramsz) == -1) {
			    kmem_free(softc->ndvram, softc->ndvramsz);
			    mutex_exit(&softc->mutex);
			    return (DDI_FAILURE);
		    }
		    bcopy(fb_ndvram, softc->ndvram, softc->ndvramsz);
		    ddi_unmap_regs(devi, 0, &fb_ndvram, CG6_ADDR_COLOR +
			softc->_w * softc->_h, softc->ndvramsz);

		    /* Save other frame buffer state */
		    (void) cg6_cntxsave(S_FBC(softc), S_TEC(softc),
			softc->curctx);
	    }
	    softc->cg6_suspended = 1;
	    mutex_exit(&softc->mutex);
	    return (DDI_SUCCESS);

	default:
	    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 0
	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);
	/* TODO: uart (future) */
	if (softc->_fb && softc->mapped_by_driver)
	    ddi_unmap_regs(devi, 0, (caddr_t *)&softc->_fb, CG6_ADDR_COLOR,
		    softc->mapped_by_driver);
#endif
	if (softc->intrstats) {
		kstat_delete(softc->intrstats);
	}
	softc->intrstats = NULL;

	if (softc->vrtpage != NULL)
	    ddi_umem_free(softc->vrtcookie);

	mutex_destroy(&softc->mutex);

	cv_destroy(&softc->vrtsleep);

	ASSERT(softc->curctx == NULL);

	/* free softc struct */
	(void) ddi_soft_state_free(cg6_softc_head, instance);
	pm_destroy_components(devi);

	return (DDI_SUCCESS);
}

static int
cg6_power(dev_info_t *dip, int cmpt, int level)
{
	register struct cg6_softc *softc;

	if (cmpt != 1 || 0 > level || level > 1 ||
	    !(softc = (struct cg6_softc *)ddi_get_driver_private(dip)))
		return (DDI_FAILURE);

	if (level) {
		/* Turn on sync and video. */
		mutex_enter(&softc->mutex);
		S_THC(softc)->l_thc_hcmisc |= THC_HCMISC_RESET;
		drv_usecwait(500);
		S_THC(softc)->l_thc_hcmisc |=
		(THC_HCMISC_SYNCEN | THC_HCMISC_VIDEO);
		S_THC(softc)->l_thc_hcmisc &= ~THC_HCMISC_RESET;
		cg6_update_cmap(softc, (uint_t)_ZERO_, CG6_CMAP_ENTRIES);
		cg6_int_enable(softc);
		mutex_exit(&softc->mutex);
	} else {
		/* Turn off sync and video. */
		mutex_enter(&softc->mutex);
		S_THC(softc)->l_thc_hcmisc |= THC_HCMISC_RESET;
		drv_usecwait(500);
		S_THC(softc)->l_thc_hcmisc &=
		~(THC_HCMISC_VIDEO | THC_HCMISC_SYNCEN);
		S_THC(softc)->l_thc_hcmisc &= ~THC_HCMISC_RESET;
		mutex_exit(&softc->mutex);
	}
	return (DDI_SUCCESS);
}

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

	instance = getminor((dev_t)arg);

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
	    if ((softc = getsoftc(instance)) == NULL) {
		error = DDI_FAILURE;
	    } else {
		*result = (void *) softc->devi;
		error = DDI_SUCCESS;
	    }
	    break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *) (uintptr_t)instance;
		error = DDI_SUCCESS;
		break;
	default:
	    error = DDI_FAILURE;
	}
	return (error);
}

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

	DEBUGF(2, (CE_CONT, "cg6_open(%d), mem used=%d\n", unit, total_memory));

	/*
	 * is this gorp necessary?
	 */
	if (otyp != OTYP_CHR) {
	    error = EINVAL;
	} else
	if (softc == NULL) {
	    error = ENXIO;
	}

	return (error);
}

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

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

	if (otyp != OTYP_CHR) {
	    error = EINVAL;
	} else if (softc == NULL) {
	    error = ENXIO;
	} else {
	    mutex_enter(&softc->mutex);
	    cg6_reset_cmap(softc->cmap_rgb, CG6_CMAP_ENTRIES);
	    cg6_restore_prom_cmap(softc, softc->cmap_rgb, CG6_CMAP_ENTRIES);
	    softc->cur.enable = 0;
	    softc->curctx = NULL;
	    cg6_reset(softc);
	    mutex_exit(&softc->mutex);
	}

	return (error);
}

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

	DEBUGF(off ? 5 : 1, (CE_CONT, "cg6_mmap(%d, 0x%x)\n",
	    getminor(dev), (uint_t)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 if ((diff = off - CG3_MMAP_OFFSET) >= 0 &&
		diff < softc->fbmappable)
	    rval = softc->fbpfnum + diff / pagesize;
	else if (off < CG6_VBASE) {

	/*
	 * getting more and more complicated; what we return depends on what
	 * we're emulating
	 */

	    if (softc->emulation == FBTYPE_SUN3COLOR) {
		if (off >= 0 && off < softc->fbmappable)
		    rval = softc->fbpfnum + diff / pagesize;
		else
		    page = (caddr_t)-1;
	    } else {		/* softc->emulation == FBTYPE_SUN4COLOR */
		if (off >= 0 && off < softc->dummysize)
		    page = softc->rom;
		else if ((diff = off - softc->dummysize) < softc->fbmappable)
		    rval = softc->fbpfnum + diff / pagesize;
	    }
	    /* TODO: bw2? */
	} 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 ((int)rval);	/* XXX64 */
}

/*ARGSUSED*/
static
cg6_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *cred, int *rval)
{
	struct cg6_softc *softc = getsoftc(getminor(dev));
	int    cursor_cmap;
	int    i;

	uchar_t *iobuf_cmap_red;
	uchar_t *iobuf_cmap_green;
	uchar_t *iobuf_cmap_blue;
	uchar_t *stack_cmap;

	STRUCT_DECL(fbcmap, fbcmap);
	STRUCT_DECL(fbcursor, fbcursor);

	uint_t   index;
	uint_t   count;
	uchar_t *map;
	uint_t   entries;

	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 (ddi_copyout((caddr_t)&cg6_ident,
			    (caddr_t)data,
			    sizeof (struct vis_identifier),
			    mode))
		return (EFAULT);
	    break;

	case FBIOPUTCMAP:
	case FBIOGETCMAP:


	cmap_ioctl:

		if (cursor_cmap == 0) {
			STRUCT_INIT(fbcmap, mode);
			if (ddi_copyin((caddr_t)data,
			    STRUCT_BUF(fbcmap), STRUCT_SIZE(fbcmap), mode))
				return (EFAULT);
		}
		index = STRUCT_FGET(fbcmap, index);
		count = STRUCT_FGET(fbcmap, count);

		if (count == 0) {
			return (0);
		}
		if (cursor_cmap == 0) {
			switch (PIX_ATTRGROUP(index)) {

			case 0:
			case PIXPG_8BIT_COLOR:
			    map = softc->cmap_rgb;
			    entries = CG6_CMAP_ENTRIES;
			    break;
			default:
			    return (EINVAL);
			}
		} else {
			map = softc->omap_rgb;
			entries = 2;
		}

		if ((index &= PIX_ALL_PLANES) >= entries ||
			index + count > entries) {
			return (EINVAL);
		}
		/*
		 * Allocate memory for color map RGB entries.
		 */
		stack_cmap = kmem_alloc((CG6_CMAP_ENTRIES * 3), KM_SLEEP);

		iobuf_cmap_red = stack_cmap;
		iobuf_cmap_green = stack_cmap + CG6_CMAP_ENTRIES;
		iobuf_cmap_blue = stack_cmap + (CG6_CMAP_ENTRIES * 2);

		if (cmd == FBIOPUTCMAP) {
			int error;

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

			if (error = ddi_copyin(
			    STRUCT_FGETP(fbcmap, red),
			    iobuf_cmap_red, count, mode)) {
				kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
				return (error);
			}

			if (error = ddi_copyin(
			    STRUCT_FGETP(fbcmap, green),
			    iobuf_cmap_green, count, mode)) {
				kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
				return (error);
			}
			if (error = ddi_copyin(
			    STRUCT_FGETP(fbcmap, blue),
			    iobuf_cmap_blue, count, mode)) {
				kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
				return (error);
			}

			mutex_enter(&softc->mutex);
			map += index * 3;
			if (cg6_update_pending(softc))
			    cg6_int_disable(softc);

			/*
			 * Copy color map entries from stack to the color map
			 * table in the softc area.
			 */

			cg6_cmap_bcopy(iobuf_cmap_red, map++, count);
			cg6_cmap_bcopy(iobuf_cmap_green, map++, count);
			cg6_cmap_bcopy(iobuf_cmap_blue, map, count);

			/* cursor colormap update */
			if (entries < CG6_CMAP_ENTRIES)
			    count = 0;
			cg6_update_cmap(softc, index, count);
			cg6_int_enable(softc);
			mutex_exit(&softc->mutex);

		} else {
			/* FBIOGETCMAP */
			DEBUGF(3, (CE_CONT, "FBIOGETCMAP\n"));

			mutex_enter(&softc->mutex);
			map += index * 3;

			/*
			 * Copy color map entries from soft area to
			 * local storage and prepare for a copyout
			 */

			cg6_cmap_bcopy(iobuf_cmap_red, map++, -count);
			cg6_cmap_bcopy(iobuf_cmap_green, map++, -count);
			cg6_cmap_bcopy(iobuf_cmap_blue, map, -count);

			mutex_exit(&softc->mutex);

			if (ddi_copyout(iobuf_cmap_red,
			    STRUCT_FGETP(fbcmap, red), count,
			    mode)) {
				kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
				return (EFAULT);
			}
			if (ddi_copyout(iobuf_cmap_green,
			    STRUCT_FGETP(fbcmap, green), count,
			    mode)) {
				kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
				return (EFAULT);
			}
			if (ddi_copyout(iobuf_cmap_blue,
			    STRUCT_FGETP(fbcmap, blue), count,
			    mode)) {
				kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
				return (EFAULT);
			}
		}
		kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
		break;

	case FBIOSATTR: {
		struct fbsattr attr;

		if (ddi_copyin((caddr_t)data,
				(caddr_t)&attr,
				sizeof (attr),
				mode))
			return (EFAULT);
		DEBUGF(3, (CE_CONT, "FBIOSATTR, type=%d\n", attr.emu_type));
		if (attr.emu_type != -1)
			switch (attr.emu_type) {

			case FBTYPE_SUN3COLOR:
			case FBTYPE_SUN4COLOR:
			case FBTYPE_SUN2BW:
			case FBTYPE_SUNFAST_COLOR:
				    mutex_enter(&softc->mutex);
				    softc->emulation = attr.emu_type;
				    mutex_exit(&softc->mutex);
				    break;
			default:
				    return (EINVAL);
		}
		/* ignore device-dependent stuff */
	}
	break;

	case FBIOGATTR: {
		struct fbgattr attr;

		DEBUGF(3, (CE_CONT, "FBIOGATTR, emu_type=%d\n",
			softc->emulation));
		bcopy((caddr_t)&cg6_attr, (caddr_t)&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 = (int)softc->size;
		attr.sattr.emu_type = softc->emulation;
		mutex_exit(&softc->mutex);

		if (ddi_copyout((caddr_t)&attr,
				(caddr_t)data,
				sizeof (struct fbgattr),
				mode))
			return (EFAULT);
	}
	break;

	/*
	 * always claim to be a cg4 if they call this ioctl.  This is to
	 * support older software which was staticly-linked before cg6 was
	 * invented, and to support newer software which has come to expect
	 * this behavior.
	 */
	case FBIOGTYPE: {
		struct fbtype fb;

		mutex_enter(&softc->mutex);

		bcopy(&cg6_attr.fbtype, &fb, sizeof (struct fbtype));
		DEBUGF(3, (CE_CONT, "FBIOGTYPE\n"));
		fb.fb_type = FBTYPE_SUN4COLOR;
		fb.fb_width = softc->_w;
		fb.fb_height = softc->_h;
		/* XXX not quite like a cg4 */
		fb.fb_size = (int)softc->size;

		mutex_exit(&softc->mutex);

		if (ddi_copyout((caddr_t)&fb,
				(caddr_t)data,
				sizeof (struct fbtype),
				mode))
			return (EFAULT);
		}
		break;
	case FBIOSVIDEO:

		DEBUGF(3, (CE_CONT, "FBIOSVIDEO\n"));
		if (ddi_copyin((caddr_t)data,
				(caddr_t)&i,
				sizeof (int),
				mode))
			return (EFAULT);
		mutex_enter(&softc->mutex);
		cg6_set_video(softc, i & FBVIDEO_ON);
		mutex_exit(&softc->mutex);
		break;

	case FBIOGVIDEO:

		DEBUGF(3, (CE_CONT, "FBIOGVIDEO\n"));
		mutex_enter(&softc->mutex);
		i = cg6_get_video(softc) ? FBVIDEO_ON : FBVIDEO_OFF;
		mutex_exit(&softc->mutex);

		if (ddi_copyout((caddr_t)&i,
				(caddr_t)data,
				sizeof (int),
				mode))
			return (EFAULT);
		break;

	/* informational ioctls */

	case FBIOGXINFO:
		if (ddi_copyout((caddr_t)&softc->cg6info,
				(caddr_t)data,
				sizeof (struct cg6_info),
				mode))
			return (EFAULT);
		return (0);

	case FBIOMONINFO:
		if (ddi_copyout((caddr_t)&softc->moninfo,
				(caddr_t)data,
				sizeof (struct mon_info),
				mode))
			return (EFAULT);
		return (0);

	/* vertical retrace interrupt */

	case FBIOVERTICAL:

		mutex_enter(&softc->mutex);
		softc->vrtflag |= CG6VRTIOCTL;
		cg6_int_enable(softc);
		cv_wait(&softc->vrtsleep, &softc->mutex);
		mutex_exit(&softc->mutex);
		return (0);

	case FBIOVRTOFFSET:

		i = CG6_VADDR_VRT;

		if (ddi_copyout((caddr_t)&i,
				(caddr_t)data,
				sizeof (int),
				mode))
			return (EFAULT);
		return (0);

	/* HW cursor control */
	case FBIOSCURSOR: {

		int	set;
		ssize_t	cbytes;
		uint32_t	stack_image[32], stack_mask[32];

		STRUCT_INIT(fbcursor, mode);
		if (ddi_copyin((void *)data, STRUCT_BUF(fbcursor),
		    STRUCT_SIZE(fbcursor), mode))
			return (EFAULT);

		set = STRUCT_FGET(fbcursor, set);

		/* Compute cursor bitmap bytes */
		cbytes = STRUCT_FGET(fbcursor, size.y) *
		    sizeof (softc->cur.image[0]);
		if (set & FB_CUR_SETSHAPE) {
			if (STRUCT_FGET(fbcursor, size.x) > 32 ||
			    STRUCT_FGET(fbcursor, size.y) > 32) {
				return (EINVAL);
			}


			/* copy cursor image into softc */
			if (STRUCT_FGETP(fbcursor, image) &&
			    ddi_copyin(STRUCT_FGETP(fbcursor, image),
			    &stack_image, cbytes, mode))
				return (EFAULT);

			if (STRUCT_FGETP(fbcursor, mask) &&
			    ddi_copyin(STRUCT_FGETP(fbcursor, mask),
			    &stack_mask, cbytes, mode))
				return (EFAULT);
		}

		mutex_enter(&softc->mutex);
		if (set & FB_CUR_SETCUR)
			softc->cur.enable = STRUCT_FGET(fbcursor, enable);

		if (set & FB_CUR_SETPOS)
			softc->cur.pos = STRUCT_FGET(fbcursor, pos);

		if (set & FB_CUR_SETHOT)
			softc->cur.hot = STRUCT_FGET(fbcursor, hot);

		/* update hardware */

		cg6_setcurpos(softc);

		if (set & FB_CUR_SETSHAPE) {

			if (STRUCT_FGETP(fbcursor, image)) {
				bzero((caddr_t)softc->cur.image,
					sizeof (softc->cur.image));
				bcopy((caddr_t)&stack_image,
					(caddr_t)softc->cur.image,
					cbytes);
			}
			if (STRUCT_FGETP(fbcursor, mask)) {
				bzero((caddr_t)softc->cur.mask,
					sizeof (softc->cur.mask));
				bcopy((caddr_t)&stack_mask,
					(caddr_t)softc->cur.mask,
					cbytes);
			}
			/* load into hardware */
			softc->cur.size = STRUCT_FGET(fbcursor, size);
			cg6_setcurshape(softc);
		}
		mutex_exit(&softc->mutex);
		/* load colormap */
		if (set & FB_CUR_SETCMAP) {
			cursor_cmap = 1;
			cmd = FBIOPUTCMAP;
			STRUCT_SET_HANDLE(fbcmap, mode,
			    STRUCT_FADDR(fbcursor, cmap));
			goto cmap_ioctl;
		}
	}
	break;

	case FBIOGCURSOR: {
		ssize_t    cbytes;
		uint32_t stack_image[32], stack_mask[32];

		STRUCT_INIT(fbcursor, mode);
		if (ddi_copyin((void *)data, STRUCT_BUF(fbcursor),
		    STRUCT_SIZE(fbcursor), mode))
			return (EFAULT);

		mutex_enter(&softc->mutex);

		STRUCT_FSET(fbcursor, set, 0);
		STRUCT_FSET(fbcursor, enable, softc->cur.enable);
		STRUCT_FSET(fbcursor, pos, softc->cur.pos);
		STRUCT_FSET(fbcursor, hot, softc->cur.hot);
		STRUCT_FSET(fbcursor, size, softc->cur.size);
		STRUCT_FSET(fbcursor, cmap.index, 0);
		STRUCT_FSET(fbcursor, cmap.count, 2);

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

		bcopy(softc->cur.image, &stack_image, cbytes);
		bcopy(softc->cur.mask, &stack_mask, cbytes);

		mutex_exit(&softc->mutex);

		if (ddi_copyout(STRUCT_BUF(fbcursor), (void *)data,
		    STRUCT_SIZE(fbcursor), mode))
			return (EFAULT);

		/* if image pointer is non-null copy both bitmaps */
		if (STRUCT_FGETP(fbcursor, image)) {
			if (ddi_copyout(&stack_image,
			    STRUCT_FGETP(fbcursor, image),
			    cbytes, mode))
				return (EFAULT);

			if (ddi_copyout(&stack_mask,
			    STRUCT_FGETP(fbcursor, mask),
			    cbytes, mode))
				return (EFAULT);
		}

		/* if red pointer is non-null copy colormap */
		if (STRUCT_FGETP(fbcursor, cmap.red)) {
			cursor_cmap = 1;
			cmd = FBIOGETCMAP;
			/*
			 * XX64	The code used to do this:
			 *
			 * data = (int)&((struct fbcursor *)data)->cmap;
			 *
			 * However the cmap_ioctl handler doesn't look
			 * at 'data' so this assignment doesn't do anything.
			 * Instead we made it set the correct field in the
			 * fbcurpos structure we were passed in from
			 * userland.
			 */
			STRUCT_SET_HANDLE(fbcmap, mode,
			    STRUCT_FADDR(fbcursor, cmap));
			goto cmap_ioctl;
		}
	}
	break;

	case FBIOSCURPOS: {

		struct fbcurpos stack_curpos;	/* cursor position */

		if (ddi_copyin((caddr_t)data,
				(caddr_t)&stack_curpos,
				sizeof (struct fbcurpos),
				mode))
			return (EFAULT);

		mutex_enter(&softc->mutex);
		bcopy((caddr_t)&stack_curpos, (caddr_t)&softc->cur.pos,
		    sizeof (struct fbcurpos));
		cg6_setcurpos(softc);
		mutex_exit(&softc->mutex);
	}
	break;

	case FBIOGCURPOS: {
		struct fbcurpos stack_curpos;	/* cursor position */

		mutex_enter(&softc->mutex);
		bcopy((caddr_t)&softc->cur.pos, (caddr_t)&stack_curpos,
		    sizeof (struct fbcurpos));
		mutex_exit(&softc->mutex);

		if (ddi_copyout((caddr_t)&stack_curpos,
				(caddr_t)data,
				sizeof (struct fbcurpos),
				mode))
			return (EFAULT);
	}
	break;

	case FBIOGCURMAX: {
		static struct fbcurpos curmax = {32, 32};

		if (ddi_copyout((caddr_t)&curmax,
				(caddr_t)data,
				sizeof (struct fbcurpos),
				mode))
			return (EFAULT);
	}
	break;

#if	CG6DEBUG >= 3
	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  uint_t
cg6_intr(caddr_t arg)
{
	struct cg6_softc *softc = (struct cg6_softc *)arg;
	volatile uint32_t *in;
	volatile uint32_t *out;
	volatile uint32_t  tmp;

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

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

	if (!cg6_int_pending(softc)) {
	    if (softc->intr_flag) {
		softc->intr_flag = 0;
	    } else {
		if (softc->intrstats) {
		    KIOIP->intrs[KSTAT_INTR_SPURIOUS]++;
		}
		mutex_exit(&softc->interlock);
		mutex_exit(&softc->mutex);
		return (DDI_INTR_UNCLAIMED);	/* nope, not mine */
	    }
	}

	if (!(cg6_update_pending(softc) || (softc)->vrtflag)) {
	    /* TODO catch stray interrupts? */
	    cg6_int_disable_intr(softc);
	    if (softc->intrstats) {
		KIOIP->intrs[KSTAT_INTR_HARD]++;
	    }
	    mutex_exit(&softc->interlock);
	    mutex_exit(&softc->mutex);
	    return (DDI_INTR_CLAIMED);
	}
	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);
	    LOOP_T  count = softc->cmap_count;

	    /* load cursor color map */
	    if (softc->omap_update) {
		in = &softc->omap_image.omap_int[0];
		out = (uint32_t *)& 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) {
		LOOP_T  index = softc->cmap_index;

		in = &softc->cmap_image.cmap_int[0];
		out = (uint32_t *)& 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;
		PTR_INCR(uint32_t *, in, index * 3);

		/* copy 4 bytes (4/3 RGB entries) per loop iteration */
		count *= 3;
		PR_LOOPV(count,
			tmp = *in++;
		*out = tmp;
		*out = tmp <<= 8;
		*out = tmp <<= 8;
		*out = tmp <<= 8);

		softc->cmap_count = 0;
	    }
	    softc->omap_update = 0;
	}
	cg6_int_disable_intr(softc);
	if (softc->vrtflag)
	    cg6_int_enable(softc);
	if (softc->intrstats) {
	    KIOIP->intrs[KSTAT_INTR_HARD]++;
	}
	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 uchar_t *cmap, uint_t 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, uint_t index, uint_t count)
{
	uint_t   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(uchar_t *bufp, uchar_t *rgb, uint_t count)
{
	LOOP_T rcount = count;

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

/*
 * This routine restores the color map to it's post-attach time values.
 */
static void
cg6_restore_prom_cmap(struct cg6_softc *softc,
	volatile uchar_t *cmap,
	uint_t entries)
{
	volatile struct cg6_cmap *hwcmap = S_CMAP(softc);
	volatile uint32_t *in, *out, tmp;
	LOOP_T count;

	out = (uint32_t *)&hwcmap->cmap;
	in = (uint32_t *)cmap;

	if (entries != 0) {
		hwcmap->addr = 0;

		count = ((entries + 3) >> 2) * 3;
		PR_LOOPV(count, tmp = *in++;
			 *out = tmp;
			 *out = tmp <<= 8;
			 *out = tmp <<= 8;
			 *out = tmp <<= 8);
	}
}

/*
 * 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)
{
	uint_t  tmp, edge = 0;
	volatile uint_t *image, *mask, *hw;
	volatile struct thc *thc = S_THC(softc);
	int    i;

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

	image = softc->cur.image;
	mask = softc->cur.mask;
	hw = (uint_t *)&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;

	/* reinitialize TEC */
	{
	    volatile struct tec *tec = S_TEC(softc);

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

	/* reinitialize FBC config register */
	{
	    volatile uint_t  *fhc = S_FHC(softc);
	    uint_t 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);
	}
}

	/*
	 * This code is no longer used, since OBP proms now do all device
	 * initialization. Nevertheless, it is instructive and I'm going to
	 * keep it in as a comment, should anyone ever want to know how to
	 * do minimal device initialization. Note the c++ style embedded
	 * comments.
	 *
	 * cg6_init(softc)
	 *	struct cg6_softc *softc;
	 *{
	 *	// Initialize DAC
	 *	{
	 *	    register struct cg6_cmap *cmap = S_CMAP(softc);
	 *	    register char *p;
	 *
	 *	    static char dacval[] = {
	 *		4, 0xff,
	 *		5, 0,
	 *		6, 0x73,
	 *		7, 0,
	 *		0
	 *	    };
	 *
	 *	    // initialize DAC
	 *	    for (p = dacval; *p; p += 2) {
	 *		cmap->addr = p[0] << 24;
	 *		cmap->ctrl = p[1] << 24;
	 *	    }
	 *	}
	 *
	 *	// Initialize THC
	 *	{
	 *	    register struct thc *thc = S_THC(softc);
	 *	    int    vidon;
	 *
	 *	    vidon = thc_get_video(thc);
	 *	    thc->l_thc_hcmisc = THC_HCMISC_RESET | THC_HCMISC_INIT;
	 *	    thc->l_thc_hcmisc = THC_HCMISC_INIT;
	 *
	 *	    thc->l_thc_hchs = 0x010009;
	 *	    thc->l_thc_hchsdvs = 0x570000;
	 *	    thc->l_thc_hchd = 0x15005d;
	 *	    thc->l_thc_hcvs = 0x010005;
	 *	    thc->l_thc_hcvd = 0x2403a8;
	 *	    thc->l_thc_hcr = 0x00016b;
	 *
	 *	    thc->l_thc_hcmisc = THC_HCMISC_RESET | THC_HCMISC_INIT;
	 *	    thc->l_thc_hcmisc = THC_HCMISC_INIT;
	 *
	 *	    if (vidon)
	 *		thc_set_video(thc, _ONE_);
	 *
	 *	    DEBUGF(1, (CE_CONT, "TEC rev %d\n",
	 *			thc->l_thc_hcmisc >> THC_HCMISC_REV_SHIFT &
	 *			THC_HCMISC_REV_MASK));
	 *	}
	 *
	 *	//
	 *	// Initialize FHC for 1152 X 900 screen
	 *	//
	 *	{
	 *	    volatile uint_t *fhc = S_FHC(softc), rev;
	 *
	 *	    rev = *fhc >> FHC_CONFIG_REV_SHIFT & FHC_CONFIG_REV_MASK;
	 *	    DEBUGF(1, (CE_CONT, "cg6_init: FBC rev %d\n", rev));
	 *
	 *	//
	 *	// FBC0: disable fast rops FBC[01]: disable destination cache
	 *	//
	 *	    *fhc = FHC_CONFIG_1152 |
	 *		FHC_CONFIG_CPU_68020 |
	 *		FHC_CONFIG_TEST |
	 *
	 *#if FBC_REV0
	 *	    (rev == 0 ? FHC_CONFIG_FROP_DISABLE : 0) |
	 *#endif
	 *
	 *	    (rev <= 1 ? FHC_CONFIG_DST_DISABLE : 0);
	 *	}
	 *}
	 */

/*
 * from here on down, is the lego segment driver.  this virtualizes the
 * lego register file by associating a register save area with each
 * mapping of the lego device (each lego segment).  only one of these
 * mappings is valid at any time; a page fault on one of the invalid
 * mappings saves off the current lego 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-segment info:
 *	Some, but not all, segments are part of a context.
 *	Any segment that is a MAP_PRIVATE mapping 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_VRT	0x04	/* vrt page */
#define	CG6MAP_FBCTEC	0X08	/* mapping includes fbc and/or tec */
#define	CG6MAP_FB	0X10	/* mapping includes framebuffer */

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

static struct cg6map_pvt *
cg6_pvt_alloc(struct cg6_cntxt *ctx,
		uint_t type,
		offset_t off,
		size_t len,
		struct cg6_softc *softc)
{
	struct cg6map_pvt *pvt;

	/*
	 * create the private data portion of the devmap object
	 */
	pvt = kmem_zalloc(sizeof (struct cg6map_pvt), KM_SLEEP);
	pvt->type = type;
	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
 * the creation of lego (cg6) segments.
 */
/*ARGSUSED*/
static int
cg6_segmap(dev_t	dev,
	    off_t	off,
	    struct as	*as,
	    caddr_t	*addrp,
	    off_t	len,
	    uint_t	prot,
	    uint_t	maxprot,
	    uint_t	flags,
	    cred_t	*cred)
{
	struct cg6_softc *softc = getsoftc(getminor(dev));
	int	error;

	DEBUGF(3, (CE_CONT, "segmap: off=%x, len=%x\n", off, len));
	mutex_enter(&softc->mutex);

	/*
	 * check to see if this is a VRT page
	 */
	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);
	    }
	    if (softc->vrtmaps++ == 0) {
			if (softc->vrtpage == NULL) {
				softc->vrtpage = (int *)ddi_umem_alloc(
					pagesize, KM_SLEEP,
					(void **)&softc->vrtcookie);
			}
			*softc->vrtpage = 0;
			softc->vrtflag |= CG6VRTCTR;
			cg6_int_enable(softc);
	    }
	}

	/*
	 * use the devmap framework for setting up the user mapping.
	 */
	error = devmap_setup(dev, (offset_t)off, as, addrp, (size_t)len, prot,
			maxprot, flags, cred);

	mutex_exit(&softc->mutex);

	return (error);
}

/* ARGSUSED */
static int
cg6map_map(devmap_cookie_t dhp, dev_t dev, uint_t flags, offset_t off,
	size_t len, void **pvtp)
{
	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;
	uint_t	maptype = 0;

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

	/*
	 * 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 segment driver getting involved
	 * to make sure the registers are idle. This involves taking a page
	 * fault, invalidating all other process's mappings to the fb, (and
	 * performing a context switch?)
	 *
	 * Under pixrects, which maps the chips and the FB all at once, the
	 * entire mapping becomes a context. This won't hurt pixrects but
	 * entails unnecessary context switching.  Under other 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 (off + len > CG6_VADDR_FBC && off < CG6_VADDR_FBC + CG6_FBCTEC_SZ)
		maptype |= CG6MAP_FBCTEC;
	if (off + len > CG6_VADDR_COLOR && off < CG6_VADDR_COLOR + CG6_FB_SZ)
		maptype |= CG6MAP_FB;

	/*
	 * we now support MAP_SHARED and MAP_PRIVATE:
	 *
	 * MAP_SHARED means you get the shared context which is the traditional
	 * mapping method.
	 *
	 * MAP_PRIVATE means you get your very own LEGO context.
	 *
	 * Note that you can't get to here without asking for one or the other,
	 * but not both.
	 */
	if (softc->chiprev == 5 && (maptype & CG6MAP_FB))
	    flags = (flags & ~MAP_TYPE) | MAP_PRIVATE;

	if (flags & MAP_SHARED) {	/* shared mapping */
		ctx = shared_ctx;
		ctx->flag = CG6MAP_CTX;
	} else {
		ctx = ctx_map_insert(softc, maptype);
		ctx->flag |= maptype;
		DEBUGF(2, (CE_CONT, "cg6map_map: ** MAP_PRIVATE **. ctx = %x\n",
			ctx));
	}

	pvt = cg6_pvt_alloc(ctx, maptype, off, len, softc);
	pvt->dhp = dhp;

	*pvtp = pvt;

	devmap_set_ctx_timeout(dhp, cg6_ctxholdval);
	return (DDI_SUCCESS);
}

/*
 * An access has been made to a context other than the current one
 */
/* ARGSUSED */
static int
cg6map_access(devmap_cookie_t dhp, void *pvt, offset_t offset, size_t len,
	uint_t type, uint_t rw)
{
	return (devmap_do_ctxmgt(dhp, pvt, offset, len, type, rw,
		cg6map_contextmgt));
}

/*
 * called by the devmap framework to perform context switching.
 */
/* ARGSUSED */
static int
cg6map_contextmgt(devmap_cookie_t dhp, void *pvt, offset_t offset,
	size_t len, uint_t type, uint_t rw)
{
	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);

	DEBUGF(6, (CE_CONT, "cg6map_contextmgt: pvt = %x, dhp = %x, \
curctx = %x, context = %x\n",
		p, dhp, softc->curctx, p->context));
	/*
	 * 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 segdev 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) {
				err = devmap_unload(pvts->dhp, pvts->offset,
						pvts->len);
				if (err) {
					mutex_exit(&softc->mutex);
					return (err);
				}
			}

			if (cg6_cntxsave(fbc, S_TEC(softc),
					softc->curctx) == 0) {
				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) == 0) {
				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);
	if ((type == DEVMAP_LOCK) || (type == DEVMAP_UNLOCK)) {
		if ((err = devmap_load(p->dhp, offset, len, type, rw)) != 0) {
			mutex_exit(&softc->mutex);
			return (err);
		}
	} else {
		if ((err = devmap_load(p->dhp, p->offset, p->len, type,
			rw)) != 0) {
			mutex_exit(&softc->mutex);
			return (err);
		}
	}

#if 0
	for (pvts = p->context->pvt; pvts != NULL; pvts = pvts->next) {
		DEBUGF(7, (CE_CONT, "cg6map_contextmgt: pvts = %x, dhp = %x, \
cur_ctx = %x, ctx = %x\n",
			pvts, pvts->dhp, softc->curctx, pvts->context));
		if ((err = devmap_load(pvts->dhp, pvts->offset,
				pvts->len, type, rw)) != 0) {
			mutex_exit(&softc->mutex);
			return (err);
		}
	}
#endif

	mutex_exit(&softc->mutex);

	return (err);
}

/* ARGSUSED */
static void
cg6map_unmap(devmap_cookie_t dhp, void *pvtp, offset_t off, size_t len,
	devmap_cookie_t new_dhp1, void **pvtp1,
	devmap_cookie_t new_dhp2, void **pvtp2)
{
	struct cg6map_pvt *p = (struct cg6map_pvt *)pvtp;
	struct cg6_softc *softc = p->softc;
	struct cg6_cntxt *ctx = p->context;
	struct cg6map_pvt *ptmp;
	struct cg6map_pvt *ppvts;
	struct cg6_cntxt *shared_ctx    = &softc->shared_ctx;
	size_t length;

	DEBUGF(3, (CE_CONT, "cg6map_unmap: pvt = %x, dhp = %x, \
off = %x, len = %x, dhp1 = %x, dhp2 = %x\n",
			pvtp, dhp, (uint_t)off, len, new_dhp1, new_dhp2));

	mutex_enter(&softc->mutex);

	/*
	 * We are unmapping at the end of the mapping, if
	 * new_dhp1 is not NULL.
	 */
	if (new_dhp1 != NULL) {
	    ptmp = cg6_pvt_alloc(ctx,  p->type,
			p->offset,
			(off - p->offset),
			softc);
		ptmp->dhp = new_dhp1;
		*pvtp1 = ptmp;
	}

	/*
	 * We are unmapping at the beginning of the mapping, if
	 * new_dhp2 is not NULL.
	 */
	if (new_dhp2 != NULL) {
		length = p->len - len - (off -  p->offset);
	    ptmp = cg6_pvt_alloc(ctx, p->type, (off + len), length, softc);
		ptmp->dhp = new_dhp2;

		*pvtp2 = ptmp;
	}

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

	/*
	 * We want to remove the conext if both new_dhp1 and new_dhp2 are NULL.
	 */
	if (new_dhp1 == NULL && new_dhp2 == NULL) {
		/*
		 * Remove the context if this is not the shared context
		 * xand there are no more associated pvt's
		 */
		if ((ctx != shared_ctx) && (ctx->pvt == NULL)) {
			struct cg6_cntxt *ctxptr;

			if (ctx == softc->curctx)
				softc->curctx = NULL;

			/*
			 * Scan private context list for entry to remove.
			 * Check first to see if it's the head of our list.
			 */
			if (softc->pvt_ctx == ctx) {
				softc->pvt_ctx = ctx->link;
				kmem_free(ctx, sizeof (struct cg6_cntxt));
			} else {
				for (ctxptr = softc->pvt_ctx; ctxptr != NULL;
					ctxptr = ctxptr->link) {
					if (ctxptr->link == ctx) {
						ctxptr->link = ctx->link;
						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(devmap_cookie_t dhp, void *oldpvt, devmap_cookie_t new_dhp,
	void **newpvt)
{
	struct cg6map_pvt *p   = (struct cg6map_pvt *)oldpvt;
	struct cg6_softc *softc = p->softc;
	struct cg6map_pvt *pvt;
	struct cg6_cntxt *ctx;
	uint_t maptype;

	DEBUGF(3, (CE_CONT, "cg6map_dup: pvt=%x, dhp=%x, newdhp=%x\n",
			oldpvt, dhp, new_dhp));

	mutex_enter(&softc->mutex);
	if (p->context != &softc->shared_ctx) {
		maptype = p->type;
		ctx = ctx_map_insert(softc, maptype);
	} else
		ctx = &softc->shared_ctx;

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

	pvt->dhp = new_dhp;
	*newpvt = pvt;

	if (p->context && (p->context->flag & CG6MAP_VRT)) {
	    softc->vrtflag |= CG6VRTCTR;
	    if (softc->vrtmaps == 0)
			cg6_int_enable(softc);
	    softc->vrtmaps++;
	}

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

/*
 * please don't mess with these defines... they may look like
 * a strange place for defines, but the context management code
 * wants them as they are. JMP
 *
 */
#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(fbc, tec, saved)
	volatile struct fbc *fbc;
	volatile struct tec *tec;
	struct cg6_cntxt *saved;
{
	int    dreg;		/* counts through the data registers */
	uint_t  *dp;			/* points to a tec data register */

	DEBUGF(5, (CE_CONT, "saving registers for %d\n", saved->pid));

	CDELAY(!(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 more non-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 = (uint_t)L_TEC_VDC_INTRNL0;
	for (dreg = 0, dp = (uint_t *)&tec->l_tec_data00; dreg < 64;
				dreg++, dp++) {
		saved->tec.data[dreg][0] = *dp;
	}
	tec->l_tec_vdc = (uint_t)L_TEC_VDC_INTRNL1;
	for (dreg = 0, dp = (uint_t *)&tec->l_tec_data00; dreg < 64;
				dreg++, dp++) {
		saved->tec.data[dreg][1] = *dp;
	}

	return (1);
}

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

	DEBUGF(5, (CE_CONT, "restoring registers for %d\n", saved->pid));

	/*
	* reload the tec data registers.  see above for "how do they get 36 bits
	* in that itty-bitty int"
	*/
	tec->l_tec_vdc = (uint_t)L_TEC_VDC_INTRNL0;
	for (dreg = 0, dp = (uint_t *)&tec->l_tec_data00;
		dreg < 64; dreg++, dp++) {
		*dp = saved->tec.data[dreg][0];
	}
	tec->l_tec_vdc = (uint_t)L_TEC_VDC_INTRNL1;
	for (dreg = 0, dp = (uint_t *)&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;

	return (1);
}

/*
 * ctx_map_insert()
 *
 * Insert a mapping into the mapping list of a private context.  First
 * determine if there's an existing context (e.g. one with the same PID
 * as the current  one and that does not already have a mapping of this
 * type yet).  If not, allocate a new one.  Then insert mapping into this
 * context's list.
 *
 * The softc mutex must be held across calls to this routine.
 */
static
struct cg6_cntxt *
ctx_map_insert(struct cg6_softc *softc, int maptype)
{
	struct cg6_cntxt *ctx;
	pid_t curpid = getpid();

	DEBUGF(4, (CE_CONT, "ctx_map_insert: maptype=0x%x curpid=%d\n",
		maptype, curpid));

	/*
	 * If this is the first time we're here, then alloc space
	 * for new context and depart.
	 */
	if (softc->pvt_ctx == NULL) {
		ctx = (struct cg6_cntxt *)
			kmem_zalloc(sizeof (struct cg6_cntxt), KM_SLEEP);
		ctx->pid = curpid;
		ctx->link = NULL;
		softc->pvt_ctx = ctx;
		return (ctx);
	}

	/*
	 * Find existing context if one exists.  We have a match if
	 * we're the same process *and* there's not already a
	 * mapping of this type assigned.
	 */
	for (ctx = softc->pvt_ctx; ctx != NULL; ctx = ctx->link) {
		if (ctx->pid == curpid &&
			(maptype & ctx->flag & (CG6MAP_FBCTEC|CG6MAP_FB)) == 0)
			break;
	}


	/* no match, create a new one and add to softc list */
	if (ctx == NULL) {
		ctx = (struct cg6_cntxt *)
			kmem_zalloc(sizeof (struct cg6_cntxt), KM_SLEEP);
		ctx->pid = curpid;
		ctx->link = softc->pvt_ctx;
		softc->pvt_ctx = ctx;
	}

	DEBUGF(4, (CE_CONT, "ctx_map_insert: returning ctx=0x%x\n", ctx));

	return (ctx);
}

/*
 * getpid()
 *
 * Simple wrapper around process ID call to drv_getparm(9f).
 */
static pid_t
getpid()
{
	pid_t mypid;

	if (drv_getparm(PPID, &mypid) == -1)
		return (0);
	return (mypid);
}

#define	DEVMEMORY	1
#define	KERNELMEMORY	2

/*ARGSUSED*/
static int
cg6_devmap(dev_t dev, devmap_cookie_t dhp, offset_t off, size_t len,
    size_t *maplen, uint_t model)
{
	struct cg6_softc *softc = getsoftc(getminor(dev));
	dev_info_t *dip = softc->devi;
	ssize_t diff;
	int err = 0;
	caddr_t	kvaddr = NULL;
	ddi_umem_cookie_t cookie = NULL;
	offset_t offset = 0;
	uint_t	rnumber = 0;
	uint_t	type = DEVMEMORY;
	size_t	length = len;
	uint_t	map_type = 0;
	uint_t	ctxmap = 0;
	struct devmap_callback_ctl *callbackops = &cg6map_ops;

	DEBUGF(2, (CE_CONT, "cg6_devmap(%d), off=0x%x, len=%x, dhp=%x\n",
		getminor(dev), (uint_t)off, len, dhp));

	if ((diff = off - CG6_VADDR_COLOR) >= 0 && diff < softc->fbmappable) {
		if ((len + off) > (CG6_VADDR_COLOR + softc->fbmappable))
			length = CG6_VADDR_COLOR + softc->fbmappable - off;
		offset = CG6_ADDR_COLOR + diff;
		map_type = CG6MAP_FB;
	} else if ((diff = off - CG6_VADDR_FBC) >= 0 && diff < CG6_FBCTEC_SZ) {
		if ((len + off) > (CG6_VADDR_FBC + CG6_FBCTEC_SZ))
			length = (CG6_VADDR_FBC + CG6_FBCTEC_SZ) - off;
		offset = CG6_ADDR_FBC + diff;
		map_type = CG6MAP_FBCTEC;
	} else if ((diff = off - CG6_VADDR_CMAP) >= 0 && diff < CG6_CMAP_SZ) {
		if ((len + off) > (CG6_VADDR_CMAP + CG6_CMAP_SZ))
			length = (CG6_VADDR_CMAP + CG6_CMAP_SZ) - off;
		offset = CG6_ADDR_CMAP + diff;
	} else if ((diff = off - CG6_VADDR_FHC) >= 0 && diff < CG6_FHCTHC_SZ) {
		if ((len + off) > (CG6_VADDR_FHC + CG6_FHCTHC_SZ))
			length = (CG6_VADDR_FHC + CG6_FHCTHC_SZ) - off;
		offset = CG6_ADDR_FHC + diff;
	} else if ((diff = off - CG6_VADDR_ROM) >= 0 && diff < CG6_ROM_SZ) {
		if ((len + off) > (CG6_VADDR_ROM + CG6_ROM_SZ))
			length = (CG6_VADDR_ROM + CG6_ROM_SZ) - off;
		offset = softc->addr_rom + diff;
	} else if ((diff = off - CG6_VADDR_DHC) >= 0 && diff < CG6_DHC_SZ) {
		if ((len + off) > (CG6_VADDR_DHC + CG6_DHC_SZ))
			length = (CG6_VADDR_DHC + CG6_DHC_SZ) - off;
		offset = CG6_ADDR_DHC + diff;
	} else if ((diff = off - CG6_VADDR_ALT) >= 0 && diff < CG6_ALT_SZ) {
		if ((len + off) > (CG6_VADDR_ALT + CG6_ALT_SZ))
			length = (CG6_VADDR_ALT + CG6_ALT_SZ) - off;
		offset = CG6_ADDR_ALT + diff;
	} else if ((diff = off - CG6_VADDR_VRT) >= 0 && diff < CG6_VRT_SZ) {
		if ((len + off) > (CG6_VADDR_VRT + CG6_VRT_SZ))
			length = (CG6_VADDR_VRT + CG6_VRT_SZ) - off;
		type = KERNELMEMORY;
		if (softc->vrtpage != NULL)
			offset = diff;
		else
			kvaddr = (caddr_t)-1;
		cookie = softc->vrtcookie;
	} else if ((diff = off - CG3_MMAP_OFFSET) >= 0 &&
		diff < softc->fbmappable) {
		if ((len + off) > (CG3_MMAP_OFFSET + softc->fbmappable))
			length = CG3_MMAP_OFFSET + softc->fbmappable - off;
		offset = CG6_ADDR_COLOR + diff;
	} else if (off < CG6_VBASE) {
		if (softc->emulation == FBTYPE_SUN3COLOR) {
			if (off >= 0 && off < softc->fbmappable) {
				if ((len + off) > softc->fbmappable)
					length = softc->fbmappable - off;
				offset = CG6_ADDR_COLOR + diff;
			} else
				kvaddr = (caddr_t)-1;
		} else {	/* softc->emulation == FBTYPE_SUN4COLOR */
			if (off >= 0 && off < softc->dummysize) {
				if ((len + off) > softc->dummysize)
					length = softc->dummysize - off;
				offset = CG6_ADDR_COLOR + diff;
			} else if ((diff = off - softc->dummysize) <
				softc->fbmappable) {
				if ((len + off) >
					(softc->dummysize + softc->fbmappable))
					length = softc->fbmappable - off;
				offset = CG6_ADDR_COLOR + diff;
			}
		}
	} else
		kvaddr = (caddr_t)-1;

	if (kvaddr == (caddr_t)-1) {
		DEBUGF(1, (CE_CONT, "cg6_devmap: no mapping off=0x%x, len=%x\n",
			(uint_t)off, len));
		return (-1);
	}

	DEBUGF(2, (CE_CONT, "cg6_devmap: offset=0x%x, kvaddr=%x, length=%x\n",
	    (uint_t)offset, kvaddr, length));

	/*
	 * 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 segment driver getting involved
	 * to make sure the registers are idle. This involves taking a page
	 * fault, invalidating all other process's mappings to the fb, (and
	 * performing a context switch?)
	 *
	 * Under pixrects, which maps the chips and the FB all at once, the
	 * entire mapping becomes a context. This won't hurt pixrects but
	 * entails unnecessary context switching.  Under other 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.
	 */
	ctxmap = (softc->chiprev == 5) ?
		(CG6MAP_FBCTEC|CG6MAP_FB) : CG6MAP_FBCTEC;

	/*
	 * do context switching on the TEC and FBC registers.
	 */
	if (map_type & ctxmap)
		callbackops = &cg6map_ops;
	else
		callbackops = NULL;

	if (type == DEVMEMORY) {
		if ((err = devmap_devmem_setup(dhp, dip, callbackops, rnumber,
		    offset, length, PROT_ALL, DEVMAP_DEFAULTS,
		    &endian_attr)) < 0)
			return (err);
	} else {
		if ((err = devmap_umem_setup(dhp, dip, callbackops, cookie,
		    offset, length, PROT_ALL, DEVMAP_DEFAULTS,
		    &endian_attr)) < 0)
			return (err);
	}

	*maplen = roundup(length, PAGESIZE);
	return (0);
}
