Plan 9 from Bell Labs’s /usr/web/sources/contrib/blstuart/pi/sys/src/9/bcm/screen.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * bcm2385 framebuffer
 */

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"

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

enum {
	Tabstop		= 4,
	Scroll		= 8,
	Wid		= 1024,
	Ht		= 768,
	Depth		= 16,
};

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,
	},
};

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 */
	0, 			/* 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 Rectangle window;

static void myscreenputs(char *s, int n);
static void screenputc(char *buf);
static void screenwin(void);

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

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

/*
 * Support for a SPI LCD panel from Adafruit
 * based on HX8357D controller chip
 */
enum {
	TFTWidth = 480,
	TFTHeight = 320,
};

static void pitftblank(int);
static void pitftdraw(Rectangle);
static void pitftinit(void);
static long pitftread(Chan*, void*, long, vlong);
static long pitftwrite(Chan*, void*, long, vlong);
static void spicmd(uchar);
static void spidata(uchar *, int);
static void setwindow(int, int, int, int);
static void xpitftdraw(void *);

static Queue *updateq = nil;

/*
 * 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.
 */
static void
swcursorhide(void)
{
	if(swvisible == 0)
		return;
	if(swback == nil)
		return;
	swvisible = 0;
	memimagedraw(gscreen, swrect, swback, ZP, memopaque, ZP, S);
	flushmemscreen(swrect);
}

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

static void
swcursordraw(void)
{
	int dounlock;

	if(swvisible)
		return;
	if(swenabled == 0)
		return;
	if(swback == nil || swimg1 == nil || swmask1 == nil)
		return;
	dounlock = canqlock(&drawlock);
	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;
	if(dounlock)
		qunlock(&drawlock);
}

int
cursoron(int dolock)
{
	int retry;

	if (dolock)
		lock(&cursor);
	if (canqlock(&drawlock)) {
		retry = 0;
		swcursorhide();
		swcursordraw();
		qunlock(&drawlock);
	} else
		retry = 1;
	if (dolock)
		unlock(&cursor);
	return retry;
}

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

static 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(0);
	swload(curs);
	cursoron(0);
}

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

static 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);
		swenabled = 1;
	}
	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);
}

int
hwdraw(Memdrawparam *par)
{
	Memimage *dst, *src, *mask;

	if((dst=par->dst) == nil || dst->data == nil)
		return 0;
	if((src=par->src) == nil || src->data == nil)
		return 0;
	if((mask=par->mask) == nil || mask->data == nil)
		return 0;

	if(dst->data->bdata == xgdata.bdata)
		swcursoravoid(par->r);
	if(src->data->bdata == xgdata.bdata)
		swcursoravoid(par->sr);
	if(mask->data->bdata == xgdata.bdata)
		swcursoravoid(par->mr);

	pitftdraw(par->r);

	return 0;
}

static int
screensize(void)
{
	char *p;
	char *f[3];
	int width, height, depth;

	p = getconf("vgasize");
	if(p == nil || getfields(p, f, nelem(f), 0, "x") != nelem(f) ||
	    (width = atoi(f[0])) < 16 ||
	    (height = atoi(f[1])) <= 0 ||
	    (depth = atoi(f[2])) <= 0)
		return -1;
	xgscreen.r.max = Pt(width, height);
	xgscreen.depth = depth;
	return 0;
}

void
screeninit(void)
{
	uchar *fb;
	int set;
	ulong chan;

	set = screensize() == 0;
	fb = fbinit(set, &xgscreen.r.max.x, &xgscreen.r.max.y, &xgscreen.depth);
	if(fb == nil){
		print("can't initialise %dx%dx%d framebuffer \n",
			xgscreen.r.max.x, xgscreen.r.max.y, xgscreen.depth);
		return;
	}
	xgscreen.clipr = xgscreen.r;
	switch(xgscreen.depth){
	default:
		print("unsupported screen depth %d\n", xgscreen.depth);
		xgscreen.depth = 16;
		/* fall through */
	case 16:
		chan = RGB16;
		break;
	case 24:
		chan = BGR24;
		break;
	case 32:
		chan = ARGB32;
		break;
	}
	memsetchan(&xgscreen, chan);
	conf.monitor = 1;
	xgdata.bdata = fb;
	xgdata.ref = 1;
	gscreen = &xgscreen;
	gscreen->width = wordsperline(gscreen->r, gscreen->depth);

	memimageinit();
	memdefont = getmemdefont();
	screenwin();
	screenputs = myscreenputs;
	pitftinit();
}

void
flushmemscreen(Rectangle)
{
}

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 = 0;

	return 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)
{
	fbblank(blank);
	pitftblank(blank);
}

static void
myscreenputs(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;	
	}
	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;

	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;

	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);
	pitftdraw(r);
	r = Rpt(Pt(window.min.x, window.max.y-o), window.max);
	memimagedraw(gscreen, r, back, ZP, nil, ZP, S);
	flushmemscreen(r);
	pitftdraw(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[sizeof(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;
		break;
	}
}

static void
pitftinit(void)
{
	addarchfile("pitft", 0666, pitftread, pitftwrite);
}

static long
pitftread(Chan *c, void *a, long n, vlong)
{
	USED(c);
	USED(a);
	USED(n);
	return 0;
}

static void
pitftsetup(void)
{
	uchar spibuf[32];

	gpiosel(25, Output);
	spirw(0, spibuf, 1);
	spicmd(0x01);
	delay(10);
	spicmd(0x11);
	delay(10);
	spicmd(0x29);
	spicmd(0x13);
	spicmd(0x36);
	spibuf[0] = 0xe8;
	spidata(spibuf, 1);
	spicmd(0x3a);
	spibuf[0] = 0x05;
	spidata(spibuf, 1);
}

static long
pitftwrite(Chan *, void *a, long n, vlong)
{
	if(strncmp(a, "init", 4) == 0) {
		/*
		 * The HX8357 datasheet shows minimum
		 * clock cycle time of 66nS but the clock high
		 * and low times as 15nS and it seems to
		 * work at around 32MHz.
		 */
		spiclock(32);
		pitftsetup();
		updateq = qopen(16384, 1, nil, nil);
		kproc("pitft", xpitftdraw, nil);
	}
	return n;
}

static void
pitftblank(int blank)
{
	USED(blank);
}

static void
pitftdraw(Rectangle r)
{
	if(updateq == nil)
		return;
	if(r.min.x > TFTWidth || r.min.y > TFTHeight)
		return;
	/*
	 * using qproduce to make sure we don't block
	 * but if we've got a lot on the queue, it means we're
	 * redrawing the same areas over and over; clear it
	 * out and just draw the whole screen once
	 */
	if(qproduce(updateq, &r, sizeof(Rectangle)) == -1) {
		r = Rect(0, 0, TFTWidth, TFTHeight);
		qflush(updateq);
		qproduce(updateq, &r, sizeof(Rectangle));
	}
}

int
overlap(Rectangle r1, Rectangle r2)
{
	if(r1.max.x < r2.min.x)
		return 0;
	if(r1.min.x > r2.max.x)
		return 0;
	if(r1.max.y < r2.min.y)
		return 0;
	if(r1.min.y > r2.max.y)
		return 0;
	return 1;
}

int
min(int x, int y)
{
	if(x < y)
		return x;
	return y;
}

int
max(int x, int y)
{
	if(x < y)
		return y;
	return x;
}

/*
 * Because everyone wants to be holding locks when
 * they update the screen but we need to sleep in the
 * SPI code, we're decoupling this into a separate kproc().
 */
static void
xpitftdraw(void *)
{
	Rectangle rec, bb;
	Point pt;
	uchar *p;
	int i, r, c, gotrec;
	uchar spibuf[32];

	gotrec = 0;
	qread(updateq, &rec, sizeof(Rectangle));
	bb = Rect(0, 0, TFTWidth, TFTHeight);
	while(1) {
		setwindow(bb.min.x, bb.min.y,
			bb.max.x-1, bb.max.y-1);
		spicmd(0x2c);
		for(r = bb.min.y; r < bb.max.y; ++r) {
			for(c = bb.min.x; c < bb.max.x; c += 8) {
				for(i = 0; i < 8; ++i) {
					pt.y = r;
					pt.x = c + i;
					p = byteaddr(&xgscreen, pt);
					switch(xgscreen.depth) {
					case 16:		// RGB16
						spibuf[i*2+1] = p[0];
						spibuf[i*2] = p[1];
						break;
					case 24:		// BGR24
						spibuf[i*2] = (p[2] & 0xf8) | 
							(p[1] >> 5);
						spibuf[i*2+1] = (p[0] >> 3) |
							(p[1] << 3);
						break;
					case 32:		// ARGB32
						spibuf[i*2] = (p[0] & 0xf8) | 
							(p[1] >> 5);
						spibuf[i*2+1] = (p[1] >> 3) |
							(p[1] << 3);
						break;
					}
				}
				spidata(spibuf, 16);
			}
		}
		bb.max.y = -1;
		while(1) {
			if(!gotrec) {
				qread(updateq, &rec, sizeof(Rectangle));
				gotrec = 1;
			}
			if(bb.max.y != -1) {
				if(!overlap(bb, rec))
					break;
				rec.min.x = min(rec.min.x, bb.min.x);
				rec.min.y = min(rec.min.y, bb.min.y);
				rec.max.x = max(rec.max.x, bb.max.x);
				rec.max.y = max(rec.max.y, bb.max.y);
			}
			gotrec = 0;
			// Expand rows to 8 pixel alignment
			bb.min.x = rec.min.x & ~7;
			if(bb.min.x < 0)
				bb.min.x = 0;
			bb.max.x = (rec.max.x + 7) & ~7;
			if(bb.max.x > TFTWidth)
				bb.max.x = TFTWidth;
			bb.min.y = rec.min.y;
			if(bb.min.y < 0)
				bb.min.y = 0;
			bb.max.y = rec.max.y;
			if(bb.max.y > TFTHeight)
				bb.max.y = TFTHeight;
			if(qcanread(updateq)) {
				qread(updateq, &rec, sizeof(Rectangle));
				gotrec = 1;
			}
			else
				break;
		}
	}
}

static void
spicmd(uchar c)
{
	char buf;

	gpioout(25, 0);
	buf = c;
	spirw(0, &buf, 1);
}

static void
spidata(uchar *p, int n)
{
	char buf[128];

	if(n > 128)
		n = 128;
	gpioout(25, 1);
	memmove(buf, p, n);
	spirw(0, buf, n);
	gpioout(25, 0);
}

static void
setwindow(int minc, int minr, int maxc, int maxr)
{
	uchar spibuf[4];

	spicmd(0x2a);
	spibuf[0] = minc >> 8;
	spibuf[1] = minc & 0xff;
	spibuf[2] = maxc >> 8;
	spibuf[3] = maxc & 0xff;
	spidata(spibuf, 4);
	spicmd(0x2b);
	spibuf[0] = minr >> 8;
	spibuf[1] = minr & 0xff;
	spibuf[2] = maxr >> 8;
	spibuf[3] = maxr & 0xff;
	spidata(spibuf, 4);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].