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