xref: /netbsd/sys/dev/ic/ssdfb.c (revision 33ea73e5)
1 /* $NetBSD: ssdfb.c,v 1.3 2019/03/17 04:03:17 tnn 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.c,v 1.3 2019/03/17 04:03:17 tnn Exp $");
34 
35 #include "opt_ddb.h"
36 
37 #include <sys/param.h>
38 #include <sys/kernel.h>
39 #include <sys/conf.h>
40 #include <uvm/uvm_page.h>
41 #include <uvm/uvm_device.h>
42 #include <sys/condvar.h>
43 #include <sys/kmem.h>
44 #include <sys/kthread.h>
45 #include <dev/wscons/wsdisplayvar.h>
46 #include <dev/rasops/rasops.h>
47 #include <dev/ic/ssdfbvar.h>
48 
49 #if defined(DDB)
50 #include <machine/db_machdep.h>
51 #include <ddb/db_extern.h>
52 #endif
53 
54 /* userland interface */
55 static int	ssdfb_ioctl(void *, void *, u_long, void *, int, struct lwp *);
56 static paddr_t	ssdfb_mmap(void *, void *, off_t, int);
57 
58 /* wscons screen management */
59 static int	ssdfb_alloc_screen(void *, const struct wsscreen_descr *,
60 				   void **, int *, int *, long *);
61 static void	ssdfb_free_screen(void *, void *);
62 static int	ssdfb_show_screen(void *, void *, int,
63 				  void (*cb) (void *, int, int), void *);
64 
65 /* rasops hooks */
66 static void	ssdfb_putchar(void *, int, int, u_int, long);
67 static void	ssdfb_copycols(void *, int, int, int, int);
68 static void	ssdfb_erasecols(void *, int, int, int, long);
69 static void	ssdfb_copyrows(void *, int, int, int);
70 static void	ssdfb_eraserows(void *, int, int, long);
71 static void	ssdfb_cursor(void *, int, int, int);
72 
73 /* hardware interface */
74 static int	ssdfb_init(struct ssdfb_softc *);
75 static int	ssdfb_set_contrast(struct ssdfb_softc *, uint8_t, bool);
76 static int	ssdfb_set_display_on(struct ssdfb_softc *, bool, bool);
77 static int	ssdfb_set_mode(struct ssdfb_softc *, u_int);
78 
79 /* frame buffer damage tracking and synchronization */
80 static void	ssdfb_udv_attach(struct ssdfb_softc *sc);
81 static bool	ssdfb_is_modified(struct ssdfb_softc *sc);
82 static bool	ssdfb_clear_modify(struct ssdfb_softc *sc);
83 static void	ssdfb_damage(struct ssdfb_softc *);
84 static void	ssdfb_thread(void *);
85 static void	ssdfb_set_usepoll(struct ssdfb_softc *, bool);
86 static int	ssdfb_sync(struct ssdfb_softc *, bool);
87 static uint64_t	ssdfb_transpose_block_1bpp(uint8_t *, size_t);
88 static uint64_t	ssdfb_transpose_block_8bpp(uint8_t *, size_t);
89 
90 /* misc helpers */
91 static const struct ssdfb_product *
92 		ssdfb_lookup_product(ssdfb_product_id_t);
93 static int	ssdfb_pick_font(int *, struct wsdisplay_font **);
94 static void	ssdfb_clear_screen(struct ssdfb_softc *);
95 #if defined(DDB)
96 static void	ssdfb_ddb_trap_callback(int);
97 #endif
98 
99 static const char *ssdfb_controller_names[] = {
100 	[SSDFB_CONTROLLER_UNKNOWN] =	"unknown",
101 	[SSDFB_CONTROLLER_SSD1306] =	"Solomon Systech SSD1306",
102 	[SSDFB_CONTROLLER_SH1106] =	"Sino Wealth SH1106"
103 };
104 
105 /*
106  * Display module assemblies supported by this driver.
107  */
108 static const struct ssdfb_product ssdfb_products[] = {
109 	{
110 		.p_product_id =		SSDFB_PRODUCT_SSD1306_GENERIC,
111 		.p_controller_id =	SSDFB_CONTROLLER_SSD1306,
112 		.p_name =		"generic",
113 		.p_width =		128,
114 		.p_height =		64,
115 		.p_panel_shift =	0,
116 		.p_fosc =		0x8,
117 		.p_fosc_div =		0,
118 		.p_precharge =		0x1,
119 		.p_discharge =		0xf,
120 		.p_compin_cfg =		SSDFB_COM_PINS_A1_MASK
121 					| SSDFB_COM_PINS_ALTERNATIVE_MASK,
122 		.p_vcomh_deselect_level = SSD1306_VCOMH_DESELECT_LEVEL_0_77_VCC,
123 		.p_default_contrast =	0x7f,
124 		.p_multiplex_ratio =	0x3f,
125 		.p_chargepump_cmd =	SSD1306_CMD_SET_CHARGE_PUMP,
126 		.p_chargepump_arg =	SSD1306_CHARGE_PUMP_ENABLE
127 	},
128 	{
129 		.p_product_id =		SSDFB_PRODUCT_SH1106_GENERIC,
130 		.p_controller_id =	SSDFB_CONTROLLER_SH1106,
131 		.p_name =		"generic",
132 		.p_width =		128,
133 		.p_height =		64,
134 		.p_panel_shift =	2,
135 		.p_fosc =		0x5,
136 		.p_fosc_div =		0,
137 		.p_precharge =		0x2,
138 		.p_discharge =		0x2,
139 		.p_compin_cfg =		SSDFB_COM_PINS_A1_MASK
140 					| SSDFB_COM_PINS_ALTERNATIVE_MASK,
141 		.p_vcomh_deselect_level = SH1106_VCOMH_DESELECT_LEVEL_DEFAULT,
142 		.p_default_contrast =	0x80,
143 		.p_multiplex_ratio =	0x3f,
144 		.p_chargepump_cmd =	SH1106_CMD_SET_CHARGE_PUMP_7V4,
145 		.p_chargepump_arg =	SSDFB_CMD_NOP
146 	},
147 	{
148 		.p_product_id =		SSDFB_PRODUCT_ADAFRUIT_938,
149 		.p_controller_id =	SSDFB_CONTROLLER_SSD1306,
150 		.p_name =		"Adafruit Industries, LLC product 938",
151 		.p_width =		128,
152 		.p_height =		64,
153 		.p_panel_shift =	0,
154 		.p_fosc =		0x8,
155 		.p_fosc_div =		0,
156 		.p_precharge =		0x1,
157 		.p_discharge =		0xf,
158 		.p_compin_cfg =		0x12,
159 		.p_vcomh_deselect_level = 0x40,
160 		.p_default_contrast =	0x8f,
161 		.p_multiplex_ratio =	0x3f,
162 		.p_chargepump_cmd =	SSD1306_CMD_SET_CHARGE_PUMP,
163 		.p_chargepump_arg =	SSD1306_CHARGE_PUMP_ENABLE
164 	},
165 	{
166 		.p_product_id =		SSDFB_PRODUCT_ADAFRUIT_931,
167 		.p_controller_id =	SSDFB_CONTROLLER_SSD1306,
168 		.p_name =		"Adafruit Industries, LLC product 931",
169 		.p_width =		128,
170 		.p_height =		32,
171 		.p_panel_shift =	0,
172 		.p_fosc =		0x8,
173 		.p_fosc_div =		0,
174 		.p_precharge =		0x1,
175 		.p_discharge =		0xf,
176 		.p_compin_cfg =		0x2,
177 		.p_vcomh_deselect_level = 0x40,
178 		.p_default_contrast =	0x8f,
179 		.p_multiplex_ratio =	0x1f,
180 		.p_chargepump_cmd =	SSD1306_CMD_SET_CHARGE_PUMP,
181 		.p_chargepump_arg =	SSD1306_CHARGE_PUMP_ENABLE
182 	}
183 };
184 
185 static const struct wsdisplay_accessops ssdfb_accessops = {
186 	.ioctl =	ssdfb_ioctl,
187 	.mmap =		ssdfb_mmap,
188 	.alloc_screen =	ssdfb_alloc_screen,
189 	.free_screen =	ssdfb_free_screen,
190 	.show_screen =	ssdfb_show_screen
191 };
192 
193 #define SSDFB_CMD1(c) do { cmd[0] = (c); error = sc->sc_cmd(sc->sc_cookie, cmd, 1, usepoll); } while(0)
194 #define SSDFB_CMD2(c, a) do { cmd[0] = (c); cmd[1] = (a); error = sc->sc_cmd(sc->sc_cookie, cmd, 2, usepoll); } while(0)
195 
196 void
197 ssdfb_attach(struct ssdfb_softc *sc, int flags)
198 {
199 	struct wsemuldisplaydev_attach_args aa;
200 	struct rasops_info *ri = &sc->sc_ri;
201 	int error = 0;
202 	long defattr;
203 	const struct ssdfb_product *p;
204 
205 	p = ssdfb_lookup_product(flags & SSDFB_ATTACH_FLAG_PRODUCT_MASK);
206 	if (p == NULL) {
207 		aprint_error(": unknown display assembly\n");
208 		return;
209 	}
210 	sc->sc_p = p;
211 
212 	aprint_naive("\n");
213 	aprint_normal(": %s (%s)\n",
214 	    ssdfb_controller_names[p->p_controller_id],
215 	    p->p_name);
216 
217 	sc->sc_mode = WSDISPLAYIO_MODE_EMUL;
218 	sc->sc_is_console = flags & SSDFB_ATTACH_FLAG_CONSOLE ? true : false;
219 	sc->sc_inverse = flags & SSDFB_ATTACH_FLAG_INVERSE ? true : false;
220 	sc->sc_upsidedown = flags & SSDFB_ATTACH_FLAG_UPSIDEDOWN ? true : false;
221 	sc->sc_backoff = 1;
222 	sc->sc_contrast = sc->sc_p->p_default_contrast;
223 	sc->sc_gddram_len = sc->sc_p->p_width * sc->sc_p->p_height / 8;
224 	sc->sc_gddram = kmem_alloc(sc->sc_gddram_len, KM_SLEEP);
225 	if (sc->sc_gddram == NULL)
226 		goto out;
227 
228 	aprint_normal_dev(sc->sc_dev, "%dx%d%s\n", sc->sc_p->p_width,
229 	    sc->sc_p->p_height, sc->sc_is_console ? ", console" : "");
230 
231 	/*
232 	 * Initialize rasops. The native depth is 1-bit monochrome and we
233 	 * support this in text emul mode via rasops1. But modern Xorg
234 	 * userland has many rendering glitches when running with 1-bit depth
235 	 * so to better support this use case we instead declare ourselves as
236 	 * an 8-bit display with a two entry constant color map.
237 	 */
238 	error = ssdfb_pick_font(&sc->sc_fontcookie, &sc->sc_font);
239 	if (error) {
240 		aprint_error_dev(sc->sc_dev, "no font\n");
241 		goto out;
242 	}
243 	ri->ri_depth =	8;
244 	ri->ri_font =	sc->sc_font;
245 	ri->ri_width =	sc->sc_p->p_width;
246 	ri->ri_height =	sc->sc_p->p_height;
247 	ri->ri_stride =	ri->ri_width * ri->ri_depth / 8;
248 	ri->ri_hw =	sc;
249 	ri->ri_flg =	RI_FULLCLEAR;
250 	sc->sc_ri_bits_len = round_page(ri->ri_stride * ri->ri_height);
251 	ri->ri_bits	= (u_char *)uvm_km_alloc(kernel_map, sc->sc_ri_bits_len,
252 						 0, UVM_KMF_WIRED);
253 	if (ri->ri_bits == NULL)
254 		goto out;
255 
256 	error = rasops_init(ri,
257 	    sc->sc_p->p_height / sc->sc_font->fontheight,
258 	    sc->sc_p->p_width  / sc->sc_font->fontwidth);
259 	if (error)
260 		goto out;
261 
262 	ri->ri_caps &= ~WSSCREEN_WSCOLORS;
263 
264 	/*
265 	 * Save original emul ops & insert our damage notification hooks.
266 	 */
267 	sc->sc_orig_riops =	ri->ri_ops;
268 	ri->ri_ops.putchar =	ssdfb_putchar;
269 	ri->ri_ops.copycols =	ssdfb_copycols;
270 	ri->ri_ops.erasecols =	ssdfb_erasecols;
271 	ri->ri_ops.copyrows =	ssdfb_copyrows;
272 	ri->ri_ops.eraserows =	ssdfb_eraserows;
273 	ri->ri_ops.cursor =	ssdfb_cursor;
274 
275 	/*
276 	 * Set up the screen.
277 	 */
278 	sc->sc_screen_descr = (struct wsscreen_descr){
279 		.name =		"default",
280 		.ncols =	ri->ri_cols,
281 		.nrows =	ri->ri_rows,
282 		.textops =	&ri->ri_ops,
283 		.fontwidth =	ri->ri_font->fontwidth,
284 		.fontheight =	ri->ri_font->fontheight,
285 		.capabilities =	ri->ri_caps
286 	};
287 	sc->sc_screens[0] = &sc->sc_screen_descr;
288 	sc->sc_screenlist = (struct wsscreen_list){
289 		.nscreens =	1,
290 		.screens =	sc->sc_screens
291 	};
292 
293 	/*
294 	 * Initialize hardware.
295 	 */
296 	error = ssdfb_init(sc);
297 	if (error)
298 		goto out;
299 
300 	if (sc->sc_is_console)
301 		ssdfb_set_usepoll(sc, true);
302 
303 	mutex_init(&sc->sc_cond_mtx, MUTEX_DEFAULT, IPL_SCHED);
304 	cv_init(&sc->sc_cond, "ssdfb");
305 	error = kthread_create(PRI_SOFTCLOCK, KTHREAD_MPSAFE | KTHREAD_MUSTJOIN,
306 	    NULL, ssdfb_thread, sc, &sc->sc_thread, "%s",
307 	    device_xname(sc->sc_dev));
308 	if (error) {
309 		cv_destroy(&sc->sc_cond);
310 		mutex_destroy(&sc->sc_cond_mtx);
311 		goto out;
312 	}
313 
314 	/*
315 	 * Attach wsdisplay.
316 	 */
317 	if (sc->sc_is_console) {
318 		(*ri->ri_ops.allocattr)(ri, 0, 0, 0, &defattr);
319 		wsdisplay_cnattach(&sc->sc_screen_descr, ri, 0, 0, defattr);
320 #if defined(DDB)
321 		db_trap_callback = ssdfb_ddb_trap_callback;
322 #endif
323 	}
324 	aa = (struct wsemuldisplaydev_attach_args){
325 		.console =	sc->sc_is_console,
326 		.scrdata =	&sc->sc_screenlist,
327 		.accessops =	&ssdfb_accessops,
328 		.accesscookie =	sc
329 	};
330 	sc->sc_wsdisplay =
331 	    config_found(sc->sc_dev, &aa, wsemuldisplaydevprint);
332 
333 	return;
334 out:
335 	aprint_error_dev(sc->sc_dev, "attach failed: %d\n", error);
336 	if (sc->sc_gddram != NULL)
337 		kmem_free(sc->sc_gddram, sc->sc_gddram_len);
338 	if (ri->ri_bits != NULL)
339 		uvm_km_free(kernel_map, (vaddr_t)ri->ri_bits, sc->sc_ri_bits_len,
340 		    UVM_KMF_WIRED);
341 	if (sc->sc_fontcookie > 0)
342 		(void) wsfont_unlock(sc->sc_fontcookie);
343 }
344 
345 int
346 ssdfb_detach(struct ssdfb_softc *sc)
347 {
348 	mutex_enter(&sc->sc_cond_mtx);
349 	sc->sc_detaching = true;
350 	cv_broadcast(&sc->sc_cond);
351 	mutex_exit(&sc->sc_cond_mtx);
352 	kthread_join(sc->sc_thread);
353 
354 	if (sc->sc_uobj != NULL) {
355 		mutex_enter(sc->sc_uobj->vmobjlock);
356 		sc->sc_uobj->uo_refs--;
357 		mutex_exit(sc->sc_uobj->vmobjlock);
358 	}
359 	config_detach(sc->sc_wsdisplay, DETACH_FORCE);
360 
361 	cv_destroy(&sc->sc_cond);
362 	mutex_destroy(&sc->sc_cond_mtx);
363 	uvm_km_free(kernel_map, (vaddr_t)sc->sc_ri.ri_bits, sc->sc_ri_bits_len,
364 	    UVM_KMF_WIRED);
365 	kmem_free(sc->sc_gddram, sc->sc_gddram_len);
366 	(void) wsfont_unlock(sc->sc_fontcookie);
367 	return 0;
368 }
369 
370 static int
371 ssdfb_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, struct lwp *l)
372 {
373 	struct ssdfb_softc *sc = v;
374 	struct wsdisplay_param *wdp;
375 	struct wsdisplay_cmap *wc;
376 	u_char cmap[] = {0, 0xff};
377 	int error;
378 
379 	switch (cmd) {
380 	case WSDISPLAYIO_GTYPE:
381 		 *(u_int *)data = WSDISPLAY_TYPE_SSDFB;
382 		return 0;
383 	case WSDISPLAYIO_GINFO:
384 		*(struct wsdisplay_fbinfo *)data = (struct wsdisplay_fbinfo){
385 			.width =	sc->sc_ri.ri_width,
386 			.height =	sc->sc_ri.ri_height,
387 			.depth =	sc->sc_ri.ri_depth,
388 			.cmsize =	2
389 		};
390 		return 0;
391 	case WSDISPLAYIO_GET_FBINFO:
392 		return wsdisplayio_get_fbinfo(&sc->sc_ri,
393 			(struct wsdisplayio_fbinfo *)data);
394 	case WSDISPLAYIO_LINEBYTES:
395 		*(u_int *)data = sc->sc_ri.ri_stride;
396 		return 0;
397 	case WSDISPLAYIO_GETPARAM:
398 		wdp = (struct wsdisplay_param *)data;
399 		if (wdp->param != WSDISPLAYIO_PARAM_CONTRAST)
400 			return EINVAL;
401 		wdp->min = 0;
402 		wdp->max = 0xff;
403 		wdp->curval = sc->sc_contrast;
404 		return 0;
405 	case WSDISPLAYIO_SETPARAM:
406 		wdp = (struct wsdisplay_param *)data;
407 		if (wdp->param != WSDISPLAYIO_PARAM_CONTRAST)
408 			return EINVAL;
409 		if (wdp->curval < 0 || wdp->curval > 0xff)
410 			return EINVAL;
411 		return ssdfb_set_contrast(sc, wdp->curval, sc->sc_usepoll);
412 	case WSDISPLAYIO_GMODE:
413 		*(u_int *)data = sc->sc_mode;
414 		return 0;
415 	case WSDISPLAYIO_SMODE:
416 		return ssdfb_set_mode(sc, *(u_int *)data);
417 	case WSDISPLAYIO_GVIDEO:
418 		*(u_int *)data = sc->sc_display_on
419 			? WSDISPLAYIO_VIDEO_ON
420 			: WSDISPLAYIO_VIDEO_OFF;
421 		return 0;
422 	case WSDISPLAYIO_SVIDEO:
423 		switch (*(u_int *)data) {
424 		case WSDISPLAYIO_VIDEO_ON:
425 		case WSDISPLAYIO_VIDEO_OFF:
426 			break;
427 		default:
428 			return EINVAL;
429 		}
430 		return ssdfb_set_display_on(sc,
431 		    *(u_int *)data == WSDISPLAYIO_VIDEO_ON ? true : false,
432 		    sc->sc_usepoll);
433 #if 0 /* don't let userland mess with polling yet */
434 	case WSDISPLAYIO_SET_POLLING:
435 		switch (*(u_int *)data) {
436 		case 0:
437 		case 1:
438 			break;
439 		default:
440 			return EINVAL;
441 		}
442 		mutex_enter(&sc->sc_cond_mtx);
443 		ssdfb_set_usepoll(sc, *(u_int *)data ? true : false);
444 		cv_broadcast(&sc->sc_cond);
445 		mutex_exit(&sc->sc_cond_mtx);
446 		return 0;
447 #endif
448 	case WSDISPLAYIO_GETCMAP:
449 		wc = (struct wsdisplay_cmap *)data;
450 		if (wc->index >= __arraycount(cmap) ||
451 		    wc->count >  __arraycount(cmap) - wc->index)
452 			return EINVAL;
453 		error = copyout(&cmap[wc->index], wc->red, wc->count);
454 		if (error)
455 			return error;
456 		error = copyout(&cmap[wc->index], wc->green, wc->count);
457 		if (error)
458 			return error;
459 		error = copyout(&cmap[wc->index], wc->blue, wc->count);
460 		return error;
461 	case WSDISPLAYIO_PUTCMAP:
462 		return ENODEV;
463 	}
464 
465 	return EPASSTHROUGH;
466 }
467 
468 static paddr_t
469 ssdfb_mmap(void *v, void *vs, off_t off, int prot)
470 {
471 	struct ssdfb_softc *sc = (struct ssdfb_softc *)v;
472 	struct rasops_info *ri = &sc->sc_ri;
473 	vaddr_t va_base = (vaddr_t)ri->ri_bits;
474 	paddr_t pa;
475 
476 	if (off < 0 || off >= sc->sc_ri_bits_len || (off & PAGE_MASK) != 0)
477 		return -1;
478 
479 	if (!pmap_extract(pmap_kernel(), va_base + off, &pa))
480 		return -1;
481 
482 	return atop(pa);
483 }
484 
485 static int
486 ssdfb_alloc_screen(void *v, const struct wsscreen_descr *descr, void **cookiep,
487 		   int *curxp, int *curyp, long *attrp)
488 {
489 	struct ssdfb_softc *sc = v;
490 	struct rasops_info *ri = &sc->sc_ri;
491 
492 	if (sc->sc_nscreens > 0)
493 		return ENOMEM;
494 
495 	ri->ri_ops.allocattr(ri, 0, 0, 0, attrp);
496 	*cookiep = &sc->sc_ri;
497 	*curxp = 0;
498 	*curyp = 0;
499 	sc->sc_nscreens++;
500 
501 	return 0;
502 }
503 
504 static void
505 ssdfb_free_screen(void *v, void *cookie)
506 {
507 	struct ssdfb_softc *sc = v;
508 
509 	if (sc->sc_is_console)
510 		panic("ssdfb_free_screen: is console");
511 
512 	sc->sc_nscreens--;
513 }
514 
515 static int
516 ssdfb_show_screen(void *v, void *cookie, int waitok,
517 		  void (*cb) (void *, int, int), void *cb_arg)
518 {
519 	return 0;
520 }
521 
522 static void
523 ssdfb_putchar(void *cookie, int row, int col, u_int c, long attr)
524 {
525 	struct rasops_info *ri = (struct rasops_info *)cookie;
526 	struct ssdfb_softc *sc = ri->ri_hw;
527 
528 	sc->sc_orig_riops.putchar(cookie, row, col, c, attr);
529 	ssdfb_damage(sc);
530 }
531 
532 static void
533 ssdfb_copycols(void *cookie, int row, int srccol, int dstcol, int ncols)
534 {
535 	struct rasops_info *ri = (struct rasops_info *)cookie;
536 	struct ssdfb_softc *sc = ri->ri_hw;
537 
538 	sc->sc_orig_riops.copycols(cookie, row, srccol, dstcol, ncols);
539 	ssdfb_damage(sc);
540 }
541 
542 static void
543 ssdfb_erasecols(void *cookie, int row, int startcol, int ncols, long fillattr)
544 {
545 	struct rasops_info *ri = (struct rasops_info *)cookie;
546 	struct ssdfb_softc *sc = ri->ri_hw;
547 
548 	sc->sc_orig_riops.erasecols(cookie, row, startcol, ncols, fillattr);
549 	ssdfb_damage(sc);
550 }
551 
552 static void
553 ssdfb_copyrows(void *cookie, int srcrow, int dstrow, int nrows)
554 {
555 	struct rasops_info *ri = (struct rasops_info *)cookie;
556 	struct ssdfb_softc *sc = ri->ri_hw;
557 
558 	sc->sc_orig_riops.copyrows(cookie, srcrow, dstrow, nrows);
559 	ssdfb_damage(sc);
560 }
561 
562 static void
563 ssdfb_eraserows(void *cookie, int row, int nrows, long fillattr)
564 {
565 	struct rasops_info *ri = (struct rasops_info *)cookie;
566 	struct ssdfb_softc *sc = ri->ri_hw;
567 
568 	sc->sc_orig_riops.eraserows(cookie, row, nrows, fillattr);
569 	ssdfb_damage(sc);
570 }
571 
572 static void
573 ssdfb_cursor(void *cookie, int on, int row, int col)
574 {
575 	struct rasops_info *ri = (struct rasops_info *)cookie;
576 	struct ssdfb_softc *sc = ri->ri_hw;
577 
578 	sc->sc_orig_riops.cursor(cookie, on, row, col);
579 	ssdfb_damage(sc);
580 }
581 
582 static int
583 ssdfb_init(struct ssdfb_softc *sc)
584 {
585 	int error;
586 	uint8_t cmd[2];
587 	bool usepoll = true;
588 
589 	/*
590 	 * Enter sleep.
591 	 */
592 	SSDFB_CMD1(SSDFB_CMD_SET_DISPLAY_OFF);
593 	if (error)
594 		return error;
595 	SSDFB_CMD1(SSDFB_CMD_DEACTIVATE_SCROLL);
596 	if (error)
597 		return error;
598 	SSDFB_CMD1(SSDFB_CMD_ENTIRE_DISPLAY_OFF);
599 	if (error)
600 		return error;
601 
602 	/*
603 	 * Configure physical display panel layout.
604 	 */
605 	SSDFB_CMD2(SSDFB_CMD_SET_MULTIPLEX_RATIO, sc->sc_p->p_multiplex_ratio);
606 	if (error)
607 		return error;
608 	SSDFB_CMD2(SSDFB_CMD_SET_DISPLAY_OFFSET, 0);
609 	if (error)
610 		return error;
611 	SSDFB_CMD1(SSDFB_CMD_SET_DISPLAY_START_LINE_BASE + 0x00);
612 	if (error)
613 		return error;
614 	SSDFB_CMD2(SSDFB_CMD_SET_COM_PINS_HARDWARE_CFG, sc->sc_p->p_compin_cfg);
615 	if (error)
616 		return error;
617 	if (sc->sc_upsidedown) {
618 		SSDFB_CMD1(SSDFB_CMD_SET_SEGMENT_REMAP_REVERSE);
619 		if (error)
620 			return error;
621 		SSDFB_CMD1(SSDFB_CMD_SET_COM_OUTPUT_DIRECTION_REMAP);
622 		if (error)
623 			return error;
624 	} else {
625 		SSDFB_CMD1(SSDFB_CMD_SET_SEGMENT_REMAP_NORMAL);
626 		if (error)
627 			return error;
628 		SSDFB_CMD1(SSDFB_CMD_SET_COM_OUTPUT_DIRECTION_NORMAL);
629 		if (error)
630 			return error;
631 	}
632 	SSDFB_CMD1(SSDFB_CMD_SET_NORMAL_DISPLAY + (uint8_t)sc->sc_inverse);
633 	if (error)
634 		return error;
635 
636 	/*
637 	 * Configure timing characteristics.
638 	 */
639 	SSDFB_CMD2(SSDFB_CMD_SET_DISPLAY_CLOCK_RATIO,
640 	    ((sc->sc_p->p_fosc << SSDFB_DISPLAY_CLOCK_OSCILLATOR_SHIFT) &
641 	     SSDFB_DISPLAY_CLOCK_OSCILLATOR_MASK) |
642 	    ((sc->sc_p->p_fosc_div << SSDFB_DISPLAY_CLOCK_DIVIDER_SHIFT) &
643 	     SSDFB_DISPLAY_CLOCK_DIVIDER_MASK));
644 	if (error)
645 		return error;
646 	SSDFB_CMD2(SSDFB_CMD_SET_CONTRAST_CONTROL, sc->sc_contrast);
647 	if (error)
648 		return error;
649 	SSDFB_CMD2(SSDFB_CMD_SET_PRECHARGE_PERIOD,
650 	    ((sc->sc_p->p_precharge << SSDFB_PRECHARGE_SHIFT) &
651 	     SSDFB_PRECHARGE_MASK) |
652 	    ((sc->sc_p->p_discharge << SSDFB_DISCHARGE_SHIFT) &
653 	     SSDFB_DISCHARGE_MASK));
654 	if (error)
655 		return error;
656 	SSDFB_CMD2(SSDFB_CMD_SET_VCOMH_DESELECT_LEVEL,
657 	    sc->sc_p->p_vcomh_deselect_level);
658 	if (error)
659 		return error;
660 
661 	/*
662 	 * Start charge pump.
663 	 */
664 	SSDFB_CMD2(sc->sc_p->p_chargepump_cmd, sc->sc_p->p_chargepump_arg);
665 	if (error)
666 		return error;
667 
668 	if (sc->sc_p->p_controller_id == SSDFB_CONTROLLER_SH1106) {
669 		SSDFB_CMD2(SH1106_CMD_SET_DC_DC, SH1106_DC_DC_ON);
670 		if (error)
671 			return error;
672 	}
673 
674 	ssdfb_clear_screen(sc);
675 	error = ssdfb_sync(sc, usepoll);
676 	if (error)
677 		return error;
678 	error = ssdfb_set_display_on(sc, true, usepoll);
679 
680 	return error;
681 }
682 
683 static int
684 ssdfb_set_contrast(struct ssdfb_softc *sc, uint8_t value, bool usepoll)
685 {
686 	uint8_t cmd[2];
687 	int error;
688 
689 	sc->sc_contrast = value;
690 	SSDFB_CMD2(SSDFB_CMD_SET_CONTRAST_CONTROL, value);
691 
692 	return error;
693 }
694 
695 static int
696 ssdfb_set_display_on(struct ssdfb_softc *sc, bool value, bool usepoll)
697 {
698 	uint8_t cmd[1];
699 	int error;
700 	sc->sc_display_on = value;
701 
702 	SSDFB_CMD1(value ? SSDFB_CMD_SET_DISPLAY_ON : SSDFB_CMD_SET_DISPLAY_OFF);
703 
704 	return error;
705 }
706 
707 static int
708 ssdfb_set_mode(struct ssdfb_softc *sc, u_int mode)
709 {
710 	switch (mode) {
711 	case WSDISPLAYIO_MODE_EMUL:
712 	case WSDISPLAYIO_MODE_DUMBFB:
713 		break;
714 	default:
715 		return EINVAL;
716 	}
717 	if (mode == sc->sc_mode)
718 		return 0;
719 	mutex_enter(&sc->sc_cond_mtx);
720 	sc->sc_mode = mode;
721 	cv_broadcast(&sc->sc_cond);
722 	mutex_exit(&sc->sc_cond_mtx);
723 	ssdfb_clear_screen(sc);
724 	ssdfb_damage(sc);
725 
726 	return 0;
727 }
728 
729 static void
730 ssdfb_damage(struct ssdfb_softc *sc)
731 {
732 	int s;
733 
734 	if (sc->sc_usepoll) {
735 		(void) ssdfb_sync(sc, true);
736 	} else {
737 		/*
738 		 * kernel code isn't permitted to call us via kprintf at
739 		 * splhigh. In case misbehaving code calls us anyway we can't
740 		 * safely take the mutex so we skip the damage notification.
741 		 */
742 		if (sc->sc_is_console) {
743 			s = splhigh();
744 			splx(s);
745 			if (s == IPL_HIGH)
746 				return;
747 		}
748 		mutex_enter(&sc->sc_cond_mtx);
749 		sc->sc_modified = true;
750 		cv_broadcast(&sc->sc_cond);
751 		mutex_exit(&sc->sc_cond_mtx);
752 	}
753 }
754 
755 static void
756 ssdfb_udv_attach(struct ssdfb_softc *sc)
757 {
758 	extern const struct cdevsw wsdisplay_cdevsw;
759 	dev_t dev;
760 #define WSDISPLAYMINOR(unit, screen)	(((unit) << 8) | (screen))
761 	dev = makedev(cdevsw_lookup_major(&wsdisplay_cdevsw),
762 	    WSDISPLAYMINOR(device_unit(sc->sc_wsdisplay), 0));
763 	sc->sc_uobj = udv_attach(dev, VM_PROT_READ|VM_PROT_WRITE, 0,
764 	    sc->sc_ri_bits_len);
765 }
766 
767 static bool
768 ssdfb_is_modified(struct ssdfb_softc *sc)
769 {
770 	vaddr_t va, va_end;
771 
772 	if (sc->sc_mode == WSDISPLAYIO_MODE_EMUL)
773 		return sc->sc_modified;
774 
775 	if (sc->sc_uobj == NULL)
776 		return false;
777 
778 	va = (vaddr_t)sc->sc_ri.ri_bits;
779 	va_end = va + sc->sc_ri_bits_len;
780 	while (va < va_end) {
781 		if (pmap_is_modified(uvm_pageratop(va)))
782 			return true;
783 		va += PAGE_SIZE;
784 	}
785 
786 	return false;
787 }
788 
789 static bool
790 ssdfb_clear_modify(struct ssdfb_softc *sc)
791 {
792 	vaddr_t va, va_end;
793 	bool ret;
794 
795 	if (sc->sc_mode == WSDISPLAYIO_MODE_EMUL) {
796 		ret = sc->sc_modified;
797 		sc->sc_modified = false;
798 		return ret;
799 	}
800 
801 	if (sc->sc_uobj == NULL)
802 		return false;
803 
804 	va = (vaddr_t)sc->sc_ri.ri_bits;
805 	va_end = va + sc->sc_ri_bits_len;
806 	ret = false;
807 	while (va < va_end) {
808 		if (pmap_clear_modify(uvm_pageratop(va)))
809 			ret = true;
810 		va += PAGE_SIZE;
811 	}
812 
813 	return ret;
814 }
815 
816 static void
817 ssdfb_thread(void *arg)
818 {
819 	struct ssdfb_softc *sc = (struct ssdfb_softc *)arg;
820 	int error;
821 
822 	mutex_enter(&sc->sc_cond_mtx);
823 
824 	if (sc->sc_usepoll)
825 		ssdfb_set_usepoll(sc, false);
826 
827 	while(!sc->sc_detaching) {
828 		if (sc->sc_mode == WSDISPLAYIO_MODE_DUMBFB &&
829 		    sc->sc_uobj == NULL) {
830 			mutex_exit(&sc->sc_cond_mtx);
831 			ssdfb_udv_attach(sc);
832 			mutex_enter(&sc->sc_cond_mtx);
833 		}
834 		if (!ssdfb_is_modified(sc)) {
835 			if (cv_timedwait(&sc->sc_cond, &sc->sc_cond_mtx,
836 			    sc->sc_mode == WSDISPLAYIO_MODE_EMUL
837 			    ? 0 : sc->sc_backoff) == EWOULDBLOCK
838 			    && sc->sc_backoff < mstohz(200)) {
839 				sc->sc_backoff <<= 1;
840 			}
841 			continue;
842 		}
843 		sc->sc_backoff = 1;
844 		(void) ssdfb_clear_modify(sc);
845 		if (sc->sc_usepoll)
846 			continue;
847 		mutex_exit(&sc->sc_cond_mtx);
848 		error = ssdfb_sync(sc, false);
849 		if (error)
850 			device_printf(sc->sc_dev, "ssdfb_sync: error %d\n",
851 			    error);
852 		mutex_enter(&sc->sc_cond_mtx);
853 	}
854 
855 	mutex_exit(&sc->sc_cond_mtx);
856 }
857 
858 static void
859 ssdfb_set_usepoll(struct ssdfb_softc *sc, bool enable)
860 {
861 	sc->sc_usepoll = enable;
862 }
863 
864 static int
865 ssdfb_sync(struct ssdfb_softc *sc, bool usepoll)
866 {
867 	struct rasops_info *ri = &sc->sc_ri;
868 	int block_size = 8;
869 	int ri_block_stride = ri->ri_stride * block_size;
870 	int height_in_blocks = sc->sc_p->p_height / block_size;
871 	int width_in_blocks  = sc->sc_p->p_width / block_size;
872 	int ri_block_step = block_size * ri->ri_depth / 8;
873 	int x, y;
874 	union ssdfb_block *blockp;
875 	uint64_t raw_block;
876 	uint8_t *src;
877 	int x1, x2, y1, y2;
878 
879 	/*
880 	 * Transfer rasops bitmap into gddram shadow buffer while keeping track
881 	 * of the bounding box of the dirty region we scribbled over.
882 	 */
883 	x1 = width_in_blocks;
884 	x2 = -1;
885 	y1 = height_in_blocks;
886 	y2 = -1;
887 	for (y = 0; y < height_in_blocks; y++) {
888 		src = &ri->ri_bits[y*ri_block_stride];
889 		blockp = &sc->sc_gddram[y * width_in_blocks];
890 		for (x = 0; x < width_in_blocks; x++) {
891 			raw_block = (ri->ri_depth == 1)
892 			   ? ssdfb_transpose_block_1bpp(src, ri->ri_stride)
893 			   : ssdfb_transpose_block_8bpp(src, ri->ri_stride);
894 			if (raw_block != blockp->raw) {
895 				blockp->raw = raw_block;
896 				if (x1 > x)
897 					x1 = x;
898 				if (x2 < x)
899 					x2 = x;
900 				if (y1 > y)
901 					y1 = y;
902 				if (y2 < y)
903 					y2 = y;
904 			}
905 			src += ri_block_step;
906 			blockp++;
907 		}
908 	}
909 	if (x2 != -1)
910 		return sc->sc_transfer_rect(sc->sc_cookie,
911 		    x1 * block_size + sc->sc_p->p_panel_shift,
912 		    (x2 + 1) * block_size - 1 + sc->sc_p->p_panel_shift,
913 		    y1,
914 		    y2,
915 		    &sc->sc_gddram[y1 * width_in_blocks + x1].col[0],
916 		    sc->sc_p->p_width,
917 		    usepoll);
918 
919 	return 0;
920 }
921 
922 static uint64_t
923 ssdfb_transpose_block_1bpp(uint8_t *src, size_t src_stride)
924 {
925 	uint64_t x = 0;
926 	uint64_t t;
927 	int i;
928 
929 	/*
930 	 * collect the 8x8 block.
931 	 */
932 	for (i = 0; i < 8; i++) {
933 		x >>= 8;
934 		x |= (uint64_t)src[i * src_stride] << 56;
935 	}
936 
937 	/*
938 	 * Transpose it into gddram layout.
939 	 * Post-transpose bswap is the same as pre-transpose bit order reversal.
940 	 * We do this to match rasops1 bit order.
941 	 */
942 	t = (x ^ (x >> 28)) & 0x00000000F0F0F0F0ULL;
943 	x = x ^ t ^ (t << 28);
944 	t = (x ^ (x >> 14)) & 0x0000CCCC0000CCCCULL;
945 	x = x ^ t ^ (t << 14);
946 	t = (x ^ (x >>  7)) & 0x00AA00AA00AA00AAULL;
947 	x = x ^ t ^ (t <<  7);
948 	x = bswap64(x);
949 
950 	return htole64(x);
951 }
952 
953 static uint64_t
954 ssdfb_transpose_block_8bpp(uint8_t *src, size_t src_stride)
955 {
956 	uint64_t x = 0;
957 	int m, n;
958 
959 	for (m = 0; m < 8; m++) {
960 		for (n = 0; n < 8; n++) {
961 			x >>= 1;
962 			x |= src[n * src_stride + m] ? (1ULL << 63) : 0;
963 		}
964 	}
965 
966 	return htole64(x);
967 }
968 
969 static const struct ssdfb_product *
970 ssdfb_lookup_product(ssdfb_product_id_t id)
971 {
972 	int i;
973 
974 	for (i = 0; i < __arraycount(ssdfb_products); i++) {
975 		if (ssdfb_products[i].p_product_id == id)
976 			return &ssdfb_products[i];
977 	}
978 
979 	return NULL;
980 }
981 
982 static int
983 ssdfb_pick_font(int *cookiep, struct wsdisplay_font **fontp)
984 {
985 	int error;
986 	int c;
987 	struct wsdisplay_font *f;
988 	int i;
989 	uint8_t d[4][2] = {{5, 8}, {8, 8}, {8, 10} ,{8, 16}};
990 
991 	/*
992 	 * Try to find fonts in order of inreasing size.
993 	 */
994 	wsfont_init();
995 	for(i = 0; i < __arraycount(d); i++) {
996 		c = wsfont_find(NULL, d[i][0], d[i][1], 0,
997 		    WSDISPLAY_FONTORDER_L2R, WSDISPLAY_FONTORDER_L2R,
998 		    WSFONT_FIND_BITMAP);
999 		if (c > 0)
1000 			break;
1001 	}
1002 	if (c <= 0)
1003 		return ENOENT;
1004 	error = wsfont_lock(c, &f);
1005 	if (error)
1006 		return error;
1007 	*cookiep = c;
1008 	*fontp = f;
1009 
1010 	return 0;
1011 }
1012 
1013 static void
1014 ssdfb_clear_screen(struct ssdfb_softc *sc)
1015 {
1016 	struct rasops_info *ri = &sc->sc_ri;
1017 
1018 	memset(sc->sc_gddram, 0xff, sc->sc_gddram_len);
1019 	memset(ri->ri_bits, 0, sc->sc_ri_bits_len);
1020 }
1021 
1022 #if defined(DDB)
1023 static void
1024 ssdfb_ddb_trap_callback(int enable)
1025 {
1026 	extern struct cfdriver ssdfb_cd;
1027 	struct ssdfb_softc *sc;
1028 	int i;
1029 
1030 	for (i = 0; i < ssdfb_cd.cd_ndevs; i++) {
1031 		sc = device_lookup_private(&ssdfb_cd, i);
1032 		if (sc != NULL && sc->sc_is_console) {
1033 			ssdfb_set_usepoll(sc, (bool)enable);
1034 		}
1035         }
1036 }
1037 #endif
1038