aboutsummaryrefslogtreecommitdiff
path: root/drivers/gpu/drm
diff options
context:
space:
mode:
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)
+};
+
+/* -----------------------------------------------------------------------------
+ * Platform driver
+ */
+
+static int rcar_du_probe(struct platform_device *pdev)
+{
+ return drm_platform_init(&rcar_du_driver, pdev);
+}
+
+static int rcar_du_remove(struct platform_device *pdev)
+{
+ drm_platform_exit(&rcar_du_driver, pdev);
+
+ return 0;
+}
+
+static struct platform_driver rcar_du_platform_driver = {
+ .probe = rcar_du_probe,
+ .remove = rcar_du_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "rcar-du",
+ .pm = &rcar_du_pm_ops,
+ },
+};
+
+module_platform_driver(rcar_du_platform_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
new file mode 100644
index 00000000000..193cc59d495
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
@@ -0,0 +1,66 @@
+/*
+ * rcar_du_drv.h -- 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.
+ */
+
+#ifndef __RCAR_DU_DRV_H__
+#define __RCAR_DU_DRV_H__
+
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/platform_data/rcar-du.h>
+
+#include "rcar_du_crtc.h"
+#include "rcar_du_plane.h"
+
+struct clk;
+struct device;
+struct drm_device;
+
+struct rcar_du_device {
+ struct device *dev;
+ const struct rcar_du_platform_data *pdata;
+
+ void __iomem *mmio;
+ struct clk *clock;
+ unsigned int use_count;
+
+ struct drm_device *ddev;
+
+ struct rcar_du_crtc crtcs[2];
+ unsigned int used_crtcs;
+ unsigned int num_crtcs;
+
+ struct {
+ struct rcar_du_plane planes[RCAR_DU_NUM_SW_PLANES];
+ unsigned int free;
+ struct mutex lock;
+
+ struct drm_property *alpha;
+ struct drm_property *colorkey;
+ struct drm_property *zpos;
+ } planes;
+};
+
+int rcar_du_get(struct rcar_du_device *rcdu);
+void rcar_du_put(struct rcar_du_device *rcdu);
+
+static inline u32 rcar_du_read(struct rcar_du_device *rcdu, u32 reg)
+{
+ return ioread32(rcdu->mmio + reg);
+}
+
+static inline void rcar_du_write(struct rcar_du_device *rcdu, u32 reg, u32 data)
+{
+ iowrite32(data, rcdu->mmio + reg);
+}
+
+#endif /* __RCAR_DU_DRV_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
new file mode 100644
index 00000000000..9c63f39658d
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
@@ -0,0 +1,245 @@
+/*
+ * rcar_du_kms.c -- R-Car Display Unit Mode Setting
+ *
+ * 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 <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_regs.h"
+#include "rcar_du_vga.h"
+
+/* -----------------------------------------------------------------------------
+ * Format helpers
+ */
+
+static const struct rcar_du_format_info rcar_du_format_infos[] = {
+ {
+ .fourcc = DRM_FORMAT_RGB565,
+ .bpp = 16,
+ .planes = 1,
+ .pnmr = PnMR_SPIM_TP | PnMR_DDDF_16BPP,
+ .edf = PnDDCR4_EDF_NONE,
+ }, {
+ .fourcc = DRM_FORMAT_ARGB1555,
+ .bpp = 16,
+ .planes = 1,
+ .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_ARGB,
+ .edf = PnDDCR4_EDF_NONE,
+ }, {
+ .fourcc = DRM_FORMAT_XRGB1555,
+ .bpp = 16,
+ .planes = 1,
+ .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_ARGB,
+ .edf = PnDDCR4_EDF_NONE,
+ }, {
+ .fourcc = DRM_FORMAT_XRGB8888,
+ .bpp = 32,
+ .planes = 1,
+ .pnmr = PnMR_SPIM_TP | PnMR_DDDF_16BPP,
+ .edf = PnDDCR4_EDF_RGB888,
+ }, {
+ .fourcc = DRM_FORMAT_ARGB8888,
+ .bpp = 32,
+ .planes = 1,
+ .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_16BPP,
+ .edf = PnDDCR4_EDF_ARGB8888,
+ }, {
+ .fourcc = DRM_FORMAT_UYVY,
+ .bpp = 16,
+ .planes = 1,
+ .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
+ .edf = PnDDCR4_EDF_NONE,
+ }, {
+ .fourcc = DRM_FORMAT_YUYV,
+ .bpp = 16,
+ .planes = 1,
+ .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
+ .edf = PnDDCR4_EDF_NONE,
+ }, {
+ .fourcc = DRM_FORMAT_NV12,
+ .bpp = 12,
+ .planes = 2,
+ .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
+ .edf = PnDDCR4_EDF_NONE,
+ }, {
+ .fourcc = DRM_FORMAT_NV21,
+ .bpp = 12,
+ .planes = 2,
+ .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
+ .edf = PnDDCR4_EDF_NONE,
+ }, {
+ /* In YUV 4:2:2, only NV16 is supported (NV61 isn't) */
+ .fourcc = DRM_FORMAT_NV16,
+ .bpp = 16,
+ .planes = 2,
+ .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
+ .edf = PnDDCR4_EDF_NONE,
+ },
+};
+
+const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(rcar_du_format_infos); ++i) {
+ if (rcar_du_format_infos[i].fourcc == fourcc)
+ return &rcar_du_format_infos[i];
+ }
+
+ return NULL;
+}
+
+/* -----------------------------------------------------------------------------
+ * Common connector and encoder functions
+ */
+
+struct drm_encoder *
+rcar_du_connector_best_encoder(struct drm_connector *connector)
+{
+ struct rcar_du_connector *rcon = to_rcar_connector(connector);
+
+ return &rcon->encoder->encoder;
+}
+
+void rcar_du_encoder_mode_prepare(struct drm_encoder *encoder)
+{
+}
+
+void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
+
+ rcar_du_crtc_route_output(encoder->crtc, renc->output);
+}
+
+void rcar_du_encoder_mode_commit(struct drm_encoder *encoder)
+{
+}
+
+/* -----------------------------------------------------------------------------
+ * Frame buffer
+ */
+
+static struct drm_framebuffer *
+rcar_du_fb_create(struct drm_device *dev, struct drm_file *file_priv,
+ struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ const struct rcar_du_format_info *format;
+
+ format = rcar_du_format_info(mode_cmd->pixel_format);
+ if (format == NULL) {
+ dev_dbg(dev->dev, "unsupported pixel format %08x\n",
+ mode_cmd->pixel_format);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (mode_cmd->pitches[0] & 15 || mode_cmd->pitches[0] >= 8192) {
+ dev_dbg(dev->dev, "invalid pitch value %u\n",
+ mode_cmd->pitches[0]);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (format->planes == 2) {
+ if (mode_cmd->pitches[1] != mode_cmd->pitches[0]) {
+ dev_dbg(dev->dev,
+ "luma and chroma pitches do not match\n");
+ return ERR_PTR(-EINVAL);
+ }
+ }
+
+ return drm_fb_cma_create(dev, file_priv, mode_cmd);
+}
+
+static const struct drm_mode_config_funcs rcar_du_mode_config_funcs = {
+ .fb_create = rcar_du_fb_create,
+};
+
+int rcar_du_modeset_init(struct rcar_du_device *rcdu)
+{
+ struct drm_device *dev = rcdu->ddev;
+ struct drm_encoder *encoder;
+ unsigned int i;
+ int ret;
+
+ drm_mode_config_init(rcdu->ddev);
+
+ rcdu->ddev->mode_config.min_width = 0;
+ rcdu->ddev->mode_config.min_height = 0;
+ rcdu->ddev->mode_config.max_width = 4095;
+ rcdu->ddev->mode_config.max_height = 2047;
+ rcdu->ddev->mode_config.funcs = &rcar_du_mode_config_funcs;
+
+ ret = rcar_du_plane_init(rcdu);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(rcdu->crtcs); ++i)
+ rcar_du_crtc_create(rcdu, i);
+
+ rcdu->used_crtcs = 0;
+ rcdu->num_crtcs = i;
+
+ for (i = 0; i < rcdu->pdata->num_encoders; ++i) {
+ const struct rcar_du_encoder_data *pdata =
+ &rcdu->pdata->encoders[i];
+
+ if (pdata->output >= ARRAY_SIZE(rcdu->crtcs)) {
+ dev_warn(rcdu->dev,
+ "encoder %u references unexisting output %u, skipping\n",
+ i, pdata->output);
+ continue;
+ }
+
+ switch (pdata->encoder) {
+ case RCAR_DU_ENCODER_VGA:
+ rcar_du_vga_init(rcdu, &pdata->u.vga, pdata->output);
+ break;
+
+ case RCAR_DU_ENCODER_LVDS:
+ rcar_du_lvds_init(rcdu, &pdata->u.lvds, pdata->output);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Set the possible CRTCs and possible clones. All encoders can be
+ * driven by the CRTC associated with the output they're connected to,
+ * as well as by CRTC 0.
+ */
+ list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+ struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
+
+ encoder->possible_crtcs = (1 << 0) | (1 << renc->output);
+ encoder->possible_clones = 1 << 0;
+ }
+
+ ret = rcar_du_plane_register(rcdu);
+ if (ret < 0)
+ return ret;
+
+ drm_kms_helper_poll_init(rcdu->ddev);
+
+ drm_helper_disable_unused_functions(rcdu->ddev);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.h b/drivers/gpu/drm/rcar-du/rcar_du_kms.h
new file mode 100644
index 00000000000..e4d8db069a0
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.h
@@ -0,0 +1,59 @@
+/*
+ * rcar_du_kms.h -- R-Car Display Unit Mode Setting
+ *
+ * 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_KMS_H__
+#define __RCAR_DU_KMS_H__
+
+#include <linux/types.h>
+
+#include <drm/drm_crtc.h>
+
+struct rcar_du_device;
+
+struct rcar_du_format_info {
+ u32 fourcc;
+ unsigned int bpp;
+ unsigned int planes;
+ unsigned int pnmr;
+ unsigned int edf;
+};
+
+struct rcar_du_encoder {
+ struct drm_encoder encoder;
+ unsigned int output;
+};
+
+#define to_rcar_encoder(e) \
+ container_of(e, struct rcar_du_encoder, encoder)
+
+struct rcar_du_connector {
+ struct drm_connector connector;
+ struct rcar_du_encoder *encoder;
+};
+
+#define to_rcar_connector(c) \
+ container_of(c, struct rcar_du_connector, connector)
+
+const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc);
+
+struct drm_encoder *
+rcar_du_connector_best_encoder(struct drm_connector *connector);
+void rcar_du_encoder_mode_prepare(struct drm_encoder *encoder);
+void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode);
+void rcar_du_encoder_mode_commit(struct drm_encoder *encoder);
+
+int rcar_du_modeset_init(struct rcar_du_device *rcdu);
+
+#endif /* __RCAR_DU_KMS_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvds.c b/drivers/gpu/drm/rcar-du/rcar_du_lvds.c
new file mode 100644
index 00000000000..7aefe7267e1
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_lvds.c
@@ -0,0 +1,216 @@
+/*
+ * rcar_du_lvds.c -- R-Car Display Unit LVDS Encoder and Connector
+ *
+ * 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 <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "rcar_du_drv.h"
+#include "rcar_du_kms.h"
+#include "rcar_du_lvds.h"
+
+struct rcar_du_lvds_connector {
+ struct rcar_du_connector connector;
+
+ const struct rcar_du_panel_data *panel;
+};
+
+#define to_rcar_lvds_connector(c) \
+ container_of(c, struct rcar_du_lvds_connector, connector.connector)
+
+/* -----------------------------------------------------------------------------
+ * Connector
+ */
+
+static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector)
+{
+ struct rcar_du_lvds_connector *lvdscon = to_rcar_lvds_connector(connector);
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_create(connector->dev);
+ if (mode == NULL)
+ return 0;
+
+ mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER;
+ mode->clock = lvdscon->panel->mode.clock;
+ mode->hdisplay = lvdscon->panel->mode.hdisplay;
+ mode->hsync_start = lvdscon->panel->mode.hsync_start;
+ mode->hsync_end = lvdscon->panel->mode.hsync_end;
+ mode->htotal = lvdscon->panel->mode.htotal;
+ mode->vdisplay = lvdscon->panel->mode.vdisplay;
+ mode->vsync_start = lvdscon->panel->mode.vsync_start;
+ mode->vsync_end = lvdscon->panel->mode.vsync_end;
+ mode->vtotal = lvdscon->panel->mode.vtotal;
+ mode->flags = lvdscon->panel->mode.flags;
+
+ drm_mode_set_name(mode);
+ drm_mode_probed_add(connector, mode);
+
+ return 1;
+}
+
+static int rcar_du_lvds_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs connector_helper_funcs = {
+ .get_modes = rcar_du_lvds_connector_get_modes,
+ .mode_valid = rcar_du_lvds_connector_mode_valid,
+ .best_encoder = rcar_du_connector_best_encoder,
+};
+
+static void rcar_du_lvds_connector_destroy(struct drm_connector *connector)
+{
+ drm_sysfs_connector_remove(connector);
+ drm_connector_cleanup(connector);
+}
+
+static enum drm_connector_status
+rcar_du_lvds_connector_detect(struct drm_connector *connector, bool force)
+{
+ return connector_status_connected;
+}
+
+static const struct drm_connector_funcs connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .detect = rcar_du_lvds_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = rcar_du_lvds_connector_destroy,
+};
+
+static int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
+ struct rcar_du_encoder *renc,
+ const struct rcar_du_panel_data *panel)
+{
+ struct rcar_du_lvds_connector *lvdscon;
+ struct drm_connector *connector;
+ int ret;
+
+ lvdscon = devm_kzalloc(rcdu->dev, sizeof(*lvdscon), GFP_KERNEL);
+ if (lvdscon == NULL)
+ return -ENOMEM;
+
+ lvdscon->panel = panel;
+
+ connector = &lvdscon->connector.connector;
+ connector->display_info.width_mm = panel->width_mm;
+ connector->display_info.height_mm = panel->height_mm;
+
+ ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
+ DRM_MODE_CONNECTOR_LVDS);
+ if (ret < 0)
+ return ret;
+
+ drm_connector_helper_add(connector, &connector_helper_funcs);
+ ret = drm_sysfs_connector_add(connector);
+ if (ret < 0)
+ return ret;
+
+ drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF);
+ drm_object_property_set_value(&connector->base,
+ rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF);
+
+ ret = drm_mode_connector_attach_encoder(connector, &renc->encoder);
+ if (ret < 0)
+ return ret;
+
+ connector->encoder = &renc->encoder;
+ lvdscon->connector.encoder = renc;
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Encoder
+ */
+
+static void rcar_du_lvds_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+static bool rcar_du_lvds_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ const struct drm_display_mode *panel_mode;
+ struct drm_device *dev = encoder->dev;
+ struct drm_connector *connector;
+ bool found = false;
+
+ list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+ if (connector->encoder == encoder) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ dev_dbg(dev->dev, "mode_fixup: no connector found\n");
+ return false;
+ }
+
+ if (list_empty(&connector->modes)) {
+ dev_dbg(dev->dev, "mode_fixup: empty modes list\n");
+ return false;
+ }
+
+ panel_mode = list_first_entry(&connector->modes,
+ struct drm_display_mode, head);
+
+ /* We're not allowed to modify the resolution. */
+ if (mode->hdisplay != panel_mode->hdisplay ||
+ mode->vdisplay != panel_mode->vdisplay)
+ return false;
+
+ /* The flat panel mode is fixed, just copy it to the adjusted mode. */
+ drm_mode_copy(adjusted_mode, panel_mode);
+
+ return true;
+}
+
+static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
+ .dpms = rcar_du_lvds_encoder_dpms,
+ .mode_fixup = rcar_du_lvds_encoder_mode_fixup,
+ .prepare = rcar_du_encoder_mode_prepare,
+ .commit = rcar_du_encoder_mode_commit,
+ .mode_set = rcar_du_encoder_mode_set,
+};
+
+static const struct drm_encoder_funcs encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+int rcar_du_lvds_init(struct rcar_du_device *rcdu,
+ const struct rcar_du_encoder_lvds_data *data,
+ unsigned int output)
+{
+ struct rcar_du_encoder *renc;
+ int ret;
+
+ renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL);
+ if (renc == NULL)
+ return -ENOMEM;
+
+ renc->output = output;
+
+ ret = drm_encoder_init(rcdu->ddev, &renc->encoder, &encoder_funcs,
+ DRM_MODE_ENCODER_LVDS);
+ if (ret < 0)
+ return ret;
+
+ drm_encoder_helper_add(&renc->encoder, &encoder_helper_funcs);
+
+ return rcar_du_lvds_connector_init(rcdu, renc, &data->panel);
+}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvds.h b/drivers/gpu/drm/rcar-du/rcar_du_lvds.h
new file mode 100644
index 00000000000..b47f8328e10
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_lvds.h
@@ -0,0 +1,24 @@
+/*
+ * rcar_du_lvds.h -- R-Car Display Unit LVDS Encoder and Connector
+ *
+ * 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_LVDS_H__
+#define __RCAR_DU_LVDS_H__
+
+struct rcar_du_device;
+struct rcar_du_encoder_lvds_data;
+
+int rcar_du_lvds_init(struct rcar_du_device *rcdu,
+ const struct rcar_du_encoder_lvds_data *data,
+ unsigned int output);
+
+#endif /* __RCAR_DU_LVDS_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_plane.c b/drivers/gpu/drm/rcar-du/rcar_du_plane.c
new file mode 100644
index 00000000000..a65f81ddf51
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_plane.c
@@ -0,0 +1,507 @@
+/*
+ * rcar_du_plane.c -- R-Car Display Unit Planes
+ *
+ * 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 <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_drv.h"
+#include "rcar_du_kms.h"
+#include "rcar_du_plane.h"
+#include "rcar_du_regs.h"
+
+#define RCAR_DU_COLORKEY_NONE (0 << 24)
+#define RCAR_DU_COLORKEY_SOURCE (1 << 24)
+#define RCAR_DU_COLORKEY_MASK (1 << 24)
+
+struct rcar_du_kms_plane {
+ struct drm_plane plane;
+ struct rcar_du_plane *hwplane;
+};
+
+static inline struct rcar_du_plane *to_rcar_plane(struct drm_plane *plane)
+{
+ return container_of(plane, struct rcar_du_kms_plane, plane)->hwplane;
+}
+
+static u32 rcar_du_plane_read(struct rcar_du_device *rcdu,
+ unsigned int index, u32 reg)
+{
+ return rcar_du_read(rcdu, index * PLANE_OFF + reg);
+}
+
+static void rcar_du_plane_write(struct rcar_du_device *rcdu,
+ unsigned int index, u32 reg, u32 data)
+{
+ rcar_du_write(rcdu, index * PLANE_OFF + reg, data);
+}
+
+int rcar_du_plane_reserve(struct rcar_du_plane *plane,
+ const struct rcar_du_format_info *format)
+{
+ struct rcar_du_device *rcdu = plane->dev;
+ unsigned int i;
+ int ret = -EBUSY;
+
+ mutex_lock(&rcdu->planes.lock);
+
+ for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) {
+ if (!(rcdu->planes.free & (1 << i)))
+ continue;
+
+ if (format->planes == 1 ||
+ rcdu->planes.free & (1 << ((i + 1) % 8)))
+ break;
+ }
+
+ if (i == ARRAY_SIZE(rcdu->planes.planes))
+ goto done;
+
+ rcdu->planes.free &= ~(1 << i);
+ if (format->planes == 2)
+ rcdu->planes.free &= ~(1 << ((i + 1) % 8));
+
+ plane->hwindex = i;
+
+ ret = 0;
+
+done:
+ mutex_unlock(&rcdu->planes.lock);
+ return ret;
+}
+
+void rcar_du_plane_release(struct rcar_du_plane *plane)
+{
+ struct rcar_du_device *rcdu = plane->dev;
+
+ if (plane->hwindex == -1)
+ return;
+
+ mutex_lock(&rcdu->planes.lock);
+ rcdu->planes.free |= 1 << plane->hwindex;
+ if (plane->format->planes == 2)
+ rcdu->planes.free |= 1 << ((plane->hwindex + 1) % 8);
+ mutex_unlock(&rcdu->planes.lock);
+
+ plane->hwindex = -1;
+}
+
+void rcar_du_plane_update_base(struct rcar_du_plane *plane)
+{
+ struct rcar_du_device *rcdu = plane->dev;
+ unsigned int index = plane->hwindex;
+
+ /* According to the datasheet the Y position is expressed in raster line
+ * units. However, 32bpp formats seem to require a doubled Y position
+ * value. Similarly, for the second plane, NV12 and NV21 formats seem to
+ * require a halved Y position value.
+ */
+ rcar_du_plane_write(rcdu, index, PnSPXR, plane->src_x);
+ rcar_du_plane_write(rcdu, index, PnSPYR, plane->src_y *
+ (plane->format->bpp == 32 ? 2 : 1));
+ rcar_du_plane_write(rcdu, index, PnDSA0R, plane->dma[0]);
+
+ if (plane->format->planes == 2) {
+ index = (index + 1) % 8;
+
+ rcar_du_plane_write(rcdu, index, PnSPXR, plane->src_x);
+ rcar_du_plane_write(rcdu, index, PnSPYR, plane->src_y *
+ (plane->format->bpp == 16 ? 2 : 1) / 2);
+ rcar_du_plane_write(rcdu, index, PnDSA0R, plane->dma[1]);
+ }
+}
+
+void rcar_du_plane_compute_base(struct rcar_du_plane *plane,
+ struct drm_framebuffer *fb)
+{
+ struct drm_gem_cma_object *gem;
+
+ gem = drm_fb_cma_get_gem_obj(fb, 0);
+ plane->dma[0] = gem->paddr + fb->offsets[0];
+
+ if (plane->format->planes == 2) {
+ gem = drm_fb_cma_get_gem_obj(fb, 1);
+ plane->dma[1] = gem->paddr + fb->offsets[1];
+ }
+}
+
+static void rcar_du_plane_setup_mode(struct rcar_du_plane *plane,
+ unsigned int index)
+{
+ struct rcar_du_device *rcdu = plane->dev;
+ u32 colorkey;
+ u32 pnmr;
+
+ /* The PnALPHAR register controls alpha-blending in 16bpp formats
+ * (ARGB1555 and XRGB1555).
+ *
+ * For ARGB, set the alpha value to 0, and enable alpha-blending when
+ * the A bit is 0. This maps A=0 to alpha=0 and A=1 to alpha=255.
+ *
+ * For XRGB, set the alpha value to the plane-wide alpha value and
+ * enable alpha-blending regardless of the X bit value.
+ */
+ if (plane->format->fourcc != DRM_FORMAT_XRGB1555)
+ rcar_du_plane_write(rcdu, index, PnALPHAR, PnALPHAR_ABIT_0);
+ else
+ rcar_du_plane_write(rcdu, index, PnALPHAR,
+ PnALPHAR_ABIT_X | plane->alpha);
+
+ pnmr = PnMR_BM_MD | plane->format->pnmr;
+
+ /* Disable color keying when requested. YUV formats have the
+ * PnMR_SPIM_TP_OFF bit set in their pnmr field, disabling color keying
+ * automatically.
+ */
+ if ((plane->colorkey & RCAR_DU_COLORKEY_MASK) == RCAR_DU_COLORKEY_NONE)
+ pnmr |= PnMR_SPIM_TP_OFF;
+
+ /* For packed YUV formats we need to select the U/V order. */
+ if (plane->format->fourcc == DRM_FORMAT_YUYV)
+ pnmr |= PnMR_YCDF_YUYV;
+
+ rcar_du_plane_write(rcdu, index, PnMR, pnmr);
+
+ switch (plane->format->fourcc) {
+ case DRM_FORMAT_RGB565:
+ colorkey = ((plane->colorkey & 0xf80000) >> 8)
+ | ((plane->colorkey & 0x00fc00) >> 5)
+ | ((plane->colorkey & 0x0000f8) >> 3);
+ rcar_du_plane_write(rcdu, index, PnTC2R, colorkey);
+ break;
+
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_XRGB1555:
+ colorkey = ((plane->colorkey & 0xf80000) >> 9)
+ | ((plane->colorkey & 0x00f800) >> 6)
+ | ((plane->colorkey & 0x0000f8) >> 3);
+ rcar_du_plane_write(rcdu, index, PnTC2R, colorkey);
+ break;
+
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ rcar_du_plane_write(rcdu, index, PnTC3R,
+ PnTC3R_CODE | (plane->colorkey & 0xffffff));
+ break;
+ }
+}
+
+static void __rcar_du_plane_setup(struct rcar_du_plane *plane,
+ unsigned int index)
+{
+ struct rcar_du_device *rcdu = plane->dev;
+ u32 ddcr2 = PnDDCR2_CODE;
+ u32 ddcr4;
+ u32 mwr;
+
+ /* Data format
+ *
+ * The data format is selected by the DDDF field in PnMR and the EDF
+ * field in DDCR4.
+ */
+ ddcr4 = rcar_du_plane_read(rcdu, index, PnDDCR4);
+ ddcr4 &= ~PnDDCR4_EDF_MASK;
+ ddcr4 |= plane->format->edf | PnDDCR4_CODE;
+
+ rcar_du_plane_setup_mode(plane, index);
+
+ if (plane->format->planes == 2) {
+ if (plane->hwindex != index) {
+ if (plane->format->fourcc == DRM_FORMAT_NV12 ||
+ plane->format->fourcc == DRM_FORMAT_NV21)
+ ddcr2 |= PnDDCR2_Y420;
+
+ if (plane->format->fourcc == DRM_FORMAT_NV21)
+ ddcr2 |= PnDDCR2_NV21;
+
+ ddcr2 |= PnDDCR2_DIVU;
+ } else {
+ ddcr2 |= PnDDCR2_DIVY;
+ }
+ }
+
+ rcar_du_plane_write(rcdu, index, PnDDCR2, ddcr2);
+ rcar_du_plane_write(rcdu, index, PnDDCR4, ddcr4);
+
+ /* Memory pitch (expressed in pixels) */
+ if (plane->format->planes == 2)
+ mwr = plane->pitch;
+ else
+ mwr = plane->pitch * 8 / plane->format->bpp;
+
+ rcar_du_plane_write(rcdu, index, PnMWR, mwr);
+
+ /* Destination position and size */
+ rcar_du_plane_write(rcdu, index, PnDSXR, plane->width);
+ rcar_du_plane_write(rcdu, index, PnDSYR, plane->height);
+ rcar_du_plane_write(rcdu, index, PnDPXR, plane->dst_x);
+ rcar_du_plane_write(rcdu, index, PnDPYR, plane->dst_y);
+
+ /* Wrap-around and blinking, disabled */
+ rcar_du_plane_write(rcdu, index, PnWASPR, 0);
+ rcar_du_plane_write(rcdu, index, PnWAMWR, 4095);
+ rcar_du_plane_write(rcdu, index, PnBTR, 0);
+ rcar_du_plane_write(rcdu, index, PnMLR, 0);
+}
+
+void rcar_du_plane_setup(struct rcar_du_plane *plane)
+{
+ __rcar_du_plane_setup(plane, plane->hwindex);
+ if (plane->format->planes == 2)
+ __rcar_du_plane_setup(plane, (plane->hwindex + 1) % 8);
+
+ rcar_du_plane_update_base(plane);
+}
+
+static int
+rcar_du_plane_update(struct drm_plane *plane, struct drm_crtc *crtc,
+ struct drm_framebuffer *fb, int crtc_x, int crtc_y,
+ unsigned int crtc_w, unsigned int crtc_h,
+ uint32_t src_x, uint32_t src_y,
+ uint32_t src_w, uint32_t src_h)
+{
+ struct rcar_du_plane *rplane = to_rcar_plane(plane);
+ struct rcar_du_device *rcdu = plane->dev->dev_private;
+ const struct rcar_du_format_info *format;
+ unsigned int nplanes;
+ int ret;
+
+ format = rcar_du_format_info(fb->pixel_format);
+ if (format == NULL) {
+ dev_dbg(rcdu->dev, "%s: unsupported format %08x\n", __func__,
+ fb->pixel_format);
+ return -EINVAL;
+ }
+
+ if (src_w >> 16 != crtc_w || src_h >> 16 != crtc_h) {
+ dev_dbg(rcdu->dev, "%s: scaling not supported\n", __func__);
+ return -EINVAL;
+ }
+
+ nplanes = rplane->format ? rplane->format->planes : 0;
+
+ /* Reallocate hardware planes if the number of required planes has
+ * changed.
+ */
+ if (format->planes != nplanes) {
+ rcar_du_plane_release(rplane);
+ ret = rcar_du_plane_reserve(rplane, format);
+ if (ret < 0)
+ return ret;
+ }
+
+ rplane->crtc = crtc;
+ rplane->format = format;
+ rplane->pitch = fb->pitches[0];
+
+ rplane->src_x = src_x >> 16;
+ rplane->src_y = src_y >> 16;
+ rplane->dst_x = crtc_x;
+ rplane->dst_y = crtc_y;
+ rplane->width = crtc_w;
+ rplane->height = crtc_h;
+
+ rcar_du_plane_compute_base(rplane, fb);
+ rcar_du_plane_setup(rplane);
+
+ mutex_lock(&rcdu->planes.lock);
+ rplane->enabled = true;
+ rcar_du_crtc_update_planes(rplane->crtc);
+ mutex_unlock(&rcdu->planes.lock);
+
+ return 0;
+}
+
+static int rcar_du_plane_disable(struct drm_plane *plane)
+{
+ struct rcar_du_device *rcdu = plane->dev->dev_private;
+ struct rcar_du_plane *rplane = to_rcar_plane(plane);
+
+ if (!rplane->enabled)
+ return 0;
+
+ mutex_lock(&rcdu->planes.lock);
+ rplane->enabled = false;
+ rcar_du_crtc_update_planes(rplane->crtc);
+ mutex_unlock(&rcdu->planes.lock);
+
+ rcar_du_plane_release(rplane);
+
+ rplane->crtc = NULL;
+ rplane->format = NULL;
+
+ return 0;
+}
+
+/* Both the .set_property and the .update_plane operations are called with the
+ * mode_config lock held. There is this no need to explicitly protect access to
+ * the alpha and colorkey fields and the mode register.
+ */
+static void rcar_du_plane_set_alpha(struct rcar_du_plane *plane, u32 alpha)
+{
+ if (plane->alpha == alpha)
+ return;
+
+ plane->alpha = alpha;
+ if (!plane->enabled || plane->format->fourcc != DRM_FORMAT_XRGB1555)
+ return;
+
+ rcar_du_plane_setup_mode(plane, plane->hwindex);
+}
+
+static void rcar_du_plane_set_colorkey(struct rcar_du_plane *plane,
+ u32 colorkey)
+{
+ if (plane->colorkey == colorkey)
+ return;
+
+ plane->colorkey = colorkey;
+ if (!plane->enabled)
+ return;
+
+ rcar_du_plane_setup_mode(plane, plane->hwindex);
+}
+
+static void rcar_du_plane_set_zpos(struct rcar_du_plane *plane,
+ unsigned int zpos)
+{
+ struct rcar_du_device *rcdu = plane->dev;
+
+ mutex_lock(&rcdu->planes.lock);
+ if (plane->zpos == zpos)
+ goto done;
+
+ plane->zpos = zpos;
+ if (!plane->enabled)
+ goto done;
+
+ rcar_du_crtc_update_planes(plane->crtc);
+
+done:
+ mutex_unlock(&rcdu->planes.lock);
+}
+
+static int rcar_du_plane_set_property(struct drm_plane *plane,
+ struct drm_property *property,
+ uint64_t value)
+{
+ struct rcar_du_device *rcdu = plane->dev->dev_private;
+ struct rcar_du_plane *rplane = to_rcar_plane(plane);
+
+ if (property == rcdu->planes.alpha)
+ rcar_du_plane_set_alpha(rplane, value);
+ else if (property == rcdu->planes.colorkey)
+ rcar_du_plane_set_colorkey(rplane, value);
+ else if (property == rcdu->planes.zpos)
+ rcar_du_plane_set_zpos(rplane, value);
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct drm_plane_funcs rcar_du_plane_funcs = {
+ .update_plane = rcar_du_plane_update,
+ .disable_plane = rcar_du_plane_disable,
+ .set_property = rcar_du_plane_set_property,
+ .destroy = drm_plane_cleanup,
+};
+
+static const uint32_t formats[] = {
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV21,
+ DRM_FORMAT_NV16,
+};
+
+int rcar_du_plane_init(struct rcar_du_device *rcdu)
+{
+ unsigned int i;
+
+ mutex_init(&rcdu->planes.lock);
+ rcdu->planes.free = 0xff;
+
+ rcdu->planes.alpha =
+ drm_property_create_range(rcdu->ddev, 0, "alpha", 0, 255);
+ if (rcdu->planes.alpha == NULL)
+ return -ENOMEM;
+
+ /* The color key is expressed as an RGB888 triplet stored in a 32-bit
+ * integer in XRGB8888 format. Bit 24 is used as a flag to disable (0)
+ * or enable source color keying (1).
+ */
+ rcdu->planes.colorkey =
+ drm_property_create_range(rcdu->ddev, 0, "colorkey",
+ 0, 0x01ffffff);
+ if (rcdu->planes.colorkey == NULL)
+ return -ENOMEM;
+
+ rcdu->planes.zpos =
+ drm_property_create_range(rcdu->ddev, 0, "zpos", 1, 7);
+ if (rcdu->planes.zpos == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) {
+ struct rcar_du_plane *plane = &rcdu->planes.planes[i];
+
+ plane->dev = rcdu;
+ plane->hwindex = -1;
+ plane->alpha = 255;
+ plane->colorkey = RCAR_DU_COLORKEY_NONE;
+ plane->zpos = 0;
+ }
+
+ return 0;
+}
+
+int rcar_du_plane_register(struct rcar_du_device *rcdu)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < RCAR_DU_NUM_KMS_PLANES; ++i) {
+ struct rcar_du_kms_plane *plane;
+
+ plane = devm_kzalloc(rcdu->dev, sizeof(*plane), GFP_KERNEL);
+ if (plane == NULL)
+ return -ENOMEM;
+
+ plane->hwplane = &rcdu->planes.planes[i + 2];
+ plane->hwplane->zpos = 1;
+
+ ret = drm_plane_init(rcdu->ddev, &plane->plane,
+ (1 << rcdu->num_crtcs) - 1,
+ &rcar_du_plane_funcs, formats,
+ ARRAY_SIZE(formats), false);
+ if (ret < 0)
+ return ret;
+
+ drm_object_attach_property(&plane->plane.base,
+ rcdu->planes.alpha, 255);
+ drm_object_attach_property(&plane->plane.base,
+ rcdu->planes.colorkey,
+ RCAR_DU_COLORKEY_NONE);
+ drm_object_attach_property(&plane->plane.base,
+ rcdu->planes.zpos, 1);
+ }
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_plane.h b/drivers/gpu/drm/rcar-du/rcar_du_plane.h
new file mode 100644
index 00000000000..5397dba2fe5
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_plane.h
@@ -0,0 +1,67 @@
+/*
+ * rcar_du_plane.h -- R-Car Display Unit Planes
+ *
+ * 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_PLANE_H__
+#define __RCAR_DU_PLANE_H__
+
+struct drm_crtc;
+struct drm_framebuffer;
+struct rcar_du_device;
+struct rcar_du_format_info;
+
+/* The RCAR DU has 8 hardware planes, shared between KMS planes and CRTCs. As
+ * using KMS planes requires at least one of the CRTCs being enabled, no more
+ * than 7 KMS planes can be available. We thus create 7 KMS planes and
+ * 9 software planes (one for each KMS planes and one for each CRTC).
+ */
+
+#define RCAR_DU_NUM_KMS_PLANES 7
+#define RCAR_DU_NUM_HW_PLANES 8
+#define RCAR_DU_NUM_SW_PLANES 9
+
+struct rcar_du_plane {
+ struct rcar_du_device *dev;
+ struct drm_crtc *crtc;
+
+ bool enabled;
+
+ int hwindex; /* 0-based, -1 means unused */
+ unsigned int alpha;
+ unsigned int colorkey;
+ unsigned int zpos;
+
+ const struct rcar_du_format_info *format;
+
+ unsigned long dma[2];
+ unsigned int pitch;
+
+ unsigned int width;
+ unsigned int height;
+
+ unsigned int src_x;
+ unsigned int src_y;
+ unsigned int dst_x;
+ unsigned int dst_y;
+};
+
+int rcar_du_plane_init(struct rcar_du_device *rcdu);
+int rcar_du_plane_register(struct rcar_du_device *rcdu);
+void rcar_du_plane_setup(struct rcar_du_plane *plane);
+void rcar_du_plane_update_base(struct rcar_du_plane *plane);
+void rcar_du_plane_compute_base(struct rcar_du_plane *plane,
+ struct drm_framebuffer *fb);
+int rcar_du_plane_reserve(struct rcar_du_plane *plane,
+ const struct rcar_du_format_info *format);
+void rcar_du_plane_release(struct rcar_du_plane *plane);
+
+#endif /* __RCAR_DU_PLANE_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_regs.h b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
new file mode 100644
index 00000000000..69f21f19b51
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
@@ -0,0 +1,445 @@
+/*
+ * rcar_du_regs.h -- R-Car Display Unit Registers Definitions
+ *
+ * Copyright (C) 2013 Renesas Electronics 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 version 2
+ * as published by the Free Software Foundation.
+ */
+
+#ifndef __RCAR_DU_REGS_H__
+#define __RCAR_DU_REGS_H__
+
+#define DISP2_REG_OFFSET 0x30000
+
+/* -----------------------------------------------------------------------------
+ * Display Control Registers
+ */
+
+#define DSYSR 0x00000 /* display 1 */
+#define D2SYSR 0x30000 /* display 2 */
+#define DSYSR_ILTS (1 << 29)
+#define DSYSR_DSEC (1 << 20)
+#define DSYSR_IUPD (1 << 16)
+#define DSYSR_DRES (1 << 9)
+#define DSYSR_DEN (1 << 8)
+#define DSYSR_TVM_MASTER (0 << 6)
+#define DSYSR_TVM_SWITCH (1 << 6)
+#define DSYSR_TVM_TVSYNC (2 << 6)
+#define DSYSR_TVM_MASK (3 << 6)
+#define DSYSR_SCM_INT_NONE (0 << 4)
+#define DSYSR_SCM_INT_SYNC (2 << 4)
+#define DSYSR_SCM_INT_VIDEO (3 << 4)
+
+#define DSMR 0x00004
+#define D2SMR 0x30004
+#define DSMR_VSPM (1 << 28)
+#define DSMR_ODPM (1 << 27)
+#define DSMR_DIPM_DISP (0 << 25)
+#define DSMR_DIPM_CSYNC (1 << 25)
+#define DSMR_DIPM_DE (3 << 25)
+#define DSMR_DIPM_MASK (3 << 25)
+#define DSMR_CSPM (1 << 24)
+#define DSMR_DIL (1 << 19)
+#define DSMR_VSL (1 << 18)
+#define DSMR_HSL (1 << 17)
+#define DSMR_DDIS (1 << 16)
+#define DSMR_CDEL (1 << 15)
+#define DSMR_CDEM_CDE (0 << 13)
+#define DSMR_CDEM_LOW (2 << 13)
+#define DSMR_CDEM_HIGH (3 << 13)
+#define DSMR_CDEM_MASK (3 << 13)
+#define DSMR_CDED (1 << 12)
+#define DSMR_ODEV (1 << 8)
+#define DSMR_CSY_VH_OR (0 << 6)
+#define DSMR_CSY_333 (2 << 6)
+#define DSMR_CSY_222 (3 << 6)
+#define DSMR_CSY_MASK (3 << 6)
+
+#define DSSR 0x00008
+#define D2SSR 0x30008
+#define DSSR_VC1FB_DSA0 (0 << 30)
+#define DSSR_VC1FB_DSA1 (1 << 30)
+#define DSSR_VC1FB_DSA2 (2 << 30)
+#define DSSR_VC1FB_INIT (3 << 30)
+#define DSSR_VC1FB_MASK (3 << 30)
+#define DSSR_VC0FB_DSA0 (0 << 28)
+#define DSSR_VC0FB_DSA1 (1 << 28)
+#define DSSR_VC0FB_DSA2 (2 << 28)
+#define DSSR_VC0FB_INIT (3 << 28)
+#define DSSR_VC0FB_MASK (3 << 28)
+#define DSSR_DFB(n) (1 << ((n)+15))
+#define DSSR_TVR (1 << 15)
+#define DSSR_FRM (1 << 14)
+#define DSSR_VBK (1 << 11)
+#define DSSR_RINT (1 << 9)
+#define DSSR_HBK (1 << 8)
+#define DSSR_ADC(n) (1 << ((n)-1))
+
+#define DSRCR 0x0000c
+#define D2SRCR 0x3000c
+#define DSRCR_TVCL (1 << 15)
+#define DSRCR_FRCL (1 << 14)
+#define DSRCR_VBCL (1 << 11)
+#define DSRCR_RICL (1 << 9)
+#define DSRCR_HBCL (1 << 8)
+#define DSRCR_ADCL(n) (1 << ((n)-1))
+#define DSRCR_MASK 0x0000cbff
+
+#define DIER 0x00010
+#define D2IER 0x30010
+#define DIER_TVE (1 << 15)
+#define DIER_FRE (1 << 14)
+#define DIER_VBE (1 << 11)
+#define DIER_RIE (1 << 9)
+#define DIER_HBE (1 << 8)
+#define DIER_ADCE(n) (1 << ((n)-1))
+
+#define CPCR 0x00014
+#define CPCR_CP4CE (1 << 19)
+#define CPCR_CP3CE (1 << 18)
+#define CPCR_CP2CE (1 << 17)
+#define CPCR_CP1CE (1 << 16)
+
+#define DPPR 0x00018
+#define DPPR_DPE(n) (1 << ((n)*4-1))
+#define DPPR_DPS(n, p) (((p)-1) << DPPR_DPS_SHIFT(n))
+#define DPPR_DPS_SHIFT(n) (((n)-1)*4)
+#define DPPR_BPP16 (DPPR_DPE(8) | DPPR_DPS(8, 1)) /* plane1 */
+#define DPPR_BPP32_P1 (DPPR_DPE(7) | DPPR_DPS(7, 1))
+#define DPPR_BPP32_P2 (DPPR_DPE(8) | DPPR_DPS(8, 2))
+#define DPPR_BPP32 (DPPR_BPP32_P1 | DPPR_BPP32_P2) /* plane1 & 2 */
+
+#define DEFR 0x00020
+#define D2EFR 0x30020
+#define DEFR_CODE (0x7773 << 16)
+#define DEFR_EXSL (1 << 12)
+#define DEFR_EXVL (1 << 11)
+#define DEFR_EXUP (1 << 5)
+#define DEFR_VCUP (1 << 4)
+#define DEFR_DEFE (1 << 0)
+
+#define DAPCR 0x00024
+#define DAPCR_CODE (0x7773 << 16)
+#define DAPCR_AP2E (1 << 4)
+#define DAPCR_AP1E (1 << 0)
+
+#define DCPCR 0x00028
+#define DCPCR_CODE (0x7773 << 16)
+#define DCPCR_CA2B (1 << 13)
+#define DCPCR_CD2F (1 << 12)
+#define DCPCR_DC2E (1 << 8)
+#define DCPCR_CAB (1 << 5)
+#define DCPCR_CDF (1 << 4)
+#define DCPCR_DCE (1 << 0)
+
+#define DEFR2 0x00034
+#define D2EFR2 0x30034
+#define DEFR2_CODE (0x7775 << 16)
+#define DEFR2_DEFE2G (1 << 0)
+
+#define DEFR3 0x00038
+#define D2EFR3 0x30038
+#define DEFR3_CODE (0x7776 << 16)
+#define DEFR3_EVDA (1 << 14)
+#define DEFR3_EVDM_1 (1 << 12)
+#define DEFR3_EVDM_2 (2 << 12)
+#define DEFR3_EVDM_3 (3 << 12)
+#define DEFR3_VMSM2_EMA (1 << 6)
+#define DEFR3_VMSM1_ENA (1 << 4)
+#define DEFR3_DEFE3 (1 << 0)
+
+#define DEFR4 0x0003c
+#define D2EFR4 0x3003c
+#define DEFR4_CODE (0x7777 << 16)
+#define DEFR4_LRUO (1 << 5)
+#define DEFR4_SPCE (1 << 4)
+
+#define DVCSR 0x000d0
+#define DVCSR_VCnFB2_DSA0(n) (0 << ((n)*2+16))
+#define DVCSR_VCnFB2_DSA1(n) (1 << ((n)*2+16))
+#define DVCSR_VCnFB2_DSA2(n) (2 << ((n)*2+16))
+#define DVCSR_VCnFB2_INIT(n) (3 << ((n)*2+16))
+#define DVCSR_VCnFB2_MASK(n) (3 << ((n)*2+16))
+#define DVCSR_VCnFB_DSA0(n) (0 << ((n)*2))
+#define DVCSR_VCnFB_DSA1(n) (1 << ((n)*2))
+#define DVCSR_VCnFB_DSA2(n) (2 << ((n)*2))
+#define DVCSR_VCnFB_INIT(n) (3 << ((n)*2))
+#define DVCSR_VCnFB_MASK(n) (3 << ((n)*2))
+
+#define DEFR5 0x000e0
+#define DEFR5_CODE (0x66 << 24)
+#define DEFR5_YCRGB2_DIS (0 << 14)
+#define DEFR5_YCRGB2_PRI1 (1 << 14)
+#define DEFR5_YCRGB2_PRI2 (2 << 14)
+#define DEFR5_YCRGB2_PRI3 (3 << 14)
+#define DEFR5_YCRGB2_MASK (3 << 14)
+#define DEFR5_YCRGB1_DIS (0 << 12)
+#define DEFR5_YCRGB1_PRI1 (1 << 12)
+#define DEFR5_YCRGB1_PRI2 (2 << 12)
+#define DEFR5_YCRGB1_PRI3 (3 << 12)
+#define DEFR5_YCRGB1_MASK (3 << 12)
+#define DEFR5_DEFE5 (1 << 0)
+
+#define DDLTR 0x000e4
+#define DDLTR_CODE (0x7766 << 16)
+#define DDLTR_DLAR2 (1 << 6)
+#define DDLTR_DLAY2 (1 << 5)
+#define DDLTR_DLAY1 (1 << 1)
+
+#define DEFR6 0x000e8
+#define DEFR6_CODE (0x7778 << 16)
+#define DEFR6_ODPM22_D2SMR (0 << 10)
+#define DEFR6_ODPM22_DISP (2 << 10)
+#define DEFR6_ODPM22_CDE (3 << 10)
+#define DEFR6_ODPM22_MASK (3 << 10)
+#define DEFR6_ODPM12_DSMR (0 << 8)
+#define DEFR6_ODPM12_DISP (2 << 8)
+#define DEFR6_ODPM12_CDE (3 << 8)
+#define DEFR6_ODPM12_MASK (3 << 8)
+#define DEFR6_TCNE2 (1 << 6)
+#define DEFR6_MLOS1 (1 << 2)
+#define DEFR6_DEFAULT (DEFR6_CODE | DEFR6_TCNE2)
+
+/* -----------------------------------------------------------------------------
+ * Display Timing Generation Registers
+ */
+
+#define HDSR 0x00040
+#define HDER 0x00044
+#define VDSR 0x00048
+#define VDER 0x0004c
+#define HCR 0x00050
+#define HSWR 0x00054
+#define VCR 0x00058
+#define VSPR 0x0005c
+#define EQWR 0x00060
+#define SPWR 0x00064
+#define CLAMPSR 0x00070
+#define CLAMPWR 0x00074
+#define DESR 0x00078
+#define DEWR 0x0007c
+
+/* -----------------------------------------------------------------------------
+ * Display Attribute Registers
+ */
+
+#define CP1TR 0x00080
+#define CP2TR 0x00084
+#define CP3TR 0x00088
+#define CP4TR 0x0008c
+
+#define DOOR 0x00090
+#define DOOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2))
+#define CDER 0x00094
+#define CDER_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2))
+#define BPOR 0x00098
+#define BPOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2))
+
+#define RINTOFSR 0x0009c
+
+#define DSHPR 0x000c8
+#define DSHPR_CODE (0x7776 << 16)
+#define DSHPR_PRIH (0xa << 4)
+#define DSHPR_PRIL_BPP16 (0x8 << 0)
+#define DSHPR_PRIL_BPP32 (0x9 << 0)
+
+/* -----------------------------------------------------------------------------
+ * Display Plane Registers
+ */
+
+#define PLANE_OFF 0x00100
+
+#define PnMR 0x00100 /* plane 1 */
+#define PnMR_VISL_VIN0 (0 << 26) /* use Video Input 0 */
+#define PnMR_VISL_VIN1 (1 << 26) /* use Video Input 1 */
+#define PnMR_VISL_VIN2 (2 << 26) /* use Video Input 2 */
+#define PnMR_VISL_VIN3 (3 << 26) /* use Video Input 3 */
+#define PnMR_YCDF_YUYV (1 << 20) /* YUYV format */
+#define PnMR_TC_R (0 << 17) /* Tranparent color is PnTC1R */
+#define PnMR_TC_CP (1 << 17) /* Tranparent color is color palette */
+#define PnMR_WAE (1 << 16) /* Wrap around Enable */
+#define PnMR_SPIM_TP (0 << 12) /* Transparent Color */
+#define PnMR_SPIM_ALP (1 << 12) /* Alpha Blending */
+#define PnMR_SPIM_EOR (2 << 12) /* EOR */
+#define PnMR_SPIM_TP_OFF (1 << 14) /* No Transparent Color */
+#define PnMR_CPSL_CP1 (0 << 8) /* Color Palette selected 1 */
+#define PnMR_CPSL_CP2 (1 << 8) /* Color Palette selected 2 */
+#define PnMR_CPSL_CP3 (2 << 8) /* Color Palette selected 3 */
+#define PnMR_CPSL_CP4 (3 << 8) /* Color Palette selected 4 */
+#define PnMR_DC (1 << 7) /* Display Area Change */
+#define PnMR_BM_MD (0 << 4) /* Manual Display Change Mode */
+#define PnMR_BM_AR (1 << 4) /* Auto Rendering Mode */
+#define PnMR_BM_AD (2 << 4) /* Auto Display Change Mode */
+#define PnMR_BM_VC (3 << 4) /* Video Capture Mode */
+#define PnMR_DDDF_8BPP (0 << 0) /* 8bit */
+#define PnMR_DDDF_16BPP (1 << 0) /* 16bit or 32bit */
+#define PnMR_DDDF_ARGB (2 << 0) /* ARGB */
+#define PnMR_DDDF_YC (3 << 0) /* YC */
+#define PnMR_DDDF_MASK (3 << 0)
+
+#define PnMWR 0x00104
+
+#define PnALPHAR 0x00108
+#define PnALPHAR_ABIT_1 (0 << 12)
+#define PnALPHAR_ABIT_0 (1 << 12)
+#define PnALPHAR_ABIT_X (2 << 12)
+
+#define PnDSXR 0x00110
+#define PnDSYR 0x00114
+#define PnDPXR 0x00118
+#define PnDPYR 0x0011c
+
+#define PnDSA0R 0x00120
+#define PnDSA1R 0x00124
+#define PnDSA2R 0x00128
+#define PnDSA_MASK 0xfffffff0
+
+#define PnSPXR 0x00130
+#define PnSPYR 0x00134
+#define PnWASPR 0x00138
+#define PnWAMWR 0x0013c
+
+#define PnBTR 0x00140
+
+#define PnTC1R 0x00144
+#define PnTC2R 0x00148
+#define PnTC3R 0x0014c
+#define PnTC3R_CODE (0x66 << 24)
+
+#define PnMLR 0x00150
+
+#define PnSWAPR 0x00180
+#define PnSWAPR_DIGN (1 << 4)
+#define PnSWAPR_SPQW (1 << 3)
+#define PnSWAPR_SPLW (1 << 2)
+#define PnSWAPR_SPWD (1 << 1)
+#define PnSWAPR_SPBY (1 << 0)
+
+#define PnDDCR 0x00184
+#define PnDDCR_CODE (0x7775 << 16)
+#define PnDDCR_LRGB1 (1 << 11)
+#define PnDDCR_LRGB0 (1 << 10)
+
+#define PnDDCR2 0x00188
+#define PnDDCR2_CODE (0x7776 << 16)
+#define PnDDCR2_NV21 (1 << 5)
+#define PnDDCR2_Y420 (1 << 4)
+#define PnDDCR2_DIVU (1 << 1)
+#define PnDDCR2_DIVY (1 << 0)
+
+#define PnDDCR4 0x00190
+#define PnDDCR4_CODE (0x7766 << 16)
+#define PnDDCR4_SDFS_RGB (0 << 4)
+#define PnDDCR4_SDFS_YC (5 << 4)
+#define PnDDCR4_SDFS_MASK (7 << 4)
+#define PnDDCR4_EDF_NONE (0 << 0)
+#define PnDDCR4_EDF_ARGB8888 (1 << 0)
+#define PnDDCR4_EDF_RGB888 (2 << 0)
+#define PnDDCR4_EDF_RGB666 (3 << 0)
+#define PnDDCR4_EDF_MASK (7 << 0)
+
+#define APnMR 0x0a100
+#define APnMR_WAE (1 << 16) /* Wrap around Enable */
+#define APnMR_DC (1 << 7) /* Display Area Change */
+#define APnMR_BM_MD (0 << 4) /* Manual Display Change Mode */
+#define APnMR_BM_AD (2 << 4) /* Auto Display Change Mode */
+
+#define APnMWR 0x0a104
+#define APnDSA0R 0x0a120
+#define APnDSA1R 0x0a124
+#define APnDSA2R 0x0a128
+#define APnMLR 0x0a150
+
+/* -----------------------------------------------------------------------------
+ * Display Capture Registers
+ */
+
+#define DCMWR 0x0c104
+#define DC2MWR 0x0c204
+#define DCSAR 0x0c120
+#define DC2SAR 0x0c220
+#define DCMLR 0x0c150
+#define DC2MLR 0x0c250
+
+/* -----------------------------------------------------------------------------
+ * Color Palette Registers
+ */
+
+#define CP1_000R 0x01000
+#define CP1_255R 0x013fc
+#define CP2_000R 0x02000
+#define CP2_255R 0x023fc
+#define CP3_000R 0x03000
+#define CP3_255R 0x033fc
+#define CP4_000R 0x04000
+#define CP4_255R 0x043fc
+
+/* -----------------------------------------------------------------------------
+ * External Synchronization Control Registers
+ */
+
+#define ESCR 0x10000
+#define ESCR2 0x31000
+#define ESCR_DCLKOINV (1 << 25)
+#define ESCR_DCLKSEL_DCLKIN (0 << 20)
+#define ESCR_DCLKSEL_CLKS (1 << 20)
+#define ESCR_DCLKSEL_MASK (1 << 20)
+#define ESCR_DCLKDIS (1 << 16)
+#define ESCR_SYNCSEL_OFF (0 << 8)
+#define ESCR_SYNCSEL_EXVSYNC (2 << 8)
+#define ESCR_SYNCSEL_EXHSYNC (3 << 8)
+#define ESCR_FRQSEL_MASK (0x3f << 0)
+
+#define OTAR 0x10004
+#define OTAR2 0x31004
+
+/* -----------------------------------------------------------------------------
+ * Dual Display Output Control Registers
+ */
+
+#define DORCR 0x11000
+#define DORCR_PG2T (1 << 30)
+#define DORCR_DK2S (1 << 28)
+#define DORCR_PG2D_DS1 (0 << 24)
+#define DORCR_PG2D_DS2 (1 << 24)
+#define DORCR_PG2D_FIX0 (2 << 24)
+#define DORCR_PG2D_DOOR (3 << 24)
+#define DORCR_PG2D_MASK (3 << 24)
+#define DORCR_DR1D (1 << 21)
+#define DORCR_PG1D_DS1 (0 << 16)
+#define DORCR_PG1D_DS2 (1 << 16)
+#define DORCR_PG1D_FIX0 (2 << 16)
+#define DORCR_PG1D_DOOR (3 << 16)
+#define DORCR_PG1D_MASK (3 << 16)
+#define DORCR_RGPV (1 << 4)
+#define DORCR_DPRS (1 << 0)
+
+#define DPTSR 0x11004
+#define DPTSR_PnDK(n) (1 << ((n) + 16))
+#define DPTSR_PnTS(n) (1 << (n))
+
+#define DAPTSR 0x11008
+#define DAPTSR_APnDK(n) (1 << ((n) + 16))
+#define DAPTSR_APnTS(n) (1 << (n))
+
+#define DS1PR 0x11020
+#define DS2PR 0x11024
+
+/* -----------------------------------------------------------------------------
+ * YC-RGB Conversion Coefficient Registers
+ */
+
+#define YNCR 0x11080
+#define YNOR 0x11084
+#define CRNOR 0x11088
+#define CBNOR 0x1108c
+#define RCRCR 0x11090
+#define GCRCR 0x11094
+#define GCBCR 0x11098
+#define BCBCR 0x1109c
+
+#endif /* __RCAR_DU_REGS_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vga.c b/drivers/gpu/drm/rcar-du/rcar_du_vga.c
new file mode 100644
index 00000000000..327289ec380
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_vga.c
@@ -0,0 +1,149 @@
+/*
+ * rcar_du_vga.c -- R-Car Display Unit VGA DAC and Connector
+ *
+ * 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 <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "rcar_du_drv.h"
+#include "rcar_du_kms.h"
+#include "rcar_du_vga.h"
+
+/* -----------------------------------------------------------------------------
+ * Connector
+ */
+
+static int rcar_du_vga_connector_get_modes(struct drm_connector *connector)
+{
+ return 0;
+}
+
+static int rcar_du_vga_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs connector_helper_funcs = {
+ .get_modes = rcar_du_vga_connector_get_modes,
+ .mode_valid = rcar_du_vga_connector_mode_valid,
+ .best_encoder = rcar_du_connector_best_encoder,
+};
+
+static void rcar_du_vga_connector_destroy(struct drm_connector *connector)
+{
+ drm_sysfs_connector_remove(connector);
+ drm_connector_cleanup(connector);
+}
+
+static enum drm_connector_status
+rcar_du_vga_connector_detect(struct drm_connector *connector, bool force)
+{
+ return connector_status_unknown;
+}
+
+static const struct drm_connector_funcs connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .detect = rcar_du_vga_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = rcar_du_vga_connector_destroy,
+};
+
+static int rcar_du_vga_connector_init(struct rcar_du_device *rcdu,
+ struct rcar_du_encoder *renc)
+{
+ struct rcar_du_connector *rcon;
+ struct drm_connector *connector;
+ int ret;
+
+ rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL);
+ if (rcon == NULL)
+ return -ENOMEM;
+
+ connector = &rcon->connector;
+ connector->display_info.width_mm = 0;
+ connector->display_info.height_mm = 0;
+
+ ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
+ DRM_MODE_CONNECTOR_VGA);
+ if (ret < 0)
+ return ret;
+
+ drm_connector_helper_add(connector, &connector_helper_funcs);
+ ret = drm_sysfs_connector_add(connector);
+ if (ret < 0)
+ return ret;
+
+ drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF);
+ drm_object_property_set_value(&connector->base,
+ rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF);
+
+ ret = drm_mode_connector_attach_encoder(connector, &renc->encoder);
+ if (ret < 0)
+ return ret;
+
+ connector->encoder = &renc->encoder;
+ rcon->encoder = renc;
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Encoder
+ */
+
+static void rcar_du_vga_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+static bool rcar_du_vga_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
+ .dpms = rcar_du_vga_encoder_dpms,
+ .mode_fixup = rcar_du_vga_encoder_mode_fixup,
+ .prepare = rcar_du_encoder_mode_prepare,
+ .commit = rcar_du_encoder_mode_commit,
+ .mode_set = rcar_du_encoder_mode_set,
+};
+
+static const struct drm_encoder_funcs encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+int rcar_du_vga_init(struct rcar_du_device *rcdu,
+ const struct rcar_du_encoder_vga_data *data,
+ unsigned int output)
+{
+ struct rcar_du_encoder *renc;
+ int ret;
+
+ renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL);
+ if (renc == NULL)
+ return -ENOMEM;
+
+ renc->output = output;
+
+ ret = drm_encoder_init(rcdu->ddev, &renc->encoder, &encoder_funcs,
+ DRM_MODE_ENCODER_DAC);
+ if (ret < 0)
+ return ret;
+
+ drm_encoder_helper_add(&renc->encoder, &encoder_helper_funcs);
+
+ return rcar_du_vga_connector_init(rcdu, renc);
+}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vga.h b/drivers/gpu/drm/rcar-du/rcar_du_vga.h
new file mode 100644
index 00000000000..66b4d2d7190
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_vga.h
@@ -0,0 +1,24 @@
+/*
+ * rcar_du_vga.h -- R-Car Display Unit VGA DAC and Connector
+ *
+ * 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_VGA_H__
+#define __RCAR_DU_VGA_H__
+
+struct rcar_du_device;
+struct rcar_du_encoder_vga_data;
+
+int rcar_du_vga_init(struct rcar_du_device *rcdu,
+ const struct rcar_du_encoder_vga_data *data,
+ unsigned int output);
+
+#endif /* __RCAR_DU_VGA_H__ */