/*
 * ti omap35 display subsystem (dss)
 *
 * can handle 2ⁿ bits per pixel for 0 < n ≤ 4, and 12 and 24 bits.
 * can handle	1024×768 at 60 Hz with pixel clock of 63.5 MHz
 *		1280×800 at 59.91 Hz with pixel clock of 71 MHz
 *		1400×1050 lcd at 50 MHz with pixel clock of 75 MHz
 * has 256 24-bit entries in RGB palette
 */
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "ureg.h"
#include "../port/error.h"

#define	Image	IMAGE
#include <draw.h>
#include <memdraw.h>
#include <cursor.h>
#include "screen.h"
// #include "gamma.h"

enum {
	Tabstop	= 4,		/* should be 8 */
	Scroll	= 8,		/* lines to scroll at one time */
	/*
	 * screen settings for Wid and Ht, should a bit more dynamic?
	 * http://www.epanorama.net/faq/vga2rgb/calc.html
	 * used to calculate settings.
	 */

//	Hbp     = (248-1) << 20,
//	Hfp     = (48-1) << 8,
//	Hsw     = 112-1,

//	Vbp     = 38 << 20,
//	Vfp     = 1 << 8,
//	Vsw     = 3,

	Tft	= 0x60,

	Loadmode = 2 << 1,
	Fifosize = 0x400,

	/* dispc sysconfig */
	Midlemode	= 2 << 12,
	Sidlemode	= 2 << 3,
	EnableWakeup	= 1 << 2,
	Autoidle	= 1 << 0,

	/* dispc pool_freq */
	Ipc		= 1 << 14,
	Ihs		= 1 << 13,
	Ivs		= 1 << 12,
	Acb		= 0x28,

	/* gfx attribs */
	Burstsize	= 2 << 6,
	Format		= 6 << 1,
	Gfxenable	= 1 << 0,

	/* dispc control */
	Gpout1		= 1 << 16,
	Gpout0		= 1 << 15,
	Tftdata		= 3 << 8,
	Digital		= 1 << 6,
	Lcd		= 1 << 5,
	Stntft		= 1 << 3,
	Digitalen	= 1 << 1,
//	Lcden		= 1 << 0,	/* unused */
};

typedef struct Dispcregs Dispc;
typedef struct Dssregs Dss;
typedef struct Ioregs Ioregs;

struct Ioregs {				/* common registers, 68 (0x44) bytes */
	ulong 	rev;
	uchar	_pad0[0x10-0x4];
	ulong	sysconf;
	ulong	sysstat;
	ulong	irqstat1;

	/* Dispc only regs */
	ulong	irqen1;
	ulong	wkupen;
	ulong	_pad1;
	ulong	irqsts2;
	ulong	irqen2;
	ulong	_pad2[4];

	ulong	ctrl;
};

struct Dssregs {			/* display subsys at 0x48050000 */
	Ioregs;
	ulong	sdicrtl;
	ulong	pllcrtl;
	uchar	_pad3[0x5c-0x4c];
	ulong	sdistat;
};

struct Dispcregs {			/* display ctlr at 0x48050400 */
	Ioregs;
	ulong	config;
	ulong	_pad3;
	ulong 	defaultcolor[2];
	ulong	transcolor[2];
	ulong	linestat;
	ulong	linenum;
	ulong	timing_h;
	ulong	timing_v;
	ulong	pol_req;
	ulong	divisor;
	ulong	alpha;
	ulong	digsize;
	ulong	lcdsize;

	ulong	base[2];	/* should allocate both to avoid dithering */
	ulong	pos;
	ulong	size;
	ulong	_pad4[4];
	ulong	attrib;
	ulong	fifothr;
	ulong	fifosize;
	ulong	rowinc;
	ulong	pixelinc;
	ulong	winskip;
	ulong	palette;		/* gfx_table_ba */
	uchar	_pad5[0x5d4 - 0x4bc];

	ulong	datacycle[3];
	uchar	_pad5[0x620 - 0x5e0];

	ulong	cprcoefr;
	ulong	cprcoefg;
	ulong	cprcoefb;
	ulong	preload;
};

int	drawdebug;
Point	ZP = {0, 0};
Cursor	arrow = {
	{ -1, -1 },
	{ 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C,
	  0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04,
	  0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04,
	  0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40,
	},
	{ 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0,
	  0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8,
	  0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8,
	  0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00,
	},
};

OScreen oscreen;
Settings settings[] = {
[Res800x600]   {  800,  600, 60, RGB16,  40000,	 88, 40, 128,	23, 1, 5, },
[Res1024x768]  { 1024,  768, 60, RGB16,  65000,	160, 24, 136,	29, 3, 7, },
[Res1280x1024] { 1280, 1024, 60, RGB16, 108000,	248, 48, 112,	38, 1, 4, },
[Res1400x1050] { 1400, 1050, 50, RGB16, 108000, 248, 48, 112,	38, 1, 4, }, // TODO
};
Omap3fb *framebuf;
Memimage *gscreen;

static Memdata xgdata;

static Memimage xgscreen =
{
	{ 0, 0, Wid, Ht },	/* r */
	{ 0, 0, Wid, Ht },	/* clipr */
	Depth,			/* depth */
	3,			/* nchan */
	RGB16,			/* chan */
	nil,			/* cmap */
	&xgdata,		/* data */
	0,			/* zero */
	Wid*(Depth/BI2BY)/BY2WD, /* width in words of a single scan line */
	0,			/* layer */
	0,			/* flags */
};

static Memimage *conscol;
static Memimage *back;

static Memsubfont *memdefont;

static Lock screenlock;

static Point	curpos;
static int	h, w;
static int	landscape = 0;	/* screen orientation, default is 0: portrait */
static ushort	*vscreen;	/* virtual screen */
static Rectangle window;

static Dispc *dispc = (Dispc *)PHYSDISPC;
static Dss *dss	= (Dss *)PHYSDSS;

static	void	omapscreenputs(char *s, int n);
static	ulong	rep(ulong, int);
static	void	screenputc(char *buf);
static	void	screenwin(void);

/*
 * Software cursor. 
 */
int	swvisible;	/* is the cursor visible? */
int	swenabled;	/* is the cursor supposed to be on the screen? */
Memimage*	swback;	/* screen under cursor */
Memimage*	swimg;	/* cursor image */
Memimage*	swmask;	/* cursor mask */
Memimage*	swimg1;
Memimage*	swmask1;

Point	swoffset;
Rectangle	swrect;	/* screen rectangle in swback */
Point	swpt;	/* desired cursor location */
Point	swvispt;	/* actual cursor location */
int	swvers;	/* incremented each time cursor image changes */
int	swvisvers;	/* the version on the screen */

static void
lcdoff(void)
{
	dispc->ctrl &= ~1;		/* disable the lcd */
	coherence();

	dispc->irqstat1 |= 1;		/* set framedone */
	coherence();

	/* the lcd never comes ready, so don't bother with this */
#ifdef notdef
	/* spin until the frame is complete, but not forever */
	for(cnt = 50; !(dispc->irqstat1 & 1) && cnt-- > 0; )
		delay(10);
#endif
	delay(20);			/* worst case for 1 frame, 50Hz */
}

static void
dssstart(void)
{
	/* should reset the dss system */
	dss->sysconf |= 1;
	coherence();
}

/* see spruf98i §15.6.7.4.2 */
static void
configdispc(void)
{
	Settings *sp;

	sp = oscreen.settings;
	dss->ctrl &= 0x78;		/* choose dss clock */
	dispc->sysconf = Midlemode | Sidlemode | EnableWakeup | Autoidle;
	dispc->config = Loadmode;
	coherence();

	/* pll */
	dispc->defaultcolor[0] = 0;	/* set background color to black? */
	dispc->defaultcolor[1] = 0;
	dispc->transcolor[0] = 0;	/* set transparency to full */
	dispc->transcolor[1] = 0;

	dispc->timing_h = (sp->hbp-1) << 20 | (sp->hfp-1) << 8 |
			(sp->hsw-1);
	dispc->timing_v = sp->vbp << 20 | sp->vfp << 8 |
			(sp->vsw-1);

	dispc->pol_req = Ipc | Ihs | Ivs | Acb;
	dispc->divisor = 1 << 16 | HOWMANY(432000, sp->pixelclock);

	dispc->lcdsize = (sp->ht - 1) << 16 | (sp->wid - 1);
	coherence();

	dispc->base[0] = PADDR(framebuf->pixel);
	dispc->base[1] = PADDR(framebuf->pixel);

	dispc->pos = 0;			/* place screen in the left corner */
	/* use the whole screen */
	dispc->size = (sp->ht - 1) << 16 | (sp->wid - 1);

	/* what mode does plan 9 use for fb? */
	dispc->attrib = Burstsize | Format | Gfxenable;

	dispc->preload = Tft;
	dispc->fifosize = Fifosize;
	/* 1008 is max for our Burstsize */
	dispc->fifothr = (Fifosize - 1) << 16 | (1008 - 1);

	/* 1 byte is one pixel (not true, we use 2 bytes per pixel) */
	dispc->rowinc = 1;
	dispc->pixelinc = 1;
	dispc->winskip = 0;		/* don't skip anything */
	coherence();

	// dispc->palette = PADDR(framebuf->palette);
}

static void
lcdon(int enable)
{
	dispc->ctrl = Gpout1 | Gpout0 | Tftdata | Digital | Lcd | Stntft |
		Digitalen | enable;
	coherence();
	delay(10);
}

static void
lcdstop(void)
{
	configscreengpio();
	screenclockson();

	lcdoff();
}

static void
lcdinit(void)
{
	lcdstop();

	dssstart();
	configdispc();
}

/* Paint the image data with blue pixels */
void
screentest(void)
{
	int i;

	for (i = nelem(framebuf->pixel) - 1; i >= 0; i--)
		framebuf->pixel[i] = 0x1f;			/* blue */
//	memset(framebuf->pixel, ~0, sizeof framebuf->pixel);	/* white */
}

void
screenpower(int on)
{
	blankscreen(on == 0);
}

/*
 * called with drawlock locked for us, most of the time.
 * kernel prints at inopportune times might mean we don't
 * hold the lock, but memimagedraw is now reentrant so
 * that should be okay: worst case we get cursor droppings.
 */
void
swcursorhide(void)
{
	if(swvisible == 0)
		return;
	if(swback == nil)
		return;
	swvisible = 0;
	memimagedraw(gscreen, swrect, swback, ZP, memopaque, ZP, S);
	flushmemscreen(swrect);
}

void
swcursoravoid(Rectangle r)
{
	if(swvisible && rectXrect(r, swrect))
		swcursorhide();
}

void
swcursordraw(void)
{
	if(swvisible)
		return;
	if(swenabled == 0)
		return;
	if(swback == nil || swimg1 == nil || swmask1 == nil)
		return;
//	assert(!canqlock(&drawlock));		// assertion fails on omap
	swvispt = swpt;
	swvisvers = swvers;
	swrect = rectaddpt(Rect(0,0,16,16), swvispt);
	memimagedraw(swback, swback->r, gscreen, swpt, memopaque, ZP, S);
	memimagedraw(gscreen, swrect, swimg1, ZP, swmask1, ZP, SoverD);
	flushmemscreen(swrect);
	swvisible = 1;
}

int
cursoron(int dolock)
{
	if (dolock)
		lock(&oscreen);
	cursoroff(0);
	swcursordraw();
	if (dolock)
		unlock(&oscreen);
	return 0;
}

void
cursoroff(int dolock)
{
	if (dolock)
		lock(&oscreen);
	swcursorhide();
	if (dolock)
		unlock(&oscreen);
}

void
swload(Cursor *curs)
{
	uchar *ip, *mp;
	int i, j, set, clr;

	if(!swimg || !swmask || !swimg1 || !swmask1)
		return;
	/*
	 * Build cursor image and mask.
	 * Image is just the usual cursor image
	 * but mask is a transparent alpha mask.
	 * 
	 * The 16x16x8 memimages do not have
	 * padding at the end of their scan lines.
	 */
	ip = byteaddr(swimg, ZP);
	mp = byteaddr(swmask, ZP);
	for(i=0; i<32; i++){
		set = curs->set[i];
		clr = curs->clr[i];
		for(j=0x80; j; j>>=1){
			*ip++ = set&j ? 0x00 : 0xFF;
			*mp++ = (clr|set)&j ? 0xFF : 0x00;
		}
	}
	swoffset = curs->offset;
	swvers++;
	memimagedraw(swimg1,  swimg1->r,  swimg,  ZP, memopaque, ZP, S);
	memimagedraw(swmask1, swmask1->r, swmask, ZP, memopaque, ZP, S);
}

/* called from devmouse */
void
setcursor(Cursor* curs)
{
	cursoroff(1);
	oscreen.Cursor = *curs;
	swload(curs);
	cursoron(1);
}

int
swmove(Point p)
{
	swpt = addpt(p, swoffset);
	return 0;
}

void
swcursorclock(void)
{
	int x;

	if(!swenabled)
		return;
	swmove(mousexy());
	if(swvisible && eqpt(swpt, swvispt) && swvers==swvisvers)
		return;

	x = splhi();
	if(swenabled)
	if(!swvisible || !eqpt(swpt, swvispt) || swvers!=swvisvers)
	if(canqlock(&drawlock)){
		swcursorhide();
		swcursordraw();
		qunlock(&drawlock);
	}
	splx(x);
}

void
swcursorinit(void)
{
	static int init;

	if(!init){
		init = 1;
		addclock0link(swcursorclock, 10);
	}
	if(swback){
		freememimage(swback);
		freememimage(swmask);
		freememimage(swmask1);
		freememimage(swimg);
		freememimage(swimg1);
	}

	swback  = allocmemimage(Rect(0,0,32,32), gscreen->chan);
	swmask  = allocmemimage(Rect(0,0,16,16), GREY8);
	swmask1 = allocmemimage(Rect(0,0,16,16), GREY1);
	swimg   = allocmemimage(Rect(0,0,16,16), GREY8);
	swimg1  = allocmemimage(Rect(0,0,16,16), GREY1);
	if(swback==nil || swmask==nil || swmask1==nil || swimg==nil || swimg1 == nil){
		print("software cursor: allocmemimage fails\n");
		return;
	}

	memfillcolor(swmask, DOpaque);
	memfillcolor(swmask1, DOpaque);
	memfillcolor(swimg, DBlack);
	memfillcolor(swimg1, DBlack);
}

/* called from main and possibly later from devdss to change resolution */
void
screeninit(void)
{
	static int first = 1;

	if (first) {
		iprint("screeninit...");
		oscreen.settings = &settings[Res1280x1024];

		lcdstop();
		if (framebuf)
			free(framebuf);
		/* mode is 16*32 = 512 */
		framebuf = xspanalloc(sizeof *framebuf, 16*32, 0);
	}

	lcdinit();
	lcdon(1);
	if (first) {
		memimageinit();
		memdefont = getmemdefont();
		screentest();
	}

	xgdata.ref = 1;
	xgdata.bdata = (uchar *)framebuf->pixel;

	gscreen = &xgscreen;
	gscreen->r = Rect(0, 0, Wid, Ht);
	gscreen->clipr = gscreen->r;
	/* width, in words, of a single scan line */
	gscreen->width = Wid * (Depth / BI2BY) / BY2WD;
	flushmemscreen(gscreen->r);

	blanktime = 3;				/* minutes */

	if (first) {
		iprint("on: blue for 3 seconds...");
		delay(3*1000);
		iprint("\n");

		screenwin();		/* draw border & top orange bar */
		screenputs = omapscreenputs;
		iprint("screen: frame buffer at %#p for %dx%d\n",
			framebuf, oscreen.settings->wid, oscreen.settings->ht);

		swenabled = 1;
		swcursorinit();		/* needs gscreen set */
		setcursor(&arrow);

		first = 0;
	}
}

/* flushmemscreen should change buffer? */
void
flushmemscreen(Rectangle r)
{
	ulong start, end;

	if (r.min.x < 0)
		r.min.x = 0;
	if (r.max.x > Wid)
		r.max.x = Wid;
	if (r.min.y < 0)
		r.min.y = 0;
	if (r.max.y > Ht)
		r.max.y = Ht;
	if (rectclip(&r, gscreen->r) == 0)
		return;
	start = (ulong)&framebuf->pixel[r.min.y*Wid + r.min.x];
	end   = (ulong)&framebuf->pixel[(r.max.y - 1)*Wid + r.max.x -1];
	cachedwbse((ulong *)start, end - start);
}

/*
 * export screen to devdraw
 */
uchar*
attachscreen(Rectangle *r, ulong *chan, int *d, int *width, int *softscreen)
{
	*r = gscreen->r;
	*d = gscreen->depth;
	*chan = gscreen->chan;
	*width = gscreen->width;
	*softscreen = (landscape == 0);
	return (uchar *)gscreen->data->bdata;
}

void
getcolor(ulong p, ulong *pr, ulong *pg, ulong *pb)
{
	USED(p, pr, pg, pb);
}

int
setcolor(ulong p, ulong r, ulong g, ulong b)
{
	USED(p, r, g, b);
	return 0;
}

void
blankscreen(int blank)
{
	if (blank)
		lcdon(0);
	else {
		lcdinit();
		lcdon(1);
	}
}

static void
omapscreenputs(char *s, int n)
{
	int i;
	Rune r;
	char buf[4];

	if (!islo()) {
		/* don't deadlock trying to print in interrupt */
		if (!canlock(&screenlock))
			return;			/* discard s */
	} else
		lock(&screenlock);

	while (n > 0) {
		i = chartorune(&r, s);
		if (i == 0) {
			s++;
			--n;
			continue;
		}
		memmove(buf, s, i);
		buf[i] = 0;
		n -= i;
		s += i;
		screenputc(buf);
	}
	unlock(&screenlock);
}

static void
screenwin(void)
{
	char *greet;
	Memimage *orange;
	Point p, q;
	Rectangle r;

	memsetchan(gscreen, RGB16);

	back = memwhite;
	conscol = memblack;

	orange = allocmemimage(Rect(0, 0, 1, 1), RGB16);
	orange->flags |= Frepl;
	orange->clipr = gscreen->r;
	orange->data->bdata[0] = 0x40;		/* magic: colour? */
	orange->data->bdata[1] = 0xfd;		/* magic: colour? */

	w = memdefont->info[' '].width;
	h = memdefont->height;

	r = insetrect(gscreen->r, 4);

	memimagedraw(gscreen, r, memblack, ZP, memopaque, ZP, S);
	window = insetrect(r, 4);
	memimagedraw(gscreen, window, memwhite, ZP, memopaque, ZP, S);

	memimagedraw(gscreen, Rect(window.min.x, window.min.y,
		window.max.x, window.min.y + h + 5 + 6), orange, ZP, nil, ZP, S);
	freememimage(orange);
	window = insetrect(window, 5);

	greet = " Plan 9 Console ";
	p = addpt(window.min, Pt(10, 0));
	q = memsubfontwidth(memdefont, greet);
	memimagestring(gscreen, p, conscol, ZP, memdefont, greet);
	flushmemscreen(r);
	window.min.y += h + 6;
	curpos = window.min;
	window.max.y = window.min.y + ((window.max.y - window.min.y) / h) * h;
}

static void
scroll(void)
{
	int o;
	Point p;
	Rectangle r;

	/* move window contents up Scroll text lines */
	o = Scroll * h;
	r = Rpt(window.min, Pt(window.max.x, window.max.y - o));
	p = Pt(window.min.x, window.min.y + o);
	memimagedraw(gscreen, r, gscreen, p, nil, p, S);
	flushmemscreen(r);

	/* clear the bottom Scroll text lines */
	r = Rpt(Pt(window.min.x, window.max.y - o), window.max);
	memimagedraw(gscreen, r, back, ZP, nil, ZP, S);
	flushmemscreen(r);

	curpos.y -= o;
}

static void
screenputc(char *buf)
{
	int w;
	uint pos;
	Point p;
	Rectangle r;
	static int *xp;
	static int xbuf[256];

	if (xp < xbuf || xp >= &xbuf[nelem(xbuf)])
		xp = xbuf;

	switch (buf[0]) {
	case '\n':
		if (curpos.y + h >= window.max.y)
			scroll();
		curpos.y += h;
		screenputc("\r");
		break;
	case '\r':
		xp = xbuf;
		curpos.x = window.min.x;
		break;
	case '\t':
		p = memsubfontwidth(memdefont, " ");
		w = p.x;
		if (curpos.x >= window.max.x - Tabstop * w)
			screenputc("\n");

		pos = (curpos.x - window.min.x) / w;
		pos = Tabstop - pos % Tabstop;
		*xp++ = curpos.x;
		r = Rect(curpos.x, curpos.y, curpos.x + pos * w, curpos.y + h);
		memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
		flushmemscreen(r);
		curpos.x += pos * w;
		break;
	case '\b':
		if (xp <= xbuf)
			break;
		xp--;
		r = Rect(*xp, curpos.y, curpos.x, curpos.y + h);
		memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
		flushmemscreen(r);
		curpos.x = *xp;
		break;
	case '\0':
		break;
	default:
		p = memsubfontwidth(memdefont, buf);
		w = p.x;

		if (curpos.x >= window.max.x - w)
			screenputc("\n");

		*xp++ = curpos.x;
		r = Rect(curpos.x, curpos.y, curpos.x + w, curpos.y + h);
		memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
		memimagestring(gscreen, curpos, conscol, ZP, memdefont, buf);
		flushmemscreen(r);
		curpos.x += w;
	}
}
