xref: /netbsd-src/sys/dev/spi/ssdfb_spi.c (revision 490e40c2193e7b89b98f63d459b36f56578bb5bb)
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