1 /* $NetBSD: ssdfb_spi.c,v 1.14 2022/01/19 13:33:49 thorpej Exp $ */
2
3 /*
4 * Copyright (c) 2019 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Tobias Nygren.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: ssdfb_spi.c,v 1.14 2022/01/19 13:33:49 thorpej Exp $");
34
35 #include <sys/param.h>
36 #include <sys/device.h>
37 #include <sys/kernel.h>
38 #include <dev/wscons/wsdisplayvar.h>
39 #include <dev/rasops/rasops.h>
40 #include <dev/spi/spivar.h>
41 #include <dev/ic/ssdfbvar.h>
42 #include "opt_fdt.h"
43 #ifdef FDT
44 #include <dev/fdt/fdtvar.h>
45 #endif
46
47 struct bs_state {
48 uint8_t *base;
49 uint8_t *cur;
50 uint8_t mask;
51 };
52
53 struct ssdfb_spi_softc {
54 struct ssdfb_softc sc;
55 struct spi_handle *sc_sh;
56 #ifdef FDT
57 struct fdtbus_gpio_pin *sc_gpio_dc;
58 struct fdtbus_gpio_pin *sc_gpio_res;
59 #endif
60 bool sc_3wiremode;
61 bool sc_late_dc_deassert;
62 uint8_t sc_padding_cmd;
63 };
64
65 static int ssdfb_spi_match(device_t, cfdata_t, void *);
66 static void ssdfb_spi_attach(device_t, device_t, void *);
67
68 static int ssdfb_spi_cmd_3wire(void *, uint8_t *, size_t, bool);
69 static int ssdfb_spi_xfer_rect_3wire_ssd1322(void *, uint8_t, uint8_t,
70 uint8_t, uint8_t, uint8_t *, size_t, bool);
71
72 static int ssdfb_spi_cmd_4wire(void *, uint8_t *, size_t, bool);
73 static int ssdfb_spi_xfer_rect_4wire_sh1106(void *, uint8_t, uint8_t,
74 uint8_t, uint8_t, uint8_t *, size_t, bool);
75 static int ssdfb_spi_xfer_rect_4wire_ssd1306(void *, uint8_t, uint8_t,
76 uint8_t, uint8_t, uint8_t *, size_t, bool);
77 static int ssdfb_spi_xfer_rect_4wire_ssd1322(void *, uint8_t, uint8_t,
78 uint8_t, uint8_t, uint8_t *, size_t, bool);
79 static int ssdfb_spi_xfer_rect_4wire_ssd1353(void *, uint8_t, uint8_t,
80 uint8_t, uint8_t, uint8_t *, size_t, bool);
81
82 static void ssdfb_bitstream_init(struct bs_state *, uint8_t *);
83 static void ssdfb_bitstream_append(struct bs_state *, uint8_t, uint8_t);
84 static void ssdfb_bitstream_append_cmd(struct bs_state *, uint8_t);
85 static void ssdfb_bitstream_append_data(struct bs_state *, uint8_t *,
86 size_t);
87 static void ssdfb_bitstream_final(struct bs_state *, uint8_t);
88
89 CFATTACH_DECL_NEW(ssdfb_spi, sizeof(struct ssdfb_spi_softc),
90 ssdfb_spi_match, ssdfb_spi_attach, NULL, NULL);
91
92 static const struct device_compatible_entry compat_data[] = {
93 { .compat = "solomon,ssd1306", .value = SSDFB_PRODUCT_SSD1306_GENERIC },
94 { .compat = "sino,sh1106", .value = SSDFB_PRODUCT_SH1106_GENERIC },
95 { .compat = "solomon,ssd1322", .value = SSDFB_PRODUCT_SSD1322_GENERIC },
96 { .compat = "solomon,ssd1353", .value = SSDFB_PRODUCT_SSD1353_GENERIC },
97 { .compat = "dep160128a", .value = SSDFB_PRODUCT_DEP_160128A_RGB },
98 DEVICE_COMPAT_EOL
99 };
100
101 static int
ssdfb_spi_match(device_t parent,cfdata_t match,void * aux)102 ssdfb_spi_match(device_t parent, cfdata_t match, void *aux)
103 {
104 struct spi_attach_args *sa = aux;
105
106 return spi_compatible_match(sa, match, compat_data);
107 }
108
109 static void
ssdfb_spi_attach(device_t parent,device_t self,void * aux)110 ssdfb_spi_attach(device_t parent, device_t self, void *aux)
111 {
112 struct ssdfb_spi_softc *sc = device_private(self);
113 struct cfdata *cf = device_cfdata(self);
114 struct spi_attach_args *sa = aux;
115 int flags = cf->cf_flags;
116 int error;
117
118 sc->sc.sc_dev = self;
119 sc->sc_sh = sa->sa_handle;
120 sc->sc.sc_cookie = (void *)sc;
121 if ((flags & SSDFB_ATTACH_FLAG_PRODUCT_MASK) == SSDFB_PRODUCT_UNKNOWN) {
122 const struct device_compatible_entry *dce =
123 spi_compatible_lookup(sa, compat_data);
124 if (dce)
125 flags |= (int)dce->value;
126 else
127 flags |= SSDFB_PRODUCT_SSD1322_GENERIC;
128 }
129
130 /*
131 * SSD1306 and SSD1322 data sheets specify 100ns cycle time.
132 */
133 error = spi_configure(self, sa->sa_handle, SPI_MODE_0, 10000000);
134 if (error) {
135 return;
136 }
137
138 /*
139 * Note on interface modes.
140 *
141 * 3 wire mode sends 9 bit sequences over the MOSI, MSB contains
142 * the bit that determines if the lower 8 bits are command or data.
143 *
144 * 4 wire mode sends 8 bit sequences and requires an auxiliary GPIO
145 * pin for the command/data bit.
146 */
147 #ifdef FDT
148 const int phandle = sa->sa_cookie;
149 sc->sc_gpio_dc =
150 fdtbus_gpio_acquire(phandle, "dc-gpio", GPIO_PIN_OUTPUT);
151 if (!sc->sc_gpio_dc)
152 sc->sc_gpio_dc =
153 fdtbus_gpio_acquire(phandle, "cd-gpio", GPIO_PIN_OUTPUT);
154 sc->sc_3wiremode = (sc->sc_gpio_dc == NULL);
155 sc->sc_gpio_res =
156 fdtbus_gpio_acquire(phandle, "res-gpio", GPIO_PIN_OUTPUT);
157 if (sc->sc_gpio_res) {
158 fdtbus_gpio_write_raw(sc->sc_gpio_res, 0);
159 DELAY(100);
160 fdtbus_gpio_write_raw(sc->sc_gpio_res, 1);
161 DELAY(100);
162 }
163 #else
164 sc->sc_3wiremode = true;
165 #endif
166
167 sc->sc.sc_cmd = sc->sc_3wiremode
168 ? ssdfb_spi_cmd_3wire
169 : ssdfb_spi_cmd_4wire;
170
171 switch (flags & SSDFB_ATTACH_FLAG_PRODUCT_MASK) {
172 case SSDFB_PRODUCT_SH1106_GENERIC:
173 sc->sc.sc_transfer_rect = sc->sc_3wiremode
174 ? NULL
175 : ssdfb_spi_xfer_rect_4wire_sh1106;
176 sc->sc_padding_cmd = SSDFB_CMD_NOP;
177 sc->sc_late_dc_deassert = true;
178 break;
179 case SSDFB_PRODUCT_SSD1306_GENERIC:
180 sc->sc.sc_transfer_rect = sc->sc_3wiremode
181 ? NULL
182 : ssdfb_spi_xfer_rect_4wire_ssd1306;
183 sc->sc_padding_cmd = SSDFB_CMD_NOP;
184 sc->sc_late_dc_deassert = true;
185 break;
186 case SSDFB_PRODUCT_SSD1322_GENERIC:
187 sc->sc.sc_transfer_rect = sc->sc_3wiremode
188 ? ssdfb_spi_xfer_rect_3wire_ssd1322
189 : ssdfb_spi_xfer_rect_4wire_ssd1322;
190 sc->sc_padding_cmd = SSD1322_CMD_WRITE_RAM;
191 break;
192 case SSDFB_PRODUCT_SSD1353_GENERIC:
193 case SSDFB_PRODUCT_DEP_160128A_RGB:
194 sc->sc.sc_transfer_rect = sc->sc_3wiremode
195 ? NULL /* not supported here */
196 : ssdfb_spi_xfer_rect_4wire_ssd1353;
197 break;
198 }
199
200 if (!sc->sc.sc_transfer_rect) {
201 aprint_error(": sc_transfer_rect not implemented\n");
202 return;
203 }
204
205 ssdfb_attach(&sc->sc, flags);
206
207 aprint_normal_dev(self, "%d-wire SPI interface\n",
208 sc->sc_3wiremode == true ? 3 : 4);
209 }
210
211 static int
ssdfb_spi_cmd_3wire(void * cookie,uint8_t * cmd,size_t len,bool usepoll)212 ssdfb_spi_cmd_3wire(void *cookie, uint8_t *cmd, size_t len, bool usepoll)
213 {
214 struct ssdfb_spi_softc *sc = (struct ssdfb_spi_softc *)cookie;
215 uint8_t bitstream[16 * 9 / 8];
216 struct bs_state s;
217
218 KASSERT(len > 0 && len <= 16);
219 ssdfb_bitstream_init(&s, bitstream);
220 ssdfb_bitstream_append_cmd(&s, *cmd);
221 cmd++;
222 len--;
223 ssdfb_bitstream_append_data(&s, cmd, len);
224 ssdfb_bitstream_final(&s, sc->sc_padding_cmd);
225
226 return spi_send(sc->sc_sh, s.cur - s.base, bitstream);
227 }
228
229 static int
ssdfb_spi_xfer_rect_3wire_ssd1322(void * cookie,uint8_t fromcol,uint8_t tocol,uint8_t fromrow,uint8_t torow,uint8_t * p,size_t stride,bool usepoll)230 ssdfb_spi_xfer_rect_3wire_ssd1322(void *cookie, uint8_t fromcol, uint8_t tocol,
231 uint8_t fromrow, uint8_t torow, uint8_t *p, size_t stride, bool usepoll)
232 {
233 struct ssdfb_spi_softc *sc = (struct ssdfb_spi_softc *)cookie;
234 uint8_t bitstream[128 * 9 / 8];
235 struct bs_state s;
236 size_t rlen = (tocol + 1 - fromcol) * 2;
237 int error;
238
239 /*
240 * Unlike iic(4), there is no way to force spi(4) to use polling.
241 */
242 if (usepoll && !cold)
243 return 0;
244
245 ssdfb_bitstream_init(&s, bitstream);
246 ssdfb_bitstream_append_cmd(&s, SSD1322_CMD_SET_ROW_ADDRESS);
247 ssdfb_bitstream_append_data(&s, &fromrow, 1);
248 ssdfb_bitstream_append_data(&s, &torow, 1);
249 ssdfb_bitstream_append_cmd(&s, SSD1322_CMD_SET_COLUMN_ADDRESS);
250 ssdfb_bitstream_append_data(&s, &fromcol, 1);
251 ssdfb_bitstream_append_data(&s, &tocol, 1);
252 ssdfb_bitstream_append_cmd(&s, SSD1322_CMD_WRITE_RAM);
253 ssdfb_bitstream_final(&s, sc->sc_padding_cmd);
254 error = spi_send(sc->sc_sh, s.cur - s.base, bitstream);
255 if (error)
256 return error;
257
258 KASSERT(rlen <= 128);
259 while (fromrow <= torow) {
260 ssdfb_bitstream_init(&s, bitstream);
261 ssdfb_bitstream_append_data(&s, p, rlen);
262 ssdfb_bitstream_final(&s, sc->sc_padding_cmd);
263 error = spi_send(sc->sc_sh, s.cur - s.base, bitstream);
264 if (error)
265 return error;
266 fromrow++;
267 p += stride;
268 }
269
270 return 0;
271 }
272
273 static void
ssdfb_bitstream_init(struct bs_state * s,uint8_t * dst)274 ssdfb_bitstream_init(struct bs_state *s, uint8_t *dst)
275 {
276 s->base = s->cur = dst;
277 s->mask = 0x80;
278 }
279
280 static void
ssdfb_bitstream_append(struct bs_state * s,uint8_t b,uint8_t srcmask)281 ssdfb_bitstream_append(struct bs_state *s, uint8_t b, uint8_t srcmask)
282 {
283 while(srcmask) {
284 if (b & srcmask)
285 *s->cur |= s->mask;
286 else
287 *s->cur &= ~s->mask;
288 srcmask >>= 1;
289 s->mask >>= 1;
290 if (!s->mask) {
291 s->mask = 0x80;
292 s->cur++;
293 }
294 }
295 }
296
297 static void
ssdfb_bitstream_append_cmd(struct bs_state * s,uint8_t cmd)298 ssdfb_bitstream_append_cmd(struct bs_state *s, uint8_t cmd)
299 {
300 ssdfb_bitstream_append(s, 0, 1);
301 ssdfb_bitstream_append(s, cmd, 0x80);
302 }
303
304 static void
ssdfb_bitstream_append_data(struct bs_state * s,uint8_t * data,size_t len)305 ssdfb_bitstream_append_data(struct bs_state *s, uint8_t *data, size_t len)
306 {
307 while(len--) {
308 ssdfb_bitstream_append(s, 1, 1);
309 ssdfb_bitstream_append(s, *data++, 0x80);
310 }
311 }
312
313 static void
ssdfb_bitstream_final(struct bs_state * s,uint8_t padding_cmd)314 ssdfb_bitstream_final(struct bs_state *s, uint8_t padding_cmd)
315 {
316 while (s->mask != 0x80) {
317 ssdfb_bitstream_append_cmd(s, padding_cmd);
318 }
319 }
320
321 static void
ssdfb_spi_4wire_set_dc(struct ssdfb_spi_softc * sc,int value)322 ssdfb_spi_4wire_set_dc(struct ssdfb_spi_softc *sc, int value)
323 {
324 #ifdef FDT
325 fdtbus_gpio_write_raw(sc->sc_gpio_dc, value);
326 #else
327 panic("ssdfb_spi_4wire_set_dc");
328 #endif
329 }
330
331 static int
ssdfb_spi_cmd_4wire(void * cookie,uint8_t * cmd,size_t len,bool usepoll)332 ssdfb_spi_cmd_4wire(void *cookie, uint8_t *cmd, size_t len, bool usepoll)
333 {
334 struct ssdfb_spi_softc *sc = (struct ssdfb_spi_softc *)cookie;
335 int error;
336
337 ssdfb_spi_4wire_set_dc(sc, 0);
338 error = spi_send(sc->sc_sh, 1, cmd);
339 if (error)
340 return error;
341 if (len > 1) {
342 if (!sc->sc_late_dc_deassert)
343 ssdfb_spi_4wire_set_dc(sc, 1);
344 len--;
345 cmd++;
346 error = spi_send(sc->sc_sh, len, cmd);
347 if (error)
348 return error;
349 }
350
351 return 0;
352 }
353
354 static int
ssdfb_spi_xfer_rect_4wire_sh1106(void * cookie,uint8_t fromcol,uint8_t tocol,uint8_t frompage,uint8_t topage,uint8_t * p,size_t stride,bool usepoll)355 ssdfb_spi_xfer_rect_4wire_sh1106(void *cookie, uint8_t fromcol, uint8_t tocol,
356 uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll)
357 {
358 struct ssdfb_spi_softc *sc = (struct ssdfb_spi_softc *)cookie;
359 size_t rlen = tocol + 1 - fromcol;
360 int error;
361 uint8_t cmd[] = {
362 SSDFB_CMD_SET_PAGE_START_ADDRESS_BASE + frompage,
363 SSDFB_CMD_SET_HIGHER_COLUMN_START_ADDRESS_BASE + (fromcol >> 4),
364 SSDFB_CMD_SET_LOWER_COLUMN_START_ADDRESS_BASE + (fromcol & 0xf)
365 };
366
367 if (usepoll && !cold)
368 return 0;
369
370 while (frompage <= topage) {
371 cmd[0] = SSDFB_CMD_SET_PAGE_START_ADDRESS_BASE + frompage;
372 ssdfb_spi_4wire_set_dc(sc, 0);
373 error = spi_send(sc->sc_sh, sizeof(cmd), cmd);
374 if (error)
375 return error;
376 ssdfb_spi_4wire_set_dc(sc, 1);
377 error = spi_send(sc->sc_sh, rlen, p);
378 if (error)
379 return error;
380 frompage++;
381 p += stride;
382 }
383
384 return 0;
385 }
386
387 static int
ssdfb_spi_xfer_rect_4wire_ssd1306(void * cookie,uint8_t fromcol,uint8_t tocol,uint8_t frompage,uint8_t topage,uint8_t * p,size_t stride,bool usepoll)388 ssdfb_spi_xfer_rect_4wire_ssd1306(void *cookie, uint8_t fromcol, uint8_t tocol,
389 uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll)
390 {
391 struct ssdfb_spi_softc *sc = (struct ssdfb_spi_softc *)cookie;
392 size_t rlen = tocol + 1 - fromcol;
393 int error;
394 uint8_t cmd[] = {
395 SSD1306_CMD_SET_MEMORY_ADDRESSING_MODE,
396 SSD1306_MEMORY_ADDRESSING_MODE_HORIZONTAL,
397 SSD1306_CMD_SET_COLUMN_ADDRESS,
398 fromcol,
399 tocol,
400 SSD1306_CMD_SET_PAGE_ADDRESS,
401 frompage,
402 topage
403 };
404
405 if (usepoll && !cold)
406 return 0;
407
408 ssdfb_spi_4wire_set_dc(sc, 0);
409 error = spi_send(sc->sc_sh, sizeof(cmd), cmd);
410 if (error)
411 return error;
412 ssdfb_spi_4wire_set_dc(sc, 1);
413
414 while (frompage <= topage) {
415 error = spi_send(sc->sc_sh, rlen, p);
416 if (error)
417 return error;
418 frompage++;
419 p += stride;
420 }
421
422 return 0;
423 }
424
425 static int
ssdfb_spi_xfer_rect_4wire_ssd1322(void * cookie,uint8_t fromcol,uint8_t tocol,uint8_t fromrow,uint8_t torow,uint8_t * p,size_t stride,bool usepoll)426 ssdfb_spi_xfer_rect_4wire_ssd1322(void *cookie, uint8_t fromcol, uint8_t tocol,
427 uint8_t fromrow, uint8_t torow, uint8_t *p, size_t stride, bool usepoll)
428 {
429 struct ssdfb_spi_softc *sc = (struct ssdfb_spi_softc *)cookie;
430 size_t rlen = (tocol + 1 - fromcol) * 2;
431 int error;
432 uint8_t cmd;
433 uint8_t data[2];
434
435 if (usepoll && !cold)
436 return 0;
437
438 ssdfb_spi_4wire_set_dc(sc, 0);
439 cmd = SSD1322_CMD_SET_ROW_ADDRESS;
440 error = spi_send(sc->sc_sh, sizeof(cmd), &cmd);
441 if (error)
442 return error;
443 ssdfb_spi_4wire_set_dc(sc, 1);
444 data[0] = fromrow;
445 data[1] = torow;
446 error = spi_send(sc->sc_sh, sizeof(data), data);
447 if (error)
448 return error;
449
450 ssdfb_spi_4wire_set_dc(sc, 0);
451 cmd = SSD1322_CMD_SET_COLUMN_ADDRESS;
452 error = spi_send(sc->sc_sh, sizeof(cmd), &cmd);
453 if (error)
454 return error;
455 ssdfb_spi_4wire_set_dc(sc, 1);
456 data[0] = fromcol;
457 data[1] = tocol;
458 error = spi_send(sc->sc_sh, sizeof(data), data);
459 if (error)
460 return error;
461
462 ssdfb_spi_4wire_set_dc(sc, 0);
463 cmd = SSD1322_CMD_WRITE_RAM;
464 error = spi_send(sc->sc_sh, sizeof(cmd), &cmd);
465 if (error)
466 return error;
467
468 ssdfb_spi_4wire_set_dc(sc, 1);
469 while (fromrow <= torow) {
470 error = spi_send(sc->sc_sh, rlen, p);
471 if (error)
472 return error;
473 fromrow++;
474 p += stride;
475 }
476
477 return 0;
478 }
479
480 static int
ssdfb_spi_xfer_rect_4wire_ssd1353(void * cookie,uint8_t fromcol,uint8_t tocol,uint8_t fromrow,uint8_t torow,uint8_t * p,size_t stride,bool usepoll)481 ssdfb_spi_xfer_rect_4wire_ssd1353(void *cookie, uint8_t fromcol, uint8_t tocol,
482 uint8_t fromrow, uint8_t torow, uint8_t *p, size_t stride, bool usepoll)
483 {
484 struct ssdfb_spi_softc *sc = (struct ssdfb_spi_softc *)cookie;
485 size_t rlen = (tocol + 1 - fromcol) * 3;
486 uint8_t bitstream[160 * 3];
487 uint8_t *dstp, *srcp, *endp;
488 int error;
489 uint8_t cmd;
490 uint8_t data[2];
491
492 if (usepoll && !cold)
493 return 0;
494
495 ssdfb_spi_4wire_set_dc(sc, 0);
496 cmd = SSD1353_CMD_SET_ROW_ADDRESS;
497 error = spi_send(sc->sc_sh, sizeof(cmd), &cmd);
498 if (error)
499 return error;
500 ssdfb_spi_4wire_set_dc(sc, 1);
501 data[0] = fromrow;
502 data[1] = torow;
503 if (sc->sc.sc_upsidedown) {
504 /* fix picture outside frame on 160x128 panel */
505 data[0] += 132 - sc->sc.sc_p->p_height;
506 data[1] += 132 - sc->sc.sc_p->p_height;
507 }
508 error = spi_send(sc->sc_sh, sizeof(data), data);
509 if (error)
510 return error;
511
512 ssdfb_spi_4wire_set_dc(sc, 0);
513 cmd = SSD1353_CMD_SET_COLUMN_ADDRESS;
514 error = spi_send(sc->sc_sh, sizeof(cmd), &cmd);
515 if (error)
516 return error;
517 ssdfb_spi_4wire_set_dc(sc, 1);
518 data[0] = fromcol;
519 data[1] = tocol;
520 error = spi_send(sc->sc_sh, sizeof(data), data);
521 if (error)
522 return error;
523
524 ssdfb_spi_4wire_set_dc(sc, 0);
525 cmd = SSD1353_CMD_WRITE_RAM;
526 error = spi_send(sc->sc_sh, sizeof(cmd), &cmd);
527 if (error)
528 return error;
529
530 ssdfb_spi_4wire_set_dc(sc, 1);
531 KASSERT(rlen <= sizeof(bitstream));
532 while (fromrow <= torow) {
533 /* downconvert each row from 32bpp rgba to 18bpp panel format */
534 dstp = bitstream;
535 endp = dstp + rlen;
536 srcp = p;
537 while (dstp < endp) {
538 *dstp++ = (*srcp++) >> 2;
539 *dstp++ = (*srcp++) >> 2;
540 *dstp++ = (*srcp++) >> 2;
541 srcp++;
542 }
543 error = spi_send(sc->sc_sh, rlen, bitstream);
544 if (error)
545 return error;
546 fromrow++;
547 p += stride;
548 }
549
550 return 0;
551 }
552