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);

Reply via email to