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