xref: /netbsd/sys/dev/ic/ssdfb.c (revision fc683fa3)
1*fc683fa3Stnn /* $NetBSD: ssdfb.c,v 1.22 2021/08/21 17:50:02 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*fc683fa3Stnn __KERNEL_RCSID(0, "$NetBSD: ssdfb.c,v 1.22 2021/08/21 17:50:02 tnn Exp $");
34e690818eStnn 
35e690818eStnn #include "opt_ddb.h"
36e690818eStnn 
37e690818eStnn #include <sys/param.h>
38e690818eStnn #include <sys/kernel.h>
3933ea73e5Stnn #include <sys/conf.h>
40e690818eStnn #include <sys/condvar.h>
41e690818eStnn #include <sys/kmem.h>
42e690818eStnn #include <sys/kthread.h>
43ea5c7e85Sriastradh 
44ea5c7e85Sriastradh #include <uvm/uvm_device.h>
45ea5c7e85Sriastradh #include <uvm/uvm_extern.h>
46f379a12fStnn #ifdef pmap_is_modified
47f379a12fStnn #include <uvm/uvm_page.h>
48f379a12fStnn #endif
49ea5c7e85Sriastradh 
50e690818eStnn #include <dev/wscons/wsdisplayvar.h>
51e690818eStnn #include <dev/rasops/rasops.h>
52e690818eStnn #include <dev/ic/ssdfbvar.h>
53e690818eStnn 
54e690818eStnn #if defined(DDB)
55e690818eStnn #include <machine/db_machdep.h>
56e690818eStnn #include <ddb/db_extern.h>
57e690818eStnn #endif
58e690818eStnn 
59e690818eStnn /* userland interface */
60e690818eStnn static int	ssdfb_ioctl(void *, void *, u_long, void *, int, struct lwp *);
61e690818eStnn static paddr_t	ssdfb_mmap(void *, void *, off_t, int);
62e690818eStnn 
63e690818eStnn /* wscons screen management */
64e690818eStnn static int	ssdfb_alloc_screen(void *, const struct wsscreen_descr *,
65e690818eStnn 				   void **, int *, int *, long *);
66e690818eStnn static void	ssdfb_free_screen(void *, void *);
67e690818eStnn static int	ssdfb_show_screen(void *, void *, int,
68e690818eStnn 				  void (*cb) (void *, int, int), void *);
69e690818eStnn 
70e690818eStnn /* rasops hooks */
71e690818eStnn static void	ssdfb_putchar(void *, int, int, u_int, long);
72e690818eStnn static void	ssdfb_copycols(void *, int, int, int, int);
73e690818eStnn static void	ssdfb_erasecols(void *, int, int, int, long);
74e690818eStnn static void	ssdfb_copyrows(void *, int, int, int);
75e690818eStnn static void	ssdfb_eraserows(void *, int, int, long);
76e690818eStnn static void	ssdfb_cursor(void *, int, int, int);
77e690818eStnn 
78e690818eStnn /* hardware interface */
798e57c9bfStnn static int	ssdfb_init_ssd1306(struct ssdfb_softc *);
80ef712effStnn static int	ssdfb_init_ssd1322(struct ssdfb_softc *);
8123cac501Stnn static int	ssdfb_init_ssd1353(struct ssdfb_softc *);
82e690818eStnn static int	ssdfb_set_contrast(struct ssdfb_softc *, uint8_t, bool);
83e690818eStnn static int	ssdfb_set_display_on(struct ssdfb_softc *, bool, bool);
84e690818eStnn static int	ssdfb_set_mode(struct ssdfb_softc *, u_int);
85e690818eStnn 
86e690818eStnn /* frame buffer damage tracking and synchronization */
8733ea73e5Stnn static void	ssdfb_udv_attach(struct ssdfb_softc *sc);
88e690818eStnn static bool	ssdfb_is_modified(struct ssdfb_softc *sc);
89e690818eStnn static bool	ssdfb_clear_modify(struct ssdfb_softc *sc);
90e690818eStnn static void	ssdfb_damage(struct ssdfb_softc *);
91e690818eStnn static void	ssdfb_thread(void *);
92e690818eStnn static void	ssdfb_set_usepoll(struct ssdfb_softc *, bool);
93e690818eStnn static int	ssdfb_sync(struct ssdfb_softc *, bool);
94ef712effStnn static int	ssdfb_sync_ssd1306(struct ssdfb_softc *, bool);
95ef712effStnn static int	ssdfb_sync_ssd1322(struct ssdfb_softc *, bool);
9623cac501Stnn static int	ssdfb_sync_ssd1353(struct ssdfb_softc *, bool);
97ef712effStnn static uint64_t	ssdfb_transpose_block(uint8_t *, size_t);
98e690818eStnn 
99e690818eStnn /* misc helpers */
100e690818eStnn static const struct ssdfb_product *
101e690818eStnn 		ssdfb_lookup_product(ssdfb_product_id_t);
102e690818eStnn static int	ssdfb_pick_font(int *, struct wsdisplay_font **);
103e690818eStnn static void	ssdfb_clear_screen(struct ssdfb_softc *);
104e690818eStnn #if defined(DDB)
105e690818eStnn static void	ssdfb_ddb_trap_callback(int);
106e690818eStnn #endif
107e690818eStnn 
108e690818eStnn static const char *ssdfb_controller_names[] = {
109e690818eStnn 	[SSDFB_CONTROLLER_UNKNOWN] =	"unknown",
110e690818eStnn 	[SSDFB_CONTROLLER_SSD1306] =	"Solomon Systech SSD1306",
1118e57c9bfStnn 	[SSDFB_CONTROLLER_SH1106] =	"Sino Wealth SH1106",
11223cac501Stnn 	[SSDFB_CONTROLLER_SSD1322] =	"Solomon Systech SSD1322",
11323cac501Stnn 	[SSDFB_CONTROLLER_SSD1353] =	"Solomon Systech SSD1353"
114e690818eStnn };
115e690818eStnn 
116e690818eStnn /*
117e690818eStnn  * Display module assemblies supported by this driver.
118e690818eStnn  */
119e690818eStnn static const struct ssdfb_product ssdfb_products[] = {
120e690818eStnn 	{
121e690818eStnn 		.p_product_id =		SSDFB_PRODUCT_SSD1306_GENERIC,
122e690818eStnn 		.p_controller_id =	SSDFB_CONTROLLER_SSD1306,
123e690818eStnn 		.p_name =		"generic",
124e690818eStnn 		.p_width =		128,
125e690818eStnn 		.p_height =		64,
126ef712effStnn 		.p_bits_per_pixel =	1,
127e690818eStnn 		.p_panel_shift =	0,
128e690818eStnn 		.p_fosc =		0x8,
129e690818eStnn 		.p_fosc_div =		0,
130e690818eStnn 		.p_precharge =		0x1,
131e690818eStnn 		.p_discharge =		0xf,
132e690818eStnn 		.p_compin_cfg =		SSDFB_COM_PINS_A1_MASK
133e690818eStnn 					| SSDFB_COM_PINS_ALTERNATIVE_MASK,
134e690818eStnn 		.p_vcomh_deselect_level = SSD1306_VCOMH_DESELECT_LEVEL_0_77_VCC,
135e690818eStnn 		.p_default_contrast =	0x7f,
136e690818eStnn 		.p_multiplex_ratio =	0x3f,
137ef712effStnn 		.p_init =		ssdfb_init_ssd1306,
138ef712effStnn 		.p_sync =		ssdfb_sync_ssd1306
139e690818eStnn 	},
140e690818eStnn 	{
141e690818eStnn 		.p_product_id =		SSDFB_PRODUCT_SH1106_GENERIC,
142e690818eStnn 		.p_controller_id =	SSDFB_CONTROLLER_SH1106,
143e690818eStnn 		.p_name =		"generic",
144e690818eStnn 		.p_width =		128,
145e690818eStnn 		.p_height =		64,
146ef712effStnn 		.p_bits_per_pixel =	1,
147e690818eStnn 		.p_panel_shift =	2,
148e690818eStnn 		.p_fosc =		0x5,
149e690818eStnn 		.p_fosc_div =		0,
150e690818eStnn 		.p_precharge =		0x2,
151e690818eStnn 		.p_discharge =		0x2,
152e690818eStnn 		.p_compin_cfg =		SSDFB_COM_PINS_A1_MASK
153e690818eStnn 					| SSDFB_COM_PINS_ALTERNATIVE_MASK,
154e690818eStnn 		.p_vcomh_deselect_level = SH1106_VCOMH_DESELECT_LEVEL_DEFAULT,
155e690818eStnn 		.p_default_contrast =	0x80,
156e690818eStnn 		.p_multiplex_ratio =	0x3f,
157ef712effStnn 		.p_init =		ssdfb_init_ssd1306,
158ef712effStnn 		.p_sync =		ssdfb_sync_ssd1306
159e690818eStnn 	},
160e690818eStnn 	{
161e690818eStnn 		.p_product_id =		SSDFB_PRODUCT_ADAFRUIT_938,
162e690818eStnn 		.p_controller_id =	SSDFB_CONTROLLER_SSD1306,
163e690818eStnn 		.p_name =		"Adafruit Industries, LLC product 938",
164e690818eStnn 		.p_width =		128,
165e690818eStnn 		.p_height =		64,
166ef712effStnn 		.p_bits_per_pixel =	1,
167e690818eStnn 		.p_panel_shift =	0,
168e690818eStnn 		.p_fosc =		0x8,
169e690818eStnn 		.p_fosc_div =		0,
170e690818eStnn 		.p_precharge =		0x1,
171e690818eStnn 		.p_discharge =		0xf,
172e690818eStnn 		.p_compin_cfg =		0x12,
173e690818eStnn 		.p_vcomh_deselect_level = 0x40,
174e690818eStnn 		.p_default_contrast =	0x8f,
175e690818eStnn 		.p_multiplex_ratio =	0x3f,
176ef712effStnn 		.p_init =		ssdfb_init_ssd1306,
177ef712effStnn 		.p_sync =		ssdfb_sync_ssd1306
178e690818eStnn 	},
179e690818eStnn 	{
180e690818eStnn 		.p_product_id =		SSDFB_PRODUCT_ADAFRUIT_931,
181e690818eStnn 		.p_controller_id =	SSDFB_CONTROLLER_SSD1306,
182e690818eStnn 		.p_name =		"Adafruit Industries, LLC product 931",
183e690818eStnn 		.p_width =		128,
184e690818eStnn 		.p_height =		32,
185ef712effStnn 		.p_bits_per_pixel =	1,
186e690818eStnn 		.p_panel_shift =	0,
187e690818eStnn 		.p_fosc =		0x8,
188e690818eStnn 		.p_fosc_div =		0,
189e690818eStnn 		.p_precharge =		0x1,
190e690818eStnn 		.p_discharge =		0xf,
191e690818eStnn 		.p_compin_cfg =		0x2,
192e690818eStnn 		.p_vcomh_deselect_level = 0x40,
193e690818eStnn 		.p_default_contrast =	0x8f,
194e690818eStnn 		.p_multiplex_ratio =	0x1f,
195ef712effStnn 		.p_init =		ssdfb_init_ssd1306,
196ef712effStnn 		.p_sync =		ssdfb_sync_ssd1306
197ef712effStnn 	},
198ef712effStnn 	{
199ef712effStnn 		.p_product_id =		SSDFB_PRODUCT_SSD1322_GENERIC,
200ef712effStnn 		.p_controller_id =	SSDFB_CONTROLLER_SSD1322,
201ef712effStnn 		.p_name =		"generic",
202ef712effStnn 		.p_width =		256,
203ef712effStnn 		.p_height =		64,
204ef712effStnn 		.p_bits_per_pixel =	4,
205ef712effStnn 		.p_panel_shift =	28,
206ef712effStnn 		.p_vcomh_deselect_level = SSD1322_DEFAULT_VCOMH,
207ef712effStnn 		.p_fosc =		SSD1322_DEFAULT_FREQUENCY,
208ef712effStnn 		.p_fosc_div =		SSD1322_DEFAULT_DIVIDER,
209ef712effStnn 		.p_default_contrast =	SSD1322_DEFAULT_CONTRAST_CURRENT,
210ef712effStnn 		.p_multiplex_ratio =	0x3f,
211ef712effStnn 		.p_init =		ssdfb_init_ssd1322,
212ef712effStnn 		.p_sync =		ssdfb_sync_ssd1322
21323cac501Stnn 	},
21423cac501Stnn 	{
21523cac501Stnn 		.p_product_id =		SSDFB_PRODUCT_SSD1353_GENERIC,
21623cac501Stnn 		.p_controller_id =	SSDFB_CONTROLLER_SSD1353,
21723cac501Stnn 		.p_name =		"generic",
21823cac501Stnn 		.p_width =		160,
21923cac501Stnn 		.p_height =		132,
22023cac501Stnn 		.p_bits_per_pixel =	32,
22123cac501Stnn 		.p_rgb = 		true,
22223cac501Stnn 		.p_panel_shift =	0,
22323cac501Stnn 		.p_compin_cfg = SSD1353_REMAP_RGB | SSD1353_REMAP_SPLIT_ODD_EVEN
22423cac501Stnn 		    | __SHIFTIN(2, SSD1353_REMAP_PIXEL_FORMAT_MASK),
22523cac501Stnn 		.p_vcomh_deselect_level = SSD1353_DEFAULT_VCOMH,
22623cac501Stnn 		.p_fosc =		SSD1353_DEFAULT_FREQUENCY,
22723cac501Stnn 		.p_fosc_div =		SSD1353_DEFAULT_DIVIDER,
22823cac501Stnn 		.p_default_contrast =	SSD1353_DEFAULT_CONTRAST_CONTROL,
22923cac501Stnn 		.p_multiplex_ratio =	0x83,
23023cac501Stnn 		.p_init =		ssdfb_init_ssd1353,
23123cac501Stnn 		.p_sync =		ssdfb_sync_ssd1353
23223cac501Stnn 	},
23323cac501Stnn 	{
23423cac501Stnn 		.p_product_id =		SSDFB_PRODUCT_DEP_160128A_RGB,
23523cac501Stnn 		.p_controller_id =	SSDFB_CONTROLLER_SSD1353,
23623cac501Stnn 		.p_name =		"Display Elektronik GmbH DEP 160128A(1)-RGB",
23723cac501Stnn 		.p_width =		160,
23823cac501Stnn 		.p_height =		128,
23923cac501Stnn 		.p_bits_per_pixel =	32,
24023cac501Stnn 		.p_rgb = 		true,
24123cac501Stnn 		.p_panel_shift =	0,
24223cac501Stnn 		.p_compin_cfg = SSD1353_REMAP_RGB | SSD1353_REMAP_SPLIT_ODD_EVEN
24323cac501Stnn 		    | __SHIFTIN(2, SSD1353_REMAP_PIXEL_FORMAT_MASK),
24423cac501Stnn 		.p_vcomh_deselect_level = SSD1353_DEFAULT_VCOMH,
24523cac501Stnn 		.p_fosc =		SSD1353_DEFAULT_FREQUENCY,
24623cac501Stnn 		.p_fosc_div =		SSD1353_DEFAULT_DIVIDER,
24723cac501Stnn 		.p_default_contrast =	SSD1353_DEFAULT_CONTRAST_CONTROL,
24823cac501Stnn 		.p_multiplex_ratio =	0x83,
24923cac501Stnn 		.p_init =		ssdfb_init_ssd1353,
25023cac501Stnn 		.p_sync =		ssdfb_sync_ssd1353
251e690818eStnn 	}
252e690818eStnn };
253e690818eStnn 
254e690818eStnn static const struct wsdisplay_accessops ssdfb_accessops = {
255e690818eStnn 	.ioctl =	ssdfb_ioctl,
256e690818eStnn 	.mmap =		ssdfb_mmap,
257e690818eStnn 	.alloc_screen =	ssdfb_alloc_screen,
258e690818eStnn 	.free_screen =	ssdfb_free_screen,
259e690818eStnn 	.show_screen =	ssdfb_show_screen
260e690818eStnn };
261e690818eStnn 
262e690818eStnn #define SSDFB_CMD1(c) do { cmd[0] = (c); error = sc->sc_cmd(sc->sc_cookie, cmd, 1, usepoll); } while(0)
263e690818eStnn #define SSDFB_CMD2(c, a) do { cmd[0] = (c); cmd[1] = (a); error = sc->sc_cmd(sc->sc_cookie, cmd, 2, usepoll); } while(0)
264ef712effStnn #define SSDFB_CMD3(c, a, b) do { cmd[0] = (c); cmd[1] = (a); cmd[2] = (b); error = sc->sc_cmd(sc->sc_cookie, cmd, 3, usepoll); } while(0)
265e690818eStnn 
266e690818eStnn void
ssdfb_attach(struct ssdfb_softc * sc,int flags)267e690818eStnn ssdfb_attach(struct ssdfb_softc *sc, int flags)
268e690818eStnn {
269e690818eStnn 	struct wsemuldisplaydev_attach_args aa;
270e690818eStnn 	struct rasops_info *ri = &sc->sc_ri;
271e690818eStnn 	int error = 0;
272e690818eStnn 	long defattr;
273e690818eStnn 	const struct ssdfb_product *p;
2747660f3d3Stnn 	int kt_flags;
275e690818eStnn 
276e690818eStnn 	p = ssdfb_lookup_product(flags & SSDFB_ATTACH_FLAG_PRODUCT_MASK);
277e690818eStnn 	if (p == NULL) {
278e690818eStnn 		aprint_error(": unknown display assembly\n");
279e690818eStnn 		return;
280e690818eStnn 	}
281e690818eStnn 	sc->sc_p = p;
282e690818eStnn 
283e690818eStnn 	aprint_naive("\n");
284e690818eStnn 	aprint_normal(": %s (%s)\n",
285e690818eStnn 	    ssdfb_controller_names[p->p_controller_id],
286e690818eStnn 	    p->p_name);
287e690818eStnn 
288e690818eStnn 	sc->sc_mode = WSDISPLAYIO_MODE_EMUL;
289e690818eStnn 	sc->sc_is_console = flags & SSDFB_ATTACH_FLAG_CONSOLE ? true : false;
290e690818eStnn 	sc->sc_inverse = flags & SSDFB_ATTACH_FLAG_INVERSE ? true : false;
291e690818eStnn 	sc->sc_upsidedown = flags & SSDFB_ATTACH_FLAG_UPSIDEDOWN ? true : false;
292e690818eStnn 	sc->sc_backoff = 1;
293e690818eStnn 	sc->sc_contrast = sc->sc_p->p_default_contrast;
294ef712effStnn 	sc->sc_gddram_len = sc->sc_p->p_width * sc->sc_p->p_height
295ef712effStnn 	    * sc->sc_p->p_bits_per_pixel / 8;
296e690818eStnn 	sc->sc_gddram = kmem_alloc(sc->sc_gddram_len, KM_SLEEP);
297e690818eStnn 	if (sc->sc_gddram == NULL)
298e690818eStnn 		goto out;
299e690818eStnn 
300e690818eStnn 	aprint_normal_dev(sc->sc_dev, "%dx%d%s\n", sc->sc_p->p_width,
301e690818eStnn 	    sc->sc_p->p_height, sc->sc_is_console ? ", console" : "");
302e690818eStnn 
303e690818eStnn 	/*
304e690818eStnn 	 * Initialize rasops. The native depth is 1-bit monochrome and we
305e690818eStnn 	 * support this in text emul mode via rasops1. But modern Xorg
306e690818eStnn 	 * userland has many rendering glitches when running with 1-bit depth
307e690818eStnn 	 * so to better support this use case we instead declare ourselves as
308e690818eStnn 	 * an 8-bit display with a two entry constant color map.
309e690818eStnn 	 */
310e690818eStnn 	error = ssdfb_pick_font(&sc->sc_fontcookie, &sc->sc_font);
311e690818eStnn 	if (error) {
312e690818eStnn 		aprint_error_dev(sc->sc_dev, "no font\n");
313e690818eStnn 		goto out;
314e690818eStnn 	}
315ef712effStnn #ifdef SSDFB_USE_NATIVE_DEPTH
316ef712effStnn 	ri->ri_depth = sc->sc_p->p_bits_per_pixel;
317ef712effStnn #else
318d6ec530eStnn 	if (sc->sc_p->p_rgb && sc->sc_p->p_bits_per_pixel == 32) {
319d6ec530eStnn 		ri->ri_depth = sc->sc_p->p_bits_per_pixel;
320d6ec530eStnn 		ri->ri_rnum = 8;
321d6ec530eStnn 		ri->ri_gnum = 8;
322d6ec530eStnn 		ri->ri_bnum = 8;
323d6ec530eStnn #if _BYTE_ORDER == _LITTLE_ENDIAN
324d6ec530eStnn 		ri->ri_rpos = 0;
325d6ec530eStnn 		ri->ri_gpos = 8;
326d6ec530eStnn 		ri->ri_bpos = 16;
327d6ec530eStnn #else
328d6ec530eStnn 		ri->ri_rpos = 24;
329d6ec530eStnn 		ri->ri_gpos = 16;
330d6ec530eStnn 		ri->ri_bpos = 8;
331d6ec530eStnn #endif
332d6ec530eStnn 	} else {
333e690818eStnn 		ri->ri_depth = 8;
334d6ec530eStnn 	}
335ef712effStnn #endif
336e690818eStnn 	ri->ri_font =	sc->sc_font;
337e690818eStnn 	ri->ri_width =	sc->sc_p->p_width;
338e690818eStnn 	ri->ri_height =	sc->sc_p->p_height;
339e690818eStnn 	ri->ri_stride =	ri->ri_width * ri->ri_depth / 8;
340e690818eStnn 	ri->ri_hw =	sc;
341d6ec530eStnn 	ri->ri_flg =	RI_FULLCLEAR;
342d6ec530eStnn 	if (!sc->sc_p->p_rgb) {
343d6ec530eStnn 		ri->ri_flg |= RI_FORCEMONO;
344d6ec530eStnn 	}
345e690818eStnn 	sc->sc_ri_bits_len = round_page(ri->ri_stride * ri->ri_height);
346e690818eStnn 	ri->ri_bits	= (u_char *)uvm_km_alloc(kernel_map, sc->sc_ri_bits_len,
347e690818eStnn 						 0, UVM_KMF_WIRED);
348e690818eStnn 	if (ri->ri_bits == NULL)
349e690818eStnn 		goto out;
350e690818eStnn 
351e690818eStnn 	error = rasops_init(ri,
352e690818eStnn 	    sc->sc_p->p_height / sc->sc_font->fontheight,
353e690818eStnn 	    sc->sc_p->p_width  / sc->sc_font->fontwidth);
354e690818eStnn 	if (error)
355e690818eStnn 		goto out;
356e690818eStnn 
357d6ec530eStnn 	if (!sc->sc_p->p_rgb) {
358e690818eStnn 		ri->ri_caps &= ~WSSCREEN_WSCOLORS;
359d6ec530eStnn 	}
360e690818eStnn 
361e690818eStnn 	/*
362e690818eStnn 	 * Save original emul ops & insert our damage notification hooks.
363e690818eStnn 	 */
364e690818eStnn 	sc->sc_orig_riops =	ri->ri_ops;
365e690818eStnn 	ri->ri_ops.putchar =	ssdfb_putchar;
366e690818eStnn 	ri->ri_ops.copycols =	ssdfb_copycols;
367e690818eStnn 	ri->ri_ops.erasecols =	ssdfb_erasecols;
368e690818eStnn 	ri->ri_ops.copyrows =	ssdfb_copyrows;
369e690818eStnn 	ri->ri_ops.eraserows =	ssdfb_eraserows;
370e690818eStnn 	ri->ri_ops.cursor =	ssdfb_cursor;
371e690818eStnn 
372e690818eStnn 	/*
373e690818eStnn 	 * Set up the screen.
374e690818eStnn 	 */
375e690818eStnn 	sc->sc_screen_descr = (struct wsscreen_descr){
376e690818eStnn 		.name =		"default",
377e690818eStnn 		.ncols =	ri->ri_cols,
378e690818eStnn 		.nrows =	ri->ri_rows,
379e690818eStnn 		.textops =	&ri->ri_ops,
380e690818eStnn 		.fontwidth =	ri->ri_font->fontwidth,
381e690818eStnn 		.fontheight =	ri->ri_font->fontheight,
382e690818eStnn 		.capabilities =	ri->ri_caps
383e690818eStnn 	};
384e690818eStnn 	sc->sc_screens[0] = &sc->sc_screen_descr;
385e690818eStnn 	sc->sc_screenlist = (struct wsscreen_list){
386e690818eStnn 		.nscreens =	1,
387e690818eStnn 		.screens =	sc->sc_screens
388e690818eStnn 	};
389e690818eStnn 
390e690818eStnn 	/*
391e690818eStnn 	 * Initialize hardware.
392e690818eStnn 	 */
3938e57c9bfStnn 	error = p->p_init(sc);
394e690818eStnn 	if (error)
395e690818eStnn 		goto out;
396e690818eStnn 
397e690818eStnn 	if (sc->sc_is_console)
398e690818eStnn 		ssdfb_set_usepoll(sc, true);
399e690818eStnn 
4007660f3d3Stnn 	mutex_init(&sc->sc_cond_mtx, MUTEX_DEFAULT,
4017660f3d3Stnn 	    ISSET(flags, SSDFB_ATTACH_FLAG_MPSAFE) ? IPL_SCHED : IPL_BIO);
402e690818eStnn 	cv_init(&sc->sc_cond, "ssdfb");
4037660f3d3Stnn 	kt_flags = KTHREAD_MUSTJOIN;
4047660f3d3Stnn 	/* XXX spi(4) is not MPSAFE yet. */
4057660f3d3Stnn 	if (ISSET(flags, SSDFB_ATTACH_FLAG_MPSAFE))
4067660f3d3Stnn 		kt_flags |= KTHREAD_MPSAFE;
4077660f3d3Stnn 	error = kthread_create(PRI_SOFTCLOCK, kt_flags, NULL, ssdfb_thread, sc,
4087660f3d3Stnn 	    &sc->sc_thread, "%s", device_xname(sc->sc_dev));
409e690818eStnn 	if (error) {
410e690818eStnn 		cv_destroy(&sc->sc_cond);
411e690818eStnn 		mutex_destroy(&sc->sc_cond_mtx);
412e690818eStnn 		goto out;
413e690818eStnn 	}
414e690818eStnn 
415e690818eStnn 	/*
416e690818eStnn 	 * Attach wsdisplay.
417e690818eStnn 	 */
418e690818eStnn 	if (sc->sc_is_console) {
419e690818eStnn 		(*ri->ri_ops.allocattr)(ri, 0, 0, 0, &defattr);
420e690818eStnn 		wsdisplay_cnattach(&sc->sc_screen_descr, ri, 0, 0, defattr);
421e690818eStnn #if defined(DDB)
422e690818eStnn 		db_trap_callback = ssdfb_ddb_trap_callback;
423e690818eStnn #endif
424e690818eStnn 	}
425e690818eStnn 	aa = (struct wsemuldisplaydev_attach_args){
426e690818eStnn 		.console =	sc->sc_is_console,
427e690818eStnn 		.scrdata =	&sc->sc_screenlist,
428e690818eStnn 		.accessops =	&ssdfb_accessops,
429e690818eStnn 		.accesscookie =	sc
430e690818eStnn 	};
431e690818eStnn 	sc->sc_wsdisplay =
432beecddb6Sthorpej 	    config_found(sc->sc_dev, &aa, wsemuldisplaydevprint, CFARGS_NONE);
433e690818eStnn 
434e690818eStnn 	return;
435e690818eStnn out:
436e690818eStnn 	aprint_error_dev(sc->sc_dev, "attach failed: %d\n", error);
437e690818eStnn 	if (sc->sc_gddram != NULL)
438e690818eStnn 		kmem_free(sc->sc_gddram, sc->sc_gddram_len);
439e690818eStnn 	if (ri->ri_bits != NULL)
440e690818eStnn 		uvm_km_free(kernel_map, (vaddr_t)ri->ri_bits, sc->sc_ri_bits_len,
441e690818eStnn 		    UVM_KMF_WIRED);
442e690818eStnn 	if (sc->sc_fontcookie > 0)
443e690818eStnn 		(void) wsfont_unlock(sc->sc_fontcookie);
444e690818eStnn }
445e690818eStnn 
446e690818eStnn int
ssdfb_detach(struct ssdfb_softc * sc)447e690818eStnn ssdfb_detach(struct ssdfb_softc *sc)
448e690818eStnn {
449e690818eStnn 	mutex_enter(&sc->sc_cond_mtx);
450e690818eStnn 	sc->sc_detaching = true;
451e690818eStnn 	cv_broadcast(&sc->sc_cond);
452e690818eStnn 	mutex_exit(&sc->sc_cond_mtx);
453e690818eStnn 	kthread_join(sc->sc_thread);
454e690818eStnn 
45533ea73e5Stnn 	if (sc->sc_uobj != NULL) {
456b4dac182Sad 		rw_enter(sc->sc_uobj->vmobjlock, RW_WRITER);
45733ea73e5Stnn 		sc->sc_uobj->uo_refs--;
458b4dac182Sad 		rw_exit(sc->sc_uobj->vmobjlock);
45933ea73e5Stnn 	}
460e690818eStnn 	config_detach(sc->sc_wsdisplay, DETACH_FORCE);
461e690818eStnn 
462e690818eStnn 	cv_destroy(&sc->sc_cond);
463e690818eStnn 	mutex_destroy(&sc->sc_cond_mtx);
464e690818eStnn 	uvm_km_free(kernel_map, (vaddr_t)sc->sc_ri.ri_bits, sc->sc_ri_bits_len,
465e690818eStnn 	    UVM_KMF_WIRED);
466e690818eStnn 	kmem_free(sc->sc_gddram, sc->sc_gddram_len);
467e690818eStnn 	(void) wsfont_unlock(sc->sc_fontcookie);
468e690818eStnn 	return 0;
469e690818eStnn }
470e690818eStnn 
471e690818eStnn static int
ssdfb_ioctl(void * v,void * vs,u_long cmd,void * data,int flag,struct lwp * l)472e690818eStnn ssdfb_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, struct lwp *l)
473e690818eStnn {
474e690818eStnn 	struct ssdfb_softc *sc = v;
475e690818eStnn 	struct wsdisplay_param *wdp;
476e690818eStnn 	struct wsdisplay_cmap *wc;
477ef712effStnn 	u_char cmap[16];
478ef712effStnn 	int cmaplen = 1 << sc->sc_p->p_bits_per_pixel;
479ef712effStnn 	int i;
480ef712effStnn 	struct wsdisplayio_fbinfo *fbi;
481e690818eStnn 	int error;
482e690818eStnn 
483e690818eStnn 	switch (cmd) {
484e690818eStnn 	case WSDISPLAYIO_GTYPE:
485e690818eStnn 		 *(u_int *)data = WSDISPLAY_TYPE_SSDFB;
486e690818eStnn 		return 0;
487e690818eStnn 	case WSDISPLAYIO_GINFO:
488e690818eStnn 		*(struct wsdisplay_fbinfo *)data = (struct wsdisplay_fbinfo){
489e690818eStnn 			.width =	sc->sc_ri.ri_width,
490e690818eStnn 			.height =	sc->sc_ri.ri_height,
491e690818eStnn 			.depth =	sc->sc_ri.ri_depth,
492ef712effStnn 			.cmsize =	cmaplen
493e690818eStnn 		};
494e690818eStnn 		return 0;
495e690818eStnn 	case WSDISPLAYIO_GET_FBINFO:
496ef712effStnn 		fbi = (struct wsdisplayio_fbinfo *)data;
497ef712effStnn 		error = wsdisplayio_get_fbinfo(&sc->sc_ri, fbi);
498d6ec530eStnn 		if (!sc->sc_p->p_rgb) {
499ef712effStnn 			fbi->fbi_subtype.fbi_cmapinfo.cmap_entries = cmaplen;
500ef712effStnn 			/* fbi->fbi_pixeltype = WSFB_GREYSCALE */;
501d6ec530eStnn 		}
502ef712effStnn 		return error;
503e690818eStnn 	case WSDISPLAYIO_LINEBYTES:
504e690818eStnn 		*(u_int *)data = sc->sc_ri.ri_stride;
505e690818eStnn 		return 0;
506e690818eStnn 	case WSDISPLAYIO_GETPARAM:
507e690818eStnn 		wdp = (struct wsdisplay_param *)data;
508e690818eStnn 		if (wdp->param != WSDISPLAYIO_PARAM_CONTRAST)
509e690818eStnn 			return EINVAL;
510e690818eStnn 		wdp->min = 0;
511e690818eStnn 		wdp->max = 0xff;
512e690818eStnn 		wdp->curval = sc->sc_contrast;
513e690818eStnn 		return 0;
514e690818eStnn 	case WSDISPLAYIO_SETPARAM:
515e690818eStnn 		wdp = (struct wsdisplay_param *)data;
516e690818eStnn 		if (wdp->param != WSDISPLAYIO_PARAM_CONTRAST)
517e690818eStnn 			return EINVAL;
518e690818eStnn 		if (wdp->curval < 0 || wdp->curval > 0xff)
519e690818eStnn 			return EINVAL;
520e690818eStnn 		return ssdfb_set_contrast(sc, wdp->curval, sc->sc_usepoll);
521e690818eStnn 	case WSDISPLAYIO_GMODE:
522e690818eStnn 		*(u_int *)data = sc->sc_mode;
523e690818eStnn 		return 0;
524e690818eStnn 	case WSDISPLAYIO_SMODE:
525e690818eStnn 		return ssdfb_set_mode(sc, *(u_int *)data);
526e690818eStnn 	case WSDISPLAYIO_GVIDEO:
527e690818eStnn 		*(u_int *)data = sc->sc_display_on
528e690818eStnn 			? WSDISPLAYIO_VIDEO_ON
529e690818eStnn 			: WSDISPLAYIO_VIDEO_OFF;
530e690818eStnn 		return 0;
531e690818eStnn 	case WSDISPLAYIO_SVIDEO:
532e690818eStnn 		switch (*(u_int *)data) {
533e690818eStnn 		case WSDISPLAYIO_VIDEO_ON:
534e690818eStnn 		case WSDISPLAYIO_VIDEO_OFF:
535e690818eStnn 			break;
536e690818eStnn 		default:
537e690818eStnn 			return EINVAL;
538e690818eStnn 		}
539e690818eStnn 		return ssdfb_set_display_on(sc,
540e690818eStnn 		    *(u_int *)data == WSDISPLAYIO_VIDEO_ON ? true : false,
541e690818eStnn 		    sc->sc_usepoll);
542e690818eStnn #if 0 /* don't let userland mess with polling yet */
543e690818eStnn 	case WSDISPLAYIO_SET_POLLING:
544e690818eStnn 		switch (*(u_int *)data) {
545e690818eStnn 		case 0:
546e690818eStnn 		case 1:
547e690818eStnn 			break;
548e690818eStnn 		default:
549e690818eStnn 			return EINVAL;
550e690818eStnn 		}
551e690818eStnn 		mutex_enter(&sc->sc_cond_mtx);
552e690818eStnn 		ssdfb_set_usepoll(sc, *(u_int *)data ? true : false);
553e690818eStnn 		cv_broadcast(&sc->sc_cond);
554e690818eStnn 		mutex_exit(&sc->sc_cond_mtx);
555e690818eStnn 		return 0;
556e690818eStnn #endif
557e690818eStnn 	case WSDISPLAYIO_GETCMAP:
558d6ec530eStnn 		if (sc->sc_p->p_rgb)
559d6ec530eStnn 			return ENOTSUP;
560e690818eStnn 		wc = (struct wsdisplay_cmap *)data;
561ef712effStnn 		if (wc->index >= cmaplen ||
562ef712effStnn 		    wc->count > cmaplen - wc->index)
563e690818eStnn 			return EINVAL;
564ef712effStnn 		for(i = 0; i < cmaplen; i++) {
565ef712effStnn 			cmap[i] = 255 * i / (cmaplen - 1);
566ef712effStnn 		}
567e690818eStnn 		error = copyout(&cmap[wc->index], wc->red, wc->count);
568e690818eStnn 		if (error)
569e690818eStnn 			return error;
570e690818eStnn 		error = copyout(&cmap[wc->index], wc->green, wc->count);
571e690818eStnn 		if (error)
572e690818eStnn 			return error;
573e690818eStnn 		error = copyout(&cmap[wc->index], wc->blue, wc->count);
574e690818eStnn 		return error;
575e690818eStnn 	case WSDISPLAYIO_PUTCMAP:
576e690818eStnn 		return ENODEV;
577e690818eStnn 	}
578e690818eStnn 
579e690818eStnn 	return EPASSTHROUGH;
580e690818eStnn }
581e690818eStnn 
582e690818eStnn static paddr_t
ssdfb_mmap(void * v,void * vs,off_t off,int prot)583e690818eStnn ssdfb_mmap(void *v, void *vs, off_t off, int prot)
584e690818eStnn {
585e690818eStnn 	struct ssdfb_softc *sc = (struct ssdfb_softc *)v;
586e690818eStnn 	struct rasops_info *ri = &sc->sc_ri;
587e690818eStnn 	vaddr_t va_base = (vaddr_t)ri->ri_bits;
588e690818eStnn 	paddr_t pa;
589e690818eStnn 
590e690818eStnn 	if (off < 0 || off >= sc->sc_ri_bits_len || (off & PAGE_MASK) != 0)
591e690818eStnn 		return -1;
592e690818eStnn 
593e690818eStnn 	if (!pmap_extract(pmap_kernel(), va_base + off, &pa))
594e690818eStnn 		return -1;
595e690818eStnn 
596e690818eStnn 	return atop(pa);
597e690818eStnn }
598e690818eStnn 
599e690818eStnn static int
ssdfb_alloc_screen(void * v,const struct wsscreen_descr * descr,void ** cookiep,int * curxp,int * curyp,long * attrp)600e690818eStnn ssdfb_alloc_screen(void *v, const struct wsscreen_descr *descr, void **cookiep,
601e690818eStnn 		   int *curxp, int *curyp, long *attrp)
602e690818eStnn {
603e690818eStnn 	struct ssdfb_softc *sc = v;
604e690818eStnn 	struct rasops_info *ri = &sc->sc_ri;
605e690818eStnn 
606e690818eStnn 	if (sc->sc_nscreens > 0)
607e690818eStnn 		return ENOMEM;
608e690818eStnn 
609e690818eStnn 	ri->ri_ops.allocattr(ri, 0, 0, 0, attrp);
610e690818eStnn 	*cookiep = &sc->sc_ri;
611e690818eStnn 	*curxp = 0;
612e690818eStnn 	*curyp = 0;
613e690818eStnn 	sc->sc_nscreens++;
614e690818eStnn 
615e690818eStnn 	return 0;
616e690818eStnn }
617e690818eStnn 
618e690818eStnn static void
ssdfb_free_screen(void * v,void * cookie)619e690818eStnn ssdfb_free_screen(void *v, void *cookie)
620e690818eStnn {
621e690818eStnn 	struct ssdfb_softc *sc = v;
622e690818eStnn 
623e690818eStnn 	if (sc->sc_is_console)
624e690818eStnn 		panic("ssdfb_free_screen: is console");
625e690818eStnn 
626e690818eStnn 	sc->sc_nscreens--;
627e690818eStnn }
628e690818eStnn 
629e690818eStnn static int
ssdfb_show_screen(void * v,void * cookie,int waitok,void (* cb)(void *,int,int),void * cb_arg)630e690818eStnn ssdfb_show_screen(void *v, void *cookie, int waitok,
631e690818eStnn 		  void (*cb) (void *, int, int), void *cb_arg)
632e690818eStnn {
633e690818eStnn 	return 0;
634e690818eStnn }
635e690818eStnn 
636e690818eStnn static void
ssdfb_putchar(void * cookie,int row,int col,u_int c,long attr)637e690818eStnn ssdfb_putchar(void *cookie, int row, int col, u_int c, long attr)
638e690818eStnn {
639e690818eStnn 	struct rasops_info *ri = (struct rasops_info *)cookie;
640e690818eStnn 	struct ssdfb_softc *sc = ri->ri_hw;
641e690818eStnn 
642e690818eStnn 	sc->sc_orig_riops.putchar(cookie, row, col, c, attr);
643e690818eStnn 	ssdfb_damage(sc);
644e690818eStnn }
645e690818eStnn 
646e690818eStnn static void
ssdfb_copycols(void * cookie,int row,int srccol,int dstcol,int ncols)647e690818eStnn ssdfb_copycols(void *cookie, int row, int srccol, int dstcol, int ncols)
648e690818eStnn {
649e690818eStnn 	struct rasops_info *ri = (struct rasops_info *)cookie;
650e690818eStnn 	struct ssdfb_softc *sc = ri->ri_hw;
651e690818eStnn 
652e690818eStnn 	sc->sc_orig_riops.copycols(cookie, row, srccol, dstcol, ncols);
653e690818eStnn 	ssdfb_damage(sc);
654e690818eStnn }
655e690818eStnn 
656e690818eStnn static void
ssdfb_erasecols(void * cookie,int row,int startcol,int ncols,long fillattr)657e690818eStnn ssdfb_erasecols(void *cookie, int row, int startcol, int ncols, long fillattr)
658e690818eStnn {
659e690818eStnn 	struct rasops_info *ri = (struct rasops_info *)cookie;
660e690818eStnn 	struct ssdfb_softc *sc = ri->ri_hw;
661e690818eStnn 
662e690818eStnn 	sc->sc_orig_riops.erasecols(cookie, row, startcol, ncols, fillattr);
663e690818eStnn 	ssdfb_damage(sc);
664e690818eStnn }
665e690818eStnn 
666e690818eStnn static void
ssdfb_copyrows(void * cookie,int srcrow,int dstrow,int nrows)667e690818eStnn ssdfb_copyrows(void *cookie, int srcrow, int dstrow, int nrows)
668e690818eStnn {
669e690818eStnn 	struct rasops_info *ri = (struct rasops_info *)cookie;
670e690818eStnn 	struct ssdfb_softc *sc = ri->ri_hw;
671e690818eStnn 
672e690818eStnn 	sc->sc_orig_riops.copyrows(cookie, srcrow, dstrow, nrows);
673e690818eStnn 	ssdfb_damage(sc);
674e690818eStnn }
675e690818eStnn 
676e690818eStnn static void
ssdfb_eraserows(void * cookie,int row,int nrows,long fillattr)677e690818eStnn ssdfb_eraserows(void *cookie, int row, int nrows, long fillattr)
678e690818eStnn {
679e690818eStnn 	struct rasops_info *ri = (struct rasops_info *)cookie;
680e690818eStnn 	struct ssdfb_softc *sc = ri->ri_hw;
681e690818eStnn 
682e690818eStnn 	sc->sc_orig_riops.eraserows(cookie, row, nrows, fillattr);
683e690818eStnn 	ssdfb_damage(sc);
684e690818eStnn }
685e690818eStnn 
686e690818eStnn static void
ssdfb_cursor(void * cookie,int on,int row,int col)687e690818eStnn ssdfb_cursor(void *cookie, int on, int row, int col)
688e690818eStnn {
689e690818eStnn 	struct rasops_info *ri = (struct rasops_info *)cookie;
690e690818eStnn 	struct ssdfb_softc *sc = ri->ri_hw;
691e690818eStnn 
692e690818eStnn 	sc->sc_orig_riops.cursor(cookie, on, row, col);
693e690818eStnn 	ssdfb_damage(sc);
694e690818eStnn }
695e690818eStnn 
696e690818eStnn static int
ssdfb_init_ssd1306(struct ssdfb_softc * sc)6978e57c9bfStnn ssdfb_init_ssd1306(struct ssdfb_softc *sc)
698e690818eStnn {
699e690818eStnn 	int error;
70033ea73e5Stnn 	uint8_t cmd[2];
701e690818eStnn 	bool usepoll = true;
702e690818eStnn 
703e690818eStnn 	/*
704e690818eStnn 	 * Enter sleep.
705e690818eStnn 	 */
706e690818eStnn 	SSDFB_CMD1(SSDFB_CMD_SET_DISPLAY_OFF);
707e690818eStnn 	if (error)
708e690818eStnn 		return error;
709e690818eStnn 	SSDFB_CMD1(SSDFB_CMD_DEACTIVATE_SCROLL);
710e690818eStnn 	if (error)
711e690818eStnn 		return error;
712e690818eStnn 	SSDFB_CMD1(SSDFB_CMD_ENTIRE_DISPLAY_OFF);
713e690818eStnn 	if (error)
714e690818eStnn 		return error;
715e690818eStnn 
716e690818eStnn 	/*
717e690818eStnn 	 * Configure physical display panel layout.
718e690818eStnn 	 */
719e690818eStnn 	SSDFB_CMD2(SSDFB_CMD_SET_MULTIPLEX_RATIO, sc->sc_p->p_multiplex_ratio);
720e690818eStnn 	if (error)
721e690818eStnn 		return error;
722e690818eStnn 	SSDFB_CMD2(SSDFB_CMD_SET_DISPLAY_OFFSET, 0);
723e690818eStnn 	if (error)
724e690818eStnn 		return error;
725e690818eStnn 	SSDFB_CMD1(SSDFB_CMD_SET_DISPLAY_START_LINE_BASE + 0x00);
726e690818eStnn 	if (error)
727e690818eStnn 		return error;
728e690818eStnn 	SSDFB_CMD2(SSDFB_CMD_SET_COM_PINS_HARDWARE_CFG, sc->sc_p->p_compin_cfg);
729e690818eStnn 	if (error)
730e690818eStnn 		return error;
731e690818eStnn 	if (sc->sc_upsidedown) {
732e690818eStnn 		SSDFB_CMD1(SSDFB_CMD_SET_SEGMENT_REMAP_REVERSE);
733e690818eStnn 		if (error)
734e690818eStnn 			return error;
735e690818eStnn 		SSDFB_CMD1(SSDFB_CMD_SET_COM_OUTPUT_DIRECTION_REMAP);
736e690818eStnn 		if (error)
737e690818eStnn 			return error;
738e690818eStnn 	} else {
739e690818eStnn 		SSDFB_CMD1(SSDFB_CMD_SET_SEGMENT_REMAP_NORMAL);
740e690818eStnn 		if (error)
741e690818eStnn 			return error;
742e690818eStnn 		SSDFB_CMD1(SSDFB_CMD_SET_COM_OUTPUT_DIRECTION_NORMAL);
743e690818eStnn 		if (error)
744e690818eStnn 			return error;
745e690818eStnn 	}
746e690818eStnn 	SSDFB_CMD1(SSDFB_CMD_SET_NORMAL_DISPLAY + (uint8_t)sc->sc_inverse);
747e690818eStnn 	if (error)
748e690818eStnn 		return error;
749e690818eStnn 
750e690818eStnn 	/*
751e690818eStnn 	 * Configure timing characteristics.
752e690818eStnn 	 */
753e690818eStnn 	SSDFB_CMD2(SSDFB_CMD_SET_DISPLAY_CLOCK_RATIO,
75409fa88a1Stnn 	    __SHIFTIN(sc->sc_p->p_fosc, SSDFB_DISPLAY_CLOCK_OSCILLATOR_MASK) |
75509fa88a1Stnn 	    __SHIFTIN(sc->sc_p->p_fosc_div, SSDFB_DISPLAY_CLOCK_DIVIDER_MASK));
756e690818eStnn 	if (error)
757e690818eStnn 		return error;
758e690818eStnn 	SSDFB_CMD2(SSDFB_CMD_SET_CONTRAST_CONTROL, sc->sc_contrast);
759e690818eStnn 	if (error)
760e690818eStnn 		return error;
761e690818eStnn 	SSDFB_CMD2(SSDFB_CMD_SET_PRECHARGE_PERIOD,
76209fa88a1Stnn 	    __SHIFTIN(sc->sc_p->p_precharge, SSDFB_PRECHARGE_MASK) |
76309fa88a1Stnn 	    __SHIFTIN(sc->sc_p->p_discharge, SSDFB_DISCHARGE_MASK));
764e690818eStnn 	if (error)
765e690818eStnn 		return error;
766e690818eStnn 	SSDFB_CMD2(SSDFB_CMD_SET_VCOMH_DESELECT_LEVEL,
767e690818eStnn 	    sc->sc_p->p_vcomh_deselect_level);
768e690818eStnn 	if (error)
769e690818eStnn 		return error;
770e690818eStnn 
771e690818eStnn 	/*
772ef712effStnn 	 * Start charge pumps.
773e690818eStnn 	 */
774ef712effStnn 	if (sc->sc_p->p_controller_id == SSDFB_CONTROLLER_SH1106) {
775ef712effStnn 		SSDFB_CMD1(SH1106_CMD_SET_CHARGE_PUMP_7V4);
776e690818eStnn 		if (error)
777e690818eStnn 			return error;
778e690818eStnn 		SSDFB_CMD2(SH1106_CMD_SET_DC_DC, SH1106_DC_DC_ON);
779e690818eStnn 		if (error)
780e690818eStnn 			return error;
781ef712effStnn 	} else {
782ef712effStnn 		SSDFB_CMD2(SSD1306_CMD_SET_CHARGE_PUMP,
783ef712effStnn 		    SSD1306_CHARGE_PUMP_ENABLE);
784ef712effStnn 		if (error)
785ef712effStnn 			return error;
786e690818eStnn 	}
787e690818eStnn 
788e690818eStnn 	ssdfb_clear_screen(sc);
789ef712effStnn 	error = sc->sc_p->p_sync(sc, usepoll);
790ef712effStnn 	if (error)
791ef712effStnn 		return error;
792ef712effStnn 	error = ssdfb_set_display_on(sc, true, usepoll);
793ef712effStnn 
794ef712effStnn 	return error;
795ef712effStnn }
796ef712effStnn 
797ef712effStnn static int
ssdfb_init_ssd1322(struct ssdfb_softc * sc)798ef712effStnn ssdfb_init_ssd1322(struct ssdfb_softc *sc)
799ef712effStnn {
800ef712effStnn 	int error;
801ef712effStnn 	uint8_t cmd[3];
802ef712effStnn 	bool usepoll = true;
803ef712effStnn 	uint8_t remap;
804ef712effStnn 	uint8_t dualcom;
805ef712effStnn 
806ef712effStnn 	/*
807ef712effStnn 	 * Enter sleep.
808ef712effStnn 	 */
809ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_SET_COMMAND_LOCK, SSD1322_COMMAND_UNLOCK_MAGIC);
810ef712effStnn 	if (error)
811ef712effStnn 		return error;
812ef712effStnn 	SSDFB_CMD1(SSD1322_CMD_SET_SLEEP_MODE_ON);
813ef712effStnn 	if (error)
814ef712effStnn 		return error;
815ef712effStnn 
816ef712effStnn 	/*
817ef712effStnn 	 * Start charge pumps.
818ef712effStnn 	 */
819ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_FUNCTION_SELECTION,
820ef712effStnn 	    SSD1322_FUNCTION_SELECTION_INTERNAL_VDD);
821ef712effStnn 	if (error)
822ef712effStnn 		return error;
823ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_SET_VCOMH, sc->sc_p->p_vcomh_deselect_level);
824ef712effStnn 	if (error)
825ef712effStnn 		return error;
826ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_SET_PRE_CHARGE_VOLTAGE_LEVEL,
827ef712effStnn 	    SSD1322_DEFAULT_PRE_CHARGE_VOLTAGE_LEVEL);
828ef712effStnn 	if (error)
829ef712effStnn 		return error;
830ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_SET_GPIO,
831ef712effStnn 	    SSD1322_GPIO0_DISABLED | SSD1322_GPIO1_DISABLED);
832ef712effStnn 	if (error)
833ef712effStnn 		return error;
834ef712effStnn 
835ef712effStnn 	/*
836ef712effStnn 	 * Configure timing characteristics.
837ef712effStnn 	 */
838ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_SET_FRONT_CLOCK_DIVIDER,
839ef712effStnn 	   __SHIFTIN(sc->sc_p->p_fosc, SSD1322_FREQUENCY_MASK) |
840ef712effStnn 	   __SHIFTIN(sc->sc_p->p_fosc_div, SSD1322_DIVIDER_MASK));
841ef712effStnn 	if (error)
842ef712effStnn 		return error;
843ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_SET_PHASE_LENGTH,
844ef712effStnn 	   __SHIFTIN(SSD1322_DEFAULT_PHASE_2,
845ef712effStnn 	    SSD1322_PHASE_LENGTH_PHASE_2_MASK) |
846ef712effStnn 	    __SHIFTIN(SSD1322_DEFAULT_PHASE_1,
847ef712effStnn 	    SSD1322_PHASE_LENGTH_PHASE_1_MASK));
848ef712effStnn 	if (error)
849ef712effStnn 		return error;
850ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_SET_SECOND_PRECHARGE_PERIOD,
851fd450c25Stnn 	    SSD1322_DEFAULT_SECOND_PRECHARGE_PERIOD);
852ef712effStnn 	if (error)
853ef712effStnn 		return error;
854ef712effStnn 
855ef712effStnn 	/*
856ef712effStnn 	 * Configure physical display panel layout.
857ef712effStnn 	 */
858fd450c25Stnn 	SSDFB_CMD2(SSD1322_CMD_SET_MULTIPLEX_RATIO, sc->sc_p->p_multiplex_ratio);
859ef712effStnn 	if (error)
860ef712effStnn 		return error;
861ef712effStnn 	if (sc->sc_upsidedown)
862ef712effStnn 		remap = 0x10;
863ef712effStnn 	else
864ef712effStnn 		remap = 0x2;
865ef712effStnn 	dualcom = 0x1;
866ef712effStnn 	if (sc->sc_p->p_multiplex_ratio <= 63)
867ef712effStnn 		dualcom |= 0x10;
868ef712effStnn 	SSDFB_CMD3(SSD1322_CMD_SET_REMAP_AND_DUAL_COM_LINE_MODE, remap, dualcom);
869ef712effStnn 	if (error)
870ef712effStnn 		return error;
871ef712effStnn 
872ef712effStnn 	/*
873ef712effStnn 	 * Contrast settings.
874ef712effStnn 	 */
875ef712effStnn 	SSDFB_CMD1(SSD1322_CMD_SET_DEFAULT_GRAY_SCALE_TABLE);
876ef712effStnn 	if (error)
877ef712effStnn 		return error;
878ef712effStnn 	SSDFB_CMD3(SSD1322_CMD_DISPLAY_ENHANCEMENT_A,
879ef712effStnn 	    SSD1322_DISPLAY_ENHANCEMENT_A_MAGIC1,
880ef712effStnn 	    SSD1322_DISPLAY_ENHANCEMENT_A_MAGIC2);
881ef712effStnn 	if (error)
882ef712effStnn 		return error;
883ef712effStnn 	SSDFB_CMD3(SSD1322_CMD_DISPLAY_ENHANCEMENT_B,
884ef712effStnn 	    SSD1322_DISPLAY_ENHANCEMENT_B_MAGIC1,
885ef712effStnn 	    SSD1322_DISPLAY_ENHANCEMENT_B_MAGIC2);
886ef712effStnn 	if (error)
887ef712effStnn 		return error;
888ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_SET_CONTRAST_CURRENT,
889ef712effStnn 	    sc->sc_contrast);
890ef712effStnn 	if (error)
891ef712effStnn 		return error;
892ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_MASTER_CONTRAST_CURRENT_CONTROL,
893ef712effStnn 	    SSD1322_DEFAULT_MASTER_CONTRAST_CURRENT_CONTROL);
894ef712effStnn 	if (error)
895ef712effStnn 		return error;
896ef712effStnn 
897ef712effStnn 	/*
898ef712effStnn 	 * Reset display engine state.
899ef712effStnn 	 */
900ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_SET_DISPLAY_OFFSET, 0x00);
901ef712effStnn 	if (error)
902ef712effStnn 		return error;
903ef712effStnn 	SSDFB_CMD2(SSD1322_CMD_SET_DISPLAY_START_LINE, 0x00);
904ef712effStnn 	if (error)
905ef712effStnn 		return error;
906ef712effStnn 	SSDFB_CMD1(SSD1322_CMD_NORMAL_DISPLAY + (uint8_t)sc->sc_inverse);
907ef712effStnn 	if (error)
908ef712effStnn 		return error;
909ef712effStnn 	SSDFB_CMD1(SSD1322_CMD_EXIT_PARTIAL_DISPLAY);
910ef712effStnn 	if (error)
911ef712effStnn 		return error;
912ef712effStnn 
913ef712effStnn 	ssdfb_clear_screen(sc);
914e690818eStnn 	error = ssdfb_sync(sc, usepoll);
915e690818eStnn 	if (error)
916e690818eStnn 		return error;
917ef712effStnn 
918e690818eStnn 	error = ssdfb_set_display_on(sc, true, usepoll);
919e690818eStnn 
920e690818eStnn 	return error;
921e690818eStnn }
922e690818eStnn 
923e690818eStnn static int
ssdfb_init_ssd1353(struct ssdfb_softc * sc)92423cac501Stnn ssdfb_init_ssd1353(struct ssdfb_softc *sc)
92523cac501Stnn {
92623cac501Stnn 	int error;
92723cac501Stnn 	uint8_t cmd[3];
92823cac501Stnn 	bool usepoll = true;
92923cac501Stnn 	uint8_t remap;
93023cac501Stnn 
93123cac501Stnn 	/*
93223cac501Stnn 	 * Enter sleep.
93323cac501Stnn 	 */
93423cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_COMMAND_LOCK, SSD1353_COMMAND_UNLOCK_MAGIC);
93523cac501Stnn 	if (error)
93623cac501Stnn 		return error;
93723cac501Stnn 	SSDFB_CMD1(SSD1353_CMD_RESET);
93823cac501Stnn 	if (error)
93923cac501Stnn 		return error;
94023cac501Stnn 	SSDFB_CMD1(SSD1353_CMD_DEACTIVATE_SCROLL);
94123cac501Stnn 	if (error)
94223cac501Stnn 		return error;
94323cac501Stnn 	SSDFB_CMD1(SSD1353_CMD_SET_DISPLAY_OFF);
94423cac501Stnn 	if (error)
94523cac501Stnn 		return error;
94623cac501Stnn 
94723cac501Stnn 	/*
94823cac501Stnn 	 * Start charge pumps.
94923cac501Stnn 	 */
95023cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_VCOMH, sc->sc_p->p_vcomh_deselect_level);
95123cac501Stnn 	if (error)
95223cac501Stnn 		return error;
95323cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_PRE_CHARGE_VOLTAGE_LEVEL,
95423cac501Stnn 	    SSD1353_DEFAULT_PRE_CHARGE_VOLTAGE_LEVEL);
95523cac501Stnn 	if (error)
95623cac501Stnn 		return error;
95723cac501Stnn 
95823cac501Stnn 	/*
95923cac501Stnn 	 * Configure timing characteristics.
96023cac501Stnn 	 */
96123cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_FRONT_CLOCK_DIVIDER,
96223cac501Stnn 	   __SHIFTIN(sc->sc_p->p_fosc, SSD1322_FREQUENCY_MASK) |
96323cac501Stnn 	   __SHIFTIN(sc->sc_p->p_fosc_div, SSD1322_DIVIDER_MASK));
96423cac501Stnn 	if (error)
96523cac501Stnn 		return error;
96623cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_PHASE_LENGTH,
96723cac501Stnn 	   __SHIFTIN(SSD1353_DEFAULT_PHASE_2,
96823cac501Stnn 	    SSD1322_PHASE_LENGTH_PHASE_2_MASK) |
96923cac501Stnn 	    __SHIFTIN(SSD1353_DEFAULT_PHASE_1,
97023cac501Stnn 	    SSD1322_PHASE_LENGTH_PHASE_1_MASK));
97123cac501Stnn 	if (error)
97223cac501Stnn 		return error;
97323cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_SECOND_PRECHARGE_PERIOD,
97423cac501Stnn 	    SSD1353_DEFAULT_SECOND_PRECHARGE_PERIOD);
97523cac501Stnn 	if (error)
97623cac501Stnn 		return error;
97723cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_SECOND_PRECHARGE_SPEED,
97823cac501Stnn 	    SSD1353_DEFAULT_SECOND_PRECHARGE_SPEED);
97923cac501Stnn 	if (error)
98023cac501Stnn 		return error;
98123cac501Stnn 
98223cac501Stnn 	/*
98323cac501Stnn 	 * Configure physical display panel layout.
98423cac501Stnn 	 */
98523cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_MULTIPLEX_RATIO, sc->sc_p->p_multiplex_ratio);
98623cac501Stnn 	if (error)
98723cac501Stnn 		return error;
98823cac501Stnn         remap = sc->sc_p->p_compin_cfg;
98923cac501Stnn         if (sc->sc_upsidedown)
99023cac501Stnn                 remap ^= SSD1353_REMAP_COM_DIRECTION;
99123cac501Stnn         else
99223cac501Stnn                 remap ^= SSD1353_REMAP_SEG_DIRECTION;
99323cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_REMAP_COLOR_DEPTH, remap);
99423cac501Stnn 	if (error)
99523cac501Stnn 		return error;
99623cac501Stnn 
99723cac501Stnn 	/*
99823cac501Stnn 	 * Contrast settings.
99923cac501Stnn 	 */
100023cac501Stnn 	SSDFB_CMD1(SSD1353_CMD_SET_DEFAULT_GRAY_SCALE_TABLE);
100123cac501Stnn 	if (error)
100223cac501Stnn 		return error;
100323cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_CONTRAST_CONTROL_A, sc->sc_contrast);
100423cac501Stnn 	if (error)
100523cac501Stnn 		return error;
100623cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_CONTRAST_CONTROL_B, sc->sc_contrast);
100723cac501Stnn 	if (error)
100823cac501Stnn 		return error;
100923cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_CONTRAST_CONTROL_C, sc->sc_contrast);
101023cac501Stnn 	if (error)
101123cac501Stnn 		return error;
101223cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_MASTER_CURRENT_CONTROL,
101323cac501Stnn 	    SSD1353_DEFAULT_MASTER_CURRENT_ATTENUATION);
101423cac501Stnn 	if (error)
101523cac501Stnn 		return error;
101623cac501Stnn 
101723cac501Stnn 	/*
101823cac501Stnn 	 * Reset display engine state.
101923cac501Stnn 	 */
102023cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_DISPLAY_OFFSET, 0x00);
102123cac501Stnn 	if (error)
102223cac501Stnn 		return error;
102323cac501Stnn 	SSDFB_CMD2(SSD1353_CMD_SET_DISPLAY_START_LINE, 0x00);
102423cac501Stnn 	if (error)
102523cac501Stnn 		return error;
102623cac501Stnn 	SSDFB_CMD1(sc->sc_inverse
102723cac501Stnn 	    ? SSD1353_CMD_INVERSE_DISPLAY
102823cac501Stnn 	    : SSD1353_CMD_NORMAL_DISPLAY);
102923cac501Stnn 	if (error)
103023cac501Stnn 		return error;
103123cac501Stnn 
103223cac501Stnn 	ssdfb_clear_screen(sc);
103323cac501Stnn 	error = ssdfb_sync(sc, usepoll);
103423cac501Stnn 	if (error)
103523cac501Stnn 		return error;
103623cac501Stnn 
103723cac501Stnn 	error = ssdfb_set_display_on(sc, true, usepoll);
103823cac501Stnn 
103923cac501Stnn 	return error;
104023cac501Stnn }
104123cac501Stnn 
104223cac501Stnn static int
ssdfb_set_contrast(struct ssdfb_softc * sc,uint8_t value,bool usepoll)1043e690818eStnn ssdfb_set_contrast(struct ssdfb_softc *sc, uint8_t value, bool usepoll)
1044e690818eStnn {
1045e690818eStnn 	uint8_t cmd[2];
104623cac501Stnn 	int error;
1047e690818eStnn 
104823cac501Stnn 	cmd[1] = sc->sc_contrast = value;
1049ef712effStnn 	switch (sc->sc_p->p_controller_id) {
1050ef712effStnn 	case SSDFB_CONTROLLER_SSD1322:
1051ef712effStnn 		cmd[0] = SSD1322_CMD_SET_CONTRAST_CURRENT;
1052ef712effStnn 		break;
105323cac501Stnn 	case SSDFB_CONTROLLER_SSD1353:
105423cac501Stnn 		cmd[0] = SSD1353_CMD_SET_CONTRAST_CONTROL_A;
105523cac501Stnn 		error = sc->sc_cmd(sc->sc_cookie, cmd, sizeof(cmd), usepoll);
105623cac501Stnn 		if (error)
105723cac501Stnn 			return error;
105823cac501Stnn 		cmd[0] = SSD1353_CMD_SET_CONTRAST_CONTROL_B;
105923cac501Stnn 		error = sc->sc_cmd(sc->sc_cookie, cmd, sizeof(cmd), usepoll);
106023cac501Stnn 		if (error)
106123cac501Stnn 			return error;
106223cac501Stnn 		cmd[0] = SSD1353_CMD_SET_CONTRAST_CONTROL_C;
1063*fc683fa3Stnn 		break;
1064ef712effStnn 	default:
1065ef712effStnn 		cmd[0] = SSDFB_CMD_SET_CONTRAST_CONTROL;
1066*fc683fa3Stnn 		break;
1067ef712effStnn 	}
1068e690818eStnn 
1069ef712effStnn 	return sc->sc_cmd(sc->sc_cookie, cmd, sizeof(cmd), usepoll);
1070e690818eStnn }
1071e690818eStnn 
1072e690818eStnn static int
ssdfb_set_display_on(struct ssdfb_softc * sc,bool value,bool usepoll)1073e690818eStnn ssdfb_set_display_on(struct ssdfb_softc *sc, bool value, bool usepoll)
1074e690818eStnn {
1075e690818eStnn 	uint8_t cmd[1];
1076e690818eStnn 	int error;
1077e690818eStnn 	sc->sc_display_on = value;
1078e690818eStnn 
1079e690818eStnn 	SSDFB_CMD1(value ? SSDFB_CMD_SET_DISPLAY_ON : SSDFB_CMD_SET_DISPLAY_OFF);
1080e690818eStnn 
1081e690818eStnn 	return error;
1082e690818eStnn }
1083e690818eStnn 
1084e690818eStnn static int
ssdfb_set_mode(struct ssdfb_softc * sc,u_int mode)1085e690818eStnn ssdfb_set_mode(struct ssdfb_softc *sc, u_int mode)
1086e690818eStnn {
1087e690818eStnn 	switch (mode) {
1088e690818eStnn 	case WSDISPLAYIO_MODE_EMUL:
1089e690818eStnn 	case WSDISPLAYIO_MODE_DUMBFB:
1090e690818eStnn 		break;
1091e690818eStnn 	default:
1092e690818eStnn 		return EINVAL;
1093e690818eStnn 	}
1094e690818eStnn 	if (mode == sc->sc_mode)
1095e690818eStnn 		return 0;
1096e690818eStnn 	mutex_enter(&sc->sc_cond_mtx);
1097e690818eStnn 	sc->sc_mode = mode;
1098e690818eStnn 	cv_broadcast(&sc->sc_cond);
1099e690818eStnn 	mutex_exit(&sc->sc_cond_mtx);
1100e690818eStnn 	ssdfb_clear_screen(sc);
1101e690818eStnn 	ssdfb_damage(sc);
1102e690818eStnn 
1103e690818eStnn 	return 0;
1104e690818eStnn }
1105e690818eStnn 
1106e690818eStnn static void
ssdfb_damage(struct ssdfb_softc * sc)1107e690818eStnn ssdfb_damage(struct ssdfb_softc *sc)
1108e690818eStnn {
1109e690818eStnn 	int s;
1110e690818eStnn 
1111e690818eStnn 	if (sc->sc_usepoll) {
1112e690818eStnn 		(void) ssdfb_sync(sc, true);
1113e690818eStnn 	} else {
1114e690818eStnn 		/*
1115e690818eStnn 		 * kernel code isn't permitted to call us via kprintf at
1116e690818eStnn 		 * splhigh. In case misbehaving code calls us anyway we can't
1117e690818eStnn 		 * safely take the mutex so we skip the damage notification.
1118e690818eStnn 		 */
1119e690818eStnn 		if (sc->sc_is_console) {
1120e690818eStnn 			s = splhigh();
1121e690818eStnn 			splx(s);
1122e690818eStnn 			if (s == IPL_HIGH)
1123e690818eStnn 				return;
1124e690818eStnn 		}
1125e690818eStnn 		mutex_enter(&sc->sc_cond_mtx);
1126e690818eStnn 		sc->sc_modified = true;
1127e690818eStnn 		cv_broadcast(&sc->sc_cond);
1128e690818eStnn 		mutex_exit(&sc->sc_cond_mtx);
1129e690818eStnn 	}
1130e690818eStnn }
1131e690818eStnn 
113233ea73e5Stnn static void
ssdfb_udv_attach(struct ssdfb_softc * sc)113333ea73e5Stnn ssdfb_udv_attach(struct ssdfb_softc *sc)
113433ea73e5Stnn {
113533ea73e5Stnn 	extern const struct cdevsw wsdisplay_cdevsw;
113633ea73e5Stnn 	dev_t dev;
113733ea73e5Stnn #define WSDISPLAYMINOR(unit, screen)	(((unit) << 8) | (screen))
113833ea73e5Stnn 	dev = makedev(cdevsw_lookup_major(&wsdisplay_cdevsw),
113933ea73e5Stnn 	    WSDISPLAYMINOR(device_unit(sc->sc_wsdisplay), 0));
114033ea73e5Stnn 	sc->sc_uobj = udv_attach(dev, VM_PROT_READ|VM_PROT_WRITE, 0,
114133ea73e5Stnn 	    sc->sc_ri_bits_len);
114233ea73e5Stnn }
114333ea73e5Stnn 
1144e690818eStnn static bool
ssdfb_is_modified(struct ssdfb_softc * sc)1145e690818eStnn ssdfb_is_modified(struct ssdfb_softc *sc)
1146e690818eStnn {
1147e690818eStnn 	vaddr_t va, va_end;
1148e690818eStnn 
1149e690818eStnn 	if (sc->sc_mode == WSDISPLAYIO_MODE_EMUL)
1150e690818eStnn 		return sc->sc_modified;
1151e690818eStnn 
115233ea73e5Stnn 	if (sc->sc_uobj == NULL)
115333ea73e5Stnn 		return false;
115433ea73e5Stnn 
1155e690818eStnn 	va = (vaddr_t)sc->sc_ri.ri_bits;
1156e690818eStnn 	va_end = va + sc->sc_ri_bits_len;
1157e690818eStnn 	while (va < va_end) {
1158e690818eStnn 		if (pmap_is_modified(uvm_pageratop(va)))
1159e690818eStnn 			return true;
1160e690818eStnn 		va += PAGE_SIZE;
1161e690818eStnn 	}
1162e690818eStnn 
1163e690818eStnn 	return false;
1164e690818eStnn }
1165e690818eStnn 
1166e690818eStnn static bool
ssdfb_clear_modify(struct ssdfb_softc * sc)1167e690818eStnn ssdfb_clear_modify(struct ssdfb_softc *sc)
1168e690818eStnn {
1169e690818eStnn 	vaddr_t va, va_end;
1170e690818eStnn 	bool ret;
1171e690818eStnn 
1172e690818eStnn 	if (sc->sc_mode == WSDISPLAYIO_MODE_EMUL) {
1173ef712effStnn 		mutex_enter(&sc->sc_cond_mtx);
1174e690818eStnn 		ret = sc->sc_modified;
1175e690818eStnn 		sc->sc_modified = false;
1176ef712effStnn 		mutex_exit(&sc->sc_cond_mtx);
1177e690818eStnn 		return ret;
1178e690818eStnn 	}
1179e690818eStnn 
118033ea73e5Stnn 	if (sc->sc_uobj == NULL)
118133ea73e5Stnn 		return false;
118233ea73e5Stnn 
1183e690818eStnn 	va = (vaddr_t)sc->sc_ri.ri_bits;
1184e690818eStnn 	va_end = va + sc->sc_ri_bits_len;
1185e690818eStnn 	ret = false;
1186e690818eStnn 	while (va < va_end) {
1187e690818eStnn 		if (pmap_clear_modify(uvm_pageratop(va)))
1188e690818eStnn 			ret = true;
1189e690818eStnn 		va += PAGE_SIZE;
1190e690818eStnn 	}
1191e690818eStnn 
1192e690818eStnn 	return ret;
1193e690818eStnn }
1194e690818eStnn 
1195e690818eStnn static void
ssdfb_thread(void * arg)11962f60d4f5Stnn ssdfb_thread(void *arg)
11972f60d4f5Stnn {
1198e690818eStnn 	struct ssdfb_softc *sc = (struct ssdfb_softc *)arg;
1199e690818eStnn 	int error;
1200e690818eStnn 
1201e690818eStnn 	mutex_enter(&sc->sc_cond_mtx);
1202e690818eStnn 
1203e690818eStnn 	if (sc->sc_usepoll)
1204e690818eStnn 		ssdfb_set_usepoll(sc, false);
1205e690818eStnn 
1206e690818eStnn 	while(!sc->sc_detaching) {
120733ea73e5Stnn 		if (sc->sc_mode == WSDISPLAYIO_MODE_DUMBFB &&
120833ea73e5Stnn 		    sc->sc_uobj == NULL) {
120933ea73e5Stnn 			mutex_exit(&sc->sc_cond_mtx);
121033ea73e5Stnn 			ssdfb_udv_attach(sc);
121133ea73e5Stnn 			mutex_enter(&sc->sc_cond_mtx);
121233ea73e5Stnn 		}
1213e690818eStnn 		if (!ssdfb_is_modified(sc)) {
1214e690818eStnn 			if (cv_timedwait(&sc->sc_cond, &sc->sc_cond_mtx,
1215e690818eStnn 			    sc->sc_mode == WSDISPLAYIO_MODE_EMUL
1216e690818eStnn 			    ? 0 : sc->sc_backoff) == EWOULDBLOCK
1217e690818eStnn 			    && sc->sc_backoff < mstohz(200)) {
1218e690818eStnn 				sc->sc_backoff <<= 1;
1219e690818eStnn 			}
1220e690818eStnn 			continue;
1221e690818eStnn 		}
1222e690818eStnn 		sc->sc_backoff = 1;
1223e690818eStnn 		mutex_exit(&sc->sc_cond_mtx);
1224ef712effStnn 		(void) ssdfb_clear_modify(sc);
1225ef712effStnn 		if (!sc->sc_usepoll) {
1226e690818eStnn 			error = ssdfb_sync(sc, false);
1227e690818eStnn 			if (error)
1228ef712effStnn 				device_printf(sc->sc_dev,
1229ef712effStnn 				    "ssdfb_sync: error %d\n",
1230e690818eStnn 				    error);
1231ef712effStnn 		}
1232e690818eStnn 		mutex_enter(&sc->sc_cond_mtx);
1233e690818eStnn 	}
1234e690818eStnn 
1235e690818eStnn 	mutex_exit(&sc->sc_cond_mtx);
123650090521Stnn 	kthread_exit(0);
1237e690818eStnn }
1238e690818eStnn 
1239e690818eStnn static void
ssdfb_set_usepoll(struct ssdfb_softc * sc,bool enable)12402f60d4f5Stnn ssdfb_set_usepoll(struct ssdfb_softc *sc, bool enable)
12412f60d4f5Stnn {
1242e690818eStnn 	sc->sc_usepoll = enable;
1243e690818eStnn }
1244e690818eStnn 
1245e690818eStnn static int
ssdfb_sync(struct ssdfb_softc * sc,bool usepoll)12462f60d4f5Stnn ssdfb_sync(struct ssdfb_softc *sc, bool usepoll)
12472f60d4f5Stnn {
1248ef712effStnn 	return sc->sc_p->p_sync(sc, usepoll);
1249ef712effStnn }
1250ef712effStnn 
1251ef712effStnn static int
ssdfb_sync_ssd1306(struct ssdfb_softc * sc,bool usepoll)1252ef712effStnn ssdfb_sync_ssd1306(struct ssdfb_softc *sc, bool usepoll)
1253ef712effStnn {
1254e690818eStnn 	struct rasops_info *ri = &sc->sc_ri;
1255e690818eStnn 	int block_size = 8;
1256e690818eStnn 	int ri_block_stride = ri->ri_stride * block_size;
1257e690818eStnn 	int height_in_blocks = sc->sc_p->p_height / block_size;
1258e690818eStnn 	int width_in_blocks  = sc->sc_p->p_width / block_size;
1259e690818eStnn 	int ri_block_step = block_size * ri->ri_depth / 8;
1260e690818eStnn 	int x, y;
1261e690818eStnn 	union ssdfb_block *blockp;
1262e690818eStnn 	uint64_t raw_block;
1263e690818eStnn 	uint8_t *src;
1264e690818eStnn 	int x1, x2, y1, y2;
1265e690818eStnn 
1266e690818eStnn 	/*
1267e690818eStnn 	 * Transfer rasops bitmap into gddram shadow buffer while keeping track
1268e690818eStnn 	 * of the bounding box of the dirty region we scribbled over.
1269e690818eStnn 	 */
1270e690818eStnn 	x1 = width_in_blocks;
1271e690818eStnn 	x2 = -1;
1272e690818eStnn 	y1 = height_in_blocks;
1273e690818eStnn 	y2 = -1;
1274e690818eStnn 	for (y = 0; y < height_in_blocks; y++) {
1275e690818eStnn 		src = &ri->ri_bits[y * ri_block_stride];
1276e690818eStnn 		blockp = &sc->sc_gddram[y * width_in_blocks];
1277e690818eStnn 		for (x = 0; x < width_in_blocks; x++) {
1278ef712effStnn 			raw_block = ssdfb_transpose_block(src, ri->ri_stride);
1279e690818eStnn 			if (raw_block != blockp->raw) {
1280e690818eStnn 				blockp->raw = raw_block;
1281e690818eStnn 				if (x1 > x)
1282e690818eStnn 					x1 = x;
1283e690818eStnn 				if (x2 < x)
1284e690818eStnn 					x2 = x;
1285e690818eStnn 				if (y1 > y)
1286e690818eStnn 					y1 = y;
1287e690818eStnn 				if (y2 < y)
1288e690818eStnn 					y2 = y;
1289e690818eStnn 			}
1290e690818eStnn 			src += ri_block_step;
1291e690818eStnn 			blockp++;
1292e690818eStnn 		}
1293e690818eStnn 	}
1294e690818eStnn 	if (x2 != -1)
1295e690818eStnn 		return sc->sc_transfer_rect(sc->sc_cookie,
1296e690818eStnn 		    x1 * block_size + sc->sc_p->p_panel_shift,
1297e690818eStnn 		    (x2 + 1) * block_size - 1 + sc->sc_p->p_panel_shift,
1298e690818eStnn 		    y1,
1299e690818eStnn 		    y2,
1300e690818eStnn 		    &sc->sc_gddram[y1 * width_in_blocks + x1].col[0],
1301e690818eStnn 		    sc->sc_p->p_width,
1302e690818eStnn 		    usepoll);
1303e690818eStnn 
1304e690818eStnn 	return 0;
1305e690818eStnn }
1306e690818eStnn 
1307ef712effStnn static int
ssdfb_sync_ssd1322(struct ssdfb_softc * sc,bool usepoll)1308ef712effStnn ssdfb_sync_ssd1322(struct ssdfb_softc *sc, bool usepoll)
1309ef712effStnn {
1310ef712effStnn 	struct rasops_info *ri = &sc->sc_ri;
1311ef712effStnn 	int block_size_w = 4;
1312ef712effStnn 	int width = sc->sc_p->p_width;
1313ef712effStnn 	int height = sc->sc_p->p_height;
1314ef712effStnn 	int width_in_blocks = width / block_size_w;
1315ef712effStnn 	int x, y;
1316ef712effStnn 	uint16_t *blockp;
1317ef712effStnn 	uint16_t raw_block;
1318ef712effStnn 	uint16_t *src;
1319ef712effStnn 	uint32_t *src32;
1320ef712effStnn 	int x1, x2, y1, y2;
1321ef712effStnn 
1322ef712effStnn 	/*
1323ef712effStnn 	 * Transfer rasops bitmap into gddram shadow buffer while keeping track
1324ef712effStnn 	 * of the bounding box of the dirty region we scribbled over.
1325ef712effStnn 	 */
1326d6ec530eStnn 	x1 = width;
1327ef712effStnn 	x2 = -1;
1328d6ec530eStnn 	y1 = height;
1329ef712effStnn 	y2 = -1;
1330ef712effStnn 	blockp = (uint16_t*)sc->sc_gddram;
1331ef712effStnn 	for (y = 0; y < height; y++) {
1332ef712effStnn 		src = (uint16_t*)&ri->ri_bits[y * ri->ri_stride];
1333ef712effStnn 		src32 = (uint32_t*)src;
1334ef712effStnn 		for (x = 0; x < width_in_blocks; x++) {
1335ef712effStnn #ifdef SSDFB_USE_NATIVE_DEPTH
1336ef712effStnn 			raw_block =
1337ef712effStnn 			    ((*src << 12) & 0xf000) |
1338ef712effStnn 			    ((*src << 4)  & 0x0f00) |
1339ef712effStnn 			    ((*src >> 4)  & 0x00f0) |
1340ef712effStnn 			    ((*src >> 12) & 0x000f);
1341ef712effStnn 			src++;
1342ef712effStnn #else
1343ef712effStnn 			raw_block =
13448ed75fc0Stnn #  if _BYTE_ORDER == _LITTLE_ENDIAN
1345ef712effStnn 			    ((*src32 <<  8) & 0x0f00) |
1346ef712effStnn 			    ((*src32 <<  4) & 0xf000) |
1347ef712effStnn 			    ((*src32 >> 16) & 0x000f) |
1348ef712effStnn 			    ((*src32 >> 20) & 0x00f0);
13498ed75fc0Stnn #  else
13508ed75fc0Stnn 			    ((*src32 >> 24) & 0x000f) |
13518ed75fc0Stnn 			    ((*src32 >> 12) & 0x00f0) |
13528ed75fc0Stnn 			    ((*src32      ) & 0x0f00) |
13538ed75fc0Stnn 			    ((*src32 << 12) & 0xf000);
1354ef712effStnn #  endif
1355ef712effStnn 			src32++;
1356ef712effStnn #endif
1357ef712effStnn 			if (raw_block != *blockp) {
1358ef712effStnn 				*blockp = raw_block;
1359ef712effStnn 				if (x1 > x)
1360ef712effStnn 					x1 = x;
1361ef712effStnn 				if (x2 < x)
1362ef712effStnn 					x2 = x;
1363ef712effStnn 				if (y1 > y)
1364ef712effStnn 					y1 = y;
1365ef712effStnn 				if (y2 < y)
1366ef712effStnn 					y2 = y;
1367ef712effStnn 			}
1368ef712effStnn 			blockp++;
1369ef712effStnn 		}
1370ef712effStnn 	}
1371ef712effStnn 	blockp = (uint16_t*)sc->sc_gddram;
1372ef712effStnn 	if (x2 != -1)
1373ef712effStnn 		return sc->sc_transfer_rect(sc->sc_cookie,
1374ef712effStnn 		    x1 + sc->sc_p->p_panel_shift,
1375ef712effStnn 		    x2 + sc->sc_p->p_panel_shift,
1376ef712effStnn 		    y1,
1377ef712effStnn 		    y2,
1378ef712effStnn 		    (uint8_t*)&blockp[y1 * width_in_blocks + x1],
1379ef712effStnn 		    width * sc->sc_p->p_bits_per_pixel / 8,
1380ef712effStnn 		    usepoll);
1381ef712effStnn 	return 0;
1382ef712effStnn }
1383ef712effStnn 
138423cac501Stnn static int
ssdfb_sync_ssd1353(struct ssdfb_softc * sc,bool usepoll)138523cac501Stnn ssdfb_sync_ssd1353(struct ssdfb_softc *sc, bool usepoll)
138623cac501Stnn {
138723cac501Stnn 	int width = sc->sc_p->p_width;
138823cac501Stnn 	int height = sc->sc_p->p_height;
138923cac501Stnn 	struct rasops_info *ri = &sc->sc_ri;
139023cac501Stnn 	int x, y;
139123cac501Stnn 	uint32_t *src, *blockp;
139223cac501Stnn 	int x1, x2, y1, y2;
139323cac501Stnn 
139423cac501Stnn 	/*
139523cac501Stnn 	 * Transfer rasops bitmap into gddram shadow buffer while keeping track
139623cac501Stnn 	 * of the bounding box of the dirty region we scribbled over.
139723cac501Stnn 	 */
139823cac501Stnn 	x1 = width;
139923cac501Stnn 	x2 = -1;
140023cac501Stnn 	y1 = height;
140123cac501Stnn 	y2 = -1;
140223cac501Stnn 	blockp = (uint32_t*)sc->sc_gddram;
140323cac501Stnn 	for (y = 0; y < height; y++) {
140423cac501Stnn 		src = (uint32_t*)&ri->ri_bits[y * ri->ri_stride];
140523cac501Stnn 		for (x = 0; x < width; x++) {
140623cac501Stnn 			if (*blockp != *src) {
140723cac501Stnn 				*blockp = *src;
140823cac501Stnn 				if (x1 > x)
140923cac501Stnn 					x1 = x;
141023cac501Stnn 				if (x2 < x)
141123cac501Stnn 					x2 = x;
141223cac501Stnn 				if (y1 > y)
141323cac501Stnn 					y1 = y;
141423cac501Stnn 				if (y2 < y)
141523cac501Stnn 					y2 = y;
141623cac501Stnn 			}
141723cac501Stnn 			blockp++;
141823cac501Stnn 			src++;
141923cac501Stnn 		}
142023cac501Stnn 	}
142123cac501Stnn 
142223cac501Stnn 	blockp = (uint32_t*)sc->sc_gddram;
142323cac501Stnn 	if (x2 != -1)
142423cac501Stnn 		return sc->sc_transfer_rect(sc->sc_cookie,
142523cac501Stnn 		    x1 + sc->sc_p->p_panel_shift,
142623cac501Stnn 		    x2 + sc->sc_p->p_panel_shift,
142723cac501Stnn 		    y1,
142823cac501Stnn 		    y2,
142923cac501Stnn 		    (uint8_t*)&blockp[y1 * width + x1],
143023cac501Stnn 		    width * sc->sc_p->p_bits_per_pixel / 8,
143123cac501Stnn 		    usepoll);
143223cac501Stnn 	return 0;
143323cac501Stnn }
143423cac501Stnn 
1435e690818eStnn static uint64_t
ssdfb_transpose_block(uint8_t * src,size_t src_stride)1436ef712effStnn ssdfb_transpose_block(uint8_t *src, size_t src_stride)
1437e690818eStnn {
1438e690818eStnn 	uint64_t x = 0;
1439ef712effStnn #ifdef SSDFB_USE_NATIVE_DEPTH
1440e690818eStnn 	uint64_t t;
1441e690818eStnn 	int i;
1442e690818eStnn 
1443e690818eStnn 	/*
1444e690818eStnn 	 * collect the 8x8 block.
1445e690818eStnn 	 */
1446e690818eStnn 	for (i = 0; i < 8; i++) {
1447e690818eStnn 		x >>= 8;
1448e690818eStnn 		x |= (uint64_t)src[i * src_stride] << 56;
1449e690818eStnn 	}
1450e690818eStnn 
1451e690818eStnn 	/*
1452e690818eStnn 	 * Transpose it into gddram layout.
1453e690818eStnn 	 * Post-transpose bswap is the same as pre-transpose bit order reversal.
1454e690818eStnn 	 * We do this to match rasops1 bit order.
1455e690818eStnn 	 */
1456e690818eStnn 	t = (x ^ (x >> 28)) & 0x00000000F0F0F0F0ULL;
1457e690818eStnn 	x = x ^ t ^ (t << 28);
1458e690818eStnn 	t = (x ^ (x >> 14)) & 0x0000CCCC0000CCCCULL;
1459e690818eStnn 	x = x ^ t ^ (t << 14);
1460e690818eStnn 	t = (x ^ (x >>  7)) & 0x00AA00AA00AA00AAULL;
1461e690818eStnn 	x = x ^ t ^ (t <<  7);
1462e690818eStnn 	x = bswap64(x);
1463ef712effStnn #else
1464e690818eStnn 	int m, n;
1465e690818eStnn 
1466e690818eStnn 	for (m = 0; m < 8; m++) {
1467e690818eStnn 		for (n = 0; n < 8; n++) {
1468e690818eStnn 			x >>= 1;
1469e690818eStnn 			x |= src[n * src_stride + m] ? (1ULL << 63) : 0;
1470e690818eStnn 		}
1471e690818eStnn 	}
1472ef712effStnn #endif
1473e690818eStnn 	return htole64(x);
1474e690818eStnn }
1475e690818eStnn 
1476e690818eStnn static const struct ssdfb_product *
ssdfb_lookup_product(ssdfb_product_id_t id)14772f60d4f5Stnn ssdfb_lookup_product(ssdfb_product_id_t id)
14782f60d4f5Stnn {
1479e690818eStnn 	int i;
1480e690818eStnn 
1481e690818eStnn 	for (i = 0; i < __arraycount(ssdfb_products); i++) {
1482e690818eStnn 		if (ssdfb_products[i].p_product_id == id)
1483e690818eStnn 			return &ssdfb_products[i];
1484e690818eStnn 	}
1485e690818eStnn 
1486e690818eStnn 	return NULL;
1487e690818eStnn }
1488e690818eStnn 
1489e690818eStnn static int
ssdfb_pick_font(int * cookiep,struct wsdisplay_font ** fontp)14902f60d4f5Stnn ssdfb_pick_font(int *cookiep, struct wsdisplay_font **fontp)
14912f60d4f5Stnn {
1492e690818eStnn 	int error;
1493e690818eStnn 	int c;
1494e690818eStnn 	struct wsdisplay_font *f;
1495e690818eStnn 	int i;
1496e690818eStnn 	uint8_t d[4][2] = {{5, 8}, {8, 8}, {8, 10} ,{8, 16}};
1497e690818eStnn 
1498e690818eStnn 	/*
14996f6ddff5Stnn 	 * Try to find fonts in order of increasing size.
1500e690818eStnn 	 */
1501e690818eStnn 	wsfont_init();
1502e690818eStnn 	for(i = 0; i < __arraycount(d); i++) {
1503e690818eStnn 		c = wsfont_find(NULL, d[i][0], d[i][1], 0,
1504e690818eStnn 		    WSDISPLAY_FONTORDER_L2R, WSDISPLAY_FONTORDER_L2R,
1505e690818eStnn 		    WSFONT_FIND_BITMAP);
1506e690818eStnn 		if (c > 0)
1507e690818eStnn 			break;
1508e690818eStnn 	}
1509e690818eStnn 	if (c <= 0)
1510e690818eStnn 		return ENOENT;
1511e690818eStnn 	error = wsfont_lock(c, &f);
1512e690818eStnn 	if (error)
1513e690818eStnn 		return error;
1514e690818eStnn 	*cookiep = c;
1515e690818eStnn 	*fontp = f;
1516e690818eStnn 
1517e690818eStnn 	return 0;
1518e690818eStnn }
1519e690818eStnn 
1520e690818eStnn static void
ssdfb_clear_screen(struct ssdfb_softc * sc)1521e690818eStnn ssdfb_clear_screen(struct ssdfb_softc *sc)
1522e690818eStnn {
1523e690818eStnn 	struct rasops_info *ri = &sc->sc_ri;
1524e690818eStnn 
1525e690818eStnn 	memset(sc->sc_gddram, 0xff, sc->sc_gddram_len);
1526e690818eStnn 	memset(ri->ri_bits, 0, sc->sc_ri_bits_len);
1527e690818eStnn }
1528e690818eStnn 
1529e690818eStnn #if defined(DDB)
1530e690818eStnn static void
ssdfb_ddb_trap_callback(int enable)1531e690818eStnn ssdfb_ddb_trap_callback(int enable)
1532e690818eStnn {
1533e690818eStnn 	extern struct cfdriver ssdfb_cd;
1534e690818eStnn 	struct ssdfb_softc *sc;
1535e690818eStnn 	int i;
1536e690818eStnn 
1537e690818eStnn 	for (i = 0; i < ssdfb_cd.cd_ndevs; i++) {
1538e690818eStnn 		sc = device_lookup_private(&ssdfb_cd, i);
1539e690818eStnn 		if (sc != NULL && sc->sc_is_console) {
1540e690818eStnn 			ssdfb_set_usepoll(sc, (bool)enable);
1541e690818eStnn 		}
1542e690818eStnn         }
1543e690818eStnn }
1544e690818eStnn #endif
1545