add the low-level bits to drive the hx8357d. the original code is from bls, but this was reworked to play nicely with the normal rpi hdmi screen. this code depends on the underlying framebuffer being available. Reference: /n/atom/patch/applied/pihx8357d Date: Sun Jan 10 18:34:11 CET 2016 Signed-off-by: quanstro@quanstro.net --- /sys/src/9/bcm/scrhx8357d.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/bcm/scrhx8357d.c Sun Jan 10 18:33:09 2016 @@ -0,0 +1,301 @@ +/* + * Support for a SPI LCD panel from Adafruit + * based on HX8357D controller chip + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#define Image IMAGE +#include +#include +#include "fbscreen.h" +#include "io.h" + +enum { + TFTWidth = 480, + TFTHeight = 320, +}; + +static Queue *updateq; + +static void +pitftblank(Fbscreen*, int) +{ +} + +static void +pitftdraw(Fbscreen*, 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)); + } +} + +static 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; +} + +static int +min(int x, int y) +{ + if(x < y) + return x; + return y; +} + +static int +max(int x, int y) +{ + if(x < y) + return y; + return x; +} + +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); +} + +/* + * 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 +pitftdrawproc(void *v) +{ + Rectangle rec, bb; + Point pt; + uchar *p; + int i, r, c, gotrec; + uchar spibuf[32]; + Fbscreen *scr; + Memimage *fb; + + scr = v; + fb = scr->fb; + + 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(fb, pt); + switch(fb->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 +pitftsetup(void) +{ + uchar spibuf[32]; + + gpiosel(25, GpioOutput); + 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 void +enable(Fbscreen*) +{ + static int once; + + if(once) + return; + once = 1; + + /* + * 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", pitftdrawproc, nil); +} + +static void +disable(Fbscreen*) +{ +} + +static long +pitftwrite(Chan *, void *a, long n, vlong) +{ + if(strncmp(a, "init", 4) == 0) { + } + return n; +} + +static void +pitftflush(Fbscreen*, Rectangle) +{ +} + +static Hwscreen scrhx8357d = { + "hx8357d", + {0, 0, 480, 320}, + + enable, + disable, + pitftdraw, + pitftflush, + pitftblank, +}; + +static int +detect(Hwscreen*) +{ + return 0; +} + +int +pitftinit(Fbscreen *scr) +{ + Hwscreen *hw; + + if(detect(scr) == -1) + return -1; + + hw = scr; + *hw = scrhx8357d; + addarchfile("pitft", 0666, nil, pitftwrite); + return 0; +}