Module Name: src Committed By: jmcneill Date: Sat Nov 9 23:30:14 UTC 2019
Modified Files: src/sys/arch/arm/rockchip: files.rockchip Added Files: src/sys/arch/arm/rockchip: rk_drm.c rk_drm.h rk_dwhdmi.c rk_fb.c rk_vop.c Log Message: WIP display driver for Rockchip RK3399 To generate a diff of this commit: cvs rdiff -u -r1.20 -r1.21 src/sys/arch/arm/rockchip/files.rockchip cvs rdiff -u -r0 -r1.1 src/sys/arch/arm/rockchip/rk_drm.c \ src/sys/arch/arm/rockchip/rk_drm.h src/sys/arch/arm/rockchip/rk_dwhdmi.c \ src/sys/arch/arm/rockchip/rk_fb.c src/sys/arch/arm/rockchip/rk_vop.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/arch/arm/rockchip/files.rockchip diff -u src/sys/arch/arm/rockchip/files.rockchip:1.20 src/sys/arch/arm/rockchip/files.rockchip:1.21 --- src/sys/arch/arm/rockchip/files.rockchip:1.20 Mon Aug 5 15:22:59 2019 +++ src/sys/arch/arm/rockchip/files.rockchip Sat Nov 9 23:30:14 2019 @@ -1,4 +1,4 @@ -# $NetBSD: files.rockchip,v 1.20 2019/08/05 15:22:59 tnn Exp $ +# $NetBSD: files.rockchip,v 1.21 2019/11/09 23:30:14 jmcneill Exp $ # # Configuration info for Rockchip family SoCs # @@ -83,6 +83,26 @@ device rkpwm: pwm attach rkpwm at fdt with rk_pwm file arch/arm/rockchip/rk_pwm.c rk_pwm +# DRM master +define rkfbbus { } +device rkdrm: drmkms, ddc_read_edid, rkfbbus +attach rkdrm at fdt with rk_drm +file arch/arm/rockchip/rk_drm.c rk_drm + +# DRM framebuffer console +device rkfb: rkfbbus, drmfb, wsemuldisplaydev +attach rkfb at rkfbbus with rk_fb +file arch/arm/rockchip/rk_fb.c rk_fb + +# Visual Output Processor +device rkvop: drmkms +attach rkvop at fdt with rk_vop +file arch/arm/rockchip/rk_vop.c rk_vop + +# HDMI TX (Designware based) +attach dwhdmi at fdt with rk_dwhdmi +file arch/arm/rockchip/rk_dwhdmi.c rk_dwhdmi + # SOC parameters defflag opt_soc.h SOC_ROCKCHIP defflag opt_soc.h SOC_RK3328: SOC_ROCKCHIP Added files: Index: src/sys/arch/arm/rockchip/rk_drm.c diff -u /dev/null src/sys/arch/arm/rockchip/rk_drm.c:1.1 --- /dev/null Sat Nov 9 23:30:14 2019 +++ src/sys/arch/arm/rockchip/rk_drm.c Sat Nov 9 23:30:14 2019 @@ -0,0 +1,514 @@ +/* $NetBSD: rk_drm.c,v 1.1 2019/11/09 23:30:14 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca> + * 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 <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: rk_drm.c,v 1.1 2019/11/09 23:30:14 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/intr.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> + +#include <uvm/uvm_extern.h> +#include <uvm/uvm_object.h> +#include <uvm/uvm_device.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> + +#include <dev/fdt/fdtvar.h> +#include <dev/fdt/fdt_port.h> + +#include <arm/rockchip/rk_drm.h> + +#define RK_DRM_MAX_WIDTH 3840 +#define RK_DRM_MAX_HEIGHT 2160 + +static TAILQ_HEAD(, rk_drm_ports) rk_drm_ports = + TAILQ_HEAD_INITIALIZER(rk_drm_ports); + +static const char * const compatible[] = { + "rockchip,display-subsystem", + NULL +}; + +static const char * fb_compatible[] = { + "simple-framebuffer", + NULL +}; + +static int rk_drm_match(device_t, cfdata_t, void *); +static void rk_drm_attach(device_t, device_t, void *); + +static void rk_drm_init(device_t); +static vmem_t *rk_drm_alloc_cma_pool(struct drm_device *, size_t); + +static int rk_drm_set_busid(struct drm_device *, struct drm_master *); + +static uint32_t rk_drm_get_vblank_counter(struct drm_device *, unsigned int); +static int rk_drm_enable_vblank(struct drm_device *, unsigned int); +static void rk_drm_disable_vblank(struct drm_device *, unsigned int); + +static int rk_drm_load(struct drm_device *, unsigned long); +static int rk_drm_unload(struct drm_device *); + +static struct drm_driver rk_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME, + .dev_priv_size = 0, + .load = rk_drm_load, + .unload = rk_drm_unload, + + .gem_free_object = drm_gem_cma_free_object, + .mmap_object = drm_gem_or_legacy_mmap_object, + .gem_uvm_ops = &drm_gem_cma_uvm_ops, + + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + + .get_vblank_counter = rk_drm_get_vblank_counter, + .enable_vblank = rk_drm_enable_vblank, + .disable_vblank = rk_drm_disable_vblank, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, + + .set_busid = rk_drm_set_busid, +}; + +CFATTACH_DECL_NEW(rk_drm, sizeof(struct rk_drm_softc), + rk_drm_match, rk_drm_attach, NULL, NULL); + +static int +rk_drm_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compatible(faa->faa_phandle, compatible); +} + +static void +rk_drm_attach(device_t parent, device_t self, void *aux) +{ + struct rk_drm_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + struct drm_driver * const driver = &rk_drm_driver; + prop_dictionary_t dict = device_properties(self); + bool is_disabled; + + sc->sc_dev = self; + sc->sc_dmat = faa->faa_dmat; + sc->sc_bst = faa->faa_bst; + sc->sc_phandle = faa->faa_phandle; + + drm_debug = 0xff; + + aprint_naive("\n"); + + if (prop_dictionary_get_bool(dict, "disabled", &is_disabled) && is_disabled) { + aprint_normal(": (disabled)\n"); + return; + } + + aprint_normal("\n"); + + sc->sc_ddev = drm_dev_alloc(driver, sc->sc_dev); + if (sc->sc_ddev == NULL) { + aprint_error_dev(self, "couldn't allocate DRM device\n"); + return; + } + sc->sc_ddev->dev_private = sc; + sc->sc_ddev->bst = sc->sc_bst; + sc->sc_ddev->bus_dmat = sc->sc_dmat; + sc->sc_ddev->dmat = sc->sc_ddev->bus_dmat; + sc->sc_ddev->dmat_subregion_p = false; + + fdt_remove_bycompat(fb_compatible); + + config_defer(self, rk_drm_init); +} + +static void +rk_drm_init(device_t dev) +{ + struct rk_drm_softc * const sc = device_private(dev); + struct drm_driver * const driver = &rk_drm_driver; + int error; + + error = -drm_dev_register(sc->sc_ddev, 0); + if (error) { + drm_dev_unref(sc->sc_ddev); + aprint_error_dev(dev, "couldn't register DRM device: %d\n", + error); + return; + } + + aprint_normal_dev(dev, "initialized %s %d.%d.%d %s on minor %d\n", + driver->name, driver->major, driver->minor, driver->patchlevel, + driver->date, sc->sc_ddev->primary->index); +} + +static vmem_t * +rk_drm_alloc_cma_pool(struct drm_device *ddev, size_t cma_size) +{ + struct rk_drm_softc * const sc = rk_drm_private(ddev); + bus_dma_segment_t segs[1]; + int nsegs; + int error; + + error = bus_dmamem_alloc(sc->sc_dmat, cma_size, PAGE_SIZE, 0, + segs, 1, &nsegs, BUS_DMA_NOWAIT); + if (error) { + aprint_error_dev(sc->sc_dev, "couldn't allocate CMA pool\n"); + return NULL; + } + + return vmem_create("rkdrm", segs[0].ds_addr, segs[0].ds_len, + PAGE_SIZE, NULL, NULL, NULL, 0, VM_SLEEP, IPL_NONE); +} + +static int +rk_drm_set_busid(struct drm_device *ddev, struct drm_master *master) +{ + struct rk_drm_softc * const sc = rk_drm_private(ddev); + char id[32]; + + snprintf(id, sizeof(id), "platform:rk:%u", device_unit(sc->sc_dev)); + + master->unique = kzalloc(strlen(id) + 1, GFP_KERNEL); + if (master->unique == NULL) + return -ENOMEM; + strcpy(master->unique, id); + master->unique_len = strlen(master->unique); + + return 0; +} + +static int +rk_drm_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file, unsigned int *handle) +{ + struct rk_drm_framebuffer *sfb = to_rk_drm_framebuffer(fb); + + return drm_gem_handle_create(file, &sfb->obj->base, handle); +} + +static void +rk_drm_fb_destroy(struct drm_framebuffer *fb) +{ + struct rk_drm_framebuffer *sfb = to_rk_drm_framebuffer(fb); + + drm_framebuffer_cleanup(fb); + drm_gem_object_unreference_unlocked(&sfb->obj->base); + kmem_free(sfb, sizeof(*sfb)); +} + +static const struct drm_framebuffer_funcs rk_drm_framebuffer_funcs = { + .create_handle = rk_drm_fb_create_handle, + .destroy = rk_drm_fb_destroy, +}; + +static struct drm_framebuffer * +rk_drm_fb_create(struct drm_device *ddev, struct drm_file *file, + struct drm_mode_fb_cmd2 *cmd) +{ + struct rk_drm_framebuffer *fb; + struct drm_gem_object *gem_obj; + int error; + + if (cmd->flags) + return NULL; + + gem_obj = drm_gem_object_lookup(ddev, file, cmd->handles[0]); + if (gem_obj == NULL) + return NULL; + + fb = kmem_zalloc(sizeof(*fb), KM_SLEEP); + fb->obj = to_drm_gem_cma_obj(gem_obj); + fb->base.pitches[0] = cmd->pitches[0]; + fb->base.pitches[1] = cmd->pitches[1]; + fb->base.pitches[2] = cmd->pitches[2]; + fb->base.offsets[0] = cmd->offsets[0]; + fb->base.offsets[1] = cmd->offsets[2]; + fb->base.offsets[2] = cmd->offsets[1]; + fb->base.width = cmd->width; + fb->base.height = cmd->height; + fb->base.pixel_format = cmd->pixel_format; + fb->base.bits_per_pixel = drm_format_plane_cpp(fb->base.pixel_format, 0) * 8; + + switch (fb->base.pixel_format) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + fb->base.depth = 32; + break; + default: + break; + } + + error = drm_framebuffer_init(ddev, &fb->base, &rk_drm_framebuffer_funcs); + if (error != 0) + goto dealloc; + + return &fb->base; + +dealloc: + drm_framebuffer_cleanup(&fb->base); + kmem_free(fb, sizeof(*fb)); + drm_gem_object_unreference_unlocked(gem_obj); + + return NULL; +} + +static struct drm_mode_config_funcs rk_drm_mode_config_funcs = { + .fb_create = rk_drm_fb_create, +}; + +static int +rk_drm_fb_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) +{ + struct rk_drm_softc * const sc = rk_drm_private(helper->dev); + struct drm_device *ddev = helper->dev; + struct rk_drm_framebuffer *sfb = to_rk_drm_framebuffer(helper->fb); + struct drm_framebuffer *fb = helper->fb; + struct rk_drmfb_attach_args sfa; + size_t cma_size; + int error; + + const u_int width = sizes->surface_width; + const u_int height = sizes->surface_height; + const u_int pitch = width * (32 / 8); + + const size_t size = roundup(height * pitch, PAGE_SIZE); + + /* Reserve enough memory for the FB console plus a 4K plane, rounded to 1MB */ + cma_size = size; + cma_size += (RK_DRM_MAX_WIDTH * RK_DRM_MAX_HEIGHT * 4); + cma_size = roundup(cma_size, 1024 * 1024); + sc->sc_ddev->cma_pool = rk_drm_alloc_cma_pool(sc->sc_ddev, cma_size); + if (sc->sc_ddev->cma_pool != NULL) + aprint_normal_dev(sc->sc_dev, "reserved %u MB DRAM for CMA\n", + (u_int)(cma_size / (1024 * 1024))); + + sfb->obj = drm_gem_cma_create(ddev, size); + if (sfb->obj == NULL) { + DRM_ERROR("failed to allocate memory for framebuffer\n"); + return -ENOMEM; + } + + fb->pitches[0] = pitch; + fb->offsets[0] = 0; + fb->width = width; + fb->height = height; + fb->pixel_format = DRM_FORMAT_XRGB8888; + drm_fb_get_bpp_depth(fb->pixel_format, &fb->depth, &fb->bits_per_pixel); + + error = drm_framebuffer_init(ddev, fb, &rk_drm_framebuffer_funcs); + if (error != 0) { + DRM_ERROR("failed to initialize framebuffer\n"); + return error; + } + + memset(&sfa, 0, sizeof(sfa)); + sfa.sfa_drm_dev = ddev; + sfa.sfa_fb_helper = helper; + sfa.sfa_fb_sizes = *sizes; + sfa.sfa_fb_bst = sc->sc_bst; + sfa.sfa_fb_dmat = sc->sc_dmat; + sfa.sfa_fb_linebytes = helper->fb->pitches[0]; + + helper->fbdev = config_found_ia(ddev->dev, "rkfbbus", &sfa, NULL); + if (helper->fbdev == NULL) { + DRM_ERROR("unable to attach framebuffer\n"); + return -ENXIO; + } + + return 0; +} + +static struct drm_fb_helper_funcs rk_drm_fb_helper_funcs = { + .fb_probe = rk_drm_fb_probe, +}; + +static int +rk_drm_load(struct drm_device *ddev, unsigned long flags) +{ + struct rk_drm_softc * const sc = rk_drm_private(ddev); + struct rk_drm_ports *sport; + struct rk_drm_fbdev *fbdev; + struct fdt_endpoint *ep; + const u_int *data; + int datalen, error, num_crtc, ep_index; + + drm_mode_config_init(ddev); + ddev->mode_config.min_width = 0; + ddev->mode_config.min_height = 0; + ddev->mode_config.max_width = RK_DRM_MAX_WIDTH; + ddev->mode_config.max_height = RK_DRM_MAX_HEIGHT; + ddev->mode_config.funcs = &rk_drm_mode_config_funcs; + + num_crtc = 0; + data = fdtbus_get_prop(sc->sc_phandle, "ports", &datalen); + while (datalen >= 4) { + const int crtc_phandle = fdtbus_get_phandle_from_native(be32dec(data)); + + TAILQ_FOREACH(sport, &rk_drm_ports, entries) + if (sport->phandle == crtc_phandle && sport->ddev == NULL) { + sport->ddev = ddev; + for (ep_index = 0; (ep = fdt_endpoint_get_from_index(sport->port, 0, ep_index)) != NULL; ep_index++) { + error = fdt_endpoint_activate_direct(ep, true); + if (error != 0) + aprint_debug_dev(sc->sc_dev, + "failed to activate endpoint %d: %d\n", + ep_index, error); + } + num_crtc++; + } + + datalen -= 4; + data++; + } + + if (num_crtc == 0) { + aprint_error_dev(sc->sc_dev, "no display interface ports configured\n"); + return ENXIO; + } + + fbdev = kmem_zalloc(sizeof(*fbdev), KM_SLEEP); + + drm_fb_helper_prepare(ddev, &fbdev->helper, &rk_drm_fb_helper_funcs); + + error = drm_fb_helper_init(ddev, &fbdev->helper, num_crtc, num_crtc); + if (error) + goto drmerr; + + fbdev->helper.fb = kmem_zalloc(sizeof(struct rk_drm_framebuffer), KM_SLEEP); + + drm_fb_helper_single_add_all_connectors(&fbdev->helper); + + drm_helper_disable_unused_functions(ddev); + + drm_fb_helper_initial_config(&fbdev->helper, 32); + + /* XXX */ + ddev->irq_enabled = true; + drm_vblank_init(ddev, num_crtc); + + return 0; + +drmerr: + drm_mode_config_cleanup(ddev); + kmem_free(fbdev, sizeof(*fbdev)); + + return error; +} + +static uint32_t +rk_drm_get_vblank_counter(struct drm_device *ddev, unsigned int crtc) +{ + struct rk_drm_softc * const sc = rk_drm_private(ddev); + + if (crtc >= __arraycount(sc->sc_vbl)) + return 0; + + if (sc->sc_vbl[crtc].get_vblank_counter == NULL) + return 0; + + return sc->sc_vbl[crtc].get_vblank_counter(sc->sc_vbl[crtc].priv); +} + +static int +rk_drm_enable_vblank(struct drm_device *ddev, unsigned int crtc) +{ + struct rk_drm_softc * const sc = rk_drm_private(ddev); + + if (crtc >= __arraycount(sc->sc_vbl)) + return 0; + + if (sc->sc_vbl[crtc].enable_vblank == NULL) + return 0; + + sc->sc_vbl[crtc].enable_vblank(sc->sc_vbl[crtc].priv); + + return 0; +} + +static void +rk_drm_disable_vblank(struct drm_device *ddev, unsigned int crtc) +{ + struct rk_drm_softc * const sc = rk_drm_private(ddev); + + if (crtc >= __arraycount(sc->sc_vbl)) + return; + + if (sc->sc_vbl[crtc].disable_vblank == NULL) + return; + + sc->sc_vbl[crtc].disable_vblank(sc->sc_vbl[crtc].priv); +} + +static int +rk_drm_unload(struct drm_device *ddev) +{ + drm_mode_config_cleanup(ddev); + + return 0; +} + +int +rk_drm_register_port(int phandle, struct fdt_device_ports *port) +{ + struct rk_drm_ports *sport; + + sport = kmem_zalloc(sizeof(*sport), KM_SLEEP); + sport->phandle = phandle; + sport->port = port; + sport->ddev = NULL; + TAILQ_INSERT_TAIL(&rk_drm_ports, sport, entries); + + return 0; +} + +struct drm_device * +rk_drm_port_device(struct fdt_device_ports *port) +{ + struct rk_drm_ports *sport; + + TAILQ_FOREACH(sport, &rk_drm_ports, entries) + if (sport->port == port) + return sport->ddev; + + return NULL; +} Index: src/sys/arch/arm/rockchip/rk_drm.h diff -u /dev/null src/sys/arch/arm/rockchip/rk_drm.h:1.1 --- /dev/null Sat Nov 9 23:30:14 2019 +++ src/sys/arch/arm/rockchip/rk_drm.h Sat Nov 9 23:30:14 2019 @@ -0,0 +1,99 @@ +/* $NetBSD: rk_drm.h,v 1.1 2019/11/09 23:30:14 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca> + * 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. + */ + +#ifndef _ARM_RK_DRM_H +#define _ARM_RK_DRM_H + +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_cma_helper.h> + +#define DRIVER_AUTHOR "Jared McNeill" + +#define DRIVER_NAME "rk" +#define DRIVER_DESC "Rockchip Display Subsystem" +#define DRIVER_DATE "20191109" + +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +struct rk_framebuffer; + +#define RK_DRM_MAX_CRTC 2 + +struct rk_drm_vblank { + void *priv; + void (*enable_vblank)(void *); + void (*disable_vblank)(void *); + uint32_t (*get_vblank_counter)(void *); +}; + +struct rk_drm_softc { + device_t sc_dev; + struct drm_device *sc_ddev; + + bus_space_tag_t sc_bst; + bus_dma_tag_t sc_dmat; + + int sc_phandle; + + struct rk_drm_vblank sc_vbl[RK_DRM_MAX_CRTC]; +}; + +struct rk_drm_framebuffer { + struct drm_framebuffer base; + struct drm_gem_cma_object *obj; +}; + +struct rk_drm_ports { + int phandle; + struct fdt_device_ports *port; + struct drm_device *ddev; + TAILQ_ENTRY(rk_drm_ports) entries; +}; + +struct rk_drm_fbdev { + struct drm_fb_helper helper; +}; + +struct rk_drmfb_attach_args { + struct drm_device *sfa_drm_dev; + struct drm_fb_helper *sfa_fb_helper; + struct drm_fb_helper_surface_size sfa_fb_sizes; + bus_space_tag_t sfa_fb_bst; + bus_dma_tag_t sfa_fb_dmat; + uint32_t sfa_fb_linebytes; +}; + +#define rk_drm_private(ddev) (ddev)->dev_private +#define to_rk_drm_framebuffer(x) container_of(x, struct rk_drm_framebuffer, base) + +int rk_drm_register_port(int, struct fdt_device_ports *); +struct drm_device *rk_drm_port_device(struct fdt_device_ports *); + +#endif /* _ARM_RK_DRM_H */ Index: src/sys/arch/arm/rockchip/rk_dwhdmi.c diff -u /dev/null src/sys/arch/arm/rockchip/rk_dwhdmi.c:1.1 --- /dev/null Sat Nov 9 23:30:14 2019 +++ src/sys/arch/arm/rockchip/rk_dwhdmi.c Sat Nov 9 23:30:14 2019 @@ -0,0 +1,293 @@ +/* $NetBSD: rk_dwhdmi.c,v 1.1 2019/11/09 23:30:14 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca> + * 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 <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: rk_dwhdmi.c,v 1.1 2019/11/09 23:30:14 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/intr.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> + +#include <drm/drmP.h> + +#include <dev/fdt/fdtvar.h> +#include <dev/fdt/fdt_port.h> +#include <dev/fdt/syscon.h> + +#include <dev/ic/dw_hdmi.h> + +#define RK3399_GRF_SOC_CON20 0x6250 +#define HDMI_LCDC_SEL __BIT(6) + +static const struct dwhdmi_mpll_config rk_dwhdmi_mpll_config[] = { + { 40000, 0x00b3, 0x0000, 0x0018 }, + { 65000, 0x0072, 0x0001, 0x0028 }, + { 66000, 0x013e, 0x0003, 0x0038 }, + { 83500, 0x0072, 0x0001, 0x0028 }, + { 146250, 0x0051, 0x0002, 0x0038 }, + { 148500, 0x0051, 0x0003, 0x0000 }, + { 272000, 0x0040, 0x0003, 0x0000 }, + { 340000, 0x0040, 0x0003, 0x0000 }, + { 0, 0x0051, 0x0003, 0x0000 }, +}; + +static const struct dwhdmi_phy_config rk_dwhdmi_phy_config[] = { + { 74250, 0x8009, 0x0004, 0x0272 }, + { 148500, 0x802b, 0x0004, 0x028d }, + { 297000, 0x8039, 0x0005, 0x028d }, + { 584000, 0x8039, 0x0000, 0x019d }, + { 0, 0x0000, 0x0000, 0x0000 } +}; + +enum { + DWHDMI_PORT_INPUT = 0, + DWHDMI_PORT_OUTPUT = 1, +}; + +static const char * const compatible[] = { + "rockchip,rk3399-dw-hdmi", + NULL +}; + +struct rk_dwhdmi_softc { + struct dwhdmi_softc sc_base; + int sc_phandle; + struct clk *sc_clk_vpll; + + struct fdt_device_ports sc_ports; + struct drm_display_mode sc_curmode; + struct syscon *sc_grf; + + bool sc_activated; +}; + +#define to_rk_dwhdmi_softc(x) container_of(x, struct rk_dwhdmi_softc, sc_base) + +static void +rk_dwhdmi_select_input(struct rk_dwhdmi_softc *sc, u_int crtc_index) +{ + const uint32_t write_mask = HDMI_LCDC_SEL << 16; + const uint32_t write_val = crtc_index == 0 ? HDMI_LCDC_SEL : 0; + + syscon_lock(sc->sc_grf); + syscon_write_4(sc->sc_grf, RK3399_GRF_SOC_CON20, write_mask | write_val); + syscon_unlock(sc->sc_grf); +} + +static int +rk_dwhdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate) +{ + struct rk_dwhdmi_softc * const sc = device_private(dev); + struct fdt_endpoint *in_ep = fdt_endpoint_remote(ep); + struct fdt_endpoint *out_ep, *out_rep; + struct drm_encoder *encoder; + struct drm_bridge *bridge; + int error; + + if (!activate) + return EINVAL; + + if (fdt_endpoint_port_index(ep) != DWHDMI_PORT_INPUT) + return EINVAL; + + switch (fdt_endpoint_type(in_ep)) { + case EP_DRM_ENCODER: + encoder = fdt_endpoint_get_data(in_ep); + break; + case EP_DRM_BRIDGE: + bridge = fdt_endpoint_get_data(in_ep); + encoder = bridge->encoder; + break; + default: + encoder = NULL; + break; + } + + if (encoder == NULL) + return EINVAL; + + if (sc->sc_activated == false) { + error = dwhdmi_bind(&sc->sc_base, encoder); + if (error != 0) + return error; + sc->sc_activated = true; + } + + out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, DWHDMI_PORT_OUTPUT, 0); + if (out_ep != NULL) { + /* Ignore downstream connectors, we have our own. */ + out_rep = fdt_endpoint_remote(out_ep); + if (out_rep != NULL && fdt_endpoint_type(out_rep) == EP_DRM_CONNECTOR) + return 0; + + error = fdt_endpoint_activate(out_ep, activate); + if (error != 0) + return error; + } + + return 0; +} + +static void * +rk_dwhdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep) +{ + struct rk_dwhdmi_softc * const sc = device_private(dev); + + return &sc->sc_base.sc_bridge; +} + +static void +rk_dwhdmi_enable(struct dwhdmi_softc *dsc) +{ + struct rk_dwhdmi_softc * const sc = to_rk_dwhdmi_softc(dsc); + + const u_int crtc_index = drm_crtc_index(dsc->sc_bridge.encoder->crtc); + + rk_dwhdmi_select_input(sc, crtc_index); + + dwhdmi_phy_enable(dsc); +} + +static void +rk_dwhdmi_mode_set(struct dwhdmi_softc *dsc, + struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + struct rk_dwhdmi_softc * const sc = to_rk_dwhdmi_softc(dsc); + int error; + + if (sc->sc_clk_vpll != NULL) { + error = clk_set_rate(sc->sc_clk_vpll, adjusted_mode->clock * 1000); + if (error != 0) + device_printf(dsc->sc_dev, "couldn't set pixel clock to %u Hz: %d\n", + adjusted_mode->clock * 1000, error); + } + + dwhdmi_phy_mode_set(dsc, mode, adjusted_mode); +} + +static int +rk_dwhdmi_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compatible(faa->faa_phandle, compatible); +} + +static void +rk_dwhdmi_attach(device_t parent, device_t self, void *aux) +{ + struct rk_dwhdmi_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + bus_addr_t addr; + bus_size_t size; + + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + + /* Required */ + if (fdtbus_clock_enable(phandle, "iahb", true) != 0) { + aprint_error(": couldn't enable iahb clock\n"); + return; + } + + /* Required */ + if (fdtbus_clock_enable(phandle, "isfr", true) != 0) { + aprint_error(": couldn't enable isfr clock\n"); + return; + } + + /* Optional */ + sc->sc_clk_vpll = fdtbus_clock_get(phandle, "vpll"); + if (sc->sc_clk_vpll != NULL && clk_enable(sc->sc_clk_vpll) != 0) { + aprint_error(": couldn't enable vpll clock\n"); + return; + } + + /* Optional */ + if (fdtbus_clock_enable(phandle, "grf", false) != 0) { + aprint_error(": couldn't enable grf clock\n"); + return; + } + + /* Optional */ + if (fdtbus_clock_enable(phandle, "cec", false) != 0) { + aprint_error(": couldn't enable cec clock\n"); + return; + } + + sc->sc_base.sc_dev = self; + if (of_getprop_uint32(phandle, "reg-io-width", &sc->sc_base.sc_reg_width) != 0) + sc->sc_base.sc_reg_width = 4; + sc->sc_base.sc_bst = faa->faa_bst; + if (bus_space_map(sc->sc_base.sc_bst, addr, size, 0, &sc->sc_base.sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + sc->sc_phandle = faa->faa_phandle; + sc->sc_grf = fdtbus_syscon_acquire(phandle, "rockchip,grf"); + if (sc->sc_grf == NULL) { + aprint_error(": couldn't get grf syscon\n"); + return; + } + + aprint_naive("\n"); + aprint_normal(": HDMI TX\n"); + + sc->sc_base.sc_ic = fdtbus_i2c_acquire(phandle, "ddc-i2c-bus"); + if (of_hasprop(phandle, "ddc-i2c-bus") && sc->sc_base.sc_ic == NULL) { + aprint_error_dev(self, "couldn't find external I2C master\n"); + return; + } + + sc->sc_base.sc_flags |= DWHDMI_USE_INTERNAL_PHY; + sc->sc_base.sc_detect = dwhdmi_phy_detect; + sc->sc_base.sc_enable = rk_dwhdmi_enable; + sc->sc_base.sc_disable = dwhdmi_phy_disable; + sc->sc_base.sc_mode_set = rk_dwhdmi_mode_set; + sc->sc_base.sc_mpll_config = rk_dwhdmi_mpll_config; + sc->sc_base.sc_phy_config = rk_dwhdmi_phy_config; + + if (dwhdmi_attach(&sc->sc_base) != 0) { + aprint_error_dev(self, "failed to attach driver\n"); + return; + } + + sc->sc_ports.dp_ep_activate = rk_dwhdmi_ep_activate; + sc->sc_ports.dp_ep_get_data = rk_dwhdmi_ep_get_data; + fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_BRIDGE); +} + +CFATTACH_DECL_NEW(rk_dwhdmi, sizeof(struct rk_dwhdmi_softc), + rk_dwhdmi_match, rk_dwhdmi_attach, NULL, NULL); Index: src/sys/arch/arm/rockchip/rk_fb.c diff -u /dev/null src/sys/arch/arm/rockchip/rk_fb.c:1.1 --- /dev/null Sat Nov 9 23:30:14 2019 +++ src/sys/arch/arm/rockchip/rk_fb.c Sat Nov 9 23:30:14 2019 @@ -0,0 +1,160 @@ +/* $NetBSD: rk_fb.c,v 1.1 2019/11/09 23:30:14 jmcneill Exp $ */ + +/*- + * Copyright (c) 2015-2019 Jared McNeill <jmcne...@invisible.ca> + * 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 "opt_wsdisplay_compat.h" + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: rk_fb.c,v 1.1 2019/11/09 23:30:14 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> + +#include <dev/fdt/fdtvar.h> + +#include <drm/drmP.h> +#include <drm/drmfb.h> + +#include <arm/rockchip/rk_drm.h> + +static int rk_fb_match(device_t, cfdata_t, void *); +static void rk_fb_attach(device_t, device_t, void *); + +static bool rk_fb_shutdown(device_t, int); + +struct rk_fb_softc { + struct drmfb_softc sc_drmfb; + device_t sc_dev; + struct rk_drm_framebuffer *sc_fb; + struct rk_drmfb_attach_args sc_sfa; +}; + +static paddr_t rk_fb_mmapfb(struct drmfb_softc *, off_t, int); +static int rk_fb_ioctl(struct drmfb_softc *, u_long, void *, int, + lwp_t *); + +static const struct drmfb_params rkfb_drmfb_params = { + .dp_mmapfb = rk_fb_mmapfb, + .dp_ioctl = rk_fb_ioctl, + +}; + +CFATTACH_DECL_NEW(rk_fb, sizeof(struct rk_fb_softc), + rk_fb_match, rk_fb_attach, NULL, NULL); + +static int +rk_fb_match(device_t parent, cfdata_t cf, void *aux) +{ + return 1; +} + +static void +rk_fb_attach(device_t parent, device_t self, void *aux) +{ + struct rk_fb_softc * const sc = device_private(self); + struct rk_drmfb_attach_args * const sfa = aux; + int error; + + sc->sc_dev = self; + sc->sc_sfa = *sfa; + sc->sc_fb = to_rk_drm_framebuffer(sfa->sfa_fb_helper->fb); + + aprint_naive("\n"); + aprint_normal("\n"); + +#ifdef WSDISPLAY_MULTICONS + prop_dictionary_t dict = device_properties(self); + const bool is_console = true; + prop_dictionary_set_bool(dict, "is_console", is_console); +#endif + + const struct drmfb_attach_args da = { + .da_dev = self, + .da_fb_helper = sfa->sfa_fb_helper, + .da_fb_sizes = &sfa->sfa_fb_sizes, + .da_fb_vaddr = sc->sc_fb->obj->vaddr, + .da_fb_linebytes = sfa->sfa_fb_linebytes, + .da_params = &rkfb_drmfb_params, + }; + + error = drmfb_attach(&sc->sc_drmfb, &da); + if (error) { + aprint_error_dev(self, "failed to attach drmfb: %d\n", error); + return; + } + + pmf_device_register1(self, NULL, NULL, rk_fb_shutdown); +} + +static bool +rk_fb_shutdown(device_t self, int flags) +{ + struct rk_fb_softc * const sc = device_private(self); + + return drmfb_shutdown(&sc->sc_drmfb, flags); +} + +static paddr_t +rk_fb_mmapfb(struct drmfb_softc *sc, off_t off, int prot) +{ + struct rk_fb_softc * const tfb_sc = (struct rk_fb_softc *)sc; + struct drm_gem_cma_object *obj = tfb_sc->sc_fb->obj; + + KASSERT(off >= 0); + KASSERT(off < obj->dmasize); + + return bus_dmamem_mmap(obj->dmat, obj->dmasegs, 1, off, prot, + BUS_DMA_PREFETCHABLE); +} + +static int +rk_fb_ioctl(struct drmfb_softc *sc, u_long cmd, void *data, int flag, + lwp_t *l) +{ + struct wsdisplayio_bus_id *busid; + struct wsdisplayio_fbinfo *fbi; + struct rasops_info *ri = &sc->sc_genfb.vd.active->scr_ri; + int error; + + switch (cmd) { + case WSDISPLAYIO_GET_BUSID: + busid = data; + busid->bus_type = WSDISPLAYIO_BUS_SOC; + return 0; + case WSDISPLAYIO_GTYPE: + *(u_int *)data = WSDISPLAY_TYPE_GENFB; + return 0; + case WSDISPLAYIO_GET_FBINFO: + fbi = data; + error = wsdisplayio_get_fbinfo(ri, fbi); + fbi->fbi_flags |= WSFB_VRAM_IS_RAM; + return error; + default: + return EPASSTHROUGH; + } +} Index: src/sys/arch/arm/rockchip/rk_vop.c diff -u /dev/null src/sys/arch/arm/rockchip/rk_vop.c:1.1 --- /dev/null Sat Nov 9 23:30:14 2019 +++ src/sys/arch/arm/rockchip/rk_vop.c Sat Nov 9 23:30:14 2019 @@ -0,0 +1,659 @@ +/* $NetBSD: rk_vop.c,v 1.1 2019/11/09 23:30:14 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca> + * 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 <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: rk_vop.c,v 1.1 2019/11/09 23:30:14 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/intr.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> +#include <sys/sysctl.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> + +#include <dev/fdt/fdtvar.h> +#include <dev/fdt/fdt_port.h> + +#include <arm/rockchip/rk_drm.h> + +#define VOP_REG_CFG_DONE 0x0000 +#define REG_LOAD_EN __BIT(0) +#define VOP_SYS_CTRL 0x0008 +#define VOP_STANDBY_EN __BIT(22) +#define MIPI_OUT_EN __BIT(15) +#define EDP_OUT_EN __BIT(14) +#define HDMI_OUT_EN __BIT(13) +#define RGB_OUT_EN __BIT(12) +#define VOP_DSP_CTRL0 0x0010 +#define DSP_OUT_MODE __BITS(3,0) +#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 __BITS(7,5) +#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 __BITS(3,1) +#define WIN0_DATA_FMT_ARGB888 0 +#define WIN0_EN __BIT(0) +#define VOP_WIN0_COLOR_KEY 0x0038 +#define VOP_WIN0_VIR 0x003c +#define WIN0_VIR_STRIDE __BITS(13,0) +#define VOP_WIN0_YRGB_MST 0x0040 +#define VOP_WIN0_ACT_INFO 0x0048 +#define WIN0_ACT_HEIGHT __BITS(28,16) +#define WIN0_ACT_WIDTH __BITS(12,0) +#define VOP_WIN0_DSP_INFO 0x004c +#define WIN0_DSP_HEIGHT __BITS(27,16) +#define WIN0_DSP_WIDTH __BITS(11,0) +#define VOP_WIN0_DSP_ST 0x0050 +#define WIN0_DSP_YST __BITS(28,16) +#define WIN0_DSP_XST __BITS(12,0) +#define VOP_POST_DSP_HACT_INFO 0x0170 +#define DSP_HACT_ST_POST __BITS(28,16) +#define DSP_HACT_END_POST __BITS(12,0) +#define VOP_POST_DSP_VACT_INFO 0x0174 +#define DSP_VACT_ST_POST __BITS(28,16) +#define DSP_VACT_END_POST __BITS(12,0) +#define VOP_DSP_HTOTAL_HS_END 0x0188 +#define DSP_HTOTAL __BITS(28,16) +#define DSP_HS_END __BITS(12,0) +#define VOP_DSP_HACT_ST_END 0x018c +#define DSP_HACT_ST __BITS(28,16) +#define DSP_HACT_END __BITS(12,0) +#define VOP_DSP_VTOTAL_VS_END 0x0190 +#define DSP_VTOTAL __BITS(28,16) +#define DSP_VS_END __BITS(12,0) +#define VOP_DSP_VACT_ST_END 0x0194 +#define DSP_VACT_ST __BITS(28,16) +#define DSP_VACT_END __BITS(12,0) + +/* + * Polarity fields are in different locations depending on SoC and output type, + * but always in the same order. + */ +#define DSP_DCLK_POL __BIT(3) +#define DSP_DEN_POL __BIT(2) +#define DSP_VSYNC_POL __BIT(1) +#define DSP_HSYNC_POL __BIT(0) + +enum vop_ep_type { + VOP_EP_MIPI, + VOP_EP_EDP, + VOP_EP_HDMI, + VOP_EP_MIPI1, + VOP_EP_DP, + VOP_NEP +}; + +struct rk_vop_softc; +struct rk_vop_config; + +struct rk_vop_crtc { + struct drm_crtc base; + struct rk_vop_softc *sc; +}; + +struct rk_vop_encoder { + struct drm_encoder base; + struct rk_vop_softc *sc; + enum vop_ep_type ep_type; +}; + +struct rk_vop_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + int sc_phandle; + + struct clk *sc_dclk; + + struct rk_vop_crtc sc_crtc; + struct rk_vop_encoder sc_encoder[VOP_NEP]; + + struct fdt_device_ports sc_ports; + + struct rk_vop_config *sc_conf; +}; + +#define to_rk_vop_crtc(x) container_of(x, struct rk_vop_crtc, base) +#define to_rk_vop_encoder(x) container_of(x, struct rk_vop_encoder, base) + +#define RD4(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) +#define WR4(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) + +struct rk_vop_config { + const char *descr; + u_int out_mode; + void (*init)(struct rk_vop_softc *); + void (*set_polarity)(struct rk_vop_softc *, + enum vop_ep_type, uint32_t); +}; + +#define RK3399_VOP_MIPI_POL __BITS(31,28) +#define RK3399_VOP_EDP_POL __BITS(27,24) +#define RK3399_VOP_HDMI_POL __BITS(23,20) +#define RK3399_VOP_DP_POL __BITS(19,16) + +#define RK3399_VOP_SYS_CTRL_ENABLE __BIT(11) + +static void +rk3399_vop_set_polarity(struct rk_vop_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: + mask = RK3399_VOP_MIPI_POL; + break; + case VOP_EP_EDP: + mask = RK3399_VOP_EDP_POL; + break; + case VOP_EP_HDMI: + mask = RK3399_VOP_HDMI_POL; + break; + case VOP_EP_DP: + mask = RK3399_VOP_DP_POL; + break; + default: + return; + } + + val = RD4(sc, VOP_DSP_CTRL1); + val &= ~mask; + val |= __SHIFTIN(pol, mask); + WR4(sc, VOP_DSP_CTRL1, val); +} + +static void +rk3399_vop_init(struct rk_vop_softc *sc) +{ + uint32_t val; + + val = RD4(sc, VOP_SYS_CTRL); + val |= RK3399_VOP_SYS_CTRL_ENABLE; + WR4(sc, VOP_SYS_CTRL, val); +} + +static const struct rk_vop_config rk3399_vop_lit_config = { + .descr = "RK3399 VOPL", + .out_mode = DSP_OUT_MODE_RGB888, + .init = rk3399_vop_init, + .set_polarity = rk3399_vop_set_polarity, +}; + +static const struct rk_vop_config rk3399_vop_big_config = { + .descr = "RK3399 VOPB", + .out_mode = DSP_OUT_MODE_RGBaaa, + .init = rk3399_vop_init, + .set_polarity = rk3399_vop_set_polarity, +}; + +static const struct of_compat_data compat_data[] = { + { "rockchip,rk3399-vop-big", (uintptr_t)&rk3399_vop_big_config }, + { "rockchip,rk3399-vop-lit", (uintptr_t)&rk3399_vop_lit_config }, + { NULL } +}; + +static int +rk_vop_mode_do_set_base(struct drm_crtc *crtc, struct drm_framebuffer *fb, + int x, int y, int atomic) +{ + struct rk_vop_crtc *mixer_crtc = to_rk_vop_crtc(crtc); + struct rk_vop_softc * const sc = mixer_crtc->sc; + struct rk_drm_framebuffer *sfb = atomic? + to_rk_drm_framebuffer(fb) : + to_rk_drm_framebuffer(crtc->primary->fb); + + uint64_t paddr = (uint64_t)sfb->obj->dmamap->dm_segs[0].ds_addr; + + KASSERT((paddr & ~0xffffffff) == 0); + + /* Framebuffer start address */ + WR4(sc, VOP_WIN0_YRGB_MST, (uint32_t)paddr); + + return 0; +} + +static void +rk_vop_destroy(struct drm_crtc *crtc) +{ + drm_crtc_cleanup(crtc); +} + +static const struct drm_crtc_funcs rk_vop_crtc_funcs = { + .set_config = drm_crtc_helper_set_config, + .destroy = rk_vop_destroy, +}; + +static void +rk_vop_dpms(struct drm_crtc *crtc, int mode) +{ +} + +static bool +rk_vop_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int +rk_vop_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct rk_vop_crtc *mixer_crtc = to_rk_vop_crtc(crtc); + struct rk_vop_softc * const sc = mixer_crtc->sc; + uint32_t val; + u_int lb_mode; + int error; + + const u_int hactive = adjusted_mode->hdisplay; + const u_int hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start; + const u_int hback_porch = adjusted_mode->htotal - adjusted_mode->hsync_end; + + const u_int vactive = adjusted_mode->vdisplay; + const u_int vsync_len = adjusted_mode->vsync_end - adjusted_mode->vsync_start; + const u_int vback_porch = adjusted_mode->vtotal - adjusted_mode->vsync_end; + + error = clk_set_rate(sc->sc_dclk, adjusted_mode->clock * 1000); + if (error != 0) + DRM_ERROR("couldn't set pixel clock: %d\n", error); + + val = __SHIFTIN(hactive - 1, WIN0_ACT_WIDTH) | + __SHIFTIN(vactive - 1, WIN0_ACT_HEIGHT); + WR4(sc, VOP_WIN0_ACT_INFO, val); + + val = __SHIFTIN(hactive - 1, WIN0_DSP_WIDTH) | + __SHIFTIN(vactive - 1, WIN0_DSP_HEIGHT); + WR4(sc, VOP_WIN0_DSP_INFO, val); + + val = __SHIFTIN(hsync_len + hback_porch, WIN0_DSP_YST) | + __SHIFTIN(vsync_len + vback_porch, WIN0_DSP_XST); + WR4(sc, VOP_WIN0_DSP_ST, val); + + WR4(sc, VOP_WIN0_COLOR_KEY, 0); + + val = __SHIFTIN(hactive, WIN0_VIR_STRIDE); + WR4(sc, VOP_WIN0_VIR, val); + + if (adjusted_mode->hdisplay > 2560) + lb_mode = WIN0_LB_MODE_RGB_3840X2; + else if (adjusted_mode->hdisplay > 1920) + lb_mode = WIN0_LB_MODE_RGB_2560X4; + else if (adjusted_mode->hdisplay > 1280) + lb_mode = WIN0_LB_MODE_RGB_1920X5; + else + lb_mode = WIN0_LB_MODE_RGB_1280X8; + + val = __SHIFTIN(lb_mode, WIN0_LB_MODE) | + __SHIFTIN(WIN0_DATA_FMT_ARGB888, WIN0_DATA_FMT) | + WIN0_EN; + WR4(sc, VOP_WIN0_CTRL, val); + + rk_vop_mode_do_set_base(crtc, old_fb, x, y, 0); + + return 0; +} + +static int +rk_vop_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct rk_vop_crtc *mixer_crtc = to_rk_vop_crtc(crtc); + struct rk_vop_softc * const sc = mixer_crtc->sc; + + rk_vop_mode_do_set_base(crtc, old_fb, x, y, 0); + + /* Commit settings */ + WR4(sc, VOP_REG_CFG_DONE, REG_LOAD_EN); + + return 0; +} + +static int +rk_vop_mode_set_base_atomic(struct drm_crtc *crtc, struct drm_framebuffer *fb, + int x, int y, enum mode_set_atomic state) +{ + struct rk_vop_crtc *mixer_crtc = to_rk_vop_crtc(crtc); + struct rk_vop_softc * const sc = mixer_crtc->sc; + + rk_vop_mode_do_set_base(crtc, fb, x, y, 1); + + /* Commit settings */ + WR4(sc, VOP_REG_CFG_DONE, REG_LOAD_EN); + + return 0; +} + +static void +rk_vop_disable(struct drm_crtc *crtc) +{ +} + +static void +rk_vop_prepare(struct drm_crtc *crtc) +{ +} + +static void +rk_vop_commit(struct drm_crtc *crtc) +{ + struct rk_vop_crtc *mixer_crtc = to_rk_vop_crtc(crtc); + struct rk_vop_softc * const sc = mixer_crtc->sc; + + /* Commit settings */ + WR4(sc, VOP_REG_CFG_DONE, REG_LOAD_EN); +} + +static const struct drm_crtc_helper_funcs rk_vop_crtc_helper_funcs = { + .dpms = rk_vop_dpms, + .mode_fixup = rk_vop_mode_fixup, + .mode_set = rk_vop_mode_set, + .mode_set_base = rk_vop_mode_set_base, + .mode_set_base_atomic = rk_vop_mode_set_base_atomic, + .disable = rk_vop_disable, + .prepare = rk_vop_prepare, + .commit = rk_vop_commit, +}; + +static void +rk_vop_encoder_destroy(struct drm_encoder *encoder) +{ +} + +static const struct drm_encoder_funcs rk_vop_encoder_funcs = { + .destroy = rk_vop_encoder_destroy, +}; + +static void +rk_vop_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool +rk_vop_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void +rk_vop_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + struct rk_vop_encoder *rkencoder = to_rk_vop_encoder(encoder); + struct rk_vop_softc * const sc = rkencoder->sc; + uint32_t val; + u_int pol; + + const u_int hactive = adjusted_mode->hdisplay; + const u_int hfront_porch = adjusted_mode->hsync_start - adjusted_mode->hdisplay; + const u_int hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start; + const u_int hback_porch = adjusted_mode->htotal - adjusted_mode->hsync_end; + + const u_int vactive = adjusted_mode->vdisplay; + const u_int vfront_porch = adjusted_mode->vsync_start - adjusted_mode->vdisplay; + const u_int vsync_len = adjusted_mode->vsync_end - adjusted_mode->vsync_start; + const u_int vback_porch = adjusted_mode->vtotal - adjusted_mode->vsync_end; + + 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; + sc->sc_conf->set_polarity(sc, rkencoder->ep_type, pol); + + val = RD4(sc, VOP_SYS_CTRL); + val &= ~VOP_STANDBY_EN; + val &= ~(MIPI_OUT_EN|EDP_OUT_EN|HDMI_OUT_EN|RGB_OUT_EN); + switch (rkencoder->ep_type) { + case VOP_EP_MIPI: + case VOP_EP_MIPI1: + val |= MIPI_OUT_EN; + break; + case VOP_EP_EDP: + case VOP_EP_DP: + val |= EDP_OUT_EN; + break; + case VOP_EP_HDMI: + val |= HDMI_OUT_EN; + break; + default: + break; + } + WR4(sc, VOP_SYS_CTRL, val); + + val = RD4(sc, VOP_DSP_CTRL0); + val &= ~DSP_OUT_MODE; + val |= __SHIFTIN(sc->sc_conf->out_mode, DSP_OUT_MODE); + WR4(sc, VOP_DSP_CTRL0, val); + + val = __SHIFTIN(hsync_len + hback_porch, DSP_HACT_ST_POST) | + __SHIFTIN(hsync_len + hback_porch + hactive, DSP_HACT_END_POST); + WR4(sc, VOP_POST_DSP_HACT_INFO, val); + + val = __SHIFTIN(hsync_len + hback_porch, DSP_HACT_ST) | + __SHIFTIN(hsync_len + hback_porch + hactive, DSP_HACT_END); + WR4(sc, VOP_DSP_HACT_ST_END, val); + + val = __SHIFTIN(hsync_len, DSP_HTOTAL) | + __SHIFTIN(hsync_len + hback_porch + hactive + hfront_porch, DSP_HS_END); + WR4(sc, VOP_DSP_HTOTAL_HS_END, val); + + val = __SHIFTIN(vsync_len + vback_porch, DSP_VACT_ST_POST) | + __SHIFTIN(vsync_len + vback_porch + vactive, DSP_VACT_END_POST); + WR4(sc, VOP_POST_DSP_VACT_INFO, val); + + val = __SHIFTIN(vsync_len + vback_porch, DSP_VACT_ST) | + __SHIFTIN(vsync_len + vback_porch + vactive, DSP_VACT_END); + WR4(sc, VOP_DSP_VACT_ST_END, val); + + val = __SHIFTIN(vsync_len, DSP_VTOTAL) | + __SHIFTIN(vsync_len + vback_porch + vactive + vfront_porch, DSP_VS_END); + WR4(sc, VOP_DSP_VTOTAL_VS_END, val); +} + +static void +rk_vop_encoder_prepare(struct drm_encoder *encoder) +{ +} + +static void +rk_vop_encoder_commit(struct drm_encoder *encoder) +{ + struct rk_vop_encoder *rkencoder = to_rk_vop_encoder(encoder); + struct rk_vop_softc * const sc = rkencoder->sc; + + /* Commit settings */ + WR4(sc, VOP_REG_CFG_DONE, REG_LOAD_EN); +} + +static const struct drm_encoder_helper_funcs rk_vop_encoder_helper_funcs = { + .dpms = rk_vop_encoder_dpms, + .mode_fixup = rk_vop_encoder_mode_fixup, + .prepare = rk_vop_encoder_prepare, + .commit = rk_vop_encoder_commit, + .mode_set = rk_vop_encoder_mode_set, +}; + +static int +rk_vop_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate) +{ + struct rk_vop_softc * const sc = device_private(dev); + struct drm_device *ddev; + u_int encoder_type; + + if (!activate) + return EINVAL; + + ddev = rk_drm_port_device(&sc->sc_ports); + if (ddev == NULL) { + DRM_ERROR("couldn't find DRM device\n"); + return ENXIO; + } + + if (sc->sc_crtc.sc == NULL) { + sc->sc_crtc.sc = sc; + + drm_crtc_init(ddev, &sc->sc_crtc.base, &rk_vop_crtc_funcs); + drm_crtc_helper_add(&sc->sc_crtc.base, &rk_vop_crtc_helper_funcs); + + aprint_debug_dev(dev, "using CRTC %d for %s\n", + drm_crtc_index(&sc->sc_crtc.base), sc->sc_conf->descr); + } + + const u_int ep_index = fdt_endpoint_index(ep); + if (ep_index >= VOP_NEP) { + DRM_ERROR("endpoint index %d out of range\n", ep_index); + return ENXIO; + } + + switch (ep_index) { + case VOP_EP_MIPI: + case VOP_EP_MIPI1: + encoder_type = DRM_MODE_ENCODER_DSI; + break; + case VOP_EP_HDMI: + case VOP_EP_EDP: + case VOP_EP_DP: + encoder_type = DRM_MODE_ENCODER_TMDS; + break; + } + + sc->sc_encoder[ep_index].sc = sc; + sc->sc_encoder[ep_index].ep_type = ep_index; + sc->sc_encoder[ep_index].base.possible_crtcs = 1 << drm_crtc_index(&sc->sc_crtc.base); + drm_encoder_init(ddev, &sc->sc_encoder[ep_index].base, &rk_vop_encoder_funcs, + encoder_type); + drm_encoder_helper_add(&sc->sc_encoder[ep_index].base, &rk_vop_encoder_helper_funcs); + + return fdt_endpoint_activate(ep, activate); +} + +static void * +rk_vop_ep_get_data(device_t dev, struct fdt_endpoint *ep) +{ + struct rk_vop_softc * const sc = device_private(dev); + const u_int ep_index = fdt_endpoint_index(ep); + + if (ep_index >= VOP_NEP) + return NULL; + + if (sc->sc_encoder[ep_index].sc == NULL) + return NULL; + + return &sc->sc_encoder[ep_index].base; +} + +static int +rk_vop_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compat_data(faa->faa_phandle, compat_data); +} + +static void +rk_vop_attach(device_t parent, device_t self, void *aux) +{ + struct rk_vop_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + const char * const reset_names[] = { "axi", "ahb", "dclk" }; + const char * const clock_names[] = { "aclk_vop", "hclk_vop" }; + struct fdtbus_reset *rst; + bus_addr_t addr; + bus_size_t size; + u_int n; + + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + + fdtbus_clock_assign(phandle); + + for (n = 0; n < __arraycount(reset_names); n++) { + rst = fdtbus_reset_get(phandle, reset_names[n]); + if (rst == NULL || fdtbus_reset_deassert(rst) != 0) { + aprint_error(": couldn't de-assert reset %s\n", reset_names[n]); + return; + } + } + for (n = 0; n < __arraycount(clock_names); n++) { + if (fdtbus_clock_enable(phandle, clock_names[n], true) != 0) { + aprint_error(": couldn't enable clock %s\n", clock_names[n]); + return; + } + } + sc->sc_dclk = fdtbus_clock_get(phandle, "dclk_vop"); + if (sc->sc_dclk == NULL || clk_enable(sc->sc_dclk) != 0) { + aprint_error(": couldn't enable clock %s\n", "dclk_vop"); + return; + } + + sc->sc_dev = self; + sc->sc_bst = faa->faa_bst; + if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + sc->sc_phandle = faa->faa_phandle; + sc->sc_conf = (void *)of_search_compatible(phandle, compat_data)->data; + + aprint_naive("\n"); + aprint_normal(": %s\n", sc->sc_conf->descr); + + if (sc->sc_conf->init != NULL) + sc->sc_conf->init(sc); + + sc->sc_ports.dp_ep_activate = rk_vop_ep_activate; + sc->sc_ports.dp_ep_get_data = rk_vop_ep_get_data; + fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_ENCODER); + + const int port_phandle = of_find_firstchild_byname(phandle, "port"); + if (port_phandle > 0) + rk_drm_register_port(port_phandle, &sc->sc_ports); +} + +CFATTACH_DECL_NEW(rk_vop, sizeof(struct rk_vop_softc), + rk_vop_match, rk_vop_attach, NULL, NULL);