xref: /openbsd/sys/dev/fdt/ssdfb.c (revision 76d0caae)
1 /* $OpenBSD: ssdfb.c,v 1.12 2021/10/24 17:52:27 mpi Exp $ */
2 /*
3  * Copyright (c) 2018 Patrick Wildt <patrick@blueri.se>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/kernel.h>
21 #include <sys/device.h>
22 #include <sys/malloc.h>
23 #include <sys/stdint.h>
24 
25 #include <uvm/uvm_extern.h>
26 
27 #include <dev/i2c/i2cvar.h>
28 #include <dev/spi/spivar.h>
29 #include <dev/usb/udlio.h>
30 
31 #include <dev/ofw/openfirm.h>
32 #include <dev/ofw/ofw_gpio.h>
33 #include <dev/ofw/ofw_pinctrl.h>
34 
35 #include <dev/wscons/wsconsio.h>
36 #include <dev/wscons/wsdisplayvar.h>
37 #include <dev/rasops/rasops.h>
38 
39 #define SSDFB_SET_LOWER_COLUMN_START_ADDRESS	0x00
40 #define SSDFB_SET_HIGHER_COLUMN_START_ADDRESS	0x10
41 #define SSDFB_SET_MEMORY_ADDRESSING_MODE	0x20
42 #define SSDFB_SET_COLUMN_RANGE			0x21
43 #define SSDFB_SET_PAGE_RANGE			0x22
44 #define SSDFB_SET_START_LINE			0x40
45 #define SSDFB_SET_CONTRAST_CONTROL		0x81
46 #define SSDFB_CHARGE_PUMP			0x8d
47 #define SSDFB_SET_COLUMN_DIRECTION_NORMAL	0xa0
48 #define SSDFB_SET_COLUMN_DIRECTION_REVERSE	0xa1
49 #define SSDFB_SET_MULTIPLEX_RATIO		0xa8
50 #define SSDFB_SET_COM_OUTPUT_DIRECTION_NORMAL	0xc0
51 #define SSDFB_SET_COM_OUTPUT_DIRECTION_REMAP	0xc8
52 #define SSDFB_ENTIRE_DISPLAY_ON			0xa4
53 #define SSDFB_SET_DISPLAY_MODE_NORMAL		0xa6
54 #define SSDFB_SET_DISPLAY_MODE_INVERS		0xa7
55 #define SSDFB_SET_DISPLAY_OFF			0xae
56 #define SSDFB_SET_DISPLAY_ON			0xaf
57 #define SSDFB_SET_DISPLAY_OFFSET		0xd3
58 #define SSDFB_SET_DISPLAY_CLOCK_DIVIDE_RATIO	0xd5
59 #define SSDFB_SET_PRE_CHARGE_PERIOD		0xd9
60 #define SSDFB_SET_COM_PINS_HARD_CONF		0xda
61 #define SSDFB_SET_VCOM_DESELECT_LEVEL		0xdb
62 #define SSDFB_SET_PAGE_START_ADDRESS		0xb0
63 
64 #define SSDFB_I2C_COMMAND			0x00
65 #define SSDFB_I2C_DATA				0x40
66 
67 struct ssdfb_softc {
68 	struct device		 sc_dev;
69 	int			 sc_node;
70 	int			 sc_width;
71 	int			 sc_height;
72 	int			 sc_pgoff;
73 
74 	uint8_t			*sc_fb;
75 	size_t			 sc_fbsize;
76 	struct rasops_info	 sc_rinfo;
77 	struct wsdisplay_emulops sc_riops;
78 	int			 (*sc_ri_do_cursor)(struct rasops_info *);
79 
80 	uint8_t			 sc_brightness;
81 	int			 sc_mode;
82 
83 	uint8_t			 sc_column_range[2];
84 	uint8_t			 sc_page_range[2];
85 
86 	/* I2C */
87 	i2c_tag_t		 sc_i2c_tag;
88 	i2c_addr_t		 sc_i2c_addr;
89 
90 	/* SPI */
91 	spi_tag_t		 sc_spi_tag;
92 	struct spi_config	 sc_spi_conf;
93 	uint32_t		*sc_gpio;
94 	size_t			 sc_gpiolen;
95 	int			 sc_cd;
96 
97 	void			 (*sc_write_command)(struct ssdfb_softc *,
98 				   char *, size_t);
99 	void			 (*sc_write_data)(struct ssdfb_softc *,
100 				   char *, size_t);
101 
102 };
103 
104 int	 ssdfb_i2c_match(struct device *, void *, void *);
105 void	 ssdfb_i2c_attach(struct device *, struct device *, void *);
106 int	 ssdfb_i2c_detach(struct device *, int);
107 void	 ssdfb_i2c_write_command(struct ssdfb_softc *, char *, size_t);
108 void	 ssdfb_i2c_write_data(struct ssdfb_softc *, char *, size_t);
109 
110 int	 ssdfb_spi_match(struct device *, void *, void *);
111 void	 ssdfb_spi_attach(struct device *, struct device *, void *);
112 int	 ssdfb_spi_detach(struct device *, int);
113 void	 ssdfb_spi_write_command(struct ssdfb_softc *, char *, size_t);
114 void	 ssdfb_spi_write_data(struct ssdfb_softc *, char *, size_t);
115 
116 void	 ssdfb_attach(struct ssdfb_softc *);
117 int	 ssdfb_detach(struct ssdfb_softc *, int);
118 void	 ssdfb_write_command(struct ssdfb_softc *, char *, size_t);
119 void	 ssdfb_write_data(struct ssdfb_softc *, char *, size_t);
120 
121 void	 ssdfb_init(struct ssdfb_softc *);
122 void	 ssdfb_update(void *);
123 
124 void	 ssdfb_partial(struct ssdfb_softc *, uint32_t, uint32_t,
125 	    uint32_t, uint32_t);
126 void	 ssdfb_set_range(struct ssdfb_softc *, uint8_t, uint8_t,
127 	    uint8_t, uint8_t);
128 
129 int	 ssdfb_ioctl(void *, u_long, caddr_t, int, struct proc *);
130 paddr_t	 ssdfb_mmap(void *, off_t, int);
131 int	 ssdfb_alloc_screen(void *, const struct wsscreen_descr *, void **,
132 	    int *, int *, uint32_t *);
133 void	 ssdfb_free_screen(void *, void *);
134 int	 ssdfb_show_screen(void *, void *, int, void (*cb) (void *, int, int),
135 	    void *);
136 int	 ssdfb_list_font(void *, struct wsdisplay_font *);
137 int	 ssdfb_load_font(void *, void *, struct wsdisplay_font *);
138 
139 int	 ssdfb_putchar(void *, int, int, u_int, uint32_t);
140 int	 ssdfb_copycols(void *, int, int, int, int);
141 int	 ssdfb_erasecols(void *, int, int, int, uint32_t);
142 int	 ssdfb_copyrows(void *, int, int, int);
143 int	 ssdfb_eraserows(void *, int, int, uint32_t);
144 int	 ssdfb_do_cursor(struct rasops_info *);
145 
146 const struct cfattach ssdfb_i2c_ca = {
147 	sizeof(struct ssdfb_softc),
148 	ssdfb_i2c_match,
149 	ssdfb_i2c_attach,
150 	ssdfb_i2c_detach,
151 };
152 
153 const struct cfattach ssdfb_spi_ca = {
154 	sizeof(struct ssdfb_softc),
155 	ssdfb_spi_match,
156 	ssdfb_spi_attach,
157 	ssdfb_spi_detach,
158 };
159 
160 struct cfdriver ssdfb_cd = {
161 	NULL, "ssdfb", DV_DULL
162 };
163 
164 struct wsscreen_descr ssdfb_std_descr = { "std" };
165 
166 const struct wsscreen_descr *ssdfb_descrs[] = {
167 	&ssdfb_std_descr
168 };
169 
170 const struct wsscreen_list ssdfb_screen_list = {
171 	nitems(ssdfb_descrs), ssdfb_descrs
172 };
173 
174 struct wsdisplay_accessops ssdfb_accessops = {
175 	.ioctl = ssdfb_ioctl,
176 	.mmap = ssdfb_mmap,
177 	.alloc_screen = ssdfb_alloc_screen,
178 	.free_screen = ssdfb_free_screen,
179 	.show_screen = ssdfb_show_screen,
180 	.load_font = ssdfb_load_font,
181 	.list_font = ssdfb_list_font
182 };
183 
184 int
185 ssdfb_i2c_match(struct device *parent, void *match, void *aux)
186 {
187 	struct i2c_attach_args *ia = aux;
188 
189 	if (strcmp(ia->ia_name, "solomon,ssd1306fb-i2c") == 0 ||
190 	    strcmp(ia->ia_name, "solomon,ssd1309fb-i2c") == 0)
191 		return 1;
192 
193 	return 0;
194 }
195 
196 void
197 ssdfb_i2c_attach(struct device *parent, struct device *self, void *aux)
198 {
199 	struct ssdfb_softc *sc = (struct ssdfb_softc *)self;
200 	struct i2c_attach_args *ia = aux;
201 
202 	sc->sc_i2c_tag = ia->ia_tag;
203 	sc->sc_i2c_addr = ia->ia_addr;
204 	sc->sc_node = *(int *)ia->ia_cookie;
205 
206 	sc->sc_write_command = ssdfb_i2c_write_command;
207 	sc->sc_write_data = ssdfb_i2c_write_data;
208 
209 	ssdfb_attach(sc);
210 }
211 
212 int
213 ssdfb_i2c_detach(struct device *self, int flags)
214 {
215 	struct ssdfb_softc *sc = (struct ssdfb_softc *)self;
216 	ssdfb_detach(sc, flags);
217 	return 0;
218 }
219 
220 int
221 ssdfb_spi_match(struct device *parent, void *match, void *aux)
222 {
223 	struct spi_attach_args *sa = aux;
224 
225 	if (strcmp(sa->sa_name, "solomon,ssd1309fb-spi") == 0)
226 		return 1;
227 
228 	return 0;
229 }
230 
231 void
232 ssdfb_spi_attach(struct device *parent, struct device *self, void *aux)
233 {
234 	struct ssdfb_softc *sc = (struct ssdfb_softc *)self;
235 	struct spi_attach_args *sa = aux;
236 	ssize_t len;
237 
238 	sc->sc_spi_tag = sa->sa_tag;
239 	sc->sc_node = *(int *)sa->sa_cookie;
240 
241 	sc->sc_spi_conf.sc_bpw = 8;
242 	sc->sc_spi_conf.sc_freq = 1000 * 1000;
243 	sc->sc_spi_conf.sc_cs = OF_getpropint(sc->sc_node, "reg", 0);
244 	if (OF_getproplen(sc->sc_node, "spi-cpol") == 0)
245 		sc->sc_spi_conf.sc_flags |= SPI_CONFIG_CPOL;
246 	if (OF_getproplen(sc->sc_node, "spi-cpha") == 0)
247 		sc->sc_spi_conf.sc_flags |= SPI_CONFIG_CPHA;
248 	if (OF_getproplen(sc->sc_node, "spi-cs-high") == 0)
249 		sc->sc_spi_conf.sc_flags |= SPI_CONFIG_CS_HIGH;
250 
251 	len = OF_getproplen(sc->sc_node, "cd-gpio");
252 	if (len <= 0)
253 		return;
254 
255 	sc->sc_gpio = malloc(len, M_DEVBUF, M_WAITOK);
256 	OF_getpropintarray(sc->sc_node, "cd-gpio", sc->sc_gpio, len);
257 	sc->sc_gpiolen = len;
258 	gpio_controller_config_pin(sc->sc_gpio, GPIO_CONFIG_OUTPUT);
259 	gpio_controller_set_pin(sc->sc_gpio, 0);
260 
261 	sc->sc_write_command = ssdfb_spi_write_command;
262 	sc->sc_write_data = ssdfb_spi_write_data;
263 
264 	ssdfb_attach(sc);
265 }
266 
267 int
268 ssdfb_spi_detach(struct device *self, int flags)
269 {
270 	struct ssdfb_softc *sc = (struct ssdfb_softc *)self;
271 	ssdfb_detach(sc, flags);
272 	free(sc->sc_gpio, M_DEVBUF, sc->sc_gpiolen);
273 	return 0;
274 }
275 
276 void
277 ssdfb_attach(struct ssdfb_softc *sc)
278 {
279 	struct wsemuldisplaydev_attach_args aa;
280 	struct rasops_info *ri;
281 	uint32_t *gpio;
282 	ssize_t len;
283 
284 	pinctrl_byname(sc->sc_node, "default");
285 
286 	len = OF_getproplen(sc->sc_node, "reset-gpios");
287 	if (len > 0) {
288 		gpio = malloc(len, M_DEVBUF, M_WAITOK);
289 		OF_getpropintarray(sc->sc_node, "reset-gpios",
290 		    gpio, len);
291 		gpio_controller_config_pin(gpio, GPIO_CONFIG_OUTPUT);
292 		gpio_controller_set_pin(gpio, 1);
293 		delay(100 * 1000);
294 		gpio_controller_set_pin(gpio, 0);
295 		delay(1000 * 1000);
296 		free(gpio, M_DEVBUF, len);
297 	}
298 
299 	sc->sc_width = OF_getpropint(sc->sc_node, "solomon,width", 96);
300 	sc->sc_height = OF_getpropint(sc->sc_node, "solomon,height", 16);
301 	sc->sc_pgoff = OF_getpropint(sc->sc_node, "solomon,page-offset", 1);
302 
303 	sc->sc_fbsize = round_page((sc->sc_width * sc->sc_height) / 8);
304 	sc->sc_fb = malloc(sc->sc_fbsize, M_DEVBUF, M_WAITOK | M_ZERO);
305 
306 	sc->sc_brightness = 223;
307 
308 	ri = &sc->sc_rinfo;
309 	ri->ri_bits = malloc(sc->sc_fbsize, M_DEVBUF, M_WAITOK | M_ZERO);
310 	ri->ri_bs = mallocarray(sc->sc_width * sc->sc_height,
311 	    sizeof(struct wsdisplay_charcell), M_DEVBUF, M_WAITOK | M_ZERO);
312 	ri->ri_flg = RI_CLEAR | RI_VCONS;
313 	ri->ri_depth = 1;
314 	ri->ri_width = sc->sc_width;
315 	ri->ri_height = sc->sc_height;
316 	ri->ri_stride = ri->ri_width * ri->ri_depth / 8;
317 	ri->ri_hw = sc;
318 
319 	rasops_init(ri, sc->sc_height, sc->sc_width);
320 	ssdfb_std_descr.ncols = ri->ri_cols;
321 	ssdfb_std_descr.nrows = ri->ri_rows;
322 	ssdfb_std_descr.textops = &ri->ri_ops;
323 	ssdfb_std_descr.fontwidth = ri->ri_font->fontwidth;
324 	ssdfb_std_descr.fontheight = ri->ri_font->fontheight;
325 	ssdfb_std_descr.capabilities = ri->ri_caps;
326 
327 	sc->sc_riops.putchar = ri->ri_putchar;
328 	sc->sc_riops.copycols = ri->ri_copycols;
329 	sc->sc_riops.erasecols = ri->ri_erasecols;
330 	sc->sc_riops.copyrows = ri->ri_copyrows;
331 	sc->sc_riops.eraserows = ri->ri_eraserows;
332 	sc->sc_ri_do_cursor = ri->ri_do_cursor;
333 
334 	ri->ri_putchar = ssdfb_putchar;
335 	ri->ri_copycols = ssdfb_copycols;
336 	ri->ri_erasecols = ssdfb_erasecols;
337 	ri->ri_copyrows = ssdfb_copyrows;
338 	ri->ri_eraserows = ssdfb_eraserows;
339 	ri->ri_do_cursor = ssdfb_do_cursor;
340 
341 	printf(": %dx%d, %dbpp\n", ri->ri_width, ri->ri_height, ri->ri_depth);
342 
343 	memset(&aa, 0, sizeof(aa));
344 	aa.console = 0;
345 	aa.scrdata = &ssdfb_screen_list;
346 	aa.accessops = &ssdfb_accessops;
347 	aa.accesscookie = sc;
348 	aa.defaultscreens = 0;
349 
350 	config_found_sm(&sc->sc_dev, &aa, wsemuldisplaydevprint,
351 	    wsemuldisplaydevsubmatch);
352 	ssdfb_init(sc);
353 }
354 
355 int
356 ssdfb_detach(struct ssdfb_softc *sc, int flags)
357 {
358 	struct rasops_info *ri = &sc->sc_rinfo;
359 	free(ri->ri_bs, M_DEVBUF, sc->sc_width * sc->sc_height *
360 	    sizeof(struct wsdisplay_charcell));
361 	free(ri->ri_bits, M_DEVBUF, sc->sc_fbsize);
362 	free(sc->sc_fb, M_DEVBUF, sc->sc_fbsize);
363 	return 0;
364 }
365 
366 void
367 ssdfb_init(struct ssdfb_softc *sc)
368 {
369 	uint8_t reg[2];
370 
371 	reg[0] = SSDFB_SET_DISPLAY_OFF;
372 	ssdfb_write_command(sc, reg, 1);
373 
374 	reg[0] = SSDFB_SET_MEMORY_ADDRESSING_MODE;
375 	reg[1] = 0x00; /* Horizontal Addressing Mode */
376 	ssdfb_write_command(sc, reg, 2);
377 	reg[0] = SSDFB_SET_PAGE_START_ADDRESS;
378 	ssdfb_write_command(sc, reg, 1);
379 	ssdfb_set_range(sc, 0, sc->sc_width - 1,
380 	    0, (sc->sc_height / 8) - 1);
381 	if (OF_is_compatible(sc->sc_node, "solomon,ssd1309fb-i2c") ||
382 	    OF_is_compatible(sc->sc_node, "solomon,ssd1309fb-spi")) {
383 		reg[0] = SSDFB_SET_DISPLAY_CLOCK_DIVIDE_RATIO;
384 		reg[1] = 0xa0;
385 		ssdfb_write_command(sc, reg, 2);
386 	}
387 	if (OF_is_compatible(sc->sc_node, "solomon,ssd1306fb-i2c")) {
388 		reg[0] = SSDFB_SET_DISPLAY_CLOCK_DIVIDE_RATIO;
389 		reg[1] = 0x80;
390 		ssdfb_write_command(sc, reg, 2);
391 	}
392 	reg[0] = SSDFB_SET_MULTIPLEX_RATIO;
393 	reg[1] = 0x3f;
394 	ssdfb_write_command(sc, reg, 2);
395 	reg[0] = SSDFB_SET_DISPLAY_OFFSET;
396 	reg[1] = OF_getpropint(sc->sc_node, "solomon,com-offset", 0);
397 	ssdfb_write_command(sc, reg, 2);
398 	reg[0] = SSDFB_SET_START_LINE | 0x00;
399 	ssdfb_write_command(sc, reg, 1);
400 	reg[0] = SSDFB_SET_COLUMN_DIRECTION_NORMAL;
401 	if (OF_getproplen(sc->sc_node, "solomon,com-invdir") == 0)
402 		reg[0] = SSDFB_SET_COLUMN_DIRECTION_REVERSE;
403 	ssdfb_write_command(sc, reg, 1);
404 	reg[0] = SSDFB_SET_COM_OUTPUT_DIRECTION_REMAP;
405 	if (OF_getproplen(sc->sc_node, "solomon,segment-no-remap") == 0)
406 		reg[0] = SSDFB_SET_COM_OUTPUT_DIRECTION_NORMAL;
407 	ssdfb_write_command(sc, reg, 1);
408 	reg[0] = SSDFB_SET_COM_PINS_HARD_CONF;
409 	reg[1] = 0x12;
410 	if (OF_getproplen(sc->sc_node, "solomon,com-seq") == 0)
411 		reg[1] &= ~(1 << 4);
412 	if (OF_getproplen(sc->sc_node, "solomon,com-lrremap") == 0)
413 		reg[1] |= 1 << 5;
414 	ssdfb_write_command(sc, reg, 2);
415 	reg[0] = SSDFB_SET_CONTRAST_CONTROL;
416 	reg[1] = sc->sc_brightness;
417 	ssdfb_write_command(sc, reg, 2);
418 	reg[0] = SSDFB_SET_PRE_CHARGE_PERIOD;
419 	reg[1] = (OF_getpropint(sc->sc_node, "solomon,prechargep1", 2) & 0xf) << 0;
420 	reg[1] |= (OF_getpropint(sc->sc_node, "solomon,prechargep2", 2) & 0xf) << 4;
421 	ssdfb_write_command(sc, reg, 2);
422 	if (OF_is_compatible(sc->sc_node, "solomon,ssd1309fb-i2c") ||
423 	    OF_is_compatible(sc->sc_node, "solomon,ssd1309fb-spi")) {
424 		reg[0] = SSDFB_SET_VCOM_DESELECT_LEVEL;
425 		reg[1] = 0x34;
426 		ssdfb_write_command(sc, reg, 2);
427 	}
428 	if (OF_is_compatible(sc->sc_node, "solomon,ssd1306fb-i2c")) {
429 		reg[0] = SSDFB_SET_VCOM_DESELECT_LEVEL;
430 		reg[1] = 0x20;
431 		ssdfb_write_command(sc, reg, 2);
432 	}
433 	reg[0] = SSDFB_CHARGE_PUMP;
434 	reg[1] = 0x10;
435 	if (OF_is_compatible(sc->sc_node, "solomon,ssd1306fb-i2c"))
436 		reg[1] |= 1 << 2;
437 	ssdfb_write_command(sc, reg, 2);
438 	reg[0] = SSDFB_ENTIRE_DISPLAY_ON;
439 	ssdfb_write_command(sc, reg, 1);
440 	reg[0] = SSDFB_SET_DISPLAY_MODE_NORMAL;
441 	ssdfb_write_command(sc, reg, 1);
442 
443 	ssdfb_partial(sc, 0, sc->sc_width, 0, sc->sc_height);
444 
445 	reg[0] = SSDFB_SET_DISPLAY_ON;
446 	ssdfb_write_command(sc, reg, 1);
447 }
448 
449 void
450 ssdfb_set_range(struct ssdfb_softc *sc, uint8_t x1, uint8_t x2,
451     uint8_t y1, uint8_t y2)
452 {
453 	uint8_t reg[3];
454 
455 	y1 += sc->sc_pgoff;
456 	y2 += sc->sc_pgoff;
457 
458 	if (sc->sc_column_range[0] != x1 || sc->sc_column_range[1] != x2) {
459 		sc->sc_column_range[0] = x1;
460 		sc->sc_column_range[1] = x2;
461 		reg[0] = SSDFB_SET_COLUMN_RANGE;
462 		reg[1] = sc->sc_column_range[0];
463 		reg[2] = sc->sc_column_range[1];
464 		ssdfb_write_command(sc, reg, 3);
465 	}
466 	if (sc->sc_page_range[0] != y1 || sc->sc_page_range[1] != y2) {
467 		sc->sc_page_range[0] = y1;
468 		sc->sc_page_range[1] = y2;
469 		reg[0] = SSDFB_SET_PAGE_RANGE;
470 		reg[1] = sc->sc_page_range[0];
471 		reg[2] = sc->sc_page_range[1];
472 		ssdfb_write_command(sc, reg, 3);
473 	}
474 }
475 
476 void
477 ssdfb_partial(struct ssdfb_softc *sc, uint32_t x1, uint32_t x2,
478     uint32_t y1, uint32_t y2)
479 {
480 	struct rasops_info *ri = &sc->sc_rinfo;
481 	uint32_t off, width, height;
482 	uint8_t *bit, val;
483 	int i, j, k;
484 
485 	if (x2 < x1 || y2 < y1)
486 		return;
487 
488 	if (x2 > sc->sc_width || y2 > sc->sc_height)
489 		return;
490 
491 	y1 = y1 & ~0x7;
492 	y2 = roundup(y2, 8);
493 
494 	width = x2 - x1;
495 	height = y2 - y1;
496 
497 	memset(sc->sc_fb, 0, (width * height) / 8);
498 
499 	for (i = 0; i < height; i += 8) {
500 		for (j = 0; j < width; j++) {
501 			bit = &sc->sc_fb[(i / 8) * width + j];
502 			for (k = 0; k < 8; k++) {
503 				off = ri->ri_stride * (y1 + i + k);
504 				off += (x1 + j) / 8;
505 				val = *(ri->ri_bits + off);
506 				val &= (1 << ((x1 + j) % 8));
507 				*bit |= !!val << k;
508 			}
509 		}
510 	}
511 
512 	ssdfb_set_range(sc, x1, x2 - 1, y1 / 8, (y2 / 8) - 1);
513 	ssdfb_write_data(sc, sc->sc_fb, (width * height) / 8);
514 }
515 
516 void
517 ssdfb_write_command(struct ssdfb_softc *sc, char *buf, size_t len)
518 {
519 	return sc->sc_write_command(sc, buf, len);
520 }
521 
522 void
523 ssdfb_write_data(struct ssdfb_softc *sc, char *buf, size_t len)
524 {
525 	return sc->sc_write_data(sc, buf, len);
526 }
527 
528 void
529 ssdfb_i2c_write_command(struct ssdfb_softc *sc, char *buf, size_t len)
530 {
531 	uint8_t type;
532 
533 	type = SSDFB_I2C_COMMAND;
534 	iic_acquire_bus(sc->sc_i2c_tag, 0);
535 	if (iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
536 	    sc->sc_i2c_addr, &type, sizeof(type), buf, len, 0)) {
537 		printf("%s: cannot write\n", sc->sc_dev.dv_xname);
538 	}
539 	iic_release_bus(sc->sc_i2c_tag, 0);
540 }
541 
542 void
543 ssdfb_i2c_write_data(struct ssdfb_softc *sc, char *buf, size_t len)
544 {
545 	uint8_t type;
546 
547 	type = SSDFB_I2C_DATA;
548 	iic_acquire_bus(sc->sc_i2c_tag, 0);
549 	if (iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
550 	    sc->sc_i2c_addr, &type, sizeof(type), buf, len, 0)) {
551 		printf("%s: cannot write\n", sc->sc_dev.dv_xname);
552 	}
553 	iic_release_bus(sc->sc_i2c_tag, 0);
554 }
555 
556 void
557 ssdfb_spi_write_command(struct ssdfb_softc *sc, char *buf, size_t len)
558 {
559 	if (sc->sc_cd != 0) {
560 		gpio_controller_set_pin(sc->sc_gpio, 0);
561 		sc->sc_cd = 0;
562 		delay(1);
563 	}
564 
565 	spi_acquire_bus(sc->sc_spi_tag, 0);
566 	spi_config(sc->sc_spi_tag, &sc->sc_spi_conf);
567 	if (spi_write(sc->sc_spi_tag, buf, len))
568 		printf("%s: cannot write\n", sc->sc_dev.dv_xname);
569 	spi_release_bus(sc->sc_spi_tag, 0);
570 }
571 
572 void
573 ssdfb_spi_write_data(struct ssdfb_softc *sc, char *buf, size_t len)
574 {
575 	if (sc->sc_cd != 1) {
576 		gpio_controller_set_pin(sc->sc_gpio, 1);
577 		sc->sc_cd = 1;
578 		delay(1);
579 	}
580 
581 	spi_acquire_bus(sc->sc_spi_tag, 0);
582 	spi_config(sc->sc_spi_tag, &sc->sc_spi_conf);
583 	if (spi_write(sc->sc_spi_tag, buf, len))
584 		printf("%s: cannot write\n", sc->sc_dev.dv_xname);
585 	spi_release_bus(sc->sc_spi_tag, 0);
586 }
587 
588 int
589 ssdfb_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
590 {
591 	struct ssdfb_softc	*sc = v;
592 	struct rasops_info 	*ri = &sc->sc_rinfo;
593 	struct wsdisplay_param	*dp = (struct wsdisplay_param *)data;
594 	struct wsdisplay_fbinfo	*wdf;
595 	struct udl_ioctl_damage *d;
596 	int			 mode;
597 	uint8_t			 reg[2];
598 
599 	switch (cmd) {
600 	case WSDISPLAYIO_GETPARAM:
601 		switch (dp->param) {
602 		case WSDISPLAYIO_PARAM_BRIGHTNESS:
603 			dp->min = 0;
604 			dp->max = 255;
605 			dp->curval = sc->sc_brightness;
606 			break;
607 		default:
608 			return (-1);
609 		}
610 		break;
611 	case WSDISPLAYIO_SETPARAM:
612 		switch (dp->param) {
613 		case WSDISPLAYIO_PARAM_BRIGHTNESS:
614 			if (dp->curval == 0) {
615 				reg[0] = SSDFB_SET_DISPLAY_OFF;
616 				ssdfb_write_command(sc, reg, 1);
617 			} else if (sc->sc_brightness == 0) {
618 				reg[0] = SSDFB_SET_DISPLAY_ON;
619 				ssdfb_write_command(sc, reg, 1);
620 			}
621 			reg[0] = SSDFB_SET_CONTRAST_CONTROL;
622 			reg[1] = sc->sc_brightness = dp->curval;
623 			ssdfb_write_command(sc, reg, 2);
624 			break;
625 		default:
626 			return (-1);
627 		}
628 		break;
629 	case WSDISPLAYIO_GTYPE:
630 		*(u_int *)data = WSDISPLAY_TYPE_DL;
631 		break;
632 	case WSDISPLAYIO_GINFO:
633 		wdf = (struct wsdisplay_fbinfo *)data;
634 		wdf->width = ri->ri_width;
635 		wdf->height = ri->ri_height;
636 		wdf->depth = ri->ri_depth;
637 		wdf->cmsize = 0;	/* color map is unavailable */
638 		break;
639 	case WSDISPLAYIO_LINEBYTES:
640 		*(u_int *)data = ri->ri_stride;
641 		break;
642 	case WSDISPLAYIO_SMODE:
643 		mode = *(u_int *)data;
644 		switch (mode) {
645 		case WSDISPLAYIO_MODE_EMUL:
646 			if (sc->sc_mode != WSDISPLAYIO_MODE_EMUL) {
647 				memset(ri->ri_bits, 0, sc->sc_fbsize);
648 				ssdfb_partial(sc, 0, sc->sc_width,
649 				    0, sc->sc_height);
650 				sc->sc_mode = mode;
651 			}
652 			break;
653 		case WSDISPLAYIO_MODE_DUMBFB:
654 			if (sc->sc_mode != WSDISPLAYIO_MODE_DUMBFB) {
655 				memset(ri->ri_bits, 0, sc->sc_fbsize);
656 				ssdfb_partial(sc, 0, sc->sc_width,
657 				    0, sc->sc_height);
658 				sc->sc_mode = mode;
659 			}
660 			break;
661 		case WSDISPLAYIO_MODE_MAPPED:
662 		default:
663 			return (-1);
664 		}
665 		break;
666 	case WSDISPLAYIO_GETSUPPORTEDDEPTH:
667 		*(u_int *)data = WSDISPLAYIO_DEPTH_1;
668 		break;
669 	case UDLIO_DAMAGE:
670 		d = (struct udl_ioctl_damage *)data;
671 		d->status = UDLIO_STATUS_OK;
672 		ssdfb_partial(sc, d->x1, d->x2, d->y1, d->y2);
673 		break;
674 	default:
675 		return (-1);
676 	}
677 
678 	return (0);
679 }
680 
681 paddr_t
682 ssdfb_mmap(void *v, off_t off, int prot)
683 {
684 	struct ssdfb_softc	*sc = v;
685 	struct rasops_info	*ri = &sc->sc_rinfo;
686 	paddr_t			 pa;
687 
688 	if (off >= sc->sc_fbsize || off < 0)
689 		return (-1);
690 
691 	if (!pmap_extract(pmap_kernel(), (vaddr_t)ri->ri_bits, &pa))
692 		return (-1);
693 
694 	return (pa + off);
695 }
696 
697 int
698 ssdfb_alloc_screen(void *v, const struct wsscreen_descr *descr,
699     void **cookiep, int *curxp, int *curyp, uint32_t *attrp)
700 {
701 	struct ssdfb_softc	*sc = v;
702 	struct rasops_info	*ri = &sc->sc_rinfo;
703 
704 	return rasops_alloc_screen(ri, cookiep, curxp, curyp, attrp);
705 }
706 
707 void
708 ssdfb_free_screen(void *v, void *cookie)
709 {
710 	struct ssdfb_softc	*sc = v;
711 	struct rasops_info	*ri = &sc->sc_rinfo;
712 
713 	rasops_free_screen(ri, cookie);
714 }
715 
716 int
717 ssdfb_show_screen(void *v, void *cookie, int waitok,
718     void (*cb) (void *, int, int), void *cb_arg)
719 {
720 	struct ssdfb_softc	*sc = v;
721 	struct rasops_info	*ri = &sc->sc_rinfo;
722 
723 	return rasops_show_screen(ri, cookie, waitok, cb, cb_arg);
724 }
725 
726 int
727 ssdfb_load_font(void *v, void *cookie, struct wsdisplay_font *font)
728 {
729 	struct ssdfb_softc	*sc = v;
730 	struct rasops_info	*ri = &sc->sc_rinfo;
731 
732 	return (rasops_load_font(ri, cookie, font));
733 }
734 
735 int
736 ssdfb_list_font(void *v, struct wsdisplay_font *font)
737 {
738 	struct ssdfb_softc	*sc = v;
739 	struct rasops_info	*ri = &sc->sc_rinfo;
740 
741 	return (rasops_list_font(ri, font));
742 }
743 
744 int
745 ssdfb_putchar(void *cookie, int row, int col, u_int uc, uint32_t attr)
746 {
747 	struct rasops_info *ri = (struct rasops_info *)cookie;
748 	struct ssdfb_softc *sc = ri->ri_hw;
749 
750 	sc->sc_riops.putchar(cookie, row, col, uc, attr);
751 	ssdfb_partial(sc,
752 	    col * ri->ri_font->fontwidth,
753 	    (col + 1) * ri->ri_font->fontwidth,
754 	    row * ri->ri_font->fontheight,
755 	    (row + 1) * ri->ri_font->fontheight);
756 	return 0;
757 }
758 
759 int
760 ssdfb_copycols(void *cookie, int row, int src, int dst, int num)
761 {
762 	struct rasops_info *ri = (struct rasops_info *)cookie;
763 	struct ssdfb_softc *sc = ri->ri_hw;
764 
765 	sc->sc_riops.copycols(cookie, row, src, dst, num);
766 	ssdfb_partial(sc,
767 	    dst * ri->ri_font->fontwidth,
768 	    (dst + num) * ri->ri_font->fontwidth,
769 	    row * ri->ri_font->fontheight,
770 	    (row + 1) * ri->ri_font->fontheight);
771 	return 0;
772 }
773 
774 int
775 ssdfb_erasecols(void *cookie, int row, int col, int num, uint32_t attr)
776 {
777 	struct rasops_info *ri = (struct rasops_info *)cookie;
778 	struct ssdfb_softc *sc = ri->ri_hw;
779 
780 	sc->sc_riops.erasecols(cookie, row, col, num, attr);
781 	ssdfb_partial(sc,
782 	    col * ri->ri_font->fontwidth,
783 	    (col + num) * ri->ri_font->fontwidth,
784 	    row * ri->ri_font->fontheight,
785 	    (row + 1) * ri->ri_font->fontheight);
786 	return 0;
787 }
788 
789 int
790 ssdfb_copyrows(void *cookie, int src, int dst, int num)
791 {
792 	struct rasops_info *ri = (struct rasops_info *)cookie;
793 	struct ssdfb_softc *sc = ri->ri_hw;
794 
795 	sc->sc_riops.copyrows(cookie, src, dst, num);
796 	ssdfb_partial(sc, 0, sc->sc_width,
797 	    dst * ri->ri_font->fontheight,
798 	    (dst + num) * ri->ri_font->fontheight);
799 	return 0;
800 }
801 
802 int
803 ssdfb_eraserows(void *cookie, int row, int num, uint32_t attr)
804 {
805 	struct rasops_info *ri = (struct rasops_info *)cookie;
806 	struct ssdfb_softc *sc = ri->ri_hw;
807 
808 	sc->sc_riops.eraserows(cookie, row, num, attr);
809 	if (num == ri->ri_rows && (ri->ri_flg & RI_FULLCLEAR) != 0)
810 		ssdfb_partial(sc, 0, sc->sc_width, 0, sc->sc_height);
811 	else
812 		ssdfb_partial(sc, 0, sc->sc_width,
813 		    row * ri->ri_font->fontheight,
814 		    (row + num) * ri->ri_font->fontheight);
815 	return 0;
816 }
817 
818 int
819 ssdfb_do_cursor(struct rasops_info *ri)
820 {
821 	struct ssdfb_softc *sc = ri->ri_hw;
822 	int orow, ocol, nrow, ncol;
823 
824 	orow = ri->ri_crow;
825 	ocol = ri->ri_ccol;
826 	sc->sc_ri_do_cursor(ri);
827 	nrow = ri->ri_crow;
828 	ncol = ri->ri_ccol;
829 
830 	ssdfb_partial(sc,
831 	    ocol * ri->ri_font->fontwidth,
832 	    (ocol + 1) * ri->ri_font->fontwidth,
833 	    orow * ri->ri_font->fontheight,
834 	    (orow + 1) * ri->ri_font->fontheight);
835 	ssdfb_partial(sc,
836 	    ncol * ri->ri_font->fontwidth,
837 	    (ncol + 1) * ri->ri_font->fontwidth,
838 	    nrow * ri->ri_font->fontheight,
839 	    (nrow + 1) * ri->ri_font->fontheight);
840 
841 	return 0;
842 }
843