1 /*
2 * Support for a SPI LCD panel from Adafruit
3 * based on HX8357D controller chip
4 */
5
6 #include "u.h"
7 #include "../port/lib.h"
8 #include "mem.h"
9 #include "dat.h"
10 #include "fns.h"
11
12 #define Image IMAGE
13 #include <draw.h>
14 #include <memdraw.h>
15 #include <cursor.h>
16 #include "screen.h"
17
18 enum {
19 TFTWidth = 480,
20 TFTHeight = 320,
21 };
22
23 static void pitftblank(int);
24 static void pitftdraw(Rectangle);
25 static long pitftread(Chan*, void*, long, vlong);
26 static long pitftwrite(Chan*, void*, long, vlong);
27 static void spicmd(uchar);
28 static void spidata(uchar *, int);
29 static void setwindow(int, int, int, int);
30 static void xpitftdraw(void *);
31
32 extern Memimage xgscreen;
33 extern Lcd *lcd;
34
35 static Lcd pitft = {
36 pitftdraw,
37 pitftblank,
38 };
39
40 static Queue *updateq = nil;
41
42 void
pitftlink(void)43 pitftlink(void)
44 {
45 addarchfile("pitft", 0666, pitftread, pitftwrite);
46 }
47
48 static void
pitftsetup(void)49 pitftsetup(void)
50 {
51 uchar spibuf[32];
52
53 gpiosel(25, Output);
54 spirw(0, spibuf, 1);
55 spicmd(0x01);
56 delay(10);
57 spicmd(0x11);
58 delay(10);
59 spicmd(0x29);
60 spicmd(0x13);
61 spicmd(0x36);
62 spibuf[0] = 0xe8;
63 spidata(spibuf, 1);
64 spicmd(0x3a);
65 spibuf[0] = 0x05;
66 spidata(spibuf, 1);
67 }
68
69 static long
pitftread(Chan *,void *,long,vlong)70 pitftread(Chan *, void *, long, vlong)
71 {
72 return 0;
73 }
74
75 static long
pitftwrite(Chan *,void * a,long n,vlong)76 pitftwrite(Chan *, void *a, long n, vlong)
77 {
78 if(strncmp(a, "init", 4) == 0 && updateq == nil) {
79 /*
80 * The HX8357 datasheet shows minimum
81 * clock cycle time of 66nS but the clock high
82 * and low times as 15nS and it seems to
83 * work at around 32MHz.
84 */
85 spiclock(32);
86 pitftsetup();
87 updateq = qopen(16384, 1, nil, nil);
88 kproc("pitft", xpitftdraw, nil);
89 lcd = &pitft;
90 }
91 return n;
92 }
93
94 static void
pitftblank(int blank)95 pitftblank(int blank)
96 {
97 USED(blank);
98 }
99
100 static void
pitftdraw(Rectangle r)101 pitftdraw(Rectangle r)
102 {
103 if(updateq == nil)
104 return;
105 if(r.min.x > TFTWidth || r.min.y > TFTHeight)
106 return;
107 /*
108 * using qproduce to make sure we don't block
109 * but if we've got a lot on the queue, it means we're
110 * redrawing the same areas over and over; clear it
111 * out and just draw the whole screen once
112 */
113 if(qproduce(updateq, &r, sizeof(Rectangle)) == -1) {
114 r = Rect(0, 0, TFTWidth, TFTHeight);
115 qflush(updateq);
116 qproduce(updateq, &r, sizeof(Rectangle));
117 }
118 }
119
120 int
overlap(Rectangle r1,Rectangle r2)121 overlap(Rectangle r1, Rectangle r2)
122 {
123 if(r1.max.x < r2.min.x)
124 return 0;
125 if(r1.min.x > r2.max.x)
126 return 0;
127 if(r1.max.y < r2.min.y)
128 return 0;
129 if(r1.min.y > r2.max.y)
130 return 0;
131 return 1;
132 }
133
134 int
min(int x,int y)135 min(int x, int y)
136 {
137 if(x < y)
138 return x;
139 return y;
140 }
141
142 int
max(int x,int y)143 max(int x, int y)
144 {
145 if(x < y)
146 return y;
147 return x;
148 }
149
150 /*
151 * Because everyone wants to be holding locks when
152 * they update the screen but we need to sleep in the
153 * SPI code, we're decoupling this into a separate kproc().
154 */
155 static void
xpitftdraw(void *)156 xpitftdraw(void *)
157 {
158 Rectangle rec, bb;
159 Point pt;
160 uchar *p;
161 int i, r, c, gotrec;
162 uchar spibuf[32];
163
164 gotrec = 0;
165 qread(updateq, &rec, sizeof(Rectangle));
166 bb = Rect(0, 0, TFTWidth, TFTHeight);
167 while(1) {
168 setwindow(bb.min.x, bb.min.y,
169 bb.max.x-1, bb.max.y-1);
170 spicmd(0x2c);
171 for(r = bb.min.y; r < bb.max.y; ++r) {
172 for(c = bb.min.x; c < bb.max.x; c += 8) {
173 for(i = 0; i < 8; ++i) {
174 pt.y = r;
175 pt.x = c + i;
176 p = byteaddr(&xgscreen, pt);
177 switch(xgscreen.depth) {
178 case 16: // RGB16
179 spibuf[i*2+1] = p[0];
180 spibuf[i*2] = p[1];
181 break;
182 case 24: // BGR24
183 spibuf[i*2] = (p[2] & 0xf8) |
184 (p[1] >> 5);
185 spibuf[i*2+1] = (p[0] >> 3) |
186 (p[1] << 3);
187 break;
188 case 32: // ARGB32
189 spibuf[i*2] = (p[0] & 0xf8) |
190 (p[1] >> 5);
191 spibuf[i*2+1] = (p[1] >> 3) |
192 (p[1] << 3);
193 break;
194 }
195 }
196 spidata(spibuf, 16);
197 }
198 }
199 bb.max.y = -1;
200 while(1) {
201 if(!gotrec) {
202 qread(updateq, &rec, sizeof(Rectangle));
203 gotrec = 1;
204 }
205 if(bb.max.y != -1) {
206 if(!overlap(bb, rec))
207 break;
208 rec.min.x = min(rec.min.x, bb.min.x);
209 rec.min.y = min(rec.min.y, bb.min.y);
210 rec.max.x = max(rec.max.x, bb.max.x);
211 rec.max.y = max(rec.max.y, bb.max.y);
212 }
213 gotrec = 0;
214 // Expand rows to 8 pixel alignment
215 bb.min.x = rec.min.x & ~7;
216 if(bb.min.x < 0)
217 bb.min.x = 0;
218 bb.max.x = (rec.max.x + 7) & ~7;
219 if(bb.max.x > TFTWidth)
220 bb.max.x = TFTWidth;
221 bb.min.y = rec.min.y;
222 if(bb.min.y < 0)
223 bb.min.y = 0;
224 bb.max.y = rec.max.y;
225 if(bb.max.y > TFTHeight)
226 bb.max.y = TFTHeight;
227 if(qcanread(updateq)) {
228 qread(updateq, &rec, sizeof(Rectangle));
229 gotrec = 1;
230 }
231 else
232 break;
233 }
234 }
235 }
236
237 static void
spicmd(uchar c)238 spicmd(uchar c)
239 {
240 char buf;
241
242 gpioout(25, 0);
243 buf = c;
244 spirw(0, &buf, 1);
245 }
246
247 static void
spidata(uchar * p,int n)248 spidata(uchar *p, int n)
249 {
250 char buf[128];
251
252 if(n > 128)
253 n = 128;
254 gpioout(25, 1);
255 memmove(buf, p, n);
256 spirw(0, buf, n);
257 gpioout(25, 0);
258 }
259
260 static void
setwindow(int minc,int minr,int maxc,int maxr)261 setwindow(int minc, int minr, int maxc, int maxr)
262 {
263 uchar spibuf[4];
264
265 spicmd(0x2a);
266 spibuf[0] = minc >> 8;
267 spibuf[1] = minc & 0xff;
268 spibuf[2] = maxc >> 8;
269 spibuf[3] = maxc & 0xff;
270 spidata(spibuf, 4);
271 spicmd(0x2b);
272 spibuf[0] = minr >> 8;
273 spibuf[1] = minr & 0xff;
274 spibuf[2] = maxr >> 8;
275 spibuf[3] = maxr & 0xff;
276 spidata(spibuf, 4);
277 }
278