/* * 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 #include "screen.h" enum { TFTWidth = 480, TFTHeight = 320, }; static void pitftblank(int); static void pitftdraw(Rectangle); 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 *); extern Memimage xgscreen; extern Lcd *lcd; static Lcd pitft = { pitftdraw, pitftblank, }; static Queue *updateq = nil; void pitftlink(void) { addarchfile("pitft", 0666, pitftread, pitftwrite); } 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 pitftread(Chan *, void *, long, vlong) { return 0; } static long pitftwrite(Chan *, void *a, long n, vlong) { if(strncmp(a, "init", 4) == 0 && updateq == nil) { /* * 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); lcd = &pitft; } 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); }