/* $Id: creatorfb.c 1824 2005-05-10 07:30:07Z hg $
 * creatorfb.c: Creator/Creator3D frame buffer driver
 *
 * Copyright (C) 1997,1998,1999 Jakub Jelinek (jj@ultra.linux.cz)
 */

#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/selection.h>

#include <video/sbusfb.h>

#include <asm/upa.h>

#define	FFB_SFB8R_VOFF		0x00000000
#define	FFB_SFB8G_VOFF		0x00400000
#define	FFB_SFB8B_VOFF		0x00800000
#define	FFB_SFB8X_VOFF		0x00c00000
#define	FFB_SFB32_VOFF		0x01000000
#define	FFB_SFB64_VOFF		0x02000000
#define	FFB_FBC_REGS_VOFF	0x04000000
#define	FFB_BM_FBC_REGS_VOFF	0x04002000
#define	FFB_DFB8R_VOFF		0x04004000
#define	FFB_DFB8G_VOFF		0x04404000
#define	FFB_DFB8B_VOFF		0x04804000
#define	FFB_DFB8X_VOFF		0x04c04000
#define	FFB_DFB24_VOFF		0x05004000
#define	FFB_DFB32_VOFF		0x06004000
#define	FFB_DFB422A_VOFF	0x07004000	/* DFB 422 mode write to A */
#define	FFB_DFB422AD_VOFF	0x07804000	/* DFB 422 mode with line doubling */
#define	FFB_DFB24B_VOFF		0x08004000	/* DFB 24bit mode write to B */
#define	FFB_DFB422B_VOFF	0x09004000	/* DFB 422 mode write to B */
#define	FFB_DFB422BD_VOFF	0x09804000	/* DFB 422 mode with line doubling */
#define	FFB_SFB16Z_VOFF		0x0a004000	/* 16bit mode Z planes */
#define	FFB_SFB8Z_VOFF		0x0a404000	/* 8bit mode Z planes */
#define	FFB_SFB422_VOFF		0x0ac04000	/* SFB 422 mode write to A/B */
#define	FFB_SFB422D_VOFF	0x0b404000	/* SFB 422 mode with line doubling */
#define	FFB_FBC_KREGS_VOFF	0x0bc04000
#define	FFB_DAC_VOFF		0x0bc06000
#define	FFB_PROM_VOFF		0x0bc08000
#define	FFB_EXP_VOFF		0x0bc18000

#define	FFB_SFB8R_POFF		0x04000000UL
#define	FFB_SFB8G_POFF		0x04400000UL
#define	FFB_SFB8B_POFF		0x04800000UL
#define	FFB_SFB8X_POFF		0x04c00000UL
#define	FFB_SFB32_POFF		0x05000000UL
#define	FFB_SFB64_POFF		0x06000000UL
#define	FFB_FBC_REGS_POFF	0x00600000UL
#define	FFB_BM_FBC_REGS_POFF	0x00600000UL
#define	FFB_DFB8R_POFF		0x01000000UL
#define	FFB_DFB8G_POFF		0x01400000UL
#define	FFB_DFB8B_POFF		0x01800000UL
#define	FFB_DFB8X_POFF		0x01c00000UL
#define	FFB_DFB24_POFF		0x02000000UL
#define	FFB_DFB32_POFF		0x03000000UL
#define	FFB_FBC_KREGS_POFF	0x00610000UL
#define	FFB_DAC_POFF		0x00400000UL
#define	FFB_PROM_POFF		0x00000000UL
#define	FFB_EXP_POFF		0x00200000UL
#define FFB_DFB422A_POFF	0x09000000UL
#define FFB_DFB422AD_POFF	0x09800000UL
#define FFB_DFB24B_POFF		0x0a000000UL
#define FFB_DFB422B_POFF	0x0b000000UL
#define FFB_DFB422BD_POFF	0x0b800000UL
#define FFB_SFB16Z_POFF		0x0c800000UL
#define FFB_SFB8Z_POFF		0x0c000000UL
#define FFB_SFB422_POFF		0x0d000000UL
#define FFB_SFB422D_POFF	0x0d800000UL

/* Draw operations */
#define FFB_DRAWOP_DOT		0x00
#define FFB_DRAWOP_AADOT	0x01
#define FFB_DRAWOP_BRLINECAP	0x02
#define FFB_DRAWOP_BRLINEOPEN	0x03
#define FFB_DRAWOP_DDLINE	0x04
#define FFB_DRAWOP_AALINE	0x05
#define FFB_DRAWOP_TRIANGLE	0x06
#define FFB_DRAWOP_POLYGON	0x07
#define FFB_DRAWOP_RECTANGLE	0x08
#define FFB_DRAWOP_FASTFILL	0x09
#define FFB_DRAWOP_BCOPY	0x0a
#define FFB_DRAWOP_VSCROLL	0x0b

/* Pixel processor control */
/* Force WID */
#define FFB_PPC_FW_DISABLE	0x800000
#define FFB_PPC_FW_ENABLE	0xc00000
/* Auxiliary clip */
#define FFB_PPC_ACE_DISABLE	0x040000
#define FFB_PPC_ACE_AUX_SUB	0x080000
#define FFB_PPC_ACE_AUX_ADD	0x0c0000
/* Depth cue */
#define FFB_PPC_DCE_DISABLE	0x020000
#define FFB_PPC_DCE_ENABLE	0x030000
/* Alpha blend */
#define FFB_PPC_ABE_DISABLE	0x008000
#define FFB_PPC_ABE_ENABLE	0x00c000
/* View clip */
#define FFB_PPC_VCE_DISABLE	0x001000
#define FFB_PPC_VCE_2D		0x002000
#define FFB_PPC_VCE_3D		0x003000
/* Area pattern */
#define FFB_PPC_APE_DISABLE	0x000800
#define FFB_PPC_APE_ENABLE	0x000c00
/* Transparent background */
#define FFB_PPC_TBE_OPAQUE	0x000200
#define FFB_PPC_TBE_TRANSPARENT	0x000300
/* Z source */
#define FFB_PPC_ZS_VAR		0x000080
#define FFB_PPC_ZS_CONST	0x0000c0
/* Y source */
#define FFB_PPC_YS_VAR		0x000020
#define FFB_PPC_YS_CONST	0x000030
/* X source */
#define FFB_PPC_XS_WID		0x000004
#define FFB_PPC_XS_VAR		0x000008
#define FFB_PPC_XS_CONST	0x00000c
/* Color (BGR) source */
#define FFB_PPC_CS_VAR		0x000002
#define FFB_PPC_CS_CONST	0x000003

#define FFB_ROP_NEW                  0x83

#define FFB_UCSR_FIFO_MASK     0x00000fff
#define FFB_UCSR_FB_BUSY       0x01000000
#define FFB_UCSR_RP_BUSY       0x02000000
#define FFB_UCSR_ALL_BUSY      (FFB_UCSR_RP_BUSY|FFB_UCSR_FB_BUSY)
#define FFB_UCSR_READ_ERR      0x40000000
#define FFB_UCSR_FIFO_OVFL     0x80000000
#define FFB_UCSR_ALL_ERRORS    (FFB_UCSR_READ_ERR|FFB_UCSR_FIFO_OVFL)

struct ffb_fbc {
	/* Next vertex registers */
	u32		xxx1[3];
	volatile u32	alpha;
	volatile u32	red;
	volatile u32	green;
	volatile u32	blue;
	volatile u32	depth;
	volatile u32	y;
	volatile u32	x;
	u32		xxx2[2];
	volatile u32	ryf;
	volatile u32	rxf;
	u32		xxx3[2];
	
	volatile u32	dmyf;
	volatile u32	dmxf;
	u32		xxx4[2];
	volatile u32	ebyi;
	volatile u32	ebxi;
	u32		xxx5[2];
	volatile u32	by;
	volatile u32	bx;
	u32		dy;
	u32		dx;
	volatile u32	bh;
	volatile u32	bw;
	u32		xxx6[2];
	
	u32		xxx7[32];
	
	/* Setup unit vertex state register */
	volatile u32	suvtx;
	u32		xxx8[63];
	
	/* Control registers */
	volatile u32	ppc;
	volatile u32	wid;
	volatile u32	fg;
	volatile u32	bg;
	volatile u32	consty;
	volatile u32	constz;
	volatile u32	xclip;
	volatile u32	dcss;
	volatile u32	vclipmin;
	volatile u32	vclipmax;
	volatile u32	vclipzmin;
	volatile u32	vclipzmax;
	volatile u32	dcsf;
	volatile u32	dcsb;
	volatile u32	dczf;
	volatile u32	dczb;
	
	u32		xxx9;
	volatile u32	blendc;
	volatile u32	blendc1;
	volatile u32	blendc2;
	volatile u32	fbramitc;
	volatile u32	fbc;
	volatile u32	rop;
	volatile u32	cmp;
	volatile u32	matchab;
	volatile u32	matchc;
	volatile u32	magnab;
	volatile u32	magnc;
	volatile u32	fbcfg0;
	volatile u32	fbcfg1;
	volatile u32	fbcfg2;
	volatile u32	fbcfg3;
	
	u32		ppcfg;
	volatile u32	pick;
	volatile u32	fillmode;
	volatile u32	fbramwac;
	volatile u32	pmask;
	volatile u32	xpmask;
	volatile u32	ypmask;
	volatile u32	zpmask;
	volatile u32	clip0min;
	volatile u32	clip0max;
	volatile u32	clip1min;
	volatile u32	clip1max;
	volatile u32	clip2min;
	volatile u32	clip2max;
	volatile u32	clip3min;
	volatile u32	clip3max;
	
	/* New 3dRAM III support regs */
	volatile u32	rawblend2;
	volatile u32	rawpreblend;
	volatile u32	rawstencil;
	volatile u32	rawstencilctl;
	volatile u32	threedram1;
	volatile u32	threedram2;
	volatile u32	passin;
	volatile u32	rawclrdepth;
	volatile u32	rawpmask;
	volatile u32	rawcsrc;
	volatile u32	rawmatch;
	volatile u32	rawmagn;
	volatile u32	rawropblend;
	volatile u32	rawcmp;
	volatile u32	rawwac;
	volatile u32	fbramid;
	
	volatile u32	drawop;
	u32		xxx10[2];
	volatile u32	fontlpat;
	u32		xxx11;
	volatile u32	fontxy;
	volatile u32	fontw;
	volatile u32	fontinc;
	volatile u32	font;
	u32		xxx12[3];
	volatile u32	blend2;
	volatile u32	preblend;
	volatile u32	stencil;
	volatile u32	stencilctl;

	u32		xxx13[4];	
	volatile u32	dcss1;
	volatile u32	dcss2;
	volatile u32	dcss3;
	volatile u32	widpmask;
	volatile u32	dcs2;
	volatile u32	dcs3;
	volatile u32	dcs4;
	u32		xxx14;
	volatile u32	dcd2;
	volatile u32	dcd3;
	volatile u32	dcd4;
	u32		xxx15;
	
	volatile u32	pattern[32];
	
	u32		xxx16[256];
	
	volatile u32	devid;
	u32		xxx17[63];
	
	volatile u32	ucsr;
	u32		xxx18[31];
	
	volatile u32	mer;
};

static __inline__ void FFBFifo(struct fb_info_sbusfb *fb, int n)
{
	struct ffb_fbc *fbc;
	int cache = fb->s.ffb.fifo_cache;

	if (cache - n < 0) {
		fbc = fb->s.ffb.fbc;
		do {	cache = (upa_readl(&fbc->ucsr) & FFB_UCSR_FIFO_MASK) - 8;
		} while (cache - n < 0);
	}
	fb->s.ffb.fifo_cache = cache - n;
}

static __inline__ void FFBWait(struct ffb_fbc *ffb)
{
	int limit = 10000;

	do {
		if ((upa_readl(&ffb->ucsr) & FFB_UCSR_ALL_BUSY) == 0)
			break;
		if ((upa_readl(&ffb->ucsr) & FFB_UCSR_ALL_ERRORS) != 0) {
			upa_writel(FFB_UCSR_ALL_ERRORS, &ffb->ucsr);
		}
	} while(--limit > 0);
}

struct ffb_dac {
	volatile u32	type;
	volatile u32	value;
	volatile u32	type2;
	volatile u32	value2;
};

static struct sbus_mmap_map ffb_mmap_map[] = {
	{ FFB_SFB8R_VOFF,	FFB_SFB8R_POFF,		0x0400000 },
	{ FFB_SFB8G_VOFF,	FFB_SFB8G_POFF,		0x0400000 },
	{ FFB_SFB8B_VOFF,	FFB_SFB8B_POFF,		0x0400000 },
	{ FFB_SFB8X_VOFF,	FFB_SFB8X_POFF,		0x0400000 },
	{ FFB_SFB32_VOFF,	FFB_SFB32_POFF,		0x1000000 },
	{ FFB_SFB64_VOFF,	FFB_SFB64_POFF,		0x2000000 },
	{ FFB_FBC_REGS_VOFF,	FFB_FBC_REGS_POFF,	0x0002000 },
	{ FFB_BM_FBC_REGS_VOFF,	FFB_BM_FBC_REGS_POFF,	0x0002000 },
	{ FFB_DFB8R_VOFF,	FFB_DFB8R_POFF,		0x0400000 },
	{ FFB_DFB8G_VOFF,	FFB_DFB8G_POFF,		0x0400000 },
	{ FFB_DFB8B_VOFF,	FFB_DFB8B_POFF,		0x0400000 },
	{ FFB_DFB8X_VOFF,	FFB_DFB8X_POFF,		0x0400000 },
	{ FFB_DFB24_VOFF,	FFB_DFB24_POFF,		0x1000000 },
	{ FFB_DFB32_VOFF,	FFB_DFB32_POFF,		0x1000000 },
	{ FFB_FBC_KREGS_VOFF,	FFB_FBC_KREGS_POFF,	0x0002000 },
	{ FFB_DAC_VOFF,		FFB_DAC_POFF,		0x0002000 },
	{ FFB_PROM_VOFF,	FFB_PROM_POFF,		0x0010000 },
	{ FFB_EXP_VOFF,		FFB_EXP_POFF,		0x0002000 },
	{ FFB_DFB422A_VOFF,	FFB_DFB422A_POFF,	0x0800000 },
	{ FFB_DFB422AD_VOFF,	FFB_DFB422AD_POFF,	0x0800000 },
	{ FFB_DFB24B_VOFF,	FFB_DFB24B_POFF,	0x1000000 },
	{ FFB_DFB422B_VOFF,	FFB_DFB422B_POFF,	0x0800000 },
	{ FFB_DFB422BD_VOFF,	FFB_DFB422BD_POFF,	0x0800000 },
	{ FFB_SFB16Z_VOFF,	FFB_SFB16Z_POFF,	0x0800000 },
	{ FFB_SFB8Z_VOFF,	FFB_SFB8Z_POFF,		0x0800000 },
	{ FFB_SFB422_VOFF,	FFB_SFB422_POFF,	0x0800000 },
	{ FFB_SFB422D_VOFF,	FFB_SFB422D_POFF,	0x0800000 },
	{ 0,			0,			0	  }
};

static void ffb_setup(struct display *p)
{
	p->next_line = 8192;
	p->next_plane = 0;
}

static void ffb_clear(struct vc_data *conp, struct display *p, int sy, int sx,
		      int height, int width)
{
	struct fb_info_sbusfb *fb = (struct fb_info_sbusfb *)p->fb_info;
	register struct ffb_fbc *fbc = fb->s.ffb.fbc;
	unsigned long flags;
	u64 yx, hw;
	int fg;
	
	spin_lock_irqsave(&fb->lock, flags);
	fg = ((u32 *)p->dispsw_data)[attr_bgcol_ec(p,conp)];
	if (fg != fb->s.ffb.fg_cache) {
		FFBFifo(fb, 5);
		upa_writel(fg, &fbc->fg);
		fb->s.ffb.fg_cache = fg;
	} else
		FFBFifo(fb, 4);

	if (fontheightlog(p)) {
		yx = (u64)sy << (fontheightlog(p) + 32); hw = (u64)height << (fontheightlog(p) + 32);
	} else {
		yx = (u64)(sy * fontheight(p)) << 32; hw = (u64)(height * fontheight(p)) << 32;
	}
	if (fontwidthlog(p)) {
		yx += sx << fontwidthlog(p); hw += width << fontwidthlog(p);
	} else {
		yx += sx * fontwidth(p); hw += width * fontwidth(p);
	}
	upa_writeq(yx + fb->s.ffb.yx_margin, &fbc->by);
	upa_writeq(hw, &fbc->bh);
	spin_unlock_irqrestore(&fb->lock, flags);
}

static void ffb_fill(struct fb_info_sbusfb *fb, struct display *p, int s,
		     int count, unsigned short *boxes)
{
	register struct ffb_fbc *fbc = fb->s.ffb.fbc;
	unsigned long flags;
	int fg;

	spin_lock_irqsave(&fb->lock, flags);
	fg = ((u32 *)p->dispsw_data)[attr_bgcol(p,s)];
	if (fg != fb->s.ffb.fg_cache) {
		FFBFifo(fb, 1);
		upa_writel(fg, &fbc->fg);
		fb->s.ffb.fg_cache = fg;
	}
	while (count-- > 0) {
		FFBFifo(fb, 4);
		upa_writel(boxes[1], &fbc->by);
		upa_writel(boxes[0], &fbc->bx);
		upa_writel(boxes[3] - boxes[1], &fbc->bh);
		upa_writel(boxes[2] - boxes[0], &fbc->bw);
		boxes += 4;
	}
	spin_unlock_irqrestore(&fb->lock, flags);
}

static void ffb_putc(struct vc_data *conp, struct display *p, int c, int yy, int xx)
{
	struct fb_info_sbusfb *fb = (struct fb_info_sbusfb *)p->fb_info;
	register struct ffb_fbc *fbc = fb->s.ffb.fbc;
	unsigned long flags;
	int i, xy;
	u8 *fd;
	u64 fgbg;

	spin_lock_irqsave(&fb->lock, flags);
	if (fontheightlog(p)) {
		xy = (yy << (16 + fontheightlog(p)));
		i = ((c & p->charmask) << fontheightlog(p));
	} else {
		xy = ((yy * fontheight(p)) << 16);
		i = (c & p->charmask) * fontheight(p);
	}
	if (fontwidth(p) <= 8)
		fd = p->fontdata + i;
	else
		fd = p->fontdata + (i << 1);
	if (fontwidthlog(p))
		xy += (xx << fontwidthlog(p)) + fb->s.ffb.xy_margin;
	else
		xy += (xx * fontwidth(p)) + fb->s.ffb.xy_margin;
	fgbg = (((u64)(((u32 *)p->dispsw_data)[attr_fgcol(p,c)])) << 32) |
	       ((u32 *)p->dispsw_data)[attr_bgcol(p,c)];
	if (fgbg != *(u64 *)&fb->s.ffb.fg_cache) {
		FFBFifo(fb, 2);
		upa_writeq(fgbg, &fbc->fg);
		*(u64 *)&fb->s.ffb.fg_cache = fgbg;
	}
	FFBFifo(fb, 2 + fontheight(p));
	upa_writel(xy, &fbc->fontxy);
	upa_writel(fontwidth(p), &fbc->fontw);
	if (fontwidth(p) <= 8) {
		for (i = 0; i < fontheight(p); i++) {
			u32 val = *fd++ << 24;

			upa_writel(val, &fbc->font);
		}
	} else {
		for (i = 0; i < fontheight(p); i++) {
			u32 val = *(u16 *)fd << 16;

			upa_writel(val, &fbc->font);
			fd += 2;
		}
	}
	spin_unlock_irqrestore(&fb->lock, flags);
}

static void ffb_putcs(struct vc_data *conp, struct display *p, const unsigned short *s,
		      int count, int yy, int xx)
{
	struct fb_info_sbusfb *fb = (struct fb_info_sbusfb *)p->fb_info;
	register struct ffb_fbc *fbc = fb->s.ffb.fbc;
	unsigned long flags;
	int i, xy;
	u8 *fd1, *fd2, *fd3, *fd4;
	u16 c;
	u64 fgbg;

	spin_lock_irqsave(&fb->lock, flags);
	c = scr_readw(s);
	fgbg = (((u64)(((u32 *)p->dispsw_data)[attr_fgcol(p, c)])) << 32) |
	       ((u32 *)p->dispsw_data)[attr_bgcol(p, c)];
	if (fgbg != *(u64 *)&fb->s.ffb.fg_cache) {
		FFBFifo(fb, 2);
		upa_writeq(fgbg, &fbc->fg);
		*(u64 *)&fb->s.ffb.fg_cache = fgbg;
	}
	xy = fb->s.ffb.xy_margin;
	if (fontwidthlog(p))
		xy += (xx << fontwidthlog(p));
	else
		xy += xx * fontwidth(p);
	if (fontheightlog(p))
		xy += (yy << (16 + fontheightlog(p)));
	else
		xy += ((yy * fontheight(p)) << 16);
	if (fontwidth(p) <= 8) {
		while (count >= 4) {
			count -= 4;
			FFBFifo(fb, 2 + fontheight(p));
			upa_writel(4 * fontwidth(p), &fbc->fontw);
			upa_writel(xy, &fbc->fontxy);
			if (fontheightlog(p)) {
				fd1 = p->fontdata + ((scr_readw(s++) & p->charmask) << fontheightlog(p));
				fd2 = p->fontdata + ((scr_readw(s++) & p->charmask) << fontheightlog(p));
				fd3 = p->fontdata + ((scr_readw(s++) & p->charmask) << fontheightlog(p));
				fd4 = p->fontdata + ((scr_readw(s++) & p->charmask) << fontheightlog(p));
			} else {
				fd1 = p->fontdata + ((scr_readw(s++) & p->charmask) * fontheight(p));
				fd2 = p->fontdata + ((scr_readw(s++) & p->charmask) * fontheight(p));
				fd3 = p->fontdata + ((scr_readw(s++) & p->charmask) * fontheight(p));
				fd4 = p->fontdata + ((scr_readw(s++) & p->charmask) * fontheight(p));
			}
			if (fontwidth(p) == 8) {
				for (i = 0; i < fontheight(p); i++) {
					u32 val;

					val = ((u32)*fd4++) | ((((u32)*fd3++) | ((((u32)*fd2++) | (((u32)*fd1++) 
						<< 8)) << 8)) << 8);
					upa_writel(val, &fbc->font);
				}
				xy += 32;
			} else {
				for (i = 0; i < fontheight(p); i++) {
					u32 val = (((u32)*fd4++) | ((((u32)*fd3++) | ((((u32)*fd2++) | (((u32)*fd1++) 
						<< fontwidth(p))) << fontwidth(p))) << fontwidth(p))) << (24 - 3 * fontwidth(p));
					upa_writel(val, &fbc->font);
				}
				xy += 4 * fontwidth(p);
			}
		}
	} else {
		while (count >= 2) {
			count -= 2;
			FFBFifo(fb, 2 + fontheight(p));
			upa_writel(2 * fontwidth(p), &fbc->fontw);
			upa_writel(xy, &fbc->fontxy);
			if (fontheightlog(p)) {
				fd1 = p->fontdata + ((scr_readw(s++) & p->charmask) << (fontheightlog(p) + 1));
				fd2 = p->fontdata + ((scr_readw(s++) & p->charmask) << (fontheightlog(p) + 1));
			} else {
				fd1 = p->fontdata + (((scr_readw(s++) & p->charmask) * fontheight(p)) << 1);
				fd2 = p->fontdata + (((scr_readw(s++) & p->charmask) * fontheight(p)) << 1);
			}
			for (i = 0; i < fontheight(p); i++) {
				u32 val = ((((u32)*(u16 *)fd1) << fontwidth(p)) | ((u32)*(u16 *)fd2)) << (16 - fontwidth(p));

				upa_writel(val, &fbc->font);
				fd1 += 2; fd2 += 2;
			}
			xy += 2 * fontwidth(p);
		}
	}
	while (count) {
		count--;
		FFBFifo(fb, 2 + fontheight(p));
		upa_writel(fontwidth(p), &fbc->fontw);
		upa_writel(xy, &fbc->fontxy);
		if (fontheightlog(p))
			i = ((scr_readw(s++) & p->charmask) << fontheightlog(p));
		else
			i = ((scr_readw(s++) & p->charmask) * fontheight(p));
		if (fontwidth(p) <= 8) {
			fd1 = p->fontdata + i;
			for (i = 0; i < fontheight(p); i++) {
				u32 val = *fd1++ << 24;

				upa_writel(val, &fbc->font);
			}
		} else {
			fd1 = p->fontdata + (i << 1);
			for (i = 0; i < fontheight(p); i++) {
				u32 val = *(u16 *)fd1 << 16;

				upa_writel(val, &fbc->font);
				fd1 += 2;
			}
		}
		xy += fontwidth(p);
	}
	spin_unlock_irqrestore(&fb->lock, flags);
}

static void ffb_revc(struct display *p, int xx, int yy)
{
	/* Not used if hw cursor */
}

#if 0
static void ffb_blank(struct fb_info_sbusfb *fb)
{
	struct ffb_dac *dac = fb->s.ffb.dac;
	unsigned long flags;
	u32 tmp;

	spin_lock_irqsave(&fb->lock, flags);
	upa_writel(0x6000, &dac->type);
	tmp = (upa_readl(&dac->value) & ~0x1);
	upa_writel(0x6000, &dac->type);
	upa_writel(tmp, &dac->value);
	spin_unlock_irqrestore(&fb->lock, flags);
}
#endif

static void ffb_unblank(struct fb_info_sbusfb *fb)
{
	struct ffb_dac *dac = fb->s.ffb.dac;
	unsigned long flags;
	u32 tmp;

	spin_lock_irqsave(&fb->lock, flags);
	upa_writel(0x6000, &dac->type);
	tmp = (upa_readl(&dac->value) | 0x1);
	upa_writel(0x6000, &dac->type);
	upa_writel(tmp, &dac->value);
	spin_unlock_irqrestore(&fb->lock, flags);
}

static void ffb_loadcmap (struct fb_info_sbusfb *fb, struct display *p, int index, int count)
{
	struct ffb_dac *dac = fb->s.ffb.dac;
	unsigned long flags;
	int i, j = count;
	
	spin_lock_irqsave(&fb->lock, flags);
	upa_writel(0x2000 | index, &dac->type);
	for (i = index; j--; i++) {
		u32 val;

		/* Feed the colors in :)) */
		val = ((fb->color_map CM(i,0))) |
			((fb->color_map CM(i,1)) << 8) |
			((fb->color_map CM(i,2)) << 16);
		upa_writel(val, &dac->value);
	}
	if (!p)
		goto out;
	for (i = index, j = count; i < 16 && j--; i++)
		((u32 *)p->dispsw_data)[i] = ((fb->color_map CM(i,0))) |
			      		     ((fb->color_map CM(i,1)) << 8) |
					     ((fb->color_map CM(i,2)) << 16);
out:
	spin_unlock_irqrestore(&fb->lock, flags);
}

static struct display_switch ffb_dispsw __initdata = {
	setup:		ffb_setup,
	bmove:		fbcon_redraw_bmove,
	clear:		ffb_clear,
	putc:		ffb_putc,
	putcs:		ffb_putcs,
	revc:		ffb_revc, 
	fontwidthmask:	FONTWIDTHRANGE(1,16) /* Allow fontwidths up to 16 */
};

static void ffb_margins (struct fb_info_sbusfb *fb, struct display *p, int x_margin, int y_margin)
{
	register struct ffb_fbc *fbc = fb->s.ffb.fbc;
	unsigned long flags;

	spin_lock_irqsave(&fb->lock, flags);
	fb->s.ffb.xy_margin = (y_margin << 16) + x_margin;
	fb->s.ffb.yx_margin = (((u64)y_margin) << 32) + x_margin;
	p->screen_base += 8192 * (y_margin - fb->y_margin) + 4 * (x_margin - fb->x_margin);
	FFBWait(fbc);
	spin_unlock_irqrestore(&fb->lock, flags);
}

static __inline__ void __ffb_curs_enable (struct fb_info_sbusfb *fb, int enable)
{
	struct ffb_dac *dac = fb->s.ffb.dac;
	u32 val;

	upa_writel(0x100, &dac->type2);
	if (fb->s.ffb.dac_rev <= 2) {
		val = enable ? 3 : 0;
	} else {
		val = enable ? 0 : 3;
	}
	upa_writel(val, &dac->value2);
}

static void ffb_setcursormap (struct fb_info_sbusfb *fb, u8 *red, u8 *green, u8 *blue)
{
	struct ffb_dac *dac = fb->s.ffb.dac;
	unsigned long flags;
	
	spin_lock_irqsave(&fb->lock, flags);
	__ffb_curs_enable (fb, 0);
	upa_writel(0x102, &dac->type2);
	upa_writel((red[0] | (green[0]<<8) | (blue[0]<<16)), &dac->value2);
	upa_writel((red[1] | (green[1]<<8) | (blue[1]<<16)), &dac->value2);
	spin_unlock_irqrestore(&fb->lock, flags);
}

/* Set cursor shape */
static void ffb_setcurshape (struct fb_info_sbusfb *fb)
{
	struct ffb_dac *dac = fb->s.ffb.dac;
	unsigned long flags;
	int i, j;

	spin_lock_irqsave(&fb->lock, flags);
	__ffb_curs_enable (fb, 0);
	for (j = 0; j < 2; j++) {
		u32 val = j ? 0 : 0x80;

		upa_writel(val, &dac->type2);
		for (i = 0; i < 0x40; i++) {
			if (fb->cursor.size.fbx <= 32) {
				upa_writel(fb->cursor.bits [j][i], &dac->value2);
				upa_writel(0, &dac->value2);
			} else {
				upa_writel(fb->cursor.bits [j][2*i], &dac->value2);
				upa_writel(fb->cursor.bits [j][2*i+1], &dac->value2);
			}
		}
	}	
	spin_unlock_irqrestore(&fb->lock, flags);
}

/* Load cursor information */
static void ffb_setcursor (struct fb_info_sbusfb *fb)
{
	struct ffb_dac *dac = fb->s.ffb.dac;
	struct cg_cursor *c = &fb->cursor;
	unsigned long flags;
	u32 val;

	spin_lock_irqsave(&fb->lock, flags);
	upa_writel(0x104, &dac->type2);
	/* Should this be just 0x7ff?? 
	   Should I do some margin handling and setcurshape in that case? */
	val = (((c->cpos.fby - c->chot.fby) & 0xffff) << 16)
		|((c->cpos.fbx - c->chot.fbx) & 0xffff);
	upa_writel(val, &dac->value2);
	__ffb_curs_enable (fb, fb->cursor.enable);
	spin_unlock_irqrestore(&fb->lock, flags);
}

static void ffb_switch_from_graph (struct fb_info_sbusfb *fb)
{
	register struct ffb_fbc *fbc = fb->s.ffb.fbc;
	unsigned long flags;

	spin_lock_irqsave(&fb->lock, flags);
	FFBWait(fbc);
	fb->s.ffb.fifo_cache = 0;
	FFBFifo(fb, 8);
	upa_writel(FFB_PPC_VCE_DISABLE|FFB_PPC_TBE_OPAQUE|
		   FFB_PPC_APE_DISABLE|FFB_PPC_CS_CONST,
		   &fbc->ppc);
	upa_writel(0x2000707f, &fbc->fbc);
	upa_writel(FFB_ROP_NEW, &fbc->rop);
	upa_writel(FFB_DRAWOP_RECTANGLE, &fbc->drawop);
	upa_writel(0xffffffff, &fbc->pmask);
	upa_writel(0x10000, &fbc->fontinc);
	upa_writel(fb->s.ffb.fg_cache, &fbc->fg);
	upa_writel(fb->s.ffb.bg_cache, &fbc->bg);
	FFBWait(fbc);
	spin_unlock_irqrestore(&fb->lock, flags);
}

static int __init ffb_rasterimg (struct fb_info *info, int start)
{
	ffb_switch_from_graph (sbusfbinfo(info));
	return 0;
}

static char idstring[60] __initdata = { 0 };

static int __init creator_apply_upa_parent_ranges(int parent, struct linux_prom64_registers *regs)
{
	struct linux_prom64_ranges ranges[PROMREG_MAX];
	char name[128];
	int len, i;

	prom_getproperty(parent, "name", name, sizeof(name));
	if (strcmp(name, "upa") != 0)
		return 0;

	len = prom_getproperty(parent, "ranges", (void *) ranges, sizeof(ranges));
	if (len <= 0)
		return 1;

	len /= sizeof(struct linux_prom64_ranges);
	for (i = 0; i < len; i++) {
		struct linux_prom64_ranges *rng = &ranges[i];
		u64 phys_addr = regs->phys_addr;

		if (phys_addr >= rng->ot_child_base &&
		    phys_addr < (rng->ot_child_base + rng->or_size)) {
			regs->phys_addr -= rng->ot_child_base;
			regs->phys_addr += rng->ot_parent_base;
			return 0;
		}
	}

	return 1;
}

char __init *creatorfb_init(struct fb_info_sbusfb *fb)
{
	struct fb_fix_screeninfo *fix = &fb->fix;
	struct fb_var_screeninfo *var = &fb->var;
	struct display *disp = &fb->disp;
	struct fbtype *type = &fb->type;
	struct linux_prom64_registers regs[2*PROMREG_MAX];
	int i, afb = 0;
	unsigned int btype;
	char name[64];
	struct fb_ops *fbops;

	if (prom_getproperty(fb->prom_node, "reg", (void *) regs, sizeof(regs)) <= 0)
		return NULL;

	if (creator_apply_upa_parent_ranges(fb->prom_parent, &regs[0]))
		return NULL;
		
	disp->dispsw_data = (void *)kmalloc(16 * sizeof(u32), GFP_KERNEL);
	if (disp->dispsw_data == NULL)
		return NULL;
	memset(disp->dispsw_data, 0, 16 * sizeof(u32));

	fbops = kmalloc(sizeof(*fbops), GFP_KERNEL);
	if (fbops == NULL) {
		kfree(disp->dispsw_data);
		return NULL;
	}
	
	*fbops = *fb->info.fbops;
	fbops->fb_rasterimg = ffb_rasterimg;
	fb->info.fbops = fbops;

	prom_getstring(fb->prom_node, "name", name, sizeof(name));
	if (!strcmp(name, "SUNW,afb"))
		afb = 1;
		
	btype = prom_getintdefault(fb->prom_node, "board_type", 0);
		
	strcpy(fb->info.modename, "Creator");
	if (!afb) {
		if ((btype & 7) == 3)
		    strcpy(fix->id, "Creator 3D");
		else
		    strcpy(fix->id, "Creator");
	} else
		strcpy(fix->id, "Elite 3D");
	
	fix->visual = FB_VISUAL_TRUECOLOR;
	fix->line_length = 8192;
	fix->accel = FB_ACCEL_SUN_CREATOR;
	
	var->bits_per_pixel = 32;
	var->green.offset = 8;
	var->blue.offset = 16;
	var->accel_flags = FB_ACCELF_TEXT;
	
	disp->scrollmode = SCROLL_YREDRAW;
	disp->screen_base = (char *)__va(regs[0].phys_addr) + FFB_DFB24_POFF + 8192 * fb->y_margin + 4 * fb->x_margin;
	fb->s.ffb.xy_margin = (fb->y_margin << 16) + fb->x_margin;
	fb->s.ffb.yx_margin = (((u64)fb->y_margin) << 32) + fb->x_margin;
	fb->s.ffb.fbc = (struct ffb_fbc *)(regs[0].phys_addr + FFB_FBC_REGS_POFF);
	fb->s.ffb.dac = (struct ffb_dac *)(regs[0].phys_addr + FFB_DAC_POFF);
	fb->dispsw = ffb_dispsw;

	fb->margins = ffb_margins;
	fb->loadcmap = ffb_loadcmap;
	fb->setcursor = ffb_setcursor;
	fb->setcursormap = ffb_setcursormap;
	fb->setcurshape = ffb_setcurshape;
	fb->switch_from_graph = ffb_switch_from_graph;
	fb->fill = ffb_fill;
#if 0
	/* XXX Can't enable this for now, I've seen cases
	 * XXX where the VC was blanked, and Xsun24 was started
	 * XXX via a remote login, the sunfb code did not
	 * XXX unblank creator when it was mmap'd for some
	 * XXX reason, investigate later... -DaveM
	 */
	fb->blank = ffb_blank;
	fb->unblank = ffb_unblank;
#endif
	
	/* If there are any read errors or fifo overflow conditions,
	 * clear them now.
	 */
	if((upa_readl(&fb->s.ffb.fbc->ucsr) & FFB_UCSR_ALL_ERRORS) != 0)
		upa_writel(FFB_UCSR_ALL_ERRORS, &fb->s.ffb.fbc->ucsr);

	ffb_switch_from_graph(fb);
	
	fb->physbase = regs[0].phys_addr;
	fb->mmap_map = ffb_mmap_map;
	
	fb->cursor.hwsize.fbx = 64;
	fb->cursor.hwsize.fby = 64;
	
	type->fb_depth = 24;
	
	upa_writel(0x8000, &fb->s.ffb.dac->type);
	fb->s.ffb.dac_rev = (upa_readl(&fb->s.ffb.dac->value) >> 0x1c);
	                
	i = prom_getintdefault (fb->prom_node, "board_type", 8);

	sprintf(idstring, "%s at %016lx type %d DAC %d",
		fix->id, regs[0].phys_addr, i, fb->s.ffb.dac_rev);
	
	/* Elite3D has different DAC revision numbering, and no DAC revisions
	   have the reversed meaning of cursor enable */
	if (afb)
		fb->s.ffb.dac_rev = 10;
	
	/* Unblank it just to be sure.  When there are multiple
	 * FFB/AFB cards in the system, or it is not the OBP
	 * chosen console, it will have video outputs off in
	 * the DAC.
	 */
	ffb_unblank(fb);

	return idstring;
}

MODULE_LICENSE("GPL");
