/* $NetBSD: meson_genfb.c,v 1.2 2021/01/27 03:10:18 thorpej Exp $ */ /*- * Copyright (c) 2015-2019 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Generic framebuffer console driver */ #include "opt_wsdisplay_compat.h" #include __KERNEL_RCSID(0, "$NetBSD: meson_genfb.c,v 1.2 2021/01/27 03:10:18 thorpej Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include static const struct device_compatible_entry compat_data[] = { { .compat = "amlogic,meson8b-fb" }, DEVICE_COMPAT_EOL }; #define AMLOGIC_GENFB_DEFAULT_DEPTH 16 /* Map CEA-861-D video code (VIC) to framebuffer dimensions */ static const struct meson_genfb_vic2mode { u_int vic; u_int width; u_int height; u_int flags; #define INTERLACE __BIT(0) #define DBLSCAN __BIT(1) } meson_genfb_modes[] = { { 1, 640, 480 }, { 2, 720, 480 }, { 3, 720, 480 }, { 4, 1280, 720 }, { 5, 1920, 1080, INTERLACE }, { 6, 720, 480, DBLSCAN | INTERLACE }, { 7, 720, 480, DBLSCAN | INTERLACE }, { 8, 720, 240, DBLSCAN }, { 9, 720, 240, DBLSCAN }, { 16, 1920, 1080 }, { 17, 720, 576 }, { 18, 720, 576 }, { 19, 1280, 720 }, { 20, 1920, 1080, INTERLACE }, { 31, 1920, 1080 }, { 32, 1920, 1080 }, { 33, 1920, 1080 }, { 34, 1920, 1080 }, { 39, 1920, 1080, INTERLACE }, }; struct meson_genfb_softc { struct genfb_softc sc_gen; bus_space_tag_t sc_bst; bus_space_handle_t sc_cav_bsh; bus_space_handle_t sc_hdmi_bsh; bus_space_handle_t sc_vpu_bsh; bus_dma_tag_t sc_dmat; kmutex_t sc_lock; u_int sc_scale; bus_dma_segment_t sc_dmasegs[1]; bus_size_t sc_dmasize; bus_dmamap_t sc_dmamap; void *sc_dmap; uint32_t sc_wstype; struct sysctllog *sc_sysctllog; int sc_node_scale; }; static int meson_genfb_match(device_t, cfdata_t, void *); static void meson_genfb_attach(device_t, device_t, void *); static int meson_genfb_ioctl(void *, void *, u_long, void *, int, lwp_t *); static paddr_t meson_genfb_mmap(void *, void *, off_t, int); static bool meson_genfb_shutdown(device_t, int); static void meson_genfb_canvas_config(struct meson_genfb_softc *); static void meson_genfb_osd_config(struct meson_genfb_softc *); static void meson_genfb_scaler_config(struct meson_genfb_softc *); static void meson_genfb_init(struct meson_genfb_softc *); static int meson_genfb_alloc_videomem(struct meson_genfb_softc *); static int meson_genfb_scale_helper(SYSCTLFN_PROTO); void meson_genfb_set_console_dev(device_t); void meson_genfb_ddb_trap_callback(int); static int meson_genfb_console_phandle = -1; static device_t meson_genfb_console_dev = NULL; CFATTACH_DECL_NEW(meson_genfb, sizeof(struct meson_genfb_softc), meson_genfb_match, meson_genfb_attach, NULL, NULL); static inline uint32_t meson_genfb_hdmi_read_4(struct meson_genfb_softc *sc, uint32_t addr) { bus_space_write_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_ADDR_REG, addr); bus_space_write_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_ADDR_REG, addr); return bus_space_read_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_DATA_REG); } static __unused inline void meson_genfb_hdmi_write_4(struct meson_genfb_softc *sc, uint32_t addr, uint32_t data) { bus_space_write_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_ADDR_REG, addr); bus_space_write_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_ADDR_REG, addr); bus_space_write_4(sc->sc_bst, sc->sc_hdmi_bsh, HDMI_DATA_REG, data); } #define HDMI_READ meson_genfb_hdmi_read_4 #define HDMI_WRITE meson_genfb_hdmi_write_4 #define VPU_READ(sc, reg) \ bus_space_read_4((sc)->sc_bst, (sc)->sc_vpu_bsh, (reg)) #define VPU_WRITE(sc, reg, val) \ bus_space_write_4((sc)->sc_bst, (sc)->sc_vpu_bsh, (reg), (val)) #define CAV_READ(sc, reg) \ bus_space_read_4((sc)->sc_bst, (sc)->sc_cav_bsh, (reg)) #define CAV_WRITE(sc, reg, val) \ bus_space_write_4((sc)->sc_bst, (sc)->sc_cav_bsh, (reg), (val)) static int meson_genfb_match(device_t parent, cfdata_t match, void *aux) { struct fdt_attach_args * const faa = aux; return of_compatible_match(faa->faa_phandle, compat_data); } static void meson_genfb_attach(device_t parent, device_t self, void *aux) { struct meson_genfb_softc *sc = device_private(self); struct fdt_attach_args * const faa = aux; const int phandle = faa->faa_phandle; prop_dictionary_t dict = device_properties(self); static const struct genfb_ops zero_ops; struct genfb_ops ops = zero_ops; bus_addr_t addr[3]; bus_size_t size[3]; for (int i = 0; i < 3; i++) { if (fdtbus_get_reg(phandle, i, &addr[i], &size[i]) != 0) { aprint_error(": couldn't get register #%d\n", i); return; } } sc->sc_gen.sc_dev = self; sc->sc_bst = faa->faa_bst; sc->sc_dmat = faa->faa_dmat; if (bus_space_map(sc->sc_bst, addr[0], size[0], 0, &sc->sc_cav_bsh) != 0 || bus_space_map(sc->sc_bst, addr[1], size[1], 0, &sc->sc_hdmi_bsh) != 0 || bus_space_map(sc->sc_bst, addr[2], size[2], 0, &sc->sc_vpu_bsh) != 0) { aprint_error(": couldn't map registers\n"); return; } mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE); meson_genfb_init(sc); sc->sc_wstype = WSDISPLAY_TYPE_MESON; aprint_naive("\n"); aprint_normal("\n"); genfb_init(&sc->sc_gen); if (sc->sc_gen.sc_width == 0 || sc->sc_gen.sc_fbsize == 0) { aprint_normal_dev(self, "disabled\n"); return; } pmf_device_register1(self, NULL, NULL, meson_genfb_shutdown); #ifdef WSDISPLAY_MULTICONS const bool is_console = true; #else const bool is_console = phandle == meson_genfb_console_phandle; if (is_console) aprint_normal_dev(self, "switching to framebuffer console\n"); #endif prop_dictionary_set_bool(dict, "is_console", is_console); memset(&ops, 0, sizeof(ops)); ops.genfb_ioctl = meson_genfb_ioctl; ops.genfb_mmap = meson_genfb_mmap; genfb_attach(&sc->sc_gen, &ops); } static int meson_genfb_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, lwp_t *l) { struct meson_genfb_softc *sc = v; struct wsdisplayio_bus_id *busid; switch (cmd) { case WSDISPLAYIO_GTYPE: *(u_int *)data = sc->sc_wstype; return 0; case WSDISPLAYIO_GET_BUSID: busid = data; busid->bus_type = WSDISPLAYIO_BUS_SOC; return 0; case WSDISPLAYIO_GET_FBINFO: { struct wsdisplayio_fbinfo *fbi = data; struct rasops_info *ri = &sc->sc_gen.vd.active->scr_ri; int ret; ret = wsdisplayio_get_fbinfo(ri, fbi); fbi->fbi_flags |= WSFB_VRAM_IS_RAM; return ret; } default: return EPASSTHROUGH; } } static paddr_t meson_genfb_mmap(void *v, void *vs, off_t offset, int prot) { struct meson_genfb_softc *sc = v; if (offset < 0 || offset >= sc->sc_dmasegs[0].ds_len) return -1; return bus_dmamem_mmap(sc->sc_dmat, sc->sc_dmasegs, 1, offset, prot, BUS_DMA_PREFETCHABLE); } static bool meson_genfb_shutdown(device_t self, int flags) { genfb_enable_polling(self); return true; } static void meson_genfb_canvas_config(struct meson_genfb_softc *sc) { prop_dictionary_t cfg = device_properties(sc->sc_gen.sc_dev); const paddr_t pa = sc->sc_dmamap->dm_segs[0].ds_addr; uint32_t datal, datah, addr; u_int width, height, depth; prop_dictionary_get_uint32(cfg, "width", &width); prop_dictionary_get_uint32(cfg, "height", &height); prop_dictionary_get_uint32(cfg, "depth", &depth); const uint32_t w = (width * (depth/8)) >> 3; const uint32_t h = height; datal = CAV_READ(sc, DC_CAV_LUT_DATAL_REG); datah = CAV_READ(sc, DC_CAV_LUT_DATAH_REG); addr = CAV_READ(sc, DC_CAV_LUT_ADDR_REG); datal &= ~DC_CAV_LUT_DATAL_WIDTH_L; datal |= __SHIFTIN(w & 7, DC_CAV_LUT_DATAL_WIDTH_L); datal &= ~DC_CAV_LUT_DATAL_FBADDR; datal |= __SHIFTIN(pa >> 3, DC_CAV_LUT_DATAL_FBADDR); CAV_WRITE(sc, DC_CAV_LUT_DATAL_REG, datal); datah &= ~DC_CAV_LUT_DATAH_BLKMODE; datah |= __SHIFTIN(DC_CAV_LUT_DATAH_BLKMODE_LINEAR, DC_CAV_LUT_DATAH_BLKMODE); datah &= ~DC_CAV_LUT_DATAH_WIDTH_H; datah |= __SHIFTIN(w >> 3, DC_CAV_LUT_DATAH_WIDTH_H); datah &= ~DC_CAV_LUT_DATAH_HEIGHT; datah |= __SHIFTIN(h, DC_CAV_LUT_DATAH_HEIGHT); CAV_WRITE(sc, DC_CAV_LUT_DATAH_REG, datah); addr |= DC_CAV_LUT_ADDR_WR_EN; CAV_WRITE(sc, DC_CAV_LUT_ADDR_REG, addr); } static void meson_genfb_osd_config(struct meson_genfb_softc *sc) { prop_dictionary_t cfg = device_properties(sc->sc_gen.sc_dev); uint32_t cs, tc, w0, w1, w2, w3, w4; u_int width, height, depth; bool interlace_p; prop_dictionary_get_uint32(cfg, "width", &width); prop_dictionary_get_uint32(cfg, "height", &height); prop_dictionary_get_uint32(cfg, "depth", &depth); prop_dictionary_get_bool(cfg, "interlace", &interlace_p); cs = VPU_READ(sc, VIU_OSD2_CTRL_STAT_REG); cs |= VIU_OSD_CTRL_STAT_ENABLE; cs &= ~VIU_OSD_CTRL_STAT_GLOBAL_ALPHA; cs |= __SHIFTIN(0xff, VIU_OSD_CTRL_STAT_GLOBAL_ALPHA); cs |= VIU_OSD_CTRL_STAT_BLK0_ENABLE; cs &= ~VIU_OSD_CTRL_STAT_BLK1_ENABLE; cs &= ~VIU_OSD_CTRL_STAT_BLK2_ENABLE; cs &= ~VIU_OSD_CTRL_STAT_BLK3_ENABLE; VPU_WRITE(sc, VIU_OSD2_CTRL_STAT_REG, cs); tc = __SHIFTIN(0, VIU_OSD_TCOLOR_R) | __SHIFTIN(0, VIU_OSD_TCOLOR_G) | __SHIFTIN(0, VIU_OSD_TCOLOR_B) | __SHIFTIN(255, VIU_OSD_TCOLOR_A); VPU_WRITE(sc, VIU_OSD2_TCOLOR_AG0_REG, tc); w0 = VPU_READ(sc, VIU_OSD2_BLK0_CFG_W0_REG); w0 |= VIU_OSD_BLK_CFG_W0_RGB_EN; w0 &= ~VIU_OSD_BLK_CFG_W0_TC_ALPHA_EN; w0 &= ~VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE; w0 &= ~VIU_OSD_BLK_CFG_W0_COLOR_MATRIX; switch (depth) { case 32: w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE_32BPP, VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE); w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_COLOR_MATRIX_ARGB, VIU_OSD_BLK_CFG_W0_COLOR_MATRIX); break; case 24: w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE_24BPP, VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE); w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_COLOR_MATRIX_RGB, VIU_OSD_BLK_CFG_W0_COLOR_MATRIX); break; case 16: w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE_16BPP, VIU_OSD_BLK_CFG_W0_OSD_BLK_MODE); w0 |= __SHIFTIN(VIU_OSD_BLK_CFG_W0_COLOR_MATRIX_RGB565, VIU_OSD_BLK_CFG_W0_COLOR_MATRIX); break; } w0 |= VIU_OSD_BLK_CFG_W0_LITTLE_ENDIAN; w0 &= ~VIU_OSD_BLK_CFG_W0_RPT_Y; w0 &= ~VIU_OSD_BLK_CFG_W0_INTERP_CTRL; if (interlace_p) { w0 |= VIU_OSD_BLK_CFG_W0_INTERLACE_EN; } else { w0 &= ~VIU_OSD_BLK_CFG_W0_INTERLACE_EN; } VPU_WRITE(sc, VIU_OSD2_BLK0_CFG_W0_REG, w0); w1 = __SHIFTIN(width - 1, VIU_OSD_BLK_CFG_W1_X_END) | __SHIFTIN(0, VIU_OSD_BLK_CFG_W1_X_START); w2 = __SHIFTIN(height - 1, VIU_OSD_BLK_CFG_W2_Y_END) | __SHIFTIN(0, VIU_OSD_BLK_CFG_W2_Y_START); w3 = __SHIFTIN(width - 1, VIU_OSD_BLK_CFG_W3_H_END) | __SHIFTIN(0, VIU_OSD_BLK_CFG_W3_H_START); w4 = __SHIFTIN(height - 1, VIU_OSD_BLK_CFG_W4_V_END) | __SHIFTIN(0, VIU_OSD_BLK_CFG_W4_V_START); VPU_WRITE(sc, VIU_OSD2_BLK0_CFG_W1_REG, w1); VPU_WRITE(sc, VIU_OSD2_BLK0_CFG_W2_REG, w2); VPU_WRITE(sc, VIU_OSD2_BLK0_CFG_W3_REG, w3); VPU_WRITE(sc, VIU_OSD2_BLK0_CFG_W4_REG, w4); } static void meson_genfb_scaler_config(struct meson_genfb_softc *sc) { prop_dictionary_t cfg = device_properties(sc->sc_gen.sc_dev); uint32_t scctl, sci_wh, sco_h, sco_v, hsc, vsc, hps, vps, hip, vip; u_int width, height; bool interlace_p; prop_dictionary_get_uint32(cfg, "width", &width); prop_dictionary_get_uint32(cfg, "height", &height); prop_dictionary_get_bool(cfg, "interlace", &interlace_p); const u_int scale = sc->sc_scale; const u_int dst_w = (width * scale) / 100; const u_int dst_h = (height * scale) / 100; const u_int margin_w = (width - dst_w) / 2; const u_int margin_h = (height - dst_h) / 2; const bool scale_p = scale != 100; VPU_WRITE(sc, VPP_OSD_SC_DUMMY_DATA_REG, 0x00808000); scctl = VPU_READ(sc, VPP_OSD_SC_CTRL0_REG); scctl |= VPP_OSD_SC_CTRL0_OSD_SC_PATH_EN; scctl &= ~VPP_OSD_SC_CTRL0_OSD_SC_SEL; scctl |= __SHIFTIN(1, VPP_OSD_SC_CTRL0_OSD_SC_SEL); /* OSD2 */ scctl &= ~VPP_OSD_SC_CTRL0_DEFAULT_ALPHA; scctl |= __SHIFTIN(0, VPP_OSD_SC_CTRL0_DEFAULT_ALPHA); VPU_WRITE(sc, VPP_OSD_SC_CTRL0_REG, scctl); sci_wh = __SHIFTIN(width - 1, VPP_OSD_SCI_WH_M1_WIDTH) | __SHIFTIN((height >> interlace_p) - 1, VPP_OSD_SCI_WH_M1_HEIGHT); sco_h = __SHIFTIN(margin_w, VPP_OSD_SCO_H_START) | __SHIFTIN(width - margin_w - 1, VPP_OSD_SCO_H_END); sco_v = __SHIFTIN(margin_h >> interlace_p, VPP_OSD_SCO_V_START) | __SHIFTIN(((height - margin_h) >> interlace_p) - 1, VPP_OSD_SCO_V_END); VPU_WRITE(sc, VPP_OSD_SCI_WH_M1_REG, sci_wh); VPU_WRITE(sc, VPP_OSD_SCO_H_REG, sco_h); VPU_WRITE(sc, VPP_OSD_SCO_V_REG, sco_v); /* horizontal scaling */ hsc = VPU_READ(sc, VPP_OSD_HSC_CTRL0_REG); if (scale_p) { hsc &= ~VPP_OSD_HSC_CTRL0_BANK_LENGTH; hsc |= __SHIFTIN(4, VPP_OSD_HSC_CTRL0_BANK_LENGTH); hsc &= ~VPP_OSD_HSC_CTRL0_INI_RCV_NUM0; hsc |= __SHIFTIN(4, VPP_OSD_HSC_CTRL0_INI_RCV_NUM0); hsc &= ~VPP_OSD_HSC_CTRL0_RPT_P0_NUM0; hsc |= __SHIFTIN(1, VPP_OSD_HSC_CTRL0_RPT_P0_NUM0); hsc |= VPP_OSD_HSC_CTRL0_HSCALE_EN; } else { hsc &= ~VPP_OSD_HSC_CTRL0_HSCALE_EN; } VPU_WRITE(sc, VPP_OSD_HSC_CTRL0_REG, hsc); /* vertical scaling */ vsc = VPU_READ(sc, VPP_OSD_VSC_CTRL0_REG); if (scale_p) { vsc &= ~VPP_OSD_VSC_CTRL0_BANK_LENGTH; vsc |= __SHIFTIN(4, VPP_OSD_VSC_CTRL0_BANK_LENGTH); vsc &= ~VPP_OSD_VSC_CTRL0_TOP_INI_RCV_NUM0; vsc |= __SHIFTIN(4, VPP_OSD_VSC_CTRL0_TOP_INI_RCV_NUM0); vsc &= ~VPP_OSD_VSC_CTRL0_TOP_RPT_P0_NUM0; vsc |= __SHIFTIN(1, VPP_OSD_VSC_CTRL0_TOP_RPT_P0_NUM0); vsc &= ~VPP_OSD_VSC_CTRL0_BOT_INI_RCV_NUM0; vsc &= ~VPP_OSD_VSC_CTRL0_BOT_RPT_P0_NUM0; vsc &= ~VPP_OSC_VSC_CTRL0_INTERLACE; if (interlace_p) { /* interlace */ vsc |= VPP_OSC_VSC_CTRL0_INTERLACE; vsc |= __SHIFTIN(6, VPP_OSD_VSC_CTRL0_BOT_INI_RCV_NUM0); vsc |= __SHIFTIN(2, VPP_OSD_VSC_CTRL0_BOT_RPT_P0_NUM0); } vsc |= VPP_OSD_VSC_CTRL0_VSCALE_EN; } else { vsc &= ~VPP_OSD_VSC_CTRL0_VSCALE_EN; } VPU_WRITE(sc, VPP_OSD_VSC_CTRL0_REG, vsc); /* free scale enable */ if (scale_p) { const u_int hf_phase_step = ((width << 18) / dst_w) << 6; const u_int vf_phase_step = ((height << 20) / dst_h) << 4; const u_int bot_ini_phase = interlace_p ? ((vf_phase_step / 2) >> 8) : 0; hps = VPU_READ(sc, VPP_OSD_HSC_PHASE_STEP_REG); hps &= ~VPP_OSD_HSC_PHASE_STEP_FORMAT; hps |= __SHIFTIN(hf_phase_step, VPP_OSD_HSC_PHASE_STEP_FORMAT); VPU_WRITE(sc, VPP_OSD_HSC_PHASE_STEP_REG, hps); hip = VPU_READ(sc, VPP_OSD_HSC_INI_PHASE_REG); hip &= ~VPP_OSD_HSC_INI_PHASE_1; VPU_WRITE(sc, VPP_OSD_HSC_INI_PHASE_REG, hip); vps = VPU_READ(sc, VPP_OSD_VSC_PHASE_STEP_REG); vps &= ~VPP_OSD_VSC_PHASE_STEP_FORMAT; vps |= __SHIFTIN(hf_phase_step, VPP_OSD_VSC_PHASE_STEP_FORMAT); VPU_WRITE(sc, VPP_OSD_VSC_PHASE_STEP_REG, vps); vip = VPU_READ(sc, VPP_OSD_VSC_INI_PHASE_REG); vip &= ~VPP_OSD_VSC_INI_PHASE_1; vip |= __SHIFTIN(0, VPP_OSD_VSC_INI_PHASE_1); vip &= ~VPP_OSD_VSC_INI_PHASE_0; vip |= __SHIFTIN(bot_ini_phase, VPP_OSD_VSC_INI_PHASE_0); VPU_WRITE(sc, VPP_OSD_VSC_INI_PHASE_REG, vip); } } static void meson_genfb_init(struct meson_genfb_softc *sc) { prop_dictionary_t cfg = device_properties(sc->sc_gen.sc_dev); const struct sysctlnode *node, *devnode; u_int width = 0, height = 0, depth, flags, i, scale = 100; int error; /* * Firmware has (maybe) setup HDMI TX for us. Read the VIC from * the HDMI AVI InfoFrame (bits 6:0 in PB4) and map that to a * framebuffer geometry. */ const uint32_t vic = HDMI_READ(sc, HDMITX_AVI_INFO_ADDR + 4) & 0x7f; for (i = 0; i < __arraycount(meson_genfb_modes); i++) { if (meson_genfb_modes[i].vic == vic) { aprint_debug(" [HDMI VIC %d]", vic); width = meson_genfb_modes[i].width; height = meson_genfb_modes[i].height; flags = meson_genfb_modes[i].flags; break; } } if (width == 0 || height == 0) { aprint_error(" [UNSUPPORTED HDMI VIC %d]", vic); return; } depth = AMLOGIC_GENFB_DEFAULT_DEPTH; prop_dictionary_get_uint32(cfg, "depth", &depth); switch (depth) { case 16: case 24: break; default: aprint_error_dev(sc->sc_gen.sc_dev, "unsupported depth %d, using %d\n", depth, AMLOGIC_GENFB_DEFAULT_DEPTH); depth = AMLOGIC_GENFB_DEFAULT_DEPTH; break; } prop_dictionary_set_uint8(cfg, "depth", depth); const uint32_t fbsize = width * height * (depth / 8); sc->sc_dmasize = (fbsize + 3) & ~3; error = meson_genfb_alloc_videomem(sc); if (error) { aprint_error_dev(sc->sc_gen.sc_dev, "failed to allocate %u bytes of video memory: %d\n", (u_int)sc->sc_dmasize, error); return; } prop_dictionary_get_uint32(cfg, "scale", &scale); if (scale > 100) { scale = 100; } else if (scale < 10) { scale = 10; } sc->sc_scale = scale; prop_dictionary_set_uint32(cfg, "width", width); prop_dictionary_set_uint32(cfg, "height", height); prop_dictionary_set_bool(cfg, "dblscan", !!(flags & DBLSCAN)); prop_dictionary_set_bool(cfg, "interlace", !!(flags & INTERLACE)); prop_dictionary_set_uint16(cfg, "linebytes", width * (depth / 8)); prop_dictionary_set_uint32(cfg, "address", 0); prop_dictionary_set_uint32(cfg, "virtual_address", (uintptr_t)sc->sc_dmap); meson_genfb_canvas_config(sc); meson_genfb_osd_config(sc); meson_genfb_scaler_config(sc); /* sysctl setup */ error = sysctl_createv(&sc->sc_sysctllog, 0, NULL, &node, CTLFLAG_PERMANENT, CTLTYPE_NODE, "hw", NULL, NULL, 0, NULL, 0, CTL_HW, CTL_EOL); if (error) goto sysctl_failed; error = sysctl_createv(&sc->sc_sysctllog, 0, &node, &devnode, 0, CTLTYPE_NODE, device_xname(sc->sc_gen.sc_dev), NULL, NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); if (error) goto sysctl_failed; error = sysctl_createv(&sc->sc_sysctllog, 0, &devnode, &node, CTLFLAG_READWRITE, CTLTYPE_INT, "scale", NULL, meson_genfb_scale_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (error) goto sysctl_failed; sc->sc_node_scale = node->sysctl_num; return; sysctl_failed: aprint_error_dev(sc->sc_gen.sc_dev, "couldn't create sysctl nodes (%d)\n", error); sysctl_teardown(&sc->sc_sysctllog); } static int meson_genfb_scale_helper(SYSCTLFN_ARGS) { struct meson_genfb_softc *sc; struct sysctlnode node; int scale, oldscale, error; node = *rnode; sc = node.sysctl_data; scale = oldscale = sc->sc_scale; node.sysctl_data = &scale; error = sysctl_lookup(SYSCTLFN_CALL(&node)); if (error || newp == NULL) return error; if (scale > 100) { scale = 100; } else if (scale < 10) { scale = 10; } if (scale == oldscale) { return 0; } mutex_enter(&sc->sc_lock); sc->sc_scale = scale; meson_genfb_scaler_config(sc); mutex_exit(&sc->sc_lock); return 0; } static int meson_genfb_alloc_videomem(struct meson_genfb_softc *sc) { int error, nsegs; error = bus_dmamem_alloc(sc->sc_dmat, sc->sc_dmasize, 0x1000, 0, sc->sc_dmasegs, 1, &nsegs, BUS_DMA_WAITOK); if (error) return error; error = bus_dmamem_map(sc->sc_dmat, sc->sc_dmasegs, nsegs, sc->sc_dmasize, &sc->sc_dmap, BUS_DMA_WAITOK | BUS_DMA_COHERENT); if (error) goto free; error = bus_dmamap_create(sc->sc_dmat, sc->sc_dmasize, 1, sc->sc_dmasize, 0, BUS_DMA_WAITOK, &sc->sc_dmamap); if (error) goto unmap; error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmamap, sc->sc_dmap, sc->sc_dmasize, NULL, BUS_DMA_WAITOK); if (error) goto destroy; memset(sc->sc_dmap, 0, sc->sc_dmasize); return 0; destroy: bus_dmamap_destroy(sc->sc_dmat, sc->sc_dmamap); unmap: bus_dmamem_unmap(sc->sc_dmat, sc->sc_dmap, sc->sc_dmasize); free: bus_dmamem_free(sc->sc_dmat, sc->sc_dmasegs, nsegs); sc->sc_dmasize = 0; sc->sc_dmap = NULL; return error; } void meson_genfb_set_console_dev(device_t dev) { KASSERT(meson_genfb_console_dev == NULL); meson_genfb_console_dev = dev; } void meson_genfb_ddb_trap_callback(int where) { if (meson_genfb_console_dev == NULL) return; if (where) { genfb_enable_polling(meson_genfb_console_dev); } else { genfb_disable_polling(meson_genfb_console_dev); } } static int meson_genfb_console_match(int phandle) { return of_compatible_match(phandle, compat_data); } static void meson_genfb_console_consinit(struct fdt_attach_args *faa, u_int uart_freq) { meson_genfb_console_phandle = faa->faa_phandle; genfb_cnattach(); } static const struct fdt_console meson_genfb_fdt_console = { .match = meson_genfb_console_match, .consinit = meson_genfb_console_consinit, }; FDT_CONSOLE(meson_genfb, &meson_genfb_fdt_console);