aboutsummaryrefslogtreecommitdiff
path: root/drivers/gpu/drm
diff options
context:
space:
mode:
authorLaurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>2013-06-19 13:54:11 +0200
committerDave Airlie <airlied@redhat.com>2013-06-27 10:08:04 +1000
commit4bf8e1962f91eed5dbee168d2348983dda0a518f (patch)
tree0abd2388e0dee111b0823a73bfca29a234053e83 /drivers/gpu/drm
parent0ab3691fa8ac15ed94f9e2fa7c2958ab41d2e3f5 (diff)
drm: Renesas R-Car Display Unit DRM driver
The R-Car Display Unit (DU) DRM driver supports both superposition processors and all eight planes in RGB and YUV formats with alpha blending. Only VGA and LVDS encoders and connectors are currently supported. Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> Signed-off-by: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'drivers/gpu/drm')
-rw-r--r--drivers/gpu/drm/Kconfig2
-rw-r--r--drivers/gpu/drm/Makefile1
-rw-r--r--drivers/gpu/drm/rcar-du/Kconfig9
-rw-r--r--drivers/gpu/drm/rcar-du/Makefile8
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_crtc.c595
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_crtc.h50
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_drv.c325
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_drv.h66
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_kms.c245
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_kms.h59
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_lvds.c216
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_lvds.h24
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_plane.c507
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_plane.h67
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_regs.h445
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_vga.c149
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_vga.h24
17 files changed, 2792 insertions, 0 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index b16c50ee769..71ca63b79a4 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -213,6 +213,8 @@ source "drivers/gpu/drm/mgag200/Kconfig"
source "drivers/gpu/drm/cirrus/Kconfig"
+source "drivers/gpu/drm/rcar-du/Kconfig"
+
source "drivers/gpu/drm/shmobile/Kconfig"
source "drivers/gpu/drm/omapdrm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 1ecbe5b7312..801bcafa302 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_DRM_EXYNOS) +=exynos/
obj-$(CONFIG_DRM_GMA500) += gma500/
obj-$(CONFIG_DRM_UDL) += udl/
obj-$(CONFIG_DRM_AST) += ast/
+obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
obj-$(CONFIG_DRM_OMAP) += omapdrm/
obj-$(CONFIG_DRM_TILCDC) += tilcdc/
diff --git a/drivers/gpu/drm/rcar-du/Kconfig b/drivers/gpu/drm/rcar-du/Kconfig
new file mode 100644
index 00000000000..72887df8dd7
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/Kconfig
@@ -0,0 +1,9 @@
+config DRM_RCAR_DU
+ tristate "DRM Support for R-Car Display Unit"
+ depends on DRM && ARM
+ select DRM_KMS_HELPER
+ select DRM_KMS_CMA_HELPER
+ select DRM_GEM_CMA_HELPER
+ help
+ Choose this option if you have an R-Car chipset.
+ If M is selected the module will be called rcar-du-drm.
diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile
new file mode 100644
index 00000000000..7333c009401
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/Makefile
@@ -0,0 +1,8 @@
+rcar-du-drm-y := rcar_du_crtc.o \
+ rcar_du_drv.o \
+ rcar_du_kms.o \
+ rcar_du_lvds.o \
+ rcar_du_plane.o \
+ rcar_du_vga.o
+
+obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
new file mode 100644
index 00000000000..24183fb9359
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
@@ -0,0 +1,595 @@
+/*
+ * rcar_du_crtc.c -- R-Car Display Unit CRTCs
+ *
+ * Copyright (C) 2013 Renesas Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/mutex.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "rcar_du_crtc.h"
+#include "rcar_du_drv.h"
+#include "rcar_du_kms.h"
+#include "rcar_du_lvds.h"
+#include "rcar_du_plane.h"
+#include "rcar_du_regs.h"
+#include "rcar_du_vga.h"
+
+#define to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, crtc)
+
+static u32 rcar_du_crtc_read(struct rcar_du_crtc *rcrtc, u32 reg)
+{
+ struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
+
+ return rcar_du_read(rcdu, rcrtc->mmio_offset + reg);
+}
+
+static void rcar_du_crtc_write(struct rcar_du_crtc *rcrtc, u32 reg, u32 data)
+{
+ struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
+
+ rcar_du_write(rcdu, rcrtc->mmio_offset + reg, data);
+}
+
+static void rcar_du_crtc_clr(struct rcar_du_crtc *rcrtc, u32 reg, u32 clr)
+{
+ struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
+
+ rcar_du_write(rcdu, rcrtc->mmio_offset + reg,
+ rcar_du_read(rcdu, rcrtc->mmio_offset + reg) & ~clr);
+}
+
+static void rcar_du_crtc_set(struct rcar_du_crtc *rcrtc, u32 reg, u32 set)
+{
+ struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
+
+ rcar_du_write(rcdu, rcrtc->mmio_offset + reg,
+ rcar_du_read(rcdu, rcrtc->mmio_offset + reg) | set);
+}
+
+static void rcar_du_crtc_clr_set(struct rcar_du_crtc *rcrtc, u32 reg,
+ u32 clr, u32 set)
+{
+ struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
+ u32 value = rcar_du_read(rcdu, rcrtc->mmio_offset + reg);
+
+ rcar_du_write(rcdu, rcrtc->mmio_offset + reg, (value & ~clr) | set);
+}
+
+static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
+{
+ struct drm_crtc *crtc = &rcrtc->crtc;
+ struct rcar_du_device *rcdu = crtc->dev->dev_private;
+ const struct drm_display_mode *mode = &crtc->mode;
+ unsigned long clk;
+ u32 value;
+ u32 div;
+
+ /* Dot clock */
+ clk = clk_get_rate(rcdu->clock);
+ div = DIV_ROUND_CLOSEST(clk, mode->clock * 1000);
+ div = clamp(div, 1U, 64U) - 1;
+
+ rcar_du_write(rcdu, rcrtc->index ? ESCR2 : ESCR,
+ ESCR_DCLKSEL_CLKS | div);
+ rcar_du_write(rcdu, rcrtc->index ? OTAR2 : OTAR, 0);
+
+ /* Signal polarities */
+ value = ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : DSMR_VSL)
+ | ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : DSMR_HSL)
+ | DSMR_DIPM_DE;
+ rcar_du_crtc_write(rcrtc, DSMR, value);
+
+ /* Display timings */
+ rcar_du_crtc_write(rcrtc, HDSR, mode->htotal - mode->hsync_start - 19);
+ rcar_du_crtc_write(rcrtc, HDER, mode->htotal - mode->hsync_start +
+ mode->hdisplay - 19);
+ rcar_du_crtc_write(rcrtc, HSWR, mode->hsync_end -
+ mode->hsync_start - 1);
+ rcar_du_crtc_write(rcrtc, HCR, mode->htotal - 1);
+
+ rcar_du_crtc_write(rcrtc, VDSR, mode->vtotal - mode->vsync_end - 2);
+ rcar_du_crtc_write(rcrtc, VDER, mode->vtotal - mode->vsync_end +
+ mode->vdisplay - 2);
+ rcar_du_crtc_write(rcrtc, VSPR, mode->vtotal - mode->vsync_end +
+ mode->vsync_start - 1);
+ rcar_du_crtc_write(rcrtc, VCR, mode->vtotal - 1);
+
+ rcar_du_crtc_write(rcrtc, DESR, mode->htotal - mode->hsync_start);
+ rcar_du_crtc_write(rcrtc, DEWR, mode->hdisplay);
+}
+
+static void rcar_du_crtc_set_routing(struct rcar_du_crtc *rcrtc)
+{
+ struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
+ u32 dorcr = rcar_du_read(rcdu, DORCR);
+
+ dorcr &= ~(DORCR_PG2T | DORCR_DK2S | DORCR_PG2D_MASK);
+
+ /* Set the DU1 pins sources. Select CRTC 0 if explicitly requested and
+ * CRTC 1 in all other cases to avoid cloning CRTC 0 to DU0 and DU1 by
+ * default.
+ */
+ if (rcrtc->outputs & (1 << 1) && rcrtc->index == 0)
+ dorcr |= DORCR_PG2D_DS1;
+ else
+ dorcr |= DORCR_PG2T | DORCR_DK2S | DORCR_PG2D_DS2;
+
+ rcar_du_write(rcdu, DORCR, dorcr);
+}
+
+static void __rcar_du_start_stop(struct rcar_du_device *rcdu, bool start)
+{
+ rcar_du_write(rcdu, DSYSR,
+ (rcar_du_read(rcdu, DSYSR) & ~(DSYSR_DRES | DSYSR_DEN)) |
+ (start ? DSYSR_DEN : DSYSR_DRES));
+}
+
+static void rcar_du_start_stop(struct rcar_du_device *rcdu, bool start)
+{
+ /* Many of the configuration bits are only updated when the display
+ * reset (DRES) bit in DSYSR is set to 1, disabling *both* CRTCs. Some
+ * of those bits could be pre-configured, but others (especially the
+ * bits related to plane assignment to display timing controllers) need
+ * to be modified at runtime.
+ *
+ * Restart the display controller if a start is requested. Sorry for the
+ * flicker. It should be possible to move most of the "DRES-update" bits
+ * setup to driver initialization time and minimize the number of cases
+ * when the display controller will have to be restarted.
+ */
+ if (start) {
+ if (rcdu->used_crtcs++ != 0)
+ __rcar_du_start_stop(rcdu, false);
+ __rcar_du_start_stop(rcdu, true);
+ } else {
+ if (--rcdu->used_crtcs == 0)
+ __rcar_du_start_stop(rcdu, false);
+ }
+}
+
+void rcar_du_crtc_route_output(struct drm_crtc *crtc, unsigned int output)
+{
+ struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+
+ /* Store the route from the CRTC output to the DU output. The DU will be
+ * configured when starting the CRTC.
+ */
+ rcrtc->outputs |= 1 << output;
+}
+
+void rcar_du_crtc_update_planes(struct drm_crtc *crtc)
+{
+ struct rcar_du_device *rcdu = crtc->dev->dev_private;
+ struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+ struct rcar_du_plane *planes[RCAR_DU_NUM_HW_PLANES];
+ unsigned int num_planes = 0;
+ unsigned int prio = 0;
+ unsigned int i;
+ u32 dptsr = 0;
+ u32 dspr = 0;
+
+ for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) {
+ struct rcar_du_plane *plane = &rcdu->planes.planes[i];
+ unsigned int j;
+
+ if (plane->crtc != &rcrtc->crtc || !plane->enabled)
+ continue;
+
+ /* Insert the plane in the sorted planes array. */
+ for (j = num_planes++; j > 0; --j) {
+ if (planes[j-1]->zpos <= plane->zpos)
+ break;
+ planes[j] = planes[j-1];
+ }
+
+ planes[j] = plane;
+ prio += plane->format->planes * 4;
+ }
+
+ for (i = 0; i < num_planes; ++i) {
+ struct rcar_du_plane *plane = planes[i];
+ unsigned int index = plane->hwindex;
+
+ prio -= 4;
+ dspr |= (index + 1) << prio;
+ dptsr |= DPTSR_PnDK(index) | DPTSR_PnTS(index);
+
+ if (plane->format->planes == 2) {
+ index = (index + 1) % 8;
+
+ prio -= 4;
+ dspr |= (index + 1) << prio;
+ dptsr |= DPTSR_PnDK(index) | DPTSR_PnTS(index);
+ }
+ }
+
+ /* Select display timing and dot clock generator 2 for planes associated
+ * with superposition controller 2.
+ */
+ if (rcrtc->index) {
+ u32 value = rcar_du_read(rcdu, DPTSR);
+
+ /* The DPTSR register is updated when the display controller is
+ * stopped. We thus need to restart the DU. Once again, sorry
+ * for the flicker. One way to mitigate the issue would be to
+ * pre-associate planes with CRTCs (either with a fixed 4/4
+ * split, or through a module parameter). Flicker would then
+ * occur only if we need to break the pre-association.
+ */
+ if (value != dptsr) {
+ rcar_du_write(rcdu, DPTSR, dptsr);
+ if (rcdu->used_crtcs) {
+ __rcar_du_start_stop(rcdu, false);
+ __rcar_du_start_stop(rcdu, true);
+ }
+ }
+ }
+
+ rcar_du_write(rcdu, rcrtc->index ? DS2PR : DS1PR, dspr);
+}
+
+static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
+{
+ struct drm_crtc *crtc = &rcrtc->crtc;
+ struct rcar_du_device *rcdu = crtc->dev->dev_private;
+ unsigned int i;
+
+ if (rcrtc->started)
+ return;
+
+ if (WARN_ON(rcrtc->plane->format == NULL))
+ return;
+
+ /* Set display off and background to black */
+ rcar_du_crtc_write(rcrtc, DOOR, DOOR_RGB(0, 0, 0));
+ rcar_du_crtc_write(rcrtc, BPOR, BPOR_RGB(0, 0, 0));
+
+ /* Configure display timings and output routing */
+ rcar_du_crtc_set_display_timing(rcrtc);
+ rcar_du_crtc_set_routing(rcrtc);
+
+ mutex_lock(&rcdu->planes.lock);
+ rcrtc->plane->enabled = true;
+ rcar_du_crtc_update_planes(crtc);
+ mutex_unlock(&rcdu->planes.lock);
+
+ /* Setup planes. */
+ for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) {
+ struct rcar_du_plane *plane = &rcdu->planes.planes[i];
+
+ if (plane->crtc != crtc || !plane->enabled)
+ continue;
+
+ rcar_du_plane_setup(plane);
+ }
+
+ /* Select master sync mode. This enables display operation in master
+ * sync mode (with the HSYNC and VSYNC signals configured as outputs and
+ * actively driven).
+ */
+ rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_MASTER);
+
+ rcar_du_start_stop(rcdu, true);
+
+ rcrtc->started = true;
+}
+
+static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
+{
+ struct drm_crtc *crtc = &rcrtc->crtc;
+ struct rcar_du_device *rcdu = crtc->dev->dev_private;
+
+ if (!rcrtc->started)
+ return;
+
+ mutex_lock(&rcdu->planes.lock);
+ rcrtc->plane->enabled = false;
+ rcar_du_crtc_update_planes(crtc);
+ mutex_unlock(&rcdu->planes.lock);
+
+ /* Select switch sync mode. This stops display operation and configures
+ * the HSYNC and VSYNC signals as inputs.
+ */
+ rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_SWITCH);
+
+ rcar_du_start_stop(rcdu, false);
+
+ rcrtc->started = false;
+}
+
+void rcar_du_crtc_suspend(struct rcar_du_crtc *rcrtc)
+{
+ struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
+
+ rcar_du_crtc_stop(rcrtc);
+ rcar_du_put(rcdu);
+}
+
+void rcar_du_crtc_resume(struct rcar_du_crtc *rcrtc)
+{
+ struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
+
+ if (rcrtc->dpms != DRM_MODE_DPMS_ON)
+ return;
+
+ rcar_du_get(rcdu);
+ rcar_du_crtc_start(rcrtc);
+}
+
+static void rcar_du_crtc_update_base(struct rcar_du_crtc *rcrtc)
+{
+ struct drm_crtc *crtc = &rcrtc->crtc;
+
+ rcar_du_plane_compute_base(rcrtc->plane, crtc->fb);
+ rcar_du_plane_update_base(rcrtc->plane);
+}
+
+static void rcar_du_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+ struct rcar_du_device *rcdu = crtc->dev->dev_private;
+ struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+
+ if (rcrtc->dpms == mode)
+ return;
+
+ if (mode == DRM_MODE_DPMS_ON) {
+ rcar_du_get(rcdu);
+ rcar_du_crtc_start(rcrtc);
+ } else {
+ rcar_du_crtc_stop(rcrtc);
+ rcar_du_put(rcdu);
+ }
+
+ rcrtc->dpms = mode;
+}
+
+static bool rcar_du_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* TODO Fixup modes */
+ return true;
+}
+
+static void rcar_du_crtc_mode_prepare(struct drm_crtc *crtc)
+{
+ struct rcar_du_device *rcdu = crtc->dev->dev_private;
+ struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+
+ /* We need to access the hardware during mode set, acquire a reference
+ * to the DU.
+ */
+ rcar_du_get(rcdu);
+
+ /* Stop the CRTC and release the plane. Force the DPMS mode to off as a
+ * result.
+ */
+ rcar_du_crtc_stop(rcrtc);
+ rcar_du_plane_release(rcrtc->plane);
+
+ rcrtc->dpms = DRM_MODE_DPMS_OFF;
+}
+
+static int rcar_du_crtc_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 rcar_du_device *rcdu = crtc->dev->dev_private;
+ struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+ const struct rcar_du_format_info *format;
+ int ret;
+
+ format = rcar_du_format_info(crtc->fb->pixel_format);
+ if (format == NULL) {
+ dev_dbg(rcdu->dev, "mode_set: unsupported format %08x\n",
+ crtc->fb->pixel_format);
+ ret = -EINVAL;
+ goto error;
+ }
+
+ ret = rcar_du_plane_reserve(rcrtc->plane, format);
+ if (ret < 0)
+ goto error;
+
+ rcrtc->plane->format = format;
+ rcrtc->plane->pitch = crtc->fb->pitches[0];
+
+ rcrtc->plane->src_x = x;
+ rcrtc->plane->src_y = y;
+ rcrtc->plane->width = mode->hdisplay;
+ rcrtc->plane->height = mode->vdisplay;
+
+ rcar_du_plane_compute_base(rcrtc->plane, crtc->fb);
+
+ rcrtc->outputs = 0;
+
+ return 0;
+
+error:
+ /* There's no rollback/abort operation to clean up in case of error. We
+ * thus need to release the reference to the DU acquired in prepare()
+ * here.
+ */
+ rcar_du_put(rcdu);
+ return ret;
+}
+
+static void rcar_du_crtc_mode_commit(struct drm_crtc *crtc)
+{
+ struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+
+ /* We're done, restart the CRTC and set the DPMS mode to on. The
+ * reference to the DU acquired at prepare() time will thus be released
+ * by the DPMS handler (possibly called by the disable() handler).
+ */
+ rcar_du_crtc_start(rcrtc);
+ rcrtc->dpms = DRM_MODE_DPMS_ON;
+}
+
+static int rcar_du_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+
+ rcrtc->plane->src_x = x;
+ rcrtc->plane->src_y = y;
+
+ rcar_du_crtc_update_base(to_rcar_crtc(crtc));
+
+ return 0;
+}
+
+static void rcar_du_crtc_disable(struct drm_crtc *crtc)
+{
+ struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+
+ rcar_du_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
+ rcar_du_plane_release(rcrtc->plane);
+}
+
+static const struct drm_crtc_helper_funcs crtc_helper_funcs = {
+ .dpms = rcar_du_crtc_dpms,
+ .mode_fixup = rcar_du_crtc_mode_fixup,
+ .prepare = rcar_du_crtc_mode_prepare,
+ .commit = rcar_du_crtc_mode_commit,
+ .mode_set = rcar_du_crtc_mode_set,
+ .mode_set_base = rcar_du_crtc_mode_set_base,
+ .disable = rcar_du_crtc_disable,
+};
+
+void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc,
+ struct drm_file *file)
+{
+ struct drm_pending_vblank_event *event;
+ struct drm_device *dev = rcrtc->crtc.dev;
+ unsigned long flags;
+
+ /* Destroy the pending vertical blanking event associated with the
+ * pending page flip, if any, and disable vertical blanking interrupts.
+ */
+ spin_lock_irqsave(&dev->event_lock, flags);
+ event = rcrtc->event;
+ if (event && event->base.file_priv == file) {
+ rcrtc->event = NULL;
+ event->base.destroy(&event->base);
+ drm_vblank_put(dev, rcrtc->index);
+ }
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc)
+{
+ struct drm_pending_vblank_event *event;
+ struct drm_device *dev = rcrtc->crtc.dev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->event_lock, flags);
+ event = rcrtc->event;
+ rcrtc->event = NULL;
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+
+ if (event == NULL)
+ return;
+
+ spin_lock_irqsave(&dev->event_lock, flags);
+ drm_send_vblank_event(dev, rcrtc->index, event);
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+
+ drm_vblank_put(dev, rcrtc->index);
+}
+
+static int rcar_du_crtc_page_flip(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+ struct drm_pending_vblank_event *event)
+{
+ struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+ struct drm_device *dev = rcrtc->crtc.dev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->event_lock, flags);
+ if (rcrtc->event != NULL) {
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+ return -EBUSY;
+ }
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+
+ crtc->fb = fb;
+ rcar_du_crtc_update_base(rcrtc);
+
+ if (event) {
+ event->pipe = rcrtc->index;
+ drm_vblank_get(dev, rcrtc->index);
+ spin_lock_irqsave(&dev->event_lock, flags);
+ rcrtc->event = event;
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+ }
+
+ return 0;
+}
+
+static const struct drm_crtc_funcs crtc_funcs = {
+ .destroy = drm_crtc_cleanup,
+ .set_config = drm_crtc_helper_set_config,
+ .page_flip = rcar_du_crtc_page_flip,
+};
+
+int rcar_du_crtc_create(struct rcar_du_device *rcdu, unsigned int index)
+{
+ struct rcar_du_crtc *rcrtc = &rcdu->crtcs[index];
+ struct drm_crtc *crtc = &rcrtc->crtc;
+ int ret;
+
+ rcrtc->mmio_offset = index ? DISP2_REG_OFFSET : 0;
+ rcrtc->index = index;
+ rcrtc->dpms = DRM_MODE_DPMS_OFF;
+ rcrtc->plane = &rcdu->planes.planes[index];
+
+ rcrtc->plane->crtc = crtc;
+
+ ret = drm_crtc_init(rcdu->ddev, crtc, &crtc_funcs);
+ if (ret < 0)
+ return ret;
+
+ drm_crtc_helper_add(crtc, &crtc_helper_funcs);
+
+ return 0;
+}
+
+void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable)
+{
+ if (enable) {
+ rcar_du_crtc_write(rcrtc, DSRCR, DSRCR_VBCL);
+ rcar_du_crtc_set(rcrtc, DIER, DIER_VBE);
+ } else {
+ rcar_du_crtc_clr(rcrtc, DIER, DIER_VBE);
+ }
+}
+
+void rcar_du_crtc_irq(struct rcar_du_crtc *rcrtc)
+{
+ u32 status;
+
+ status = rcar_du_crtc_read(rcrtc, DSSR);
+ rcar_du_crtc_write(rcrtc, DSRCR, status & DSRCR_MASK);
+
+ if (status & DSSR_VBK) {
+ drm_handle_vblank(rcrtc->crtc.dev, rcrtc->index);
+ rcar_du_crtc_finish_page_flip(rcrtc);
+ }
+}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
new file mode 100644
index 00000000000..2a0365bcbd1
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
@@ -0,0 +1,50 @@
+/*
+ * rcar_du_crtc.h -- R-Car Display Unit CRTCs
+ *
+ * Copyright (C) 2013 Renesas Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __RCAR_DU_CRTC_H__
+#define __RCAR_DU_CRTC_H__
+
+#include <linux/mutex.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+
+struct rcar_du_device;
+struct rcar_du_plane;
+
+struct rcar_du_crtc {
+ struct drm_crtc crtc;
+
+ unsigned int mmio_offset;
+ unsigned int index;
+ bool started;
+
+ struct drm_pending_vblank_event *event;
+ unsigned int outputs;
+ int dpms;
+
+ struct rcar_du_plane *plane;
+};
+
+int rcar_du_crtc_create(struct rcar_du_device *rcdu, unsigned int index);
+void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable);
+void rcar_du_crtc_irq(struct rcar_du_crtc *rcrtc);
+void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc,
+ struct drm_file *file);
+void rcar_du_crtc_suspend(struct rcar_du_crtc *rcrtc);
+void rcar_du_crtc_resume(struct rcar_du_crtc *rcrtc);
+
+void rcar_du_crtc_route_output(struct drm_crtc *crtc, unsigned int output);
+void rcar_du_crtc_update_planes(struct drm_crtc *crtc);
+
+#endif /* __RCAR_DU_CRTC_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
new file mode 100644
index 00000000000..003b34ee38e
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
@@ -0,0 +1,325 @@
+/*
+ * rcar_du_drv.c -- R-Car Display Unit DRM driver
+ *
+ * Copyright (C) 2013 Renesas Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "rcar_du_crtc.h"
+#include "rcar_du_drv.h"
+#include "rcar_du_kms.h"
+#include "rcar_du_regs.h"
+
+/* -----------------------------------------------------------------------------
+ * Core device operations
+ */
+
+/*
+ * rcar_du_get - Acquire a reference to the DU
+ *
+ * Acquiring a reference enables the device clock and setup core registers. A
+ * reference must be held before accessing any hardware registers.
+ *
+ * This function must be called with the DRM mode_config lock held.
+ *
+ * Return 0 in case of success or a negative error code otherwise.
+ */
+int rcar_du_get(struct rcar_du_device *rcdu)
+{
+ int ret;
+
+ if (rcdu->use_count)
+ goto done;
+
+ /* Enable clocks before accessing the hardware. */
+ ret = clk_prepare_enable(rcdu->clock);
+ if (ret < 0)
+ return ret;
+
+ /* Enable extended features */
+ rcar_du_write(rcdu, DEFR, DEFR_CODE | DEFR_DEFE);
+ rcar_du_write(rcdu, DEFR2, DEFR2_CODE | DEFR2_DEFE2G);
+ rcar_du_write(rcdu, DEFR3, DEFR3_CODE | DEFR3_DEFE3);
+ rcar_du_write(rcdu, DEFR4, DEFR4_CODE);
+ rcar_du_write(rcdu, DEFR5, DEFR5_CODE | DEFR5_DEFE5);
+
+ /* Use DS1PR and DS2PR to configure planes priorities and connects the
+ * superposition 0 to DU0 pins. DU1 pins will be configured dynamically.
+ */
+ rcar_du_write(rcdu, DORCR, DORCR_PG1D_DS1 | DORCR_DPRS);
+
+done:
+ rcdu->use_count++;
+ return 0;
+}
+
+/*
+ * rcar_du_put - Release a reference to the DU
+ *
+ * Releasing the last reference disables the device clock.
+ *
+ * This function must be called with the DRM mode_config lock held.
+ */
+void rcar_du_put(struct rcar_du_device *rcdu)
+{
+ if (--rcdu->use_count)
+ return;
+
+ clk_disable_unprepare(rcdu->clock);
+}
+
+/* -----------------------------------------------------------------------------
+ * DRM operations
+ */
+
+static int rcar_du_unload(struct drm_device *dev)
+{
+ drm_kms_helper_poll_fini(dev);
+ drm_mode_config_cleanup(dev);
+ drm_vblank_cleanup(dev);
+ drm_irq_uninstall(dev);
+
+ dev->dev_private = NULL;
+
+ return 0;
+}
+
+static int rcar_du_load(struct drm_device *dev, unsigned long flags)
+{
+ struct platform_device *pdev = dev->platformdev;
+ struct rcar_du_platform_data *pdata = pdev->dev.platform_data;
+ struct rcar_du_device *rcdu;
+ struct resource *ioarea;
+ struct resource *mem;
+ int ret;
+
+ if (pdata == NULL) {
+ dev_err(dev->dev, "no platform data\n");
+ return -ENODEV;
+ }
+
+ rcdu = devm_kzalloc(&pdev->dev, sizeof(*rcdu), GFP_KERNEL);
+ if (rcdu == NULL) {
+ dev_err(dev->dev, "failed to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ rcdu->dev = &pdev->dev;
+ rcdu->pdata = pdata;
+ rcdu->ddev = dev;
+ dev->dev_private = rcdu;
+
+ /* I/O resources and clocks */
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (mem == NULL) {
+ dev_err(&pdev->dev, "failed to get memory resource\n");
+ return -EINVAL;
+ }
+
+ ioarea = devm_request_mem_region(&pdev->dev, mem->start,
+ resource_size(mem), pdev->name);
+ if (ioarea == NULL) {
+ dev_err(&pdev->dev, "failed to request memory region\n");
+ return -EBUSY;
+ }
+
+ rcdu->mmio = devm_ioremap_nocache(&pdev->dev, ioarea->start,
+ resource_size(ioarea));
+ if (rcdu->mmio == NULL) {
+ dev_err(&pdev->dev, "failed to remap memory resource\n");
+ return -ENOMEM;
+ }
+
+ rcdu->clock = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(rcdu->clock)) {
+ dev_err(&pdev->dev, "failed to get clock\n");
+ return -ENOENT;
+ }
+
+ /* DRM/KMS objects */
+ ret = rcar_du_modeset_init(rcdu);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to initialize DRM/KMS\n");
+ goto done;
+ }
+
+ /* IRQ and vblank handling */
+ ret = drm_vblank_init(dev, (1 << rcdu->num_crtcs) - 1);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to initialize vblank\n");
+ goto done;
+ }
+
+ ret = drm_irq_install(dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to install IRQ handler\n");
+ goto done;
+ }
+
+ platform_set_drvdata(pdev, rcdu);
+
+done:
+ if (ret)
+ rcar_du_unload(dev);
+
+ return ret;
+}
+
+static void rcar_du_preclose(struct drm_device *dev, struct drm_file *file)
+{
+ struct rcar_du_device *rcdu = dev->dev_private;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(rcdu->crtcs); ++i)
+ rcar_du_crtc_cancel_page_flip(&rcdu->crtcs[i], file);
+}
+
+static irqreturn_t rcar_du_irq(int irq, void *arg)
+{
+ struct drm_device *dev = arg;
+ struct rcar_du_device *rcdu = dev->dev_private;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(rcdu->crtcs); ++i)
+ rcar_du_crtc_irq(&rcdu->crtcs[i]);
+
+ return IRQ_HANDLED;
+}
+
+static int rcar_du_enable_vblank(struct drm_device *dev, int crtc)
+{
+ struct rcar_du_device *rcdu = dev->dev_private;
+
+ rcar_du_crtc_enable_vblank(&rcdu->crtcs[crtc], true);
+
+ return 0;
+}
+
+static void rcar_du_disable_vblank(struct drm_device *dev, int crtc)
+{
+ struct rcar_du_device *rcdu = dev->dev_private;
+
+ rcar_du_crtc_enable_vblank(&rcdu->crtcs[crtc], false);
+}
+
+static const struct file_operations rcar_du_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .unlocked_ioctl = drm_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = drm_compat_ioctl,
+#endif
+ .poll = drm_poll,
+ .read = drm_read,
+ .fasync = drm_fasync,
+ .llseek = no_llseek,
+ .mmap = drm_gem_cma_mmap,
+};
+
+static struct drm_driver rcar_du_driver = {
+ .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET
+ | DRIVER_PRIME,
+ .load = rcar_du_load,
+ .unload = rcar_du_unload,
+ .preclose = rcar_du_preclose,
+ .irq_handler = rcar_du_irq,
+ .get_vblank_counter = drm_vblank_count,
+ .enable_vblank = rcar_du_enable_vblank,
+ .disable_vblank = rcar_du_disable_vblank,
+ .gem_free_object = drm_gem_cma_free_object,
+ .gem_vm_ops = &drm_gem_cma_vm_ops,
+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_import = drm_gem_cma_dmabuf_import,
+ .gem_prime_export = drm_gem_cma_dmabuf_export,
+ .dumb_create = drm_gem_cma_dumb_create,
+ .dumb_map_offset = drm_gem_cma_dumb_map_offset,
+ .dumb_destroy = drm_gem_cma_dumb_destroy,
+ .fops = &rcar_du_fops,
+ .name = "rcar-du",
+ .desc = "Renesas R-Car Display Unit",
+ .date = "20130110",
+ .major = 1,
+ .minor = 0,
+};
+
+/* -----------------------------------------------------------------------------
+ * Power management
+ */
+
+#if CONFIG_PM_SLEEP
+static int rcar_du_pm_suspend(struct device *dev)
+{
+ struct rcar_du_device *rcdu = dev_get_drvdata(dev);
+
+ drm_kms_helper_poll_disable(rcdu->ddev);
+ /* TODO Suspend the CRTC */
+
+ return 0;
+}
+
+static int rcar_du_pm_resume(struct device *dev)
+{
+ struct rcar_du_device *rcdu = dev_get_drvdata(dev);
+
+ /* TODO Resume the CRTC */
+
+ drm_kms_helper_poll_enable(rcdu->ddev);
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops rcar_du_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(rcar_du_pm_suspend, rcar_du_pm_resume)