meson_genfb.c revision 1.3 1 /* $NetBSD: meson_genfb.c,v 1.3 2025/09/06 22:53:47 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 2015-2019 Jared McNeill <jmcneill (at) invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 /*
30 * Generic framebuffer console driver
31 */
32
33 #include "opt_wsdisplay_compat.h"
34
35 #include <sys/cdefs.h>
36 __KERNEL_RCSID(0, "$NetBSD: meson_genfb.c,v 1.3 2025/09/06 22:53:47 thorpej Exp $");
37
38 #include <sys/param.h>
39 #include <sys/types.h>
40 #include <sys/systm.h>
41 #include <sys/device.h>
42 #include <sys/conf.h>
43 #include <sys/bus.h>
44 #include <sys/kmem.h>
45 #include <sys/sysctl.h>
46
47 #include <dev/fdt/fdtvar.h>
48 #include <dev/fdt/fdt_console.h>
49
50 #include <arm/amlogic/meson_canvasreg.h>
51 #include <arm/amlogic/meson_vpureg.h>
52 #include <arm/amlogic/meson_hdmireg.h>
53
54 #include <dev/wsfb/genfbvar.h>
55
56 static const struct device_compatible_entry compat_data[] = {
57 { .compat = "amlogic,meson8b-fb" },
58 DEVICE_COMPAT_EOL
59 };
60
61 #define AMLOGIC_GENFB_DEFAULT_DEPTH 16
62
63 /* Map CEA-861-D video code (VIC) to framebuffer dimensions */
64 static const struct meson_genfb_vic2mode {
65 u_int vic;
66 u_int width;
67 u_int height;
68 u_int flags;
69 #define INTERLACE __BIT(0)
70 #define DBLSCAN __BIT(1)
71 } meson_genfb_modes[] = {
72 { 1, 640, 480 },
73 { 2, 720, 480 },
74 { 3, 720, 480 },
75 { 4, 1280, 720 },
76 { 5, 1920, 1080, INTERLACE },
77 { 6, 720, 480, DBLSCAN | INTERLACE },
78 { 7, 720, 480, DBLSCAN | INTERLACE },
79 { 8, 720, 240, DBLSCAN },
80 { 9, 720, 240, DBLSCAN },
81 { 16, 1920, 1080 },
82 { 17, 720, 576 },
83 { 18, 720, 576 },
84 { 19, 1280, 720 },
85 { 20, 1920, 1080, INTERLACE },
86 { 31, 1920, 1080 },
87 { 32, 1920, 1080 },
88 { 33, 1920, 1080 },
89 { 34, 1920, 1080 },
90 { 39, 1920, 1080, INTERLACE },
91 };
92
93 struct meson_genfb_softc {
94 struct genfb_softc sc_gen;
95 bus_space_tag_t sc_bst;
96 bus_space_handle_t sc_cav_bsh;
97 bus_space_handle_t sc_hdmi_bsh;
98 bus_space_handle_t sc_vpu_bsh;
99 bus_dma_tag_t sc_dmat;
100
101 kmutex_t sc_lock;
102
103 u_int sc_scale;
104
105 bus_dma_segment_t sc_dmasegs[1];
106 bus_size_t sc_dmasize;
107 bus_dmamap_t sc_dmamap;
108 void *sc_dmap;
109
110 uint32_t sc_wstype;
111
112 struct sysctllog *sc_sysctllog;
113 int sc_node_scale;
114 };
115
116 static int meson_genfb_match(device_t, cfdata_t, void *);
117 static void meson_genfb_attach(device_t, device_t, void *);
118
119 static int meson_genfb_ioctl(void *, void *, u_long, void *, int, lwp_t *);
120 static paddr_t meson_genfb_mmap(void *, void *, off_t, int);
121 static bool meson_genfb_shutdown(device_t, int);
122
123 static void meson_genfb_canvas_config(struct meson_genfb_softc *);
124 static void meson_genfb_osd_config(struct meson_genfb_softc *);
125 static void meson_genfb_scaler_config(struct meson_genfb_softc *);
126
127 static void meson_genfb_init(struct meson_genfb_softc *);
128 static int meson_genfb_alloc_videomem(struct meson_genfb_softc *);
129
130 static int meson_genfb_scale_helper(SYSCTLFN_PROTO);
131
132 void meson_genfb_set_console_dev(device_t);
133 void meson_genfb_ddb_trap_callback(int);
134
135 static int meson_genfb_console_phandle = -1;
136 static device_t meson_genfb_console_dev = NULL;
137
138 CFATTACH_DECL_NEW(meson_genfb, sizeof(struct meson_genfb_softc),
139 meson_genfb_match, meson_genfb_attach, NULL, NULL);
140
141 static inline uint32_t
142 meson_genfb_hdmi_read_4(struct meson_genfb_softc *sc, uint32_t addr)
143 {
144 bus_space_write_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_ADDR_REG, addr);
145 bus_space_write_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_ADDR_REG, addr);
146 return bus_space_read_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_DATA_REG);
147 }
148
149 static __unused inline void
150 meson_genfb_hdmi_write_4(struct meson_genfb_softc *sc, uint32_t addr,
151 uint32_t data)
152 {
153 bus_space_write_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_ADDR_REG, addr);
154 bus_space_write_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_ADDR_REG, addr);
155 bus_space_write_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_DATA_REG, data);
156 }
157
158 #define HDMI_READ meson_genfb_hdmi_read_4
159 #define HDMI_WRITE meson_genfb_hdmi_write_4
160
161 #define VPU_READ(sc, reg) \
162 bus_space_read_4((sc)->sc_bst, (sc)->sc_vpu_bsh, (reg))
163 #define VPU_WRITE(sc, reg, val) \
164 bus_space_write_4((sc)->sc_bst, (sc)->sc_vpu_bsh, (reg), (val))
165
166 #define CAV_READ(sc, reg) \
167 bus_space_read_4((sc)->sc_bst, (sc)->sc_cav_bsh, (reg))
168 #define CAV_WRITE(sc, reg, val) \
169 bus_space_write_4((sc)->sc_bst, (sc)->sc_cav_bsh, (reg), (val))
170
171 static int
172 meson_genfb_match(device_t parent, cfdata_t match, void *aux)
173 {
174 struct fdt_attach_args * const faa = aux;
175
176 return of_compatible_match(faa->faa_phandle, compat_data);
177 }
178
179 static void
180 meson_genfb_attach(device_t parent, device_t self, void *aux)
181 {
182 struct meson_genfb_softc *sc = device_private(self);
183 struct fdt_attach_args * const faa = aux;
184 const int phandle = faa->faa_phandle;
185 prop_dictionary_t dict = device_properties(self);
186 static const struct genfb_ops zero_ops;
187 struct genfb_ops ops = zero_ops;
188 bus_addr_t addr[3];
189 bus_size_t size[3];
190
191 for (int i = 0; i < 3; i++) {
192 if (fdtbus_get_reg(phandle, i, &addr[i], &size[i]) != 0) {
193 aprint_error(": couldn't get register #%d\n", i);
194 return;
195 }
196 }
197
198 sc->sc_gen.sc_dev = self;
199 sc->sc_bst = faa->faa_bst;
200 sc->sc_dmat = faa->faa_dmat;
201 if (bus_space_map(sc->sc_bst, addr[0], size[0], 0, &sc->sc_cav_bsh) != 0 ||
202 bus_space_map(sc->sc_bst, addr[1], size[1], 0, &sc->sc_hdmi_bsh) != 0 ||
203 bus_space_map(sc->sc_bst, addr[2], size[2], 0, &sc->sc_vpu_bsh) != 0) {
204 aprint_error(": couldn't map registers\n");
205 return;
206 }
207 mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
208
209 meson_genfb_init(sc);
210
211 sc->sc_wstype = WSDISPLAY_TYPE_MESON;
212
213 aprint_naive("\n");
214 aprint_normal("\n");
215
216 genfb_init(&sc->sc_gen);
217
218 if (sc->sc_gen.sc_width == 0 ||
219 sc->sc_gen.sc_fbsize == 0) {
220 aprint_normal_dev(self, "disabled\n");
221 return;
222 }
223
224 pmf_device_register1(self, NULL, NULL, meson_genfb_shutdown);
225
226 #ifdef WSDISPLAY_MULTICONS
227 const bool is_console = true;
228 #else
229 const bool is_console = phandle == meson_genfb_console_phandle;
230 if (is_console)
231 aprint_normal_dev(self, "switching to framebuffer console\n");
232 #endif
233 prop_dictionary_set_bool(dict, "is_console", is_console);
234
235 memset(&ops, 0, sizeof(ops));
236 ops.genfb_ioctl = meson_genfb_ioctl;
237 ops.genfb_mmap = meson_genfb_mmap;
238
239 genfb_attach(&sc->sc_gen, &ops);
240 }
241
242 static int
243 meson_genfb_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, lwp_t *l)
244 {
245 struct meson_genfb_softc *sc = v;
246 struct wsdisplayio_bus_id *busid;
247
248 switch (cmd) {
249 case WSDISPLAYIO_GTYPE:
250 *(u_int *)data = sc->sc_wstype;
251 return 0;
252 case WSDISPLAYIO_GET_BUSID:
253 busid = data;
254 busid->bus_type = WSDISPLAYIO_BUS_SOC;
255 return 0;
256 case WSDISPLAYIO_GET_FBINFO:
257 {
258 struct wsdisplayio_fbinfo *fbi = data;
259 struct rasops_info *ri = &sc->sc_gen.vd.active->scr_ri;
260 int ret;
261
262 ret = wsdisplayio_get_fbinfo(ri, fbi);
263 fbi->fbi_flags |= WSFB_VRAM_IS_RAM;
264 return ret;
265 }
266 default:
267 return EPASSTHROUGH;
268 }
269 }
270
271 static paddr_t
272 meson_genfb_mmap(void *v, void *vs, off_t offset, int prot)
273 {
274 struct meson_genfb_softc *sc = v;
275
276 if (offset < 0 || offset >= sc->sc_dmasegs[0].ds_len)
277 return -1;
278
279 return bus_dmamem_mmap(sc->sc_dmat, sc->sc_dmasegs, 1,
280 offset, prot, BUS_DMA_PREFETCHABLE);
281 }
282
283 static bool
284 meson_genfb_shutdown(device_t self, int flags)
285 {
286 genfb_enable_polling(self);
287 return true;
288 }
289
290 static void
291 meson_genfb_canvas_config(struct meson_genfb_softc *sc)
292 {
293 prop_dictionary_t cfg = device_properties(sc->sc_gen.sc_dev);
294 const paddr_t pa = sc->sc_dmamap->dm_segs[0].ds_addr;
295 uint32_t datal, datah, addr;
296 u_int width, height, depth;
297
298 prop_dictionary_get_uint32(cfg, "width", &width);
299 prop_dictionary_get_uint32(cfg, "height", &height);
300 prop_dictionary_get_uint32(cfg, "depth", &depth);
301
302 const uint32_t w = (width * (depth/8)) >> 3;
303 const uint32_t h = height;
304
305 datal = CAV_READ(sc, DC_CAV_LUT_DATAL_REG);
306 datah = CAV_READ(sc, DC_CAV_LUT_DATAH_REG);
307 addr = CAV_READ(sc, DC_CAV_LUT_ADDR_REG);
308
309 datal &= ~DC_CAV_LUT_DATAL_WIDTH_L;
310 datal |= __SHIFTIN(w & 7, DC_CAV_LUT_DATAL_WIDTH_L);
311 datal &= ~DC_CAV_LUT_DATAL_FBADDR;
312 datal |= __SHIFTIN(pa >> 3, DC_CAV_LUT_DATAL_FBADDR);
313 CAV_WRITE(sc, DC_CAV_LUT_DATAL_REG, datal);
314
315 datah &= ~DC_CAV_LUT_DATAH_BLKMODE;
316 datah |= __SHIFTIN(DC_CAV_LUT_DATAH_BLKMODE_LINEAR,
317 DC_CAV_LUT_DATAH_BLKMODE);
318 datah &= ~DC_CAV_LUT_DATAH_WIDTH_H;
319 datah |= __SHIFTIN(w >> 3, DC_CAV_LUT_DATAH_WIDTH_H);
320 datah &= ~DC_CAV_LUT_DATAH_HEIGHT;
321 datah |= __SHIFTIN(h, DC_CAV_LUT_DATAH_HEIGHT);
322 CAV_WRITE(sc, DC_CAV_LUT_DATAH_REG, datah);
323
324 addr |= DC_CAV_LUT_ADDR_WR_EN;
325 CAV_WRITE(sc, DC_CAV_LUT_ADDR_REG, addr);
326 }
327
328 static void
329 meson_genfb_osd_config(struct meson_genfb_softc *sc)
330 {
331 prop_dictionary_t cfg = device_properties(sc->sc_gen.sc_dev);
332 uint32_t cs, tc, w0, w1, w2, w3, w4;
333 u_int width, height, depth;
334 bool interlace_p;
335
336 prop_dictionary_get_uint32(cfg, "width", &width);
337 prop_dictionary_get_uint32(cfg, "height", &height);
338 prop_dictionary_get_uint32(cfg, "depth", &depth);
339 prop_dictionary_get_bool(cfg, "interlace", &interlace_p);
340
341 cs = VPU_READ(sc, VIU_OSD2_CTRL_STAT_REG);
342 cs |= VIU_OSD_CTRL_STAT_ENABLE;
343 cs &= ~VIU_OSD_CTRL_STAT_GLOBAL_ALPHA;
344 cs |= __SHIFTIN(0xff, VIU_OSD_CTRL_STAT_GLOBAL_ALPHA);
345 cs |= VIU_OSD_CTRL_STAT_BLK0_ENABLE;
346 cs &= ~VIU_OSD_CTRL_STAT_BLK1_ENABLE;
347 cs &= ~VIU_OSD_CTRL_STAT_BLK2_ENABLE;
348 cs &= ~VIU_OSD_CTRL_STAT_BLK3_ENABLE;
349 VPU_WRITE(sc, VIU_OSD2_CTRL_STAT_REG, cs);
350
351 tc = __SHIFTIN(0, VIU_OSD_TCOLOR_R) |
352 __SHIFTIN(0, VIU_OSD_TCOLOR_G) |
353 __SHIFTIN(0, VIU_OSD_TCOLOR_B) |
354 __SHIFTIN(255, VIU_OSD_TCOLOR_A);
355 VPU_WRITE(sc, VIU_OSD2_TCOLOR_AG0_REG, tc);
356
357 w0 = VPU_READ(sc, VIU_OSD2_BLK0_CFG_W0_REG);
358 w0 |= VIU_OSD_BLK_CFG_W0_RGB_EN;
359 w0 &= ~VIU_OSD_BLK_CFG_W0_TC_ALPHA_EN;
360 w0 &= ~VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE;
361 w0 &= ~VIU_OSD_BLK_CFG_W0_COLOR_MATRIX;
362 switch (depth) {
363 case 32:
364 w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE_32BPP,
365 VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE);
366 w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_COLOR_MATRIX_ARGB,
367 VIU_OSD_BLK_CFG_W0_COLOR_MATRIX);
368 break;
369 case 24:
370 w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE_24BPP,
371 VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE);
372 w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_COLOR_MATRIX_RGB,
373 VIU_OSD_BLK_CFG_W0_COLOR_MATRIX);
374 break;
375 case 16:
376 w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE_16BPP,
377 VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE);
378 w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_COLOR_MATRIX_RGB565,
379 VIU_OSD_BLK_CFG_W0_COLOR_MATRIX);
380 break;
381 }
382 w0 |= VIU_OSD_BLK_CFG_W0_LITTLE_ENDIAN;
383 w0 &= ~VIU_OSD_BLK_CFG_W0_RPT_Y;
384 w0 &= ~VIU_OSD_BLK_CFG_W0_INTERP_CTRL;
385 if (interlace_p) {
386 w0 |= VIU_OSD_BLK_CFG_W0_INTERLACE_EN;
387 } else {
388 w0 &= ~VIU_OSD_BLK_CFG_W0_INTERLACE_EN;
389 }
390 VPU_WRITE(sc, VIU_OSD2_BLK0_CFG_W0_REG, w0);
391
392 w1 = __SHIFTIN(width - 1, VIU_OSD_BLK_CFG_W1_X_END) |
393 __SHIFTIN(0, VIU_OSD_BLK_CFG_W1_X_START);
394 w2 = __SHIFTIN(height - 1, VIU_OSD_BLK_CFG_W2_Y_END) |
395 __SHIFTIN(0, VIU_OSD_BLK_CFG_W2_Y_START);
396 w3 = __SHIFTIN(width - 1, VIU_OSD_BLK_CFG_W3_H_END) |
397 __SHIFTIN(0, VIU_OSD_BLK_CFG_W3_H_START);
398 w4 = __SHIFTIN(height - 1, VIU_OSD_BLK_CFG_W4_V_END) |
399 __SHIFTIN(0, VIU_OSD_BLK_CFG_W4_V_START);
400
401 VPU_WRITE(sc, VIU_OSD2_BLK0_CFG_W1_REG, w1);
402 VPU_WRITE(sc, VIU_OSD2_BLK0_CFG_W2_REG, w2);
403 VPU_WRITE(sc, VIU_OSD2_BLK0_CFG_W3_REG, w3);
404 VPU_WRITE(sc, VIU_OSD2_BLK0_CFG_W4_REG, w4);
405 }
406
407 static void
408 meson_genfb_scaler_config(struct meson_genfb_softc *sc)
409 {
410 prop_dictionary_t cfg = device_properties(sc->sc_gen.sc_dev);
411 uint32_t scctl, sci_wh, sco_h, sco_v, hsc, vsc, hps, vps, hip, vip;
412 u_int width, height;
413 bool interlace_p;
414
415 prop_dictionary_get_uint32(cfg, "width", &width);
416 prop_dictionary_get_uint32(cfg, "height", &height);
417 prop_dictionary_get_bool(cfg, "interlace", &interlace_p);
418
419 const u_int scale = sc->sc_scale;
420 const u_int dst_w = (width * scale) / 100;
421 const u_int dst_h = (height * scale) / 100;
422 const u_int margin_w = (width - dst_w) / 2;
423 const u_int margin_h = (height - dst_h) / 2;
424 const bool scale_p = scale != 100;
425
426 VPU_WRITE(sc, VPP_OSD_SC_DUMMY_DATA_REG, 0x00808000);
427
428 scctl = VPU_READ(sc, VPP_OSD_SC_CTRL0_REG);
429 scctl |= VPP_OSD_SC_CTRL0_OSD_SC_PATH_EN;
430 scctl &= ~VPP_OSD_SC_CTRL0_OSD_SC_SEL;
431 scctl |= __SHIFTIN(1, VPP_OSD_SC_CTRL0_OSD_SC_SEL); /* OSD2 */
432 scctl &= ~VPP_OSD_SC_CTRL0_DEFAULT_ALPHA;
433 scctl |= __SHIFTIN(0, VPP_OSD_SC_CTRL0_DEFAULT_ALPHA);
434 VPU_WRITE(sc, VPP_OSD_SC_CTRL0_REG, scctl);
435
436 sci_wh = __SHIFTIN(width - 1, VPP_OSD_SCI_WH_M1_WIDTH) |
437 __SHIFTIN((height >> interlace_p) - 1, VPP_OSD_SCI_WH_M1_HEIGHT);
438 sco_h = __SHIFTIN(margin_w, VPP_OSD_SCO_H_START) |
439 __SHIFTIN(width - margin_w - 1, VPP_OSD_SCO_H_END);
440 sco_v = __SHIFTIN(margin_h >> interlace_p, VPP_OSD_SCO_V_START) |
441 __SHIFTIN(((height - margin_h) >> interlace_p) - 1,
442 VPP_OSD_SCO_V_END);
443
444 VPU_WRITE(sc, VPP_OSD_SCI_WH_M1_REG, sci_wh);
445 VPU_WRITE(sc, VPP_OSD_SCO_H_REG, sco_h);
446 VPU_WRITE(sc, VPP_OSD_SCO_V_REG, sco_v);
447
448 /* horizontal scaling */
449 hsc = VPU_READ(sc, VPP_OSD_HSC_CTRL0_REG);
450 if (scale_p) {
451 hsc &= ~VPP_OSD_HSC_CTRL0_BANK_LENGTH;
452 hsc |= __SHIFTIN(4, VPP_OSD_HSC_CTRL0_BANK_LENGTH);
453 hsc &= ~VPP_OSD_HSC_CTRL0_INI_RCV_NUM0;
454 hsc |= __SHIFTIN(4, VPP_OSD_HSC_CTRL0_INI_RCV_NUM0);
455 hsc &= ~VPP_OSD_HSC_CTRL0_RPT_P0_NUM0;
456 hsc |= __SHIFTIN(1, VPP_OSD_HSC_CTRL0_RPT_P0_NUM0);
457 hsc |= VPP_OSD_HSC_CTRL0_HSCALE_EN;
458 } else {
459 hsc &= ~VPP_OSD_HSC_CTRL0_HSCALE_EN;
460 }
461 VPU_WRITE(sc, VPP_OSD_HSC_CTRL0_REG, hsc);
462
463 /* vertical scaling */
464 vsc = VPU_READ(sc, VPP_OSD_VSC_CTRL0_REG);
465 if (scale_p) {
466 vsc &= ~VPP_OSD_VSC_CTRL0_BANK_LENGTH;
467 vsc |= __SHIFTIN(4, VPP_OSD_VSC_CTRL0_BANK_LENGTH);
468 vsc &= ~VPP_OSD_VSC_CTRL0_TOP_INI_RCV_NUM0;
469 vsc |= __SHIFTIN(4, VPP_OSD_VSC_CTRL0_TOP_INI_RCV_NUM0);
470 vsc &= ~VPP_OSD_VSC_CTRL0_TOP_RPT_P0_NUM0;
471 vsc |= __SHIFTIN(1, VPP_OSD_VSC_CTRL0_TOP_RPT_P0_NUM0);
472 vsc &= ~VPP_OSD_VSC_CTRL0_BOT_INI_RCV_NUM0;
473 vsc &= ~VPP_OSD_VSC_CTRL0_BOT_RPT_P0_NUM0;
474 vsc &= ~VPP_OSC_VSC_CTRL0_INTERLACE;
475 if (interlace_p) {
476 /* interlace */
477 vsc |= VPP_OSC_VSC_CTRL0_INTERLACE;
478 vsc |= __SHIFTIN(6, VPP_OSD_VSC_CTRL0_BOT_INI_RCV_NUM0);
479 vsc |= __SHIFTIN(2, VPP_OSD_VSC_CTRL0_BOT_RPT_P0_NUM0);
480 }
481 vsc |= VPP_OSD_VSC_CTRL0_VSCALE_EN;
482 } else {
483 vsc &= ~VPP_OSD_VSC_CTRL0_VSCALE_EN;
484 }
485 VPU_WRITE(sc, VPP_OSD_VSC_CTRL0_REG, vsc);
486
487 /* free scale enable */
488 if (scale_p) {
489 const u_int hf_phase_step = ((width << 18) / dst_w) << 6;
490 const u_int vf_phase_step = ((height << 20) / dst_h) << 4;
491 const u_int bot_ini_phase =
492 interlace_p ? ((vf_phase_step / 2) >> 8) : 0;
493
494 hps = VPU_READ(sc, VPP_OSD_HSC_PHASE_STEP_REG);
495 hps &= ~VPP_OSD_HSC_PHASE_STEP_FORMAT;
496 hps |= __SHIFTIN(hf_phase_step, VPP_OSD_HSC_PHASE_STEP_FORMAT);
497 VPU_WRITE(sc, VPP_OSD_HSC_PHASE_STEP_REG, hps);
498
499 hip = VPU_READ(sc, VPP_OSD_HSC_INI_PHASE_REG);
500 hip &= ~VPP_OSD_HSC_INI_PHASE_1;
501 VPU_WRITE(sc, VPP_OSD_HSC_INI_PHASE_REG, hip);
502
503 vps = VPU_READ(sc, VPP_OSD_VSC_PHASE_STEP_REG);
504 vps &= ~VPP_OSD_VSC_PHASE_STEP_FORMAT;
505 vps |= __SHIFTIN(hf_phase_step, VPP_OSD_VSC_PHASE_STEP_FORMAT);
506 VPU_WRITE(sc, VPP_OSD_VSC_PHASE_STEP_REG, vps);
507
508 vip = VPU_READ(sc, VPP_OSD_VSC_INI_PHASE_REG);
509 vip &= ~VPP_OSD_VSC_INI_PHASE_1;
510 vip |= __SHIFTIN(0, VPP_OSD_VSC_INI_PHASE_1);
511 vip &= ~VPP_OSD_VSC_INI_PHASE_0;
512 vip |= __SHIFTIN(bot_ini_phase, VPP_OSD_VSC_INI_PHASE_0);
513 VPU_WRITE(sc, VPP_OSD_VSC_INI_PHASE_REG, vip);
514 }
515 }
516
517 static void
518 meson_genfb_init(struct meson_genfb_softc *sc)
519 {
520 prop_dictionary_t cfg = device_properties(sc->sc_gen.sc_dev);
521 const struct sysctlnode *node, *devnode;
522 u_int width = 0, height = 0, depth, flags, i, scale = 100;
523 int error;
524
525 /*
526 * Firmware has (maybe) setup HDMI TX for us. Read the VIC from
527 * the HDMI AVI InfoFrame (bits 6:0 in PB4) and map that to a
528 * framebuffer geometry.
529 */
530 const uint32_t vic = HDMI_READ(sc, HDMITX_AVI_INFO_ADDR + 4) & 0x7f;
531 for (i = 0; i < __arraycount(meson_genfb_modes); i++) {
532 if (meson_genfb_modes[i].vic == vic) {
533 aprint_debug(" [HDMI VIC %d]", vic);
534 width = meson_genfb_modes[i].width;
535 height = meson_genfb_modes[i].height;
536 flags = meson_genfb_modes[i].flags;
537 break;
538 }
539 }
540 if (width == 0 || height == 0) {
541 aprint_error(" [UNSUPPORTED HDMI VIC %d]", vic);
542 return;
543 }
544
545 depth = AMLOGIC_GENFB_DEFAULT_DEPTH;
546 prop_dictionary_get_uint32(cfg, "depth", &depth);
547 switch (depth) {
548 case 16:
549 case 24:
550 break;
551 default:
552 aprint_error_dev(sc->sc_gen.sc_dev,
553 "unsupported depth %d, using %d\n", depth,
554 AMLOGIC_GENFB_DEFAULT_DEPTH);
555 depth = AMLOGIC_GENFB_DEFAULT_DEPTH;
556 break;
557 }
558 prop_dictionary_set_uint8(cfg, "depth", depth);
559
560 const uint32_t fbsize = width * height * (depth / 8);
561 sc->sc_dmasize = (fbsize + 3) & ~3;
562
563 error = meson_genfb_alloc_videomem(sc);
564 if (error) {
565 aprint_error_dev(sc->sc_gen.sc_dev,
566 "failed to allocate %u bytes of video memory: %d\n",
567 (u_int)sc->sc_dmasize, error);
568 return;
569 }
570
571 prop_dictionary_get_uint32(cfg, "scale", &scale);
572 if (scale > 100) {
573 scale = 100;
574 } else if (scale < 10) {
575 scale = 10;
576 }
577 sc->sc_scale = scale;
578
579 prop_dictionary_set_uint32(cfg, "width", width);
580 prop_dictionary_set_uint32(cfg, "height", height);
581 prop_dictionary_set_bool(cfg, "dblscan", !!(flags & DBLSCAN));
582 prop_dictionary_set_bool(cfg, "interlace", !!(flags & INTERLACE));
583 prop_dictionary_set_uint16(cfg, "linebytes", width * (depth / 8));
584 prop_dictionary_set_uint32(cfg, "address", 0);
585 prop_dictionary_set_uint32(cfg, "virtual_address",
586 (uintptr_t)sc->sc_dmap);
587
588 meson_genfb_canvas_config(sc);
589 meson_genfb_osd_config(sc);
590 meson_genfb_scaler_config(sc);
591
592 /* sysctl setup */
593 error = sysctl_createv(&sc->sc_sysctllog, 0, NULL, &node,
594 CTLFLAG_PERMANENT, CTLTYPE_NODE, "hw", NULL,
595 NULL, 0, NULL, 0, CTL_HW, CTL_EOL);
596 if (error)
597 goto sysctl_failed;
598 error = sysctl_createv(&sc->sc_sysctllog, 0, &node, &devnode,
599 0, CTLTYPE_NODE, device_xname(sc->sc_gen.sc_dev), NULL,
600 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
601 if (error)
602 goto sysctl_failed;
603 error = sysctl_createv(&sc->sc_sysctllog, 0, &devnode, &node,
604 CTLFLAG_READWRITE, CTLTYPE_INT, "scale", NULL,
605 meson_genfb_scale_helper, 0, (void *)sc, 0,
606 CTL_CREATE, CTL_EOL);
607 if (error)
608 goto sysctl_failed;
609 sc->sc_node_scale = node->sysctl_num;
610
611 return;
612
613 sysctl_failed:
614 aprint_error_dev(sc->sc_gen.sc_dev,
615 "couldn't create sysctl nodes (%d)\n", error);
616 sysctl_teardown(&sc->sc_sysctllog);
617 }
618
619 static int
620 meson_genfb_scale_helper(SYSCTLFN_ARGS)
621 {
622 struct meson_genfb_softc *sc;
623 struct sysctlnode node;
624 int scale, oldscale, error;
625
626 node = *rnode;
627 sc = node.sysctl_data;
628 scale = oldscale = sc->sc_scale;
629 node.sysctl_data = &scale;
630 error = sysctl_lookup(SYSCTLFN_CALL(&node));
631 if (error || newp == NULL)
632 return error;
633
634 if (scale > 100) {
635 scale = 100;
636 } else if (scale < 10) {
637 scale = 10;
638 }
639
640 if (scale == oldscale) {
641 return 0;
642 }
643
644 mutex_enter(&sc->sc_lock);
645 sc->sc_scale = scale;
646 meson_genfb_scaler_config(sc);
647 mutex_exit(&sc->sc_lock);
648
649 return 0;
650 }
651
652 static int
653 meson_genfb_alloc_videomem(struct meson_genfb_softc *sc)
654 {
655 int error, nsegs;
656
657 error = bus_dmamem_alloc(sc->sc_dmat, sc->sc_dmasize, 0x1000, 0,
658 sc->sc_dmasegs, 1, &nsegs, BUS_DMA_WAITOK);
659 if (error)
660 return error;
661 error = bus_dmamem_map(sc->sc_dmat, sc->sc_dmasegs, nsegs,
662 sc->sc_dmasize, &sc->sc_dmap, BUS_DMA_WAITOK | BUS_DMA_COHERENT);
663 if (error)
664 goto free;
665 error = bus_dmamap_create(sc->sc_dmat, sc->sc_dmasize, 1,
666 sc->sc_dmasize, 0, BUS_DMA_WAITOK, &sc->sc_dmamap);
667 if (error)
668 goto unmap;
669 error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmamap, sc->sc_dmap,
670 sc->sc_dmasize, NULL, BUS_DMA_WAITOK);
671 if (error)
672 goto destroy;
673
674 memset(sc->sc_dmap, 0, sc->sc_dmasize);
675
676 return 0;
677
678 destroy:
679 bus_dmamap_destroy(sc->sc_dmat, sc->sc_dmamap);
680 unmap:
681 bus_dmamem_unmap(sc->sc_dmat, sc->sc_dmap, sc->sc_dmasize);
682 free:
683 bus_dmamem_free(sc->sc_dmat, sc->sc_dmasegs, nsegs);
684
685 sc->sc_dmasize = 0;
686 sc->sc_dmap = NULL;
687
688 return error;
689 }
690
691 void
692 meson_genfb_set_console_dev(device_t dev)
693 {
694 KASSERT(meson_genfb_console_dev == NULL);
695 meson_genfb_console_dev = dev;
696 }
697
698 void
699 meson_genfb_ddb_trap_callback(int where)
700 {
701 if (meson_genfb_console_dev == NULL)
702 return;
703
704 if (where) {
705 genfb_enable_polling(meson_genfb_console_dev);
706 } else {
707 genfb_disable_polling(meson_genfb_console_dev);
708 }
709 }
710
711 static int
712 meson_genfb_console_match(int phandle)
713 {
714 return of_compatible_match(phandle, compat_data);
715 }
716
717 static void
718 meson_genfb_console_consinit(struct fdt_attach_args *faa, u_int uart_freq)
719 {
720 meson_genfb_console_phandle = faa->faa_phandle;
721 genfb_cnattach();
722 }
723
724 static const struct fdt_console meson_genfb_fdt_console = {
725 .match = meson_genfb_console_match,
726 .consinit = meson_genfb_console_consinit,
727 };
728
729 FDT_CONSOLE(meson_genfb, &meson_genfb_fdt_console);
730