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 <[email protected]>
+ * 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 <[email protected]>
+ * 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 <[email protected]>
+ * 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 <[email protected]>
+ * 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 <[email protected]>
+ * 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);