/* $OpenBSD: rkvop.c,v 1.8 2024/08/21 11:24:12 jsg Exp $ */ /* $NetBSD: rk_vop.c,v 1.6 2020/01/05 12:14:35 mrg Exp $ */ /*- * Copyright (c) 2019 Jared D. 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 AUTHOR ``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 AUTHOR 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VOP_REG_CFG_DONE 0x0000 #define REG_LOAD_EN (1 << 0) #define VOP_SYS_CTRL 0x0008 #define VOP_STANDBY_EN (1 << 22) #define MIPI_OUT_EN (1 << 15) #define EDP_OUT_EN (1 << 14) #define HDMI_OUT_EN (1 << 13) #define RGB_OUT_EN (1 << 12) #define VOP_DSP_CTRL0 0x0010 #define DSP_OUT_MODE(x) ((x) << 0) #define DSP_OUT_MODE_MASK 0xf #define DSP_OUT_MODE_RGB888 0 #define DSP_OUT_MODE_RGBaaa 15 #define VOP_DSP_CTRL1 0x0014 #define VOP_WIN0_CTRL 0x0030 #define WIN0_LB_MODE(x) ((x) << 5) #define WIN0_LB_MODE_MASK 0x7 #define WIN0_LB_MODE_RGB_3840X2 2 #define WIN0_LB_MODE_RGB_2560X4 3 #define WIN0_LB_MODE_RGB_1920X5 4 #define WIN0_LB_MODE_RGB_1280X8 5 #define WIN0_DATA_FMT(x) ((x) << 1) #define WIN0_DATA_FMT_MASK 0x7 #define WIN0_DATA_FMT_ARGB888 0 #define WIN0_EN (1 << 0) #define VOP_WIN0_COLOR_KEY 0x0038 #define VOP_WIN0_VIR 0x003c #define WIN0_VIR_STRIDE(x) (((x) & 0x3fff) << 0) #define VOP_WIN0_YRGB_MST 0x0040 #define VOP_WIN0_ACT_INFO 0x0048 #define WIN0_ACT_HEIGHT(x) (((x) & 0x1fff) << 16) #define WIN0_ACT_WIDTH(x) (((x) & 0x1fff) << 0) #define VOP_WIN0_DSP_INFO 0x004c #define WIN0_DSP_HEIGHT(x) (((x) & 0xfff) << 16) #define WIN0_DSP_WIDTH(x) (((x) & 0xfff) << 0) #define VOP_WIN0_DSP_ST 0x0050 #define WIN0_DSP_YST(x) (((x) & 0x1fff) << 16) #define WIN0_DSP_XST(x) (((x) & 0x1fff) << 0) #define VOP_POST_DSP_HACT_INFO 0x0170 #define DSP_HACT_ST_POST(x) (((x) & 0x1fff) << 16) #define DSP_HACT_END_POST(x) (((x) & 0x1fff) << 0) #define VOP_POST_DSP_VACT_INFO 0x0174 #define DSP_VACT_ST_POST(x) (((x) & 0x1fff) << 16) #define DSP_VACT_END_POST(x) (((x) & 0x1fff) << 0) #define VOP_DSP_HTOTAL_HS_END 0x0188 #define DSP_HS_END(x) (((x) & 0x1fff) << 16) #define DSP_HTOTAL(x) (((x) & 0x1fff) << 0) #define VOP_DSP_HACT_ST_END 0x018c #define DSP_HACT_ST(x) (((x) & 0x1fff) << 16) #define DSP_HACT_END(x) (((x) & 0x1fff) << 0) #define VOP_DSP_VTOTAL_VS_END 0x0190 #define DSP_VS_END(x) (((x) & 0x1fff) << 16) #define DSP_VTOTAL(x) (((x) & 0x1fff) << 0) #define VOP_DSP_VACT_ST_END 0x0194 #define DSP_VACT_ST(x) (((x) & 0x1fff) << 16) #define DSP_VACT_END(x) (((x) & 0x1fff) << 0) /* * Polarity fields are in different locations depending on SoC and output type, * but always in the same order. */ #define DSP_DCLK_POL (1 << 3) #define DSP_DEN_POL (1 << 2) #define DSP_VSYNC_POL (1 << 1) #define DSP_HSYNC_POL (1 << 0) enum vop_ep_type { VOP_EP_MIPI, VOP_EP_EDP, VOP_EP_HDMI, VOP_EP_MIPI1, VOP_EP_DP, VOP_NEP }; struct rkvop_softc; struct rkvop_config; struct rkvop_crtc { struct drm_crtc base; struct rkvop_softc *sc; }; struct rkvop_softc { struct device sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; int sc_node; struct rkvop_config *sc_conf; struct rkvop_crtc sc_crtc; struct drm_plane sc_plane; struct device_ports sc_ports; }; #define to_rkvop_crtc(x) container_of(x, struct rkvop_crtc, base) #define HREAD4(sc, reg) \ bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)) #define HWRITE4(sc, reg, val) \ bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) struct rkvop_config { char *descr; u_int out_mode; void (*init)(struct rkvop_softc *); void (*set_polarity)(struct rkvop_softc *, enum vop_ep_type, uint32_t); }; int rkvop_match(struct device *, void *, void *); void rkvop_attach(struct device *, struct device *, void *); void rkvop_dpms(struct drm_crtc *, int); bool rkvop_mode_fixup(struct drm_crtc *, const struct drm_display_mode *, struct drm_display_mode *); void rk3399_vop_init(struct rkvop_softc *); void rk3399_vop_set_polarity(struct rkvop_softc *, enum vop_ep_type, uint32_t); int rkvop_ep_activate(void *, struct endpoint *, void *); void *rkvop_ep_get_cookie(void *, struct endpoint *); struct rkvop_config rk3399_vop_big_config = { .descr = "RK3399 VOPB", .out_mode = DSP_OUT_MODE_RGBaaa, .init = rk3399_vop_init, .set_polarity = rk3399_vop_set_polarity, }; struct rkvop_config rk3399_vop_lit_config = { .descr = "RK3399 VOPL", .out_mode = DSP_OUT_MODE_RGB888, .init = rk3399_vop_init, .set_polarity = rk3399_vop_set_polarity, }; const struct cfattach rkvop_ca = { sizeof (struct rkvop_softc), rkvop_match, rkvop_attach }; struct cfdriver rkvop_cd = { NULL, "rkvop", DV_DULL }; int rkvop_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; return (OF_is_compatible(faa->fa_node, "rockchip,rk3399-vop-big") || OF_is_compatible(faa->fa_node, "rockchip,rk3399-vop-lit")); } void rkvop_attach(struct device *parent, struct device *self, void *aux) { struct rkvop_softc *sc = (struct rkvop_softc *)self; struct fdt_attach_args *faa = aux; paddr_t paddr; if (faa->fa_nreg < 1) return; clock_set_assigned(faa->fa_node); reset_deassert(faa->fa_node, "axi"); reset_deassert(faa->fa_node, "ahb"); reset_deassert(faa->fa_node, "dclk"); clock_enable(faa->fa_node, "aclk_vop"); clock_enable(faa->fa_node, "hclk_vop"); clock_enable(faa->fa_node, "dclk_vop"); sc->sc_iot = faa->fa_iot; if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, faa->fa_reg[0].size, 0, &sc->sc_ioh)) { printf(": can't map registers\n"); return; } sc->sc_node = faa->fa_node; if (OF_is_compatible(faa->fa_node, "rockchip,rk3399-vop-big")) sc->sc_conf = &rk3399_vop_big_config; if (OF_is_compatible(faa->fa_node, "rockchip,rk3399-vop-lit")) sc->sc_conf = &rk3399_vop_lit_config; printf(": %s\n", sc->sc_conf->descr); if (sc->sc_conf->init != NULL) sc->sc_conf->init(sc); sc->sc_ports.dp_node = faa->fa_node; sc->sc_ports.dp_cookie = sc; sc->sc_ports.dp_ep_activate = rkvop_ep_activate; sc->sc_ports.dp_ep_get_cookie = rkvop_ep_get_cookie; device_ports_register(&sc->sc_ports, EP_DRM_CRTC); paddr = HREAD4(sc, VOP_WIN0_YRGB_MST); if (paddr != 0) { uint32_t stride, height; stride = HREAD4(sc, VOP_WIN0_VIR) & 0xffff; height = (HREAD4(sc, VOP_WIN0_DSP_INFO) >> 16) + 1; rasops_claim_framebuffer(paddr, height * stride * 4, self); } } int rkvop_plane_check(struct drm_plane *plane, struct drm_atomic_state *das) { struct drm_crtc_state *crtc_state; struct drm_plane_state *state = drm_atomic_get_new_plane_state(das, plane); if (state->crtc == NULL) return 0; crtc_state = drm_atomic_get_new_crtc_state(state->state, state->crtc); if (IS_ERR(crtc_state)) return PTR_ERR(crtc_state); return drm_atomic_helper_check_plane_state(state, crtc_state, DRM_PLANE_NO_SCALING, DRM_PLANE_NO_SCALING, false, true); } void rkvop_plane_update(struct drm_plane *plane, struct drm_atomic_state *das) { struct drm_plane_state *state = plane->state; struct drm_crtc *crtc = state->crtc; struct rkvop_crtc *rkcrtc = to_rkvop_crtc(crtc); struct rkvop_softc *sc = rkcrtc->sc; struct drm_framebuffer *fb = state->fb; struct rkdrm_framebuffer *rkfb = to_rkdrm_framebuffer(fb); struct drm_rect *src = &state->src; struct drm_rect *dst = &state->dst; u_int act_width = drm_rect_width(src) >> 16; u_int act_height = drm_rect_height(src) >> 16; u_int htotal = crtc->mode.htotal; u_int vtotal = crtc->mode.vtotal; u_int hsync_start = crtc->mode.hsync_start; u_int vsync_start = crtc->mode.vsync_start; uint64_t paddr; u_int lb_mode; uint32_t val; val = WIN0_ACT_WIDTH(act_width - 1) | WIN0_ACT_HEIGHT(act_height - 1); HWRITE4(sc, VOP_WIN0_ACT_INFO, val); val = WIN0_DSP_WIDTH(drm_rect_width(dst) - 1) | WIN0_DSP_HEIGHT(drm_rect_height(dst) - 1); HWRITE4(sc, VOP_WIN0_DSP_INFO, val); val = WIN0_DSP_XST(dst->x1 + htotal - hsync_start) | WIN0_DSP_YST(dst->y1 + vtotal - vsync_start); HWRITE4(sc, VOP_WIN0_DSP_ST, val); HWRITE4(sc, VOP_WIN0_COLOR_KEY, 0); if (act_width > 2560) lb_mode = WIN0_LB_MODE_RGB_3840X2; else if (act_width > 1920) lb_mode = WIN0_LB_MODE_RGB_2560X4; else if (act_width > 1280) lb_mode = WIN0_LB_MODE_RGB_1920X5; else lb_mode = WIN0_LB_MODE_RGB_1280X8; val = WIN0_LB_MODE(lb_mode) | WIN0_DATA_FMT(WIN0_DATA_FMT_ARGB888) | WIN0_EN; HWRITE4(sc, VOP_WIN0_CTRL, val); val = WIN0_VIR_STRIDE(fb->pitches[0] / 4); HWRITE4(sc, VOP_WIN0_VIR, val); /* Framebuffer start address */ paddr = (uint64_t)rkfb->obj->dmamap->dm_segs[0].ds_addr; paddr += (src->y1 >> 16) * fb->pitches[0]; paddr += (src->x1 >> 16) * fb->format->cpp[0]; KASSERT((paddr & ~0xffffffff) == 0); HWRITE4(sc, VOP_WIN0_YRGB_MST, (uint32_t)paddr); } struct drm_plane_helper_funcs rkvop_plane_helper_funcs = { .atomic_check = rkvop_plane_check, .atomic_update = rkvop_plane_update, }; struct drm_plane_funcs rkvop_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .destroy = drm_plane_cleanup, .reset = drm_atomic_helper_plane_reset, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, }; void rkvop_dpms(struct drm_crtc *crtc, int mode) { struct rkvop_crtc *rkcrtc = to_rkvop_crtc(crtc); struct rkvop_softc *sc = rkcrtc->sc; uint32_t val; val = HREAD4(sc, VOP_SYS_CTRL); switch (mode) { case DRM_MODE_DPMS_ON: val &= ~VOP_STANDBY_EN; break; case DRM_MODE_DPMS_STANDBY: case DRM_MODE_DPMS_SUSPEND: case DRM_MODE_DPMS_OFF: val |= VOP_STANDBY_EN; break; } HWRITE4(sc, VOP_SYS_CTRL, val); /* Commit settings */ HWRITE4(sc, VOP_REG_CFG_DONE, REG_LOAD_EN); } bool rkvop_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { return true; } int rkvop_crtc_check(struct drm_crtc *crtc, struct drm_atomic_state *das) { struct drm_crtc_state *state = drm_atomic_get_new_crtc_state(das, crtc); bool enabled = state->plane_mask & drm_plane_mask(crtc->primary); if (enabled != state->enable) return -EINVAL; return drm_atomic_add_affected_planes(state->state, crtc); } void rkvop_crtc_enable(struct drm_crtc *crtc, struct drm_atomic_state *das) { struct rkvop_crtc *rkcrtc = to_rkvop_crtc(crtc); struct rkvop_softc *sc = rkcrtc->sc; struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode; uint32_t val; u_int pol; int connector_type = 0; struct drm_connector *connector; struct drm_connector_list_iter conn_iter; u_int hactive = adjusted_mode->hdisplay; u_int hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start; u_int hback_porch = adjusted_mode->htotal - adjusted_mode->hsync_end; u_int hfront_porch = adjusted_mode->hsync_start - adjusted_mode->hdisplay; u_int vactive = adjusted_mode->vdisplay; u_int vsync_len = adjusted_mode->vsync_end - adjusted_mode->vsync_start; u_int vback_porch = adjusted_mode->vtotal - adjusted_mode->vsync_end; u_int vfront_porch = adjusted_mode->vsync_start - adjusted_mode->vdisplay; clock_set_frequency(sc->sc_node, "dclk_vop", adjusted_mode->clock * 1000); pol = DSP_DCLK_POL; if ((adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) != 0) pol |= DSP_HSYNC_POL; if ((adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) != 0) pol |= DSP_VSYNC_POL; drm_connector_list_iter_begin(crtc->dev, &conn_iter); drm_for_each_connector_iter(connector, &conn_iter) { if ((connector->encoder) == NULL) continue; if (connector->encoder->crtc == crtc) { connector_type = connector->connector_type; break; } } switch (connector_type) { case DRM_MODE_CONNECTOR_HDMIA: sc->sc_conf->set_polarity(sc, VOP_EP_HDMI, pol); break; case DRM_MODE_CONNECTOR_eDP: sc->sc_conf->set_polarity(sc, VOP_EP_EDP, pol); break; } val = HREAD4(sc, VOP_SYS_CTRL); val &= ~VOP_STANDBY_EN; val &= ~(MIPI_OUT_EN|EDP_OUT_EN|HDMI_OUT_EN|RGB_OUT_EN); switch (connector_type) { case DRM_MODE_CONNECTOR_HDMIA: val |= HDMI_OUT_EN; break; case DRM_MODE_CONNECTOR_eDP: val |= EDP_OUT_EN; break; } HWRITE4(sc, VOP_SYS_CTRL, val); val = HREAD4(sc, VOP_DSP_CTRL0); val &= ~DSP_OUT_MODE(DSP_OUT_MODE_MASK); val |= DSP_OUT_MODE(sc->sc_conf->out_mode); HWRITE4(sc, VOP_DSP_CTRL0, val); val = DSP_HACT_ST_POST(hsync_len + hback_porch) | DSP_HACT_END_POST(hsync_len + hback_porch + hactive); HWRITE4(sc, VOP_POST_DSP_HACT_INFO, val); val = DSP_HACT_ST(hsync_len + hback_porch) | DSP_HACT_END(hsync_len + hback_porch + hactive); HWRITE4(sc, VOP_DSP_HACT_ST_END, val); val = DSP_HTOTAL(hsync_len) | DSP_HS_END(hsync_len + hback_porch + hactive + hfront_porch); HWRITE4(sc, VOP_DSP_HTOTAL_HS_END, val); val = DSP_VACT_ST_POST(vsync_len + vback_porch) | DSP_VACT_END_POST(vsync_len + vback_porch + vactive); HWRITE4(sc, VOP_POST_DSP_VACT_INFO, val); val = DSP_VACT_ST(vsync_len + vback_porch) | DSP_VACT_END(vsync_len + vback_porch + vactive); HWRITE4(sc, VOP_DSP_VACT_ST_END, val); val = DSP_VTOTAL(vsync_len) | DSP_VS_END(vsync_len + vback_porch + vactive + vfront_porch); HWRITE4(sc, VOP_DSP_VTOTAL_VS_END, val); } void rkvop_crtc_flush(struct drm_crtc *crtc, struct drm_atomic_state *das) { struct rkvop_crtc *rkcrtc = to_rkvop_crtc(crtc); struct rkvop_softc *sc = rkcrtc->sc; /* Commit settings */ HWRITE4(sc, VOP_REG_CFG_DONE, REG_LOAD_EN); } struct drm_crtc_helper_funcs rkvop_crtc_helper_funcs = { .dpms = rkvop_dpms, .mode_fixup = rkvop_mode_fixup, .atomic_check = rkvop_crtc_check, .atomic_enable = rkvop_crtc_enable, .atomic_flush = rkvop_crtc_flush, }; struct drm_crtc_funcs rkvop_crtc_funcs = { .reset = drm_atomic_helper_crtc_reset, .destroy = drm_crtc_cleanup, .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, }; int rkvop_ep_activate(void *cookie, struct endpoint *ep, void *arg) { struct rkvop_softc *sc = cookie; struct drm_device *ddev = arg; struct drm_plane *plane = &sc->sc_plane; struct drm_crtc *crtc = &sc->sc_crtc.base; uint32_t formats[] = { DRM_FORMAT_ARGB8888 }; int error; if (sc->sc_crtc.sc) return 0; drm_plane_helper_add(plane, &rkvop_plane_helper_funcs); error = drm_universal_plane_init(ddev, plane, 0, &rkvop_plane_funcs, formats, nitems(formats), NULL, DRM_PLANE_TYPE_PRIMARY, NULL); if (error) return -error; drm_crtc_helper_add(&sc->sc_crtc.base, &rkvop_crtc_helper_funcs); error = drm_crtc_init_with_planes(ddev, crtc, plane, NULL, &rkvop_crtc_funcs, NULL); if (error) return -error; printf("%s: using CRTC %d for %s\n", sc->sc_dev.dv_xname, drm_crtc_index(&sc->sc_crtc.base), sc->sc_conf->descr); sc->sc_crtc.sc = sc; return 0; } void * rkvop_ep_get_cookie(void *cookie, struct endpoint *ep) { struct rkvop_softc *sc = cookie; return &sc->sc_crtc.base; } /* * RK3399 VOP */ #define RK3399_VOP_POL_MASK 0xf #define RK3399_VOP_MIPI_POL(x) ((x) << 28) #define RK3399_VOP_EDP_POL(x) ((x) << 24) #define RK3399_VOP_HDMI_POL(x) ((x) << 20) #define RK3399_VOP_DP_POL(x) ((x) << 16) #define RK3399_VOP_SYS_CTRL_ENABLE (1 << 11) void rk3399_vop_init(struct rkvop_softc *sc) { uint32_t val; val = HREAD4(sc, VOP_SYS_CTRL); val |= RK3399_VOP_SYS_CTRL_ENABLE; HWRITE4(sc, VOP_SYS_CTRL, val); } void rk3399_vop_set_polarity(struct rkvop_softc *sc, enum vop_ep_type ep_type, uint32_t pol) { uint32_t mask, val; switch (ep_type) { case VOP_EP_MIPI: case VOP_EP_MIPI1: pol = RK3399_VOP_MIPI_POL(pol); mask = RK3399_VOP_MIPI_POL(RK3399_VOP_POL_MASK); break; case VOP_EP_EDP: pol = RK3399_VOP_EDP_POL(pol); mask = RK3399_VOP_EDP_POL(RK3399_VOP_POL_MASK); break; case VOP_EP_HDMI: pol = RK3399_VOP_HDMI_POL(pol); mask = RK3399_VOP_HDMI_POL(RK3399_VOP_POL_MASK); break; case VOP_EP_DP: pol = RK3399_VOP_DP_POL(pol); mask = RK3399_VOP_DP_POL(RK3399_VOP_POL_MASK); break; default: return; } val = HREAD4(sc, VOP_DSP_CTRL1); val &= ~mask; val |= pol; HWRITE4(sc, VOP_DSP_CTRL1, val); }