1*ef712effStnn /* $NetBSD: ssdfb.c,v 1.9 2019/11/02 14:18:36 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*ef712effStnn __KERNEL_RCSID(0, "$NetBSD: ssdfb.c,v 1.9 2019/11/02 14:18:36 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 */ 758e57c9bfStnn static int ssdfb_init_ssd1306(struct ssdfb_softc *); 76*ef712effStnn static int ssdfb_init_ssd1322(struct ssdfb_softc *); 77e690818eStnn static int ssdfb_set_contrast(struct ssdfb_softc *, uint8_t, bool); 78e690818eStnn static int ssdfb_set_display_on(struct ssdfb_softc *, bool, bool); 79e690818eStnn static int ssdfb_set_mode(struct ssdfb_softc *, u_int); 80e690818eStnn 81e690818eStnn /* frame buffer damage tracking and synchronization */ 8233ea73e5Stnn static void ssdfb_udv_attach(struct ssdfb_softc *sc); 83e690818eStnn static bool ssdfb_is_modified(struct ssdfb_softc *sc); 84e690818eStnn static bool ssdfb_clear_modify(struct ssdfb_softc *sc); 85e690818eStnn static void ssdfb_damage(struct ssdfb_softc *); 86e690818eStnn static void ssdfb_thread(void *); 87e690818eStnn static void ssdfb_set_usepoll(struct ssdfb_softc *, bool); 88e690818eStnn static int ssdfb_sync(struct ssdfb_softc *, bool); 89*ef712effStnn static int ssdfb_sync_ssd1306(struct ssdfb_softc *, bool); 90*ef712effStnn static int ssdfb_sync_ssd1322(struct ssdfb_softc *, bool); 91*ef712effStnn static uint64_t ssdfb_transpose_block(uint8_t *, size_t); 92e690818eStnn 93e690818eStnn /* misc helpers */ 94e690818eStnn static const struct ssdfb_product * 95e690818eStnn ssdfb_lookup_product(ssdfb_product_id_t); 96e690818eStnn static int ssdfb_pick_font(int *, struct wsdisplay_font **); 97e690818eStnn static void ssdfb_clear_screen(struct ssdfb_softc *); 98e690818eStnn #if defined(DDB) 99e690818eStnn static void ssdfb_ddb_trap_callback(int); 100e690818eStnn #endif 101e690818eStnn 102e690818eStnn static const char *ssdfb_controller_names[] = { 103e690818eStnn [SSDFB_CONTROLLER_UNKNOWN] = "unknown", 104e690818eStnn [SSDFB_CONTROLLER_SSD1306] = "Solomon Systech SSD1306", 1058e57c9bfStnn [SSDFB_CONTROLLER_SH1106] = "Sino Wealth SH1106", 1068e57c9bfStnn [SSDFB_CONTROLLER_SSD1322] = "Solomon Systech SSD1322" 107e690818eStnn }; 108e690818eStnn 109e690818eStnn /* 110e690818eStnn * Display module assemblies supported by this driver. 111e690818eStnn */ 112e690818eStnn static const struct ssdfb_product ssdfb_products[] = { 113e690818eStnn { 114e690818eStnn .p_product_id = SSDFB_PRODUCT_SSD1306_GENERIC, 115e690818eStnn .p_controller_id = SSDFB_CONTROLLER_SSD1306, 116e690818eStnn .p_name = "generic", 117e690818eStnn .p_width = 128, 118e690818eStnn .p_height = 64, 119*ef712effStnn .p_bits_per_pixel = 1, 120e690818eStnn .p_panel_shift = 0, 121e690818eStnn .p_fosc = 0x8, 122e690818eStnn .p_fosc_div = 0, 123e690818eStnn .p_precharge = 0x1, 124e690818eStnn .p_discharge = 0xf, 125e690818eStnn .p_compin_cfg = SSDFB_COM_PINS_A1_MASK 126e690818eStnn | SSDFB_COM_PINS_ALTERNATIVE_MASK, 127e690818eStnn .p_vcomh_deselect_level = SSD1306_VCOMH_DESELECT_LEVEL_0_77_VCC, 128e690818eStnn .p_default_contrast = 0x7f, 129e690818eStnn .p_multiplex_ratio = 0x3f, 130*ef712effStnn .p_init = ssdfb_init_ssd1306, 131*ef712effStnn .p_sync = ssdfb_sync_ssd1306 132e690818eStnn }, 133e690818eStnn { 134e690818eStnn .p_product_id = SSDFB_PRODUCT_SH1106_GENERIC, 135e690818eStnn .p_controller_id = SSDFB_CONTROLLER_SH1106, 136e690818eStnn .p_name = "generic", 137e690818eStnn .p_width = 128, 138e690818eStnn .p_height = 64, 139*ef712effStnn .p_bits_per_pixel = 1, 140e690818eStnn .p_panel_shift = 2, 141e690818eStnn .p_fosc = 0x5, 142e690818eStnn .p_fosc_div = 0, 143e690818eStnn .p_precharge = 0x2, 144e690818eStnn .p_discharge = 0x2, 145e690818eStnn .p_compin_cfg = SSDFB_COM_PINS_A1_MASK 146e690818eStnn | SSDFB_COM_PINS_ALTERNATIVE_MASK, 147e690818eStnn .p_vcomh_deselect_level = SH1106_VCOMH_DESELECT_LEVEL_DEFAULT, 148e690818eStnn .p_default_contrast = 0x80, 149e690818eStnn .p_multiplex_ratio = 0x3f, 150*ef712effStnn .p_init = ssdfb_init_ssd1306, 151*ef712effStnn .p_sync = ssdfb_sync_ssd1306 152e690818eStnn }, 153e690818eStnn { 154e690818eStnn .p_product_id = SSDFB_PRODUCT_ADAFRUIT_938, 155e690818eStnn .p_controller_id = SSDFB_CONTROLLER_SSD1306, 156e690818eStnn .p_name = "Adafruit Industries, LLC product 938", 157e690818eStnn .p_width = 128, 158e690818eStnn .p_height = 64, 159*ef712effStnn .p_bits_per_pixel = 1, 160e690818eStnn .p_panel_shift = 0, 161e690818eStnn .p_fosc = 0x8, 162e690818eStnn .p_fosc_div = 0, 163e690818eStnn .p_precharge = 0x1, 164e690818eStnn .p_discharge = 0xf, 165e690818eStnn .p_compin_cfg = 0x12, 166e690818eStnn .p_vcomh_deselect_level = 0x40, 167e690818eStnn .p_default_contrast = 0x8f, 168e690818eStnn .p_multiplex_ratio = 0x3f, 169*ef712effStnn .p_init = ssdfb_init_ssd1306, 170*ef712effStnn .p_sync = ssdfb_sync_ssd1306 171e690818eStnn }, 172e690818eStnn { 173e690818eStnn .p_product_id = SSDFB_PRODUCT_ADAFRUIT_931, 174e690818eStnn .p_controller_id = SSDFB_CONTROLLER_SSD1306, 175e690818eStnn .p_name = "Adafruit Industries, LLC product 931", 176e690818eStnn .p_width = 128, 177e690818eStnn .p_height = 32, 178*ef712effStnn .p_bits_per_pixel = 1, 179e690818eStnn .p_panel_shift = 0, 180e690818eStnn .p_fosc = 0x8, 181e690818eStnn .p_fosc_div = 0, 182e690818eStnn .p_precharge = 0x1, 183e690818eStnn .p_discharge = 0xf, 184e690818eStnn .p_compin_cfg = 0x2, 185e690818eStnn .p_vcomh_deselect_level = 0x40, 186e690818eStnn .p_default_contrast = 0x8f, 187e690818eStnn .p_multiplex_ratio = 0x1f, 188*ef712effStnn .p_init = ssdfb_init_ssd1306, 189*ef712effStnn .p_sync = ssdfb_sync_ssd1306 190*ef712effStnn }, 191*ef712effStnn { 192*ef712effStnn .p_product_id = SSDFB_PRODUCT_SSD1322_GENERIC, 193*ef712effStnn .p_controller_id = SSDFB_CONTROLLER_SSD1322, 194*ef712effStnn .p_name = "generic", 195*ef712effStnn .p_width = 256, 196*ef712effStnn .p_height = 64, 197*ef712effStnn .p_bits_per_pixel = 4, 198*ef712effStnn .p_panel_shift = 28, 199*ef712effStnn .p_vcomh_deselect_level = SSD1322_DEFAULT_VCOMH, 200*ef712effStnn .p_fosc = SSD1322_DEFAULT_FREQUENCY, 201*ef712effStnn .p_fosc_div = SSD1322_DEFAULT_DIVIDER, 202*ef712effStnn .p_default_contrast = SSD1322_DEFAULT_CONTRAST_CURRENT, 203*ef712effStnn .p_multiplex_ratio = 0x3f, 204*ef712effStnn .p_init = ssdfb_init_ssd1322, 205*ef712effStnn .p_sync = ssdfb_sync_ssd1322 206e690818eStnn } 207e690818eStnn }; 208e690818eStnn 209e690818eStnn static const struct wsdisplay_accessops ssdfb_accessops = { 210e690818eStnn .ioctl = ssdfb_ioctl, 211e690818eStnn .mmap = ssdfb_mmap, 212e690818eStnn .alloc_screen = ssdfb_alloc_screen, 213e690818eStnn .free_screen = ssdfb_free_screen, 214e690818eStnn .show_screen = ssdfb_show_screen 215e690818eStnn }; 216e690818eStnn 217e690818eStnn #define SSDFB_CMD1(c) do { cmd[0] = (c); error = sc->sc_cmd(sc->sc_cookie, cmd, 1, usepoll); } while(0) 218e690818eStnn #define SSDFB_CMD2(c, a) do { cmd[0] = (c); cmd[1] = (a); error = sc->sc_cmd(sc->sc_cookie, cmd, 2, usepoll); } while(0) 219*ef712effStnn #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) 220e690818eStnn 221e690818eStnn void 222e690818eStnn ssdfb_attach(struct ssdfb_softc *sc, int flags) 223e690818eStnn { 224e690818eStnn struct wsemuldisplaydev_attach_args aa; 225e690818eStnn struct rasops_info *ri = &sc->sc_ri; 226e690818eStnn int error = 0; 227e690818eStnn long defattr; 228e690818eStnn const struct ssdfb_product *p; 229*ef712effStnn int kt_flags; 230e690818eStnn 231e690818eStnn p = ssdfb_lookup_product(flags & SSDFB_ATTACH_FLAG_PRODUCT_MASK); 232e690818eStnn if (p == NULL) { 233e690818eStnn aprint_error(": unknown display assembly\n"); 234e690818eStnn return; 235e690818eStnn } 236e690818eStnn sc->sc_p = p; 237e690818eStnn 238e690818eStnn aprint_naive("\n"); 239e690818eStnn aprint_normal(": %s (%s)\n", 240e690818eStnn ssdfb_controller_names[p->p_controller_id], 241e690818eStnn p->p_name); 242e690818eStnn 243e690818eStnn sc->sc_mode = WSDISPLAYIO_MODE_EMUL; 244e690818eStnn sc->sc_is_console = flags & SSDFB_ATTACH_FLAG_CONSOLE ? true : false; 245e690818eStnn sc->sc_inverse = flags & SSDFB_ATTACH_FLAG_INVERSE ? true : false; 246e690818eStnn sc->sc_upsidedown = flags & SSDFB_ATTACH_FLAG_UPSIDEDOWN ? true : false; 247e690818eStnn sc->sc_backoff = 1; 248e690818eStnn sc->sc_contrast = sc->sc_p->p_default_contrast; 249*ef712effStnn sc->sc_gddram_len = sc->sc_p->p_width * sc->sc_p->p_height 250*ef712effStnn * sc->sc_p->p_bits_per_pixel / 8; 251e690818eStnn sc->sc_gddram = kmem_alloc(sc->sc_gddram_len, KM_SLEEP); 252e690818eStnn if (sc->sc_gddram == NULL) 253e690818eStnn goto out; 254e690818eStnn 255e690818eStnn aprint_normal_dev(sc->sc_dev, "%dx%d%s\n", sc->sc_p->p_width, 256e690818eStnn sc->sc_p->p_height, sc->sc_is_console ? ", console" : ""); 257e690818eStnn 258e690818eStnn /* 259e690818eStnn * Initialize rasops. The native depth is 1-bit monochrome and we 260e690818eStnn * support this in text emul mode via rasops1. But modern Xorg 261e690818eStnn * userland has many rendering glitches when running with 1-bit depth 262e690818eStnn * so to better support this use case we instead declare ourselves as 263e690818eStnn * an 8-bit display with a two entry constant color map. 264e690818eStnn */ 265e690818eStnn error = ssdfb_pick_font(&sc->sc_fontcookie, &sc->sc_font); 266e690818eStnn if (error) { 267e690818eStnn aprint_error_dev(sc->sc_dev, "no font\n"); 268e690818eStnn goto out; 269e690818eStnn } 270*ef712effStnn #ifdef SSDFB_USE_NATIVE_DEPTH 271*ef712effStnn ri->ri_depth = sc->sc_p->p_bits_per_pixel; 272*ef712effStnn #else 273e690818eStnn ri->ri_depth = 8; 274*ef712effStnn #endif 275e690818eStnn ri->ri_font = sc->sc_font; 276e690818eStnn ri->ri_width = sc->sc_p->p_width; 277e690818eStnn ri->ri_height = sc->sc_p->p_height; 278e690818eStnn ri->ri_stride = ri->ri_width * ri->ri_depth / 8; 279e690818eStnn ri->ri_hw = sc; 280*ef712effStnn ri->ri_flg = RI_FULLCLEAR | RI_FORCEMONO; 281e690818eStnn sc->sc_ri_bits_len = round_page(ri->ri_stride * ri->ri_height); 282e690818eStnn ri->ri_bits = (u_char *)uvm_km_alloc(kernel_map, sc->sc_ri_bits_len, 283e690818eStnn 0, UVM_KMF_WIRED); 284e690818eStnn if (ri->ri_bits == NULL) 285e690818eStnn goto out; 286e690818eStnn 287e690818eStnn error = rasops_init(ri, 288e690818eStnn sc->sc_p->p_height / sc->sc_font->fontheight, 289e690818eStnn sc->sc_p->p_width / sc->sc_font->fontwidth); 290e690818eStnn if (error) 291e690818eStnn goto out; 292e690818eStnn 293e690818eStnn ri->ri_caps &= ~WSSCREEN_WSCOLORS; 294e690818eStnn 295e690818eStnn /* 296e690818eStnn * Save original emul ops & insert our damage notification hooks. 297e690818eStnn */ 298e690818eStnn sc->sc_orig_riops = ri->ri_ops; 299e690818eStnn ri->ri_ops.putchar = ssdfb_putchar; 300e690818eStnn ri->ri_ops.copycols = ssdfb_copycols; 301e690818eStnn ri->ri_ops.erasecols = ssdfb_erasecols; 302e690818eStnn ri->ri_ops.copyrows = ssdfb_copyrows; 303e690818eStnn ri->ri_ops.eraserows = ssdfb_eraserows; 304e690818eStnn ri->ri_ops.cursor = ssdfb_cursor; 305e690818eStnn 306e690818eStnn /* 307e690818eStnn * Set up the screen. 308e690818eStnn */ 309e690818eStnn sc->sc_screen_descr = (struct wsscreen_descr){ 310e690818eStnn .name = "default", 311e690818eStnn .ncols = ri->ri_cols, 312e690818eStnn .nrows = ri->ri_rows, 313e690818eStnn .textops = &ri->ri_ops, 314e690818eStnn .fontwidth = ri->ri_font->fontwidth, 315e690818eStnn .fontheight = ri->ri_font->fontheight, 316e690818eStnn .capabilities = ri->ri_caps 317e690818eStnn }; 318e690818eStnn sc->sc_screens[0] = &sc->sc_screen_descr; 319e690818eStnn sc->sc_screenlist = (struct wsscreen_list){ 320e690818eStnn .nscreens = 1, 321e690818eStnn .screens = sc->sc_screens 322e690818eStnn }; 323e690818eStnn 324e690818eStnn /* 325e690818eStnn * Initialize hardware. 326e690818eStnn */ 3278e57c9bfStnn error = p->p_init(sc); 328e690818eStnn if (error) 329e690818eStnn goto out; 330e690818eStnn 331e690818eStnn if (sc->sc_is_console) 332e690818eStnn ssdfb_set_usepoll(sc, true); 333e690818eStnn 334e9acf857Stnn mutex_init(&sc->sc_cond_mtx, MUTEX_DEFAULT, IPL_SCHED); 335e690818eStnn cv_init(&sc->sc_cond, "ssdfb"); 336*ef712effStnn kt_flags = KTHREAD_MUSTJOIN; 337*ef712effStnn /* XXX spi(4) is not MPSAFE yet. */ 338*ef712effStnn if (ISSET(flags, SSDFB_ATTACH_FLAG_MPSAFE)) 339*ef712effStnn kt_flags |= KTHREAD_MPSAFE; 340*ef712effStnn error = kthread_create(PRI_SOFTCLOCK, kt_flags, NULL, ssdfb_thread, sc, 341*ef712effStnn &sc->sc_thread, "%s", device_xname(sc->sc_dev)); 342e690818eStnn if (error) { 343e690818eStnn cv_destroy(&sc->sc_cond); 344e690818eStnn mutex_destroy(&sc->sc_cond_mtx); 345e690818eStnn goto out; 346e690818eStnn } 347e690818eStnn 348e690818eStnn /* 349e690818eStnn * Attach wsdisplay. 350e690818eStnn */ 351e690818eStnn if (sc->sc_is_console) { 352e690818eStnn (*ri->ri_ops.allocattr)(ri, 0, 0, 0, &defattr); 353e690818eStnn wsdisplay_cnattach(&sc->sc_screen_descr, ri, 0, 0, defattr); 354e690818eStnn #if defined(DDB) 355e690818eStnn db_trap_callback = ssdfb_ddb_trap_callback; 356e690818eStnn #endif 357e690818eStnn } 358e690818eStnn aa = (struct wsemuldisplaydev_attach_args){ 359e690818eStnn .console = sc->sc_is_console, 360e690818eStnn .scrdata = &sc->sc_screenlist, 361e690818eStnn .accessops = &ssdfb_accessops, 362e690818eStnn .accesscookie = sc 363e690818eStnn }; 364e690818eStnn sc->sc_wsdisplay = 365e690818eStnn config_found(sc->sc_dev, &aa, wsemuldisplaydevprint); 366e690818eStnn 367e690818eStnn return; 368e690818eStnn out: 369e690818eStnn aprint_error_dev(sc->sc_dev, "attach failed: %d\n", error); 370e690818eStnn if (sc->sc_gddram != NULL) 371e690818eStnn kmem_free(sc->sc_gddram, sc->sc_gddram_len); 372e690818eStnn if (ri->ri_bits != NULL) 373e690818eStnn uvm_km_free(kernel_map, (vaddr_t)ri->ri_bits, sc->sc_ri_bits_len, 374e690818eStnn UVM_KMF_WIRED); 375e690818eStnn if (sc->sc_fontcookie > 0) 376e690818eStnn (void) wsfont_unlock(sc->sc_fontcookie); 377e690818eStnn } 378e690818eStnn 379e690818eStnn int 380e690818eStnn ssdfb_detach(struct ssdfb_softc *sc) 381e690818eStnn { 382e690818eStnn mutex_enter(&sc->sc_cond_mtx); 383e690818eStnn sc->sc_detaching = true; 384e690818eStnn cv_broadcast(&sc->sc_cond); 385e690818eStnn mutex_exit(&sc->sc_cond_mtx); 386e690818eStnn kthread_join(sc->sc_thread); 387e690818eStnn 38833ea73e5Stnn if (sc->sc_uobj != NULL) { 38933ea73e5Stnn mutex_enter(sc->sc_uobj->vmobjlock); 39033ea73e5Stnn sc->sc_uobj->uo_refs--; 39133ea73e5Stnn mutex_exit(sc->sc_uobj->vmobjlock); 39233ea73e5Stnn } 393e690818eStnn config_detach(sc->sc_wsdisplay, DETACH_FORCE); 394e690818eStnn 395e690818eStnn cv_destroy(&sc->sc_cond); 396e690818eStnn mutex_destroy(&sc->sc_cond_mtx); 397e690818eStnn uvm_km_free(kernel_map, (vaddr_t)sc->sc_ri.ri_bits, sc->sc_ri_bits_len, 398e690818eStnn UVM_KMF_WIRED); 399e690818eStnn kmem_free(sc->sc_gddram, sc->sc_gddram_len); 400e690818eStnn (void) wsfont_unlock(sc->sc_fontcookie); 401e690818eStnn return 0; 402e690818eStnn } 403e690818eStnn 404e690818eStnn static int 405e690818eStnn ssdfb_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, struct lwp *l) 406e690818eStnn { 407e690818eStnn struct ssdfb_softc *sc = v; 408e690818eStnn struct wsdisplay_param *wdp; 409e690818eStnn struct wsdisplay_cmap *wc; 410*ef712effStnn u_char cmap[16]; 411*ef712effStnn int cmaplen = 1 << sc->sc_p->p_bits_per_pixel; 412*ef712effStnn int i; 413*ef712effStnn struct wsdisplayio_fbinfo *fbi; 414e690818eStnn int error; 415e690818eStnn 416e690818eStnn switch (cmd) { 417e690818eStnn case WSDISPLAYIO_GTYPE: 418e690818eStnn *(u_int *)data = WSDISPLAY_TYPE_SSDFB; 419e690818eStnn return 0; 420e690818eStnn case WSDISPLAYIO_GINFO: 421e690818eStnn *(struct wsdisplay_fbinfo *)data = (struct wsdisplay_fbinfo){ 422e690818eStnn .width = sc->sc_ri.ri_width, 423e690818eStnn .height = sc->sc_ri.ri_height, 424e690818eStnn .depth = sc->sc_ri.ri_depth, 425*ef712effStnn .cmsize = cmaplen 426e690818eStnn }; 427e690818eStnn return 0; 428e690818eStnn case WSDISPLAYIO_GET_FBINFO: 429*ef712effStnn fbi = (struct wsdisplayio_fbinfo *)data; 430*ef712effStnn error = wsdisplayio_get_fbinfo(&sc->sc_ri, fbi); 431*ef712effStnn fbi->fbi_subtype.fbi_cmapinfo.cmap_entries = cmaplen; 432*ef712effStnn /* fbi->fbi_pixeltype = WSFB_GREYSCALE */; 433*ef712effStnn return error; 434e690818eStnn case WSDISPLAYIO_LINEBYTES: 435e690818eStnn *(u_int *)data = sc->sc_ri.ri_stride; 436e690818eStnn return 0; 437e690818eStnn case WSDISPLAYIO_GETPARAM: 438e690818eStnn wdp = (struct wsdisplay_param *)data; 439e690818eStnn if (wdp->param != WSDISPLAYIO_PARAM_CONTRAST) 440e690818eStnn return EINVAL; 441e690818eStnn wdp->min = 0; 442e690818eStnn wdp->max = 0xff; 443e690818eStnn wdp->curval = sc->sc_contrast; 444e690818eStnn return 0; 445e690818eStnn case WSDISPLAYIO_SETPARAM: 446e690818eStnn wdp = (struct wsdisplay_param *)data; 447e690818eStnn if (wdp->param != WSDISPLAYIO_PARAM_CONTRAST) 448e690818eStnn return EINVAL; 449e690818eStnn if (wdp->curval < 0 || wdp->curval > 0xff) 450e690818eStnn return EINVAL; 451e690818eStnn return ssdfb_set_contrast(sc, wdp->curval, sc->sc_usepoll); 452e690818eStnn case WSDISPLAYIO_GMODE: 453e690818eStnn *(u_int *)data = sc->sc_mode; 454e690818eStnn return 0; 455e690818eStnn case WSDISPLAYIO_SMODE: 456e690818eStnn return ssdfb_set_mode(sc, *(u_int *)data); 457e690818eStnn case WSDISPLAYIO_GVIDEO: 458e690818eStnn *(u_int *)data = sc->sc_display_on 459e690818eStnn ? WSDISPLAYIO_VIDEO_ON 460e690818eStnn : WSDISPLAYIO_VIDEO_OFF; 461e690818eStnn return 0; 462e690818eStnn case WSDISPLAYIO_SVIDEO: 463e690818eStnn switch (*(u_int *)data) { 464e690818eStnn case WSDISPLAYIO_VIDEO_ON: 465e690818eStnn case WSDISPLAYIO_VIDEO_OFF: 466e690818eStnn break; 467e690818eStnn default: 468e690818eStnn return EINVAL; 469e690818eStnn } 470e690818eStnn return ssdfb_set_display_on(sc, 471e690818eStnn *(u_int *)data == WSDISPLAYIO_VIDEO_ON ? true : false, 472e690818eStnn sc->sc_usepoll); 473e690818eStnn #if 0 /* don't let userland mess with polling yet */ 474e690818eStnn case WSDISPLAYIO_SET_POLLING: 475e690818eStnn switch (*(u_int *)data) { 476e690818eStnn case 0: 477e690818eStnn case 1: 478e690818eStnn break; 479e690818eStnn default: 480e690818eStnn return EINVAL; 481e690818eStnn } 482e690818eStnn mutex_enter(&sc->sc_cond_mtx); 483e690818eStnn ssdfb_set_usepoll(sc, *(u_int *)data ? true : false); 484e690818eStnn cv_broadcast(&sc->sc_cond); 485e690818eStnn mutex_exit(&sc->sc_cond_mtx); 486e690818eStnn return 0; 487e690818eStnn #endif 488e690818eStnn case WSDISPLAYIO_GETCMAP: 489e690818eStnn wc = (struct wsdisplay_cmap *)data; 490*ef712effStnn if (wc->index >= cmaplen || 491*ef712effStnn wc->count > cmaplen - wc->index) 492e690818eStnn return EINVAL; 493*ef712effStnn for(i = 0; i < cmaplen; i++) { 494*ef712effStnn cmap[i] = 255 * i / (cmaplen - 1); 495*ef712effStnn } 496e690818eStnn error = copyout(&cmap[wc->index], wc->red, wc->count); 497e690818eStnn if (error) 498e690818eStnn return error; 499e690818eStnn error = copyout(&cmap[wc->index], wc->green, wc->count); 500e690818eStnn if (error) 501e690818eStnn return error; 502e690818eStnn error = copyout(&cmap[wc->index], wc->blue, wc->count); 503e690818eStnn return error; 504e690818eStnn case WSDISPLAYIO_PUTCMAP: 505e690818eStnn return ENODEV; 506e690818eStnn } 507e690818eStnn 508e690818eStnn return EPASSTHROUGH; 509e690818eStnn } 510e690818eStnn 511e690818eStnn static paddr_t 512e690818eStnn ssdfb_mmap(void *v, void *vs, off_t off, int prot) 513e690818eStnn { 514e690818eStnn struct ssdfb_softc *sc = (struct ssdfb_softc *)v; 515e690818eStnn struct rasops_info *ri = &sc->sc_ri; 516e690818eStnn vaddr_t va_base = (vaddr_t)ri->ri_bits; 517e690818eStnn paddr_t pa; 518e690818eStnn 519e690818eStnn if (off < 0 || off >= sc->sc_ri_bits_len || (off & PAGE_MASK) != 0) 520e690818eStnn return -1; 521e690818eStnn 522e690818eStnn if (!pmap_extract(pmap_kernel(), va_base + off, &pa)) 523e690818eStnn return -1; 524e690818eStnn 525e690818eStnn return atop(pa); 526e690818eStnn } 527e690818eStnn 528e690818eStnn static int 529e690818eStnn ssdfb_alloc_screen(void *v, const struct wsscreen_descr *descr, void **cookiep, 530e690818eStnn int *curxp, int *curyp, long *attrp) 531e690818eStnn { 532e690818eStnn struct ssdfb_softc *sc = v; 533e690818eStnn struct rasops_info *ri = &sc->sc_ri; 534e690818eStnn 535e690818eStnn if (sc->sc_nscreens > 0) 536e690818eStnn return ENOMEM; 537e690818eStnn 538e690818eStnn ri->ri_ops.allocattr(ri, 0, 0, 0, attrp); 539e690818eStnn *cookiep = &sc->sc_ri; 540e690818eStnn *curxp = 0; 541e690818eStnn *curyp = 0; 542e690818eStnn sc->sc_nscreens++; 543e690818eStnn 544e690818eStnn return 0; 545e690818eStnn } 546e690818eStnn 547e690818eStnn static void 548e690818eStnn ssdfb_free_screen(void *v, void *cookie) 549e690818eStnn { 550e690818eStnn struct ssdfb_softc *sc = v; 551e690818eStnn 552e690818eStnn if (sc->sc_is_console) 553e690818eStnn panic("ssdfb_free_screen: is console"); 554e690818eStnn 555e690818eStnn sc->sc_nscreens--; 556e690818eStnn } 557e690818eStnn 558e690818eStnn static int 559e690818eStnn ssdfb_show_screen(void *v, void *cookie, int waitok, 560e690818eStnn void (*cb) (void *, int, int), void *cb_arg) 561e690818eStnn { 562e690818eStnn return 0; 563e690818eStnn } 564e690818eStnn 565e690818eStnn static void 566e690818eStnn ssdfb_putchar(void *cookie, int row, int col, u_int c, long attr) 567e690818eStnn { 568e690818eStnn struct rasops_info *ri = (struct rasops_info *)cookie; 569e690818eStnn struct ssdfb_softc *sc = ri->ri_hw; 570e690818eStnn 571e690818eStnn sc->sc_orig_riops.putchar(cookie, row, col, c, attr); 572e690818eStnn ssdfb_damage(sc); 573e690818eStnn } 574e690818eStnn 575e690818eStnn static void 576e690818eStnn ssdfb_copycols(void *cookie, int row, int srccol, int dstcol, int ncols) 577e690818eStnn { 578e690818eStnn struct rasops_info *ri = (struct rasops_info *)cookie; 579e690818eStnn struct ssdfb_softc *sc = ri->ri_hw; 580e690818eStnn 581e690818eStnn sc->sc_orig_riops.copycols(cookie, row, srccol, dstcol, ncols); 582e690818eStnn ssdfb_damage(sc); 583e690818eStnn } 584e690818eStnn 585e690818eStnn static void 586e690818eStnn ssdfb_erasecols(void *cookie, int row, int startcol, int ncols, long fillattr) 587e690818eStnn { 588e690818eStnn struct rasops_info *ri = (struct rasops_info *)cookie; 589e690818eStnn struct ssdfb_softc *sc = ri->ri_hw; 590e690818eStnn 591e690818eStnn sc->sc_orig_riops.erasecols(cookie, row, startcol, ncols, fillattr); 592e690818eStnn ssdfb_damage(sc); 593e690818eStnn } 594e690818eStnn 595e690818eStnn static void 596e690818eStnn ssdfb_copyrows(void *cookie, int srcrow, int dstrow, int nrows) 597e690818eStnn { 598e690818eStnn struct rasops_info *ri = (struct rasops_info *)cookie; 599e690818eStnn struct ssdfb_softc *sc = ri->ri_hw; 600e690818eStnn 601e690818eStnn sc->sc_orig_riops.copyrows(cookie, srcrow, dstrow, nrows); 602e690818eStnn ssdfb_damage(sc); 603e690818eStnn } 604e690818eStnn 605e690818eStnn static void 606e690818eStnn ssdfb_eraserows(void *cookie, int row, int nrows, long fillattr) 607e690818eStnn { 608e690818eStnn struct rasops_info *ri = (struct rasops_info *)cookie; 609e690818eStnn struct ssdfb_softc *sc = ri->ri_hw; 610e690818eStnn 611e690818eStnn sc->sc_orig_riops.eraserows(cookie, row, nrows, fillattr); 612e690818eStnn ssdfb_damage(sc); 613e690818eStnn } 614e690818eStnn 615e690818eStnn static void 616e690818eStnn ssdfb_cursor(void *cookie, int on, int row, int col) 617e690818eStnn { 618e690818eStnn struct rasops_info *ri = (struct rasops_info *)cookie; 619e690818eStnn struct ssdfb_softc *sc = ri->ri_hw; 620e690818eStnn 621e690818eStnn sc->sc_orig_riops.cursor(cookie, on, row, col); 622e690818eStnn ssdfb_damage(sc); 623e690818eStnn } 624e690818eStnn 625e690818eStnn static int 6268e57c9bfStnn ssdfb_init_ssd1306(struct ssdfb_softc *sc) 627e690818eStnn { 628e690818eStnn int error; 62933ea73e5Stnn uint8_t cmd[2]; 630e690818eStnn bool usepoll = true; 631e690818eStnn 632e690818eStnn /* 633e690818eStnn * Enter sleep. 634e690818eStnn */ 635e690818eStnn SSDFB_CMD1(SSDFB_CMD_SET_DISPLAY_OFF); 636e690818eStnn if (error) 637e690818eStnn return error; 638e690818eStnn SSDFB_CMD1(SSDFB_CMD_DEACTIVATE_SCROLL); 639e690818eStnn if (error) 640e690818eStnn return error; 641e690818eStnn SSDFB_CMD1(SSDFB_CMD_ENTIRE_DISPLAY_OFF); 642e690818eStnn if (error) 643e690818eStnn return error; 644e690818eStnn 645e690818eStnn /* 646e690818eStnn * Configure physical display panel layout. 647e690818eStnn */ 648e690818eStnn SSDFB_CMD2(SSDFB_CMD_SET_MULTIPLEX_RATIO, sc->sc_p->p_multiplex_ratio); 649e690818eStnn if (error) 650e690818eStnn return error; 651e690818eStnn SSDFB_CMD2(SSDFB_CMD_SET_DISPLAY_OFFSET, 0); 652e690818eStnn if (error) 653e690818eStnn return error; 654e690818eStnn SSDFB_CMD1(SSDFB_CMD_SET_DISPLAY_START_LINE_BASE + 0x00); 655e690818eStnn if (error) 656e690818eStnn return error; 657e690818eStnn SSDFB_CMD2(SSDFB_CMD_SET_COM_PINS_HARDWARE_CFG, sc->sc_p->p_compin_cfg); 658e690818eStnn if (error) 659e690818eStnn return error; 660e690818eStnn if (sc->sc_upsidedown) { 661e690818eStnn SSDFB_CMD1(SSDFB_CMD_SET_SEGMENT_REMAP_REVERSE); 662e690818eStnn if (error) 663e690818eStnn return error; 664e690818eStnn SSDFB_CMD1(SSDFB_CMD_SET_COM_OUTPUT_DIRECTION_REMAP); 665e690818eStnn if (error) 666e690818eStnn return error; 667e690818eStnn } else { 668e690818eStnn SSDFB_CMD1(SSDFB_CMD_SET_SEGMENT_REMAP_NORMAL); 669e690818eStnn if (error) 670e690818eStnn return error; 671e690818eStnn SSDFB_CMD1(SSDFB_CMD_SET_COM_OUTPUT_DIRECTION_NORMAL); 672e690818eStnn if (error) 673e690818eStnn return error; 674e690818eStnn } 675e690818eStnn SSDFB_CMD1(SSDFB_CMD_SET_NORMAL_DISPLAY + (uint8_t)sc->sc_inverse); 676e690818eStnn if (error) 677e690818eStnn return error; 678e690818eStnn 679e690818eStnn /* 680e690818eStnn * Configure timing characteristics. 681e690818eStnn */ 682e690818eStnn SSDFB_CMD2(SSDFB_CMD_SET_DISPLAY_CLOCK_RATIO, 68309fa88a1Stnn __SHIFTIN(sc->sc_p->p_fosc, SSDFB_DISPLAY_CLOCK_OSCILLATOR_MASK) | 68409fa88a1Stnn __SHIFTIN(sc->sc_p->p_fosc_div, SSDFB_DISPLAY_CLOCK_DIVIDER_MASK)); 685e690818eStnn if (error) 686e690818eStnn return error; 687e690818eStnn SSDFB_CMD2(SSDFB_CMD_SET_CONTRAST_CONTROL, sc->sc_contrast); 688e690818eStnn if (error) 689e690818eStnn return error; 690e690818eStnn SSDFB_CMD2(SSDFB_CMD_SET_PRECHARGE_PERIOD, 69109fa88a1Stnn __SHIFTIN(sc->sc_p->p_precharge, SSDFB_PRECHARGE_MASK) | 69209fa88a1Stnn __SHIFTIN(sc->sc_p->p_discharge, SSDFB_DISCHARGE_MASK)); 693e690818eStnn if (error) 694e690818eStnn return error; 695e690818eStnn SSDFB_CMD2(SSDFB_CMD_SET_VCOMH_DESELECT_LEVEL, 696e690818eStnn sc->sc_p->p_vcomh_deselect_level); 697e690818eStnn if (error) 698e690818eStnn return error; 699e690818eStnn 700e690818eStnn /* 701*ef712effStnn * Start charge pumps. 702e690818eStnn */ 703*ef712effStnn if (sc->sc_p->p_controller_id == SSDFB_CONTROLLER_SH1106) { 704*ef712effStnn SSDFB_CMD1(SH1106_CMD_SET_CHARGE_PUMP_7V4); 705e690818eStnn if (error) 706e690818eStnn return error; 707e690818eStnn SSDFB_CMD2(SH1106_CMD_SET_DC_DC, SH1106_DC_DC_ON); 708e690818eStnn if (error) 709e690818eStnn return error; 710*ef712effStnn } else { 711*ef712effStnn SSDFB_CMD2(SSD1306_CMD_SET_CHARGE_PUMP, 712*ef712effStnn SSD1306_CHARGE_PUMP_ENABLE); 713*ef712effStnn if (error) 714*ef712effStnn return error; 715e690818eStnn } 716e690818eStnn 717e690818eStnn ssdfb_clear_screen(sc); 718*ef712effStnn error = sc->sc_p->p_sync(sc, usepoll); 719*ef712effStnn if (error) 720*ef712effStnn return error; 721*ef712effStnn error = ssdfb_set_display_on(sc, true, usepoll); 722*ef712effStnn 723*ef712effStnn return error; 724*ef712effStnn } 725*ef712effStnn 726*ef712effStnn static int 727*ef712effStnn ssdfb_init_ssd1322(struct ssdfb_softc *sc) 728*ef712effStnn { 729*ef712effStnn int error; 730*ef712effStnn uint8_t cmd[3]; 731*ef712effStnn bool usepoll = true; 732*ef712effStnn uint8_t remap; 733*ef712effStnn uint8_t dualcom; 734*ef712effStnn 735*ef712effStnn /* 736*ef712effStnn * Enter sleep. 737*ef712effStnn */ 738*ef712effStnn SSDFB_CMD2(SSD1322_CMD_SET_COMMAND_LOCK, SSD1322_COMMAND_UNLOCK_MAGIC); 739*ef712effStnn if (error) 740*ef712effStnn return error; 741*ef712effStnn SSDFB_CMD1(SSD1322_CMD_SET_SLEEP_MODE_ON); 742*ef712effStnn if (error) 743*ef712effStnn return error; 744*ef712effStnn 745*ef712effStnn /* 746*ef712effStnn * Start charge pumps. 747*ef712effStnn */ 748*ef712effStnn SSDFB_CMD2(SSD1322_CMD_FUNCTION_SELECTION, 749*ef712effStnn SSD1322_FUNCTION_SELECTION_INTERNAL_VDD); 750*ef712effStnn if (error) 751*ef712effStnn return error; 752*ef712effStnn SSDFB_CMD2(SSD1322_CMD_SET_VCOMH, sc->sc_p->p_vcomh_deselect_level); 753*ef712effStnn if (error) 754*ef712effStnn return error; 755*ef712effStnn SSDFB_CMD2(SSD1322_CMD_SET_PRE_CHARGE_VOLTAGE_LEVEL, 756*ef712effStnn SSD1322_DEFAULT_PRE_CHARGE_VOLTAGE_LEVEL); 757*ef712effStnn if (error) 758*ef712effStnn return error; 759*ef712effStnn SSDFB_CMD2(SSD1322_CMD_SET_GPIO, 760*ef712effStnn SSD1322_GPIO0_DISABLED | SSD1322_GPIO1_DISABLED); 761*ef712effStnn if (error) 762*ef712effStnn return error; 763*ef712effStnn 764*ef712effStnn /* 765*ef712effStnn * Configure timing characteristics. 766*ef712effStnn */ 767*ef712effStnn SSDFB_CMD2(SSD1322_CMD_SET_FRONT_CLOCK_DIVIDER, 768*ef712effStnn __SHIFTIN(sc->sc_p->p_fosc, SSD1322_FREQUENCY_MASK) | 769*ef712effStnn __SHIFTIN(sc->sc_p->p_fosc_div, SSD1322_DIVIDER_MASK)); 770*ef712effStnn if (error) 771*ef712effStnn return error; 772*ef712effStnn SSDFB_CMD2(SSD1322_CMD_SET_PHASE_LENGTH, 773*ef712effStnn __SHIFTIN(SSD1322_DEFAULT_PHASE_2, 774*ef712effStnn SSD1322_PHASE_LENGTH_PHASE_2_MASK) | 775*ef712effStnn __SHIFTIN(SSD1322_DEFAULT_PHASE_1, 776*ef712effStnn SSD1322_PHASE_LENGTH_PHASE_1_MASK)); 777*ef712effStnn if (error) 778*ef712effStnn return error; 779*ef712effStnn SSDFB_CMD2(SSD1322_CMD_SET_SECOND_PRECHARGE_PERIOD, 780*ef712effStnn SSD1322_DEFAULT_SECOND_PRECHARGE); 781*ef712effStnn if (error) 782*ef712effStnn return error; 783*ef712effStnn 784*ef712effStnn /* 785*ef712effStnn * Configure physical display panel layout. 786*ef712effStnn */ 787*ef712effStnn SSDFB_CMD2(SSD1322_CMD_SET_MUX_RATIO, sc->sc_p->p_multiplex_ratio); 788*ef712effStnn if (error) 789*ef712effStnn return error; 790*ef712effStnn if (sc->sc_upsidedown) 791*ef712effStnn remap = 0x10; 792*ef712effStnn else 793*ef712effStnn remap = 0x2; 794*ef712effStnn dualcom = 0x1; 795*ef712effStnn if (sc->sc_p->p_multiplex_ratio <= 63) 796*ef712effStnn dualcom |= 0x10; 797*ef712effStnn SSDFB_CMD3(SSD1322_CMD_SET_REMAP_AND_DUAL_COM_LINE_MODE, remap, dualcom); 798*ef712effStnn if (error) 799*ef712effStnn return error; 800*ef712effStnn 801*ef712effStnn /* 802*ef712effStnn * Contrast settings. 803*ef712effStnn */ 804*ef712effStnn SSDFB_CMD1(SSD1322_CMD_SET_DEFAULT_GRAY_SCALE_TABLE); 805*ef712effStnn if (error) 806*ef712effStnn return error; 807*ef712effStnn SSDFB_CMD3(SSD1322_CMD_DISPLAY_ENHANCEMENT_A, 808*ef712effStnn SSD1322_DISPLAY_ENHANCEMENT_A_MAGIC1, 809*ef712effStnn SSD1322_DISPLAY_ENHANCEMENT_A_MAGIC2); 810*ef712effStnn if (error) 811*ef712effStnn return error; 812*ef712effStnn SSDFB_CMD3(SSD1322_CMD_DISPLAY_ENHANCEMENT_B, 813*ef712effStnn SSD1322_DISPLAY_ENHANCEMENT_B_MAGIC1, 814*ef712effStnn SSD1322_DISPLAY_ENHANCEMENT_B_MAGIC2); 815*ef712effStnn if (error) 816*ef712effStnn return error; 817*ef712effStnn SSDFB_CMD2(SSD1322_CMD_SET_CONTRAST_CURRENT, 818*ef712effStnn sc->sc_contrast); 819*ef712effStnn if (error) 820*ef712effStnn return error; 821*ef712effStnn SSDFB_CMD2(SSD1322_CMD_MASTER_CONTRAST_CURRENT_CONTROL, 822*ef712effStnn SSD1322_DEFAULT_MASTER_CONTRAST_CURRENT_CONTROL); 823*ef712effStnn if (error) 824*ef712effStnn return error; 825*ef712effStnn 826*ef712effStnn /* 827*ef712effStnn * Reset display engine state. 828*ef712effStnn */ 829*ef712effStnn SSDFB_CMD2(SSD1322_CMD_SET_DISPLAY_OFFSET, 0x00); 830*ef712effStnn if (error) 831*ef712effStnn return error; 832*ef712effStnn SSDFB_CMD2(SSD1322_CMD_SET_DISPLAY_START_LINE, 0x00); 833*ef712effStnn if (error) 834*ef712effStnn return error; 835*ef712effStnn SSDFB_CMD1(SSD1322_CMD_NORMAL_DISPLAY + (uint8_t)sc->sc_inverse); 836*ef712effStnn if (error) 837*ef712effStnn return error; 838*ef712effStnn SSDFB_CMD1(SSD1322_CMD_EXIT_PARTIAL_DISPLAY); 839*ef712effStnn if (error) 840*ef712effStnn return error; 841*ef712effStnn 842*ef712effStnn ssdfb_clear_screen(sc); 843e690818eStnn error = ssdfb_sync(sc, usepoll); 844e690818eStnn if (error) 845e690818eStnn return error; 846*ef712effStnn 847e690818eStnn error = ssdfb_set_display_on(sc, true, usepoll); 848e690818eStnn 849e690818eStnn return error; 850e690818eStnn } 851e690818eStnn 852e690818eStnn static int 853e690818eStnn ssdfb_set_contrast(struct ssdfb_softc *sc, uint8_t value, bool usepoll) 854e690818eStnn { 855e690818eStnn uint8_t cmd[2]; 856e690818eStnn 857*ef712effStnn switch (sc->sc_p->p_controller_id) { 858*ef712effStnn case SSDFB_CONTROLLER_SSD1322: 859*ef712effStnn cmd[0] = SSD1322_CMD_SET_CONTRAST_CURRENT; 860*ef712effStnn break; 861*ef712effStnn default: 862*ef712effStnn cmd[0] = SSDFB_CMD_SET_CONTRAST_CONTROL; 863*ef712effStnn } 864*ef712effStnn cmd[1] = sc->sc_contrast = value; 865e690818eStnn 866*ef712effStnn return sc->sc_cmd(sc->sc_cookie, cmd, sizeof(cmd), usepoll); 867e690818eStnn } 868e690818eStnn 869e690818eStnn static int 870e690818eStnn ssdfb_set_display_on(struct ssdfb_softc *sc, bool value, bool usepoll) 871e690818eStnn { 872e690818eStnn uint8_t cmd[1]; 873e690818eStnn int error; 874e690818eStnn sc->sc_display_on = value; 875e690818eStnn 876e690818eStnn SSDFB_CMD1(value ? SSDFB_CMD_SET_DISPLAY_ON : SSDFB_CMD_SET_DISPLAY_OFF); 877e690818eStnn 878e690818eStnn return error; 879e690818eStnn } 880e690818eStnn 881e690818eStnn static int 882e690818eStnn ssdfb_set_mode(struct ssdfb_softc *sc, u_int mode) 883e690818eStnn { 884e690818eStnn switch (mode) { 885e690818eStnn case WSDISPLAYIO_MODE_EMUL: 886e690818eStnn case WSDISPLAYIO_MODE_DUMBFB: 887e690818eStnn break; 888e690818eStnn default: 889e690818eStnn return EINVAL; 890e690818eStnn } 891e690818eStnn if (mode == sc->sc_mode) 892e690818eStnn return 0; 893e690818eStnn mutex_enter(&sc->sc_cond_mtx); 894e690818eStnn sc->sc_mode = mode; 895e690818eStnn cv_broadcast(&sc->sc_cond); 896e690818eStnn mutex_exit(&sc->sc_cond_mtx); 897e690818eStnn ssdfb_clear_screen(sc); 898e690818eStnn ssdfb_damage(sc); 899e690818eStnn 900e690818eStnn return 0; 901e690818eStnn } 902e690818eStnn 903e690818eStnn static void 904e690818eStnn ssdfb_damage(struct ssdfb_softc *sc) 905e690818eStnn { 906e690818eStnn int s; 907e690818eStnn 908e690818eStnn if (sc->sc_usepoll) { 909e690818eStnn (void) ssdfb_sync(sc, true); 910e690818eStnn } else { 911e690818eStnn /* 912e690818eStnn * kernel code isn't permitted to call us via kprintf at 913e690818eStnn * splhigh. In case misbehaving code calls us anyway we can't 914e690818eStnn * safely take the mutex so we skip the damage notification. 915e690818eStnn */ 916e690818eStnn if (sc->sc_is_console) { 917e690818eStnn s = splhigh(); 918e690818eStnn splx(s); 919e690818eStnn if (s == IPL_HIGH) 920e690818eStnn return; 921e690818eStnn } 922e690818eStnn mutex_enter(&sc->sc_cond_mtx); 923e690818eStnn sc->sc_modified = true; 924e690818eStnn cv_broadcast(&sc->sc_cond); 925e690818eStnn mutex_exit(&sc->sc_cond_mtx); 926e690818eStnn } 927e690818eStnn } 928e690818eStnn 92933ea73e5Stnn static void 93033ea73e5Stnn ssdfb_udv_attach(struct ssdfb_softc *sc) 93133ea73e5Stnn { 93233ea73e5Stnn extern const struct cdevsw wsdisplay_cdevsw; 93333ea73e5Stnn dev_t dev; 93433ea73e5Stnn #define WSDISPLAYMINOR(unit, screen) (((unit) << 8) | (screen)) 93533ea73e5Stnn dev = makedev(cdevsw_lookup_major(&wsdisplay_cdevsw), 93633ea73e5Stnn WSDISPLAYMINOR(device_unit(sc->sc_wsdisplay), 0)); 93733ea73e5Stnn sc->sc_uobj = udv_attach(dev, VM_PROT_READ|VM_PROT_WRITE, 0, 93833ea73e5Stnn sc->sc_ri_bits_len); 93933ea73e5Stnn } 94033ea73e5Stnn 941e690818eStnn static bool 942e690818eStnn ssdfb_is_modified(struct ssdfb_softc *sc) 943e690818eStnn { 944e690818eStnn vaddr_t va, va_end; 945e690818eStnn 946e690818eStnn if (sc->sc_mode == WSDISPLAYIO_MODE_EMUL) 947e690818eStnn return sc->sc_modified; 948e690818eStnn 94933ea73e5Stnn if (sc->sc_uobj == NULL) 95033ea73e5Stnn return false; 95133ea73e5Stnn 952e690818eStnn va = (vaddr_t)sc->sc_ri.ri_bits; 953e690818eStnn va_end = va + sc->sc_ri_bits_len; 954e690818eStnn while (va < va_end) { 955e690818eStnn if (pmap_is_modified(uvm_pageratop(va))) 956e690818eStnn return true; 957e690818eStnn va += PAGE_SIZE; 958e690818eStnn } 959e690818eStnn 960e690818eStnn return false; 961e690818eStnn } 962e690818eStnn 963e690818eStnn static bool 964e690818eStnn ssdfb_clear_modify(struct ssdfb_softc *sc) 965e690818eStnn { 966e690818eStnn vaddr_t va, va_end; 967e690818eStnn bool ret; 968e690818eStnn 969e690818eStnn if (sc->sc_mode == WSDISPLAYIO_MODE_EMUL) { 970*ef712effStnn mutex_enter(&sc->sc_cond_mtx); 971e690818eStnn ret = sc->sc_modified; 972e690818eStnn sc->sc_modified = false; 973*ef712effStnn mutex_exit(&sc->sc_cond_mtx); 974e690818eStnn return ret; 975e690818eStnn } 976e690818eStnn 97733ea73e5Stnn if (sc->sc_uobj == NULL) 97833ea73e5Stnn return false; 97933ea73e5Stnn 980e690818eStnn va = (vaddr_t)sc->sc_ri.ri_bits; 981e690818eStnn va_end = va + sc->sc_ri_bits_len; 982e690818eStnn ret = false; 983e690818eStnn while (va < va_end) { 984e690818eStnn if (pmap_clear_modify(uvm_pageratop(va))) 985e690818eStnn ret = true; 986e690818eStnn va += PAGE_SIZE; 987e690818eStnn } 988e690818eStnn 989e690818eStnn return ret; 990e690818eStnn } 991e690818eStnn 992e690818eStnn static void 9932f60d4f5Stnn ssdfb_thread(void *arg) 9942f60d4f5Stnn { 995e690818eStnn struct ssdfb_softc *sc = (struct ssdfb_softc *)arg; 996e690818eStnn int error; 997e690818eStnn 998e690818eStnn mutex_enter(&sc->sc_cond_mtx); 999e690818eStnn 1000e690818eStnn if (sc->sc_usepoll) 1001e690818eStnn ssdfb_set_usepoll(sc, false); 1002e690818eStnn 1003e690818eStnn while(!sc->sc_detaching) { 100433ea73e5Stnn if (sc->sc_mode == WSDISPLAYIO_MODE_DUMBFB && 100533ea73e5Stnn sc->sc_uobj == NULL) { 100633ea73e5Stnn mutex_exit(&sc->sc_cond_mtx); 100733ea73e5Stnn ssdfb_udv_attach(sc); 100833ea73e5Stnn mutex_enter(&sc->sc_cond_mtx); 100933ea73e5Stnn } 1010e690818eStnn if (!ssdfb_is_modified(sc)) { 1011e690818eStnn if (cv_timedwait(&sc->sc_cond, &sc->sc_cond_mtx, 1012e690818eStnn sc->sc_mode == WSDISPLAYIO_MODE_EMUL 1013e690818eStnn ? 0 : sc->sc_backoff) == EWOULDBLOCK 1014e690818eStnn && sc->sc_backoff < mstohz(200)) { 1015e690818eStnn sc->sc_backoff <<= 1; 1016e690818eStnn } 1017e690818eStnn continue; 1018e690818eStnn } 1019e690818eStnn sc->sc_backoff = 1; 1020e690818eStnn mutex_exit(&sc->sc_cond_mtx); 1021*ef712effStnn (void) ssdfb_clear_modify(sc); 1022*ef712effStnn if (!sc->sc_usepoll) { 1023e690818eStnn error = ssdfb_sync(sc, false); 1024e690818eStnn if (error) 1025*ef712effStnn device_printf(sc->sc_dev, 1026*ef712effStnn "ssdfb_sync: error %d\n", 1027e690818eStnn error); 1028*ef712effStnn } 1029e690818eStnn mutex_enter(&sc->sc_cond_mtx); 1030e690818eStnn } 1031e690818eStnn 1032e690818eStnn mutex_exit(&sc->sc_cond_mtx); 103350090521Stnn kthread_exit(0); 1034e690818eStnn } 1035e690818eStnn 1036e690818eStnn static void 10372f60d4f5Stnn ssdfb_set_usepoll(struct ssdfb_softc *sc, bool enable) 10382f60d4f5Stnn { 1039e690818eStnn sc->sc_usepoll = enable; 1040e690818eStnn } 1041e690818eStnn 1042e690818eStnn static int 10432f60d4f5Stnn ssdfb_sync(struct ssdfb_softc *sc, bool usepoll) 10442f60d4f5Stnn { 1045*ef712effStnn return sc->sc_p->p_sync(sc, usepoll); 1046*ef712effStnn } 1047*ef712effStnn 1048*ef712effStnn static int 1049*ef712effStnn ssdfb_sync_ssd1306(struct ssdfb_softc *sc, bool usepoll) 1050*ef712effStnn { 1051e690818eStnn struct rasops_info *ri = &sc->sc_ri; 1052e690818eStnn int block_size = 8; 1053e690818eStnn int ri_block_stride = ri->ri_stride * block_size; 1054e690818eStnn int height_in_blocks = sc->sc_p->p_height / block_size; 1055e690818eStnn int width_in_blocks = sc->sc_p->p_width / block_size; 1056e690818eStnn int ri_block_step = block_size * ri->ri_depth / 8; 1057e690818eStnn int x, y; 1058e690818eStnn union ssdfb_block *blockp; 1059e690818eStnn uint64_t raw_block; 1060e690818eStnn uint8_t *src; 1061e690818eStnn int x1, x2, y1, y2; 1062e690818eStnn 1063e690818eStnn /* 1064e690818eStnn * Transfer rasops bitmap into gddram shadow buffer while keeping track 1065e690818eStnn * of the bounding box of the dirty region we scribbled over. 1066e690818eStnn */ 1067e690818eStnn x1 = width_in_blocks; 1068e690818eStnn x2 = -1; 1069e690818eStnn y1 = height_in_blocks; 1070e690818eStnn y2 = -1; 1071e690818eStnn for (y = 0; y < height_in_blocks; y++) { 1072e690818eStnn src = &ri->ri_bits[y * ri_block_stride]; 1073e690818eStnn blockp = &sc->sc_gddram[y * width_in_blocks]; 1074e690818eStnn for (x = 0; x < width_in_blocks; x++) { 1075*ef712effStnn raw_block = ssdfb_transpose_block(src, ri->ri_stride); 1076e690818eStnn if (raw_block != blockp->raw) { 1077e690818eStnn blockp->raw = raw_block; 1078e690818eStnn if (x1 > x) 1079e690818eStnn x1 = x; 1080e690818eStnn if (x2 < x) 1081e690818eStnn x2 = x; 1082e690818eStnn if (y1 > y) 1083e690818eStnn y1 = y; 1084e690818eStnn if (y2 < y) 1085e690818eStnn y2 = y; 1086e690818eStnn } 1087e690818eStnn src += ri_block_step; 1088e690818eStnn blockp++; 1089e690818eStnn } 1090e690818eStnn } 1091e690818eStnn if (x2 != -1) 1092e690818eStnn return sc->sc_transfer_rect(sc->sc_cookie, 1093e690818eStnn x1 * block_size + sc->sc_p->p_panel_shift, 1094e690818eStnn (x2 + 1) * block_size - 1 + sc->sc_p->p_panel_shift, 1095e690818eStnn y1, 1096e690818eStnn y2, 1097e690818eStnn &sc->sc_gddram[y1 * width_in_blocks + x1].col[0], 1098e690818eStnn sc->sc_p->p_width, 1099e690818eStnn usepoll); 1100e690818eStnn 1101e690818eStnn return 0; 1102e690818eStnn } 1103e690818eStnn 1104*ef712effStnn static int 1105*ef712effStnn ssdfb_sync_ssd1322(struct ssdfb_softc *sc, bool usepoll) 1106*ef712effStnn { 1107*ef712effStnn struct rasops_info *ri = &sc->sc_ri; 1108*ef712effStnn int block_size_w = 4; 1109*ef712effStnn int width = sc->sc_p->p_width; 1110*ef712effStnn int height = sc->sc_p->p_height; 1111*ef712effStnn int width_in_blocks = width / block_size_w; 1112*ef712effStnn int x, y; 1113*ef712effStnn uint16_t *blockp; 1114*ef712effStnn uint16_t raw_block; 1115*ef712effStnn uint16_t *src; 1116*ef712effStnn uint32_t *src32; 1117*ef712effStnn int x1, x2, y1, y2; 1118*ef712effStnn 1119*ef712effStnn /* 1120*ef712effStnn * Transfer rasops bitmap into gddram shadow buffer while keeping track 1121*ef712effStnn * of the bounding box of the dirty region we scribbled over. 1122*ef712effStnn */ 1123*ef712effStnn x1 = sc->sc_p->p_width; 1124*ef712effStnn x2 = -1; 1125*ef712effStnn y1 = sc->sc_p->p_height; 1126*ef712effStnn y2 = -1; 1127*ef712effStnn blockp = (uint16_t*)sc->sc_gddram; 1128*ef712effStnn for (y = 0; y < height; y++) { 1129*ef712effStnn src = (uint16_t*)&ri->ri_bits[y * ri->ri_stride]; 1130*ef712effStnn src32 = (uint32_t*)src; 1131*ef712effStnn for (x = 0; x < width_in_blocks; x++) { 1132*ef712effStnn #if _BYTE_ORDER == _LITTLE_ENDIAN 1133*ef712effStnn # ifdef SSDFB_USE_NATIVE_DEPTH 1134*ef712effStnn raw_block = 1135*ef712effStnn ((*src << 12) & 0xf000) | 1136*ef712effStnn ((*src << 4) & 0x0f00) | 1137*ef712effStnn ((*src >> 4) & 0x00f0) | 1138*ef712effStnn ((*src >> 12) & 0x000f); 1139*ef712effStnn src++; 1140*ef712effStnn # else 1141*ef712effStnn raw_block = 1142*ef712effStnn ((*src32 << 8) & 0x0f00) | 1143*ef712effStnn ((*src32 << 4) & 0xf000) | 1144*ef712effStnn ((*src32 >> 16) & 0x000f) | 1145*ef712effStnn ((*src32 >> 20) & 0x00f0); 1146*ef712effStnn # endif 1147*ef712effStnn src32++; 1148*ef712effStnn #else 1149*ef712effStnn # error please add big endian host support here 1150*ef712effStnn #endif 1151*ef712effStnn if (raw_block != *blockp) { 1152*ef712effStnn *blockp = raw_block; 1153*ef712effStnn if (x1 > x) 1154*ef712effStnn x1 = x; 1155*ef712effStnn if (x2 < x) 1156*ef712effStnn x2 = x; 1157*ef712effStnn if (y1 > y) 1158*ef712effStnn y1 = y; 1159*ef712effStnn if (y2 < y) 1160*ef712effStnn y2 = y; 1161*ef712effStnn } 1162*ef712effStnn blockp++; 1163*ef712effStnn } 1164*ef712effStnn } 1165*ef712effStnn 1166*ef712effStnn blockp = (uint16_t*)sc->sc_gddram; 1167*ef712effStnn if (x2 != -1) 1168*ef712effStnn return sc->sc_transfer_rect(sc->sc_cookie, 1169*ef712effStnn x1 + sc->sc_p->p_panel_shift, 1170*ef712effStnn x2 + sc->sc_p->p_panel_shift, 1171*ef712effStnn y1, 1172*ef712effStnn y2, 1173*ef712effStnn (uint8_t*)&blockp[y1 * width_in_blocks + x1], 1174*ef712effStnn width * sc->sc_p->p_bits_per_pixel / 8, 1175*ef712effStnn usepoll); 1176*ef712effStnn return 0; 1177*ef712effStnn } 1178*ef712effStnn 1179e690818eStnn static uint64_t 1180*ef712effStnn ssdfb_transpose_block(uint8_t *src, size_t src_stride) 1181e690818eStnn { 1182e690818eStnn uint64_t x = 0; 1183*ef712effStnn #ifdef SSDFB_USE_NATIVE_DEPTH 1184e690818eStnn uint64_t t; 1185e690818eStnn int i; 1186e690818eStnn 1187e690818eStnn /* 1188e690818eStnn * collect the 8x8 block. 1189e690818eStnn */ 1190e690818eStnn for (i = 0; i < 8; i++) { 1191e690818eStnn x >>= 8; 1192e690818eStnn x |= (uint64_t)src[i * src_stride] << 56; 1193e690818eStnn } 1194e690818eStnn 1195e690818eStnn /* 1196e690818eStnn * Transpose it into gddram layout. 1197e690818eStnn * Post-transpose bswap is the same as pre-transpose bit order reversal. 1198e690818eStnn * We do this to match rasops1 bit order. 1199e690818eStnn */ 1200e690818eStnn t = (x ^ (x >> 28)) & 0x00000000F0F0F0F0ULL; 1201e690818eStnn x = x ^ t ^ (t << 28); 1202e690818eStnn t = (x ^ (x >> 14)) & 0x0000CCCC0000CCCCULL; 1203e690818eStnn x = x ^ t ^ (t << 14); 1204e690818eStnn t = (x ^ (x >> 7)) & 0x00AA00AA00AA00AAULL; 1205e690818eStnn x = x ^ t ^ (t << 7); 1206e690818eStnn x = bswap64(x); 1207*ef712effStnn #else 1208e690818eStnn int m, n; 1209e690818eStnn 1210e690818eStnn for (m = 0; m < 8; m++) { 1211e690818eStnn for (n = 0; n < 8; n++) { 1212e690818eStnn x >>= 1; 1213e690818eStnn x |= src[n * src_stride + m] ? (1ULL << 63) : 0; 1214e690818eStnn } 1215e690818eStnn } 1216*ef712effStnn #endif 1217e690818eStnn return htole64(x); 1218e690818eStnn } 1219e690818eStnn 1220e690818eStnn static const struct ssdfb_product * 12212f60d4f5Stnn ssdfb_lookup_product(ssdfb_product_id_t id) 12222f60d4f5Stnn { 1223e690818eStnn int i; 1224e690818eStnn 1225e690818eStnn for (i = 0; i < __arraycount(ssdfb_products); i++) { 1226e690818eStnn if (ssdfb_products[i].p_product_id == id) 1227e690818eStnn return &ssdfb_products[i]; 1228e690818eStnn } 1229e690818eStnn 1230e690818eStnn return NULL; 1231e690818eStnn } 1232e690818eStnn 1233e690818eStnn static int 12342f60d4f5Stnn ssdfb_pick_font(int *cookiep, struct wsdisplay_font **fontp) 12352f60d4f5Stnn { 1236e690818eStnn int error; 1237e690818eStnn int c; 1238e690818eStnn struct wsdisplay_font *f; 1239e690818eStnn int i; 1240e690818eStnn uint8_t d[4][2] = {{5, 8}, {8, 8}, {8, 10} ,{8, 16}}; 1241e690818eStnn 1242e690818eStnn /* 12436f6ddff5Stnn * Try to find fonts in order of increasing size. 1244e690818eStnn */ 1245e690818eStnn wsfont_init(); 1246e690818eStnn for(i = 0; i < __arraycount(d); i++) { 1247e690818eStnn c = wsfont_find(NULL, d[i][0], d[i][1], 0, 1248e690818eStnn WSDISPLAY_FONTORDER_L2R, WSDISPLAY_FONTORDER_L2R, 1249e690818eStnn WSFONT_FIND_BITMAP); 1250e690818eStnn if (c > 0) 1251e690818eStnn break; 1252e690818eStnn } 1253e690818eStnn if (c <= 0) 1254e690818eStnn return ENOENT; 1255e690818eStnn error = wsfont_lock(c, &f); 1256e690818eStnn if (error) 1257e690818eStnn return error; 1258e690818eStnn *cookiep = c; 1259e690818eStnn *fontp = f; 1260e690818eStnn 1261e690818eStnn return 0; 1262e690818eStnn } 1263e690818eStnn 1264e690818eStnn static void 1265e690818eStnn ssdfb_clear_screen(struct ssdfb_softc *sc) 1266e690818eStnn { 1267e690818eStnn struct rasops_info *ri = &sc->sc_ri; 1268e690818eStnn 1269e690818eStnn memset(sc->sc_gddram, 0xff, sc->sc_gddram_len); 1270e690818eStnn memset(ri->ri_bits, 0, sc->sc_ri_bits_len); 1271e690818eStnn } 1272e690818eStnn 1273e690818eStnn #if defined(DDB) 1274e690818eStnn static void 1275e690818eStnn ssdfb_ddb_trap_callback(int enable) 1276e690818eStnn { 1277e690818eStnn extern struct cfdriver ssdfb_cd; 1278e690818eStnn struct ssdfb_softc *sc; 1279e690818eStnn int i; 1280e690818eStnn 1281e690818eStnn for (i = 0; i < ssdfb_cd.cd_ndevs; i++) { 1282e690818eStnn sc = device_lookup_private(&ssdfb_cd, i); 1283e690818eStnn if (sc != NULL && sc->sc_is_console) { 1284e690818eStnn ssdfb_set_usepoll(sc, (bool)enable); 1285e690818eStnn } 1286e690818eStnn } 1287e690818eStnn } 1288e690818eStnn #endif 1289