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