diff mbox

[v3] drm: Renesas R-Car Display Unit DRM driver

Message ID 1370314420-26524-1-git-send-email-laurent.pinchart+renesas@ideasonboard.com (mailing list archive)
State New, archived
Headers show

Commit Message

Laurent Pinchart June 4, 2013, 2:53 a.m. UTC
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>
---
Hi Dave,

There has been no comment on v2, so I'd like to get v3 in 3.11 is possible.
The driver depends on the GEM CMA DMA-BUF patches I've sent earlier. If those
can't make it to 3.11 I can sent a v4 with DRM PRIME support removed, and add
it back for 3.12.

Changes since v1:

 - Use drm_encoder_cleanup() directly as .destroy handlers
 - Enable alpha blending support
 - Don't re-reserve hardware plane at each update
 - Fix planes allocation for multiplanar formats
 - Add DRM PRIME support
 - Fix race condition between page flip request and handler
 - Add configurable z-order support for planes
 - Support configurable color keying for planes
 - Update plane format after releasing hardware planes
 - Fix register access for global registers
 - Fix plane index wrap-around for multi-planar overlays

Changes since v2:

 - Enable the DE signal
 - Split hardware and KMS planes
 - Add support for the second CRTC
 - Name the encoder platform data union
 - Fix crash when disabling an already disabled plane
 - Prepare/unprepare clock
 - Centralize DU device core resource management
 - Reorganize CRTC start/stop and power management code
 - Create common encoder and connector structures
 - Add support for cloned mode on DU1
 - Add XRGB1555 format support
 - Add plane property to set global alpha value
 - Don't modify mode active size in encoder fixup
 - Use the mode active size in mode set
 - Take offsets into account in the mode_set_base handler
 - Fix plane source position configuration
 - Don't clean up mode setting if it hasn't been initialized
 - Enable extended range for display timings

 drivers/gpu/drm/Kconfig                 |   2 +
 drivers/gpu/drm/Makefile                |   1 +
 drivers/gpu/drm/rcar-du/Kconfig         |   9 +
 drivers/gpu/drm/rcar-du/Makefile        |   8 +
 drivers/gpu/drm/rcar-du/rcar_du_crtc.c  | 602 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/rcar-du/rcar_du_crtc.h  |  50 +++
 drivers/gpu/drm/rcar-du/rcar_du_drv.c   | 325 +++++++++++++++++
 drivers/gpu/drm/rcar-du/rcar_du_drv.h   |  66 ++++
 drivers/gpu/drm/rcar-du/rcar_du_kms.c   | 245 +++++++++++++
 drivers/gpu/drm/rcar-du/rcar_du_kms.h   |  59 ++++
 drivers/gpu/drm/rcar-du/rcar_du_lvds.c  | 216 ++++++++++++
 drivers/gpu/drm/rcar-du/rcar_du_lvds.h  |  24 ++
 drivers/gpu/drm/rcar-du/rcar_du_plane.c | 507 +++++++++++++++++++++++++++
 drivers/gpu/drm/rcar-du/rcar_du_plane.h |  67 ++++
 drivers/gpu/drm/rcar-du/rcar_du_regs.h  | 445 +++++++++++++++++++++++
 drivers/gpu/drm/rcar-du/rcar_du_vga.c   | 149 ++++++++
 drivers/gpu/drm/rcar-du/rcar_du_vga.h   |  24 ++
 include/linux/platform_data/rcar-du.h   |  54 +++
 18 files changed, 2853 insertions(+)
 create mode 100644 drivers/gpu/drm/rcar-du/Kconfig
 create mode 100644 drivers/gpu/drm/rcar-du/Makefile
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_crtc.c
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_crtc.h
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_drv.c
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_drv.h
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_kms.c
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_kms.h
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvds.c
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvds.h
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_plane.c
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_plane.h
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_regs.h
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_vga.c
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_vga.h
 create mode 100644 include/linux/platform_data/rcar-du.h

Comments

Daniel Vetter June 4, 2013, 2:12 p.m. UTC | #1
On Tue, Jun 04, 2013 at 04:53:40AM +0200, Laurent Pinchart wrote:
> 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>

Ok, I've done a little review, and the driver looks rather nice. With a
simpler driver like this the drm boilerplate sticks out more, so I've
dropped a few grumblings about that. But I've also spotted 3 little things
which imo should be fixed before merging. Comments inline below.

Cheers, Daniel

> ---
> Hi Dave,
> 
> There has been no comment on v2, so I'd like to get v3 in 3.11 is possible.
> The driver depends on the GEM CMA DMA-BUF patches I've sent earlier. If those
> can't make it to 3.11 I can sent a v4 with DRM PRIME support removed, and add
> it back for 3.12.
> 
> Changes since v1:
> 
>  - Use drm_encoder_cleanup() directly as .destroy handlers
>  - Enable alpha blending support
>  - Don't re-reserve hardware plane at each update
>  - Fix planes allocation for multiplanar formats
>  - Add DRM PRIME support
>  - Fix race condition between page flip request and handler
>  - Add configurable z-order support for planes
>  - Support configurable color keying for planes
>  - Update plane format after releasing hardware planes
>  - Fix register access for global registers
>  - Fix plane index wrap-around for multi-planar overlays
> 
> Changes since v2:
> 
>  - Enable the DE signal
>  - Split hardware and KMS planes
>  - Add support for the second CRTC
>  - Name the encoder platform data union
>  - Fix crash when disabling an already disabled plane
>  - Prepare/unprepare clock
>  - Centralize DU device core resource management
>  - Reorganize CRTC start/stop and power management code
>  - Create common encoder and connector structures
>  - Add support for cloned mode on DU1
>  - Add XRGB1555 format support
>  - Add plane property to set global alpha value
>  - Don't modify mode active size in encoder fixup
>  - Use the mode active size in mode set
>  - Take offsets into account in the mode_set_base handler
>  - Fix plane source position configuration
>  - Don't clean up mode setting if it hasn't been initialized
>  - Enable extended range for display timings
> 
>  drivers/gpu/drm/Kconfig                 |   2 +
>  drivers/gpu/drm/Makefile                |   1 +
>  drivers/gpu/drm/rcar-du/Kconfig         |   9 +
>  drivers/gpu/drm/rcar-du/Makefile        |   8 +
>  drivers/gpu/drm/rcar-du/rcar_du_crtc.c  | 602 ++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/rcar-du/rcar_du_crtc.h  |  50 +++
>  drivers/gpu/drm/rcar-du/rcar_du_drv.c   | 325 +++++++++++++++++
>  drivers/gpu/drm/rcar-du/rcar_du_drv.h   |  66 ++++
>  drivers/gpu/drm/rcar-du/rcar_du_kms.c   | 245 +++++++++++++
>  drivers/gpu/drm/rcar-du/rcar_du_kms.h   |  59 ++++
>  drivers/gpu/drm/rcar-du/rcar_du_lvds.c  | 216 ++++++++++++
>  drivers/gpu/drm/rcar-du/rcar_du_lvds.h  |  24 ++
>  drivers/gpu/drm/rcar-du/rcar_du_plane.c | 507 +++++++++++++++++++++++++++
>  drivers/gpu/drm/rcar-du/rcar_du_plane.h |  67 ++++
>  drivers/gpu/drm/rcar-du/rcar_du_regs.h  | 445 +++++++++++++++++++++++
>  drivers/gpu/drm/rcar-du/rcar_du_vga.c   | 149 ++++++++
>  drivers/gpu/drm/rcar-du/rcar_du_vga.h   |  24 ++
>  include/linux/platform_data/rcar-du.h   |  54 +++
>  18 files changed, 2853 insertions(+)
>  create mode 100644 drivers/gpu/drm/rcar-du/Kconfig
>  create mode 100644 drivers/gpu/drm/rcar-du/Makefile
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_crtc.c
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_crtc.h
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_drv.c
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_drv.h
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_kms.c
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_kms.h
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvds.c
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvds.h
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_plane.c
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_plane.h
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_regs.h
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_vga.c
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_vga.h
>  create mode 100644 include/linux/platform_data/rcar-du.h
> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index b16c50e..71ca63b 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 1ecbe5b..801bcaf 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 0000000..2eb7d23
> --- /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 && ARCH_SHMOBILE
> +	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 0000000..7333c00
> --- /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 0000000..c66fa4c
> --- /dev/null
> +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
> @@ -0,0 +1,602 @@
> +/*
> + * 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);
> +	}
> +}

You seem to be a prime user for atomic modeset stuff ;-) Have you looked
already a bit into sensible additions for the crtc helpers to make that
possible? Maybe a global modeset_prepare/commit hook?

> +
> +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.
> +	 */

Should we add that to crtc helpers, instead of the current "just try to
smash the old config on top of the ill-defined hw state after a failed
modeset"?

> +	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;
> +	struct timeval vblanktime;
> +	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;
> +
> +	event->event.sequence =
> +		drm_vblank_count_and_time(dev, rcrtc->index, &vblanktime);
> +	event->event.tv_sec = vblanktime.tv_sec;
> +	event->event.tv_usec = vblanktime.tv_usec;
> +
> +	spin_lock_irqsave(&dev->event_lock, flags);
> +	list_add_tail(&event->base.link, &event->base.file_priv->event_list);
> +	wake_up_interruptible(&event->base.file_priv->event_wait);
> +	spin_unlock_irqrestore(&dev->event_lock, flags);
> +

This should use the drm_send_vblank_event helper.

> +	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 0000000..2a0365b
> --- /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 0000000..003b34e
> --- /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);
> +}

Blergh, I hate our legacy vblank code which forces kms driver to jump
through int pipe -> struct drm_crtc * hoops.

> +
> +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 0000000..193cc59
> --- /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 0000000..9c63f39
> --- /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;

This looks strange. It should at least include

> +		encoder->possible_clones = 1 << i;

where i counts encoders to say that you can clone itself (userspace might
get confused, haven't checked how throughout the modeset ddx is). But it
sounds like rcar can clone encoders pretty freely (as long as they're
using crtc 0), so maybe you want to use something like drm/i915 does?

We smash all cloneable encoders into one groub with a
intel_encoder->cloneable flag and then allow cloning any cloneable encoder
to any other cloneable encoder with intel_encoder_clones in
intel_display.c

possible_clones is a bit a ill-defined part of the kms api, but I think we
still should strive for consistency. Maybe the modesetting ddx should also
grow a warning if the possible_clones mask doesn't make too much sense.


> +	}
> +
> +	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;
> +}

Hm, no fbdev legacy support or do I miss something?

> 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 0000000..e4d8db0
> --- /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 0000000..7aefe72
> --- /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 0000000..b47f832
> --- /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 0000000..a65f81d
> --- /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;
> +}

All our set_property functions have the same ugly if-ladder, I guess we
should look into reworking the interfaces so that the drm core can
directly call into the relevant callback.

But that's certainly not something which needs to be fixed right away.

A slightly more important discussion imo is whether we should standardize
plane/crtc properties a bit more. Personally I think we can wait a bit
with that until we have more drivers and generic users ... Worst case we
end up with a bit of backwards compat cruft in individual drivers.

> +
> +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 0000000..5397dba
> --- /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 0000000..69f21f1
> --- /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 0000000..2e488dd
> --- /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 drm_add_modes_noedid(connector, 1280, 768);
> +}

This (and the dummy detect function below) looks a bit funny, since it
essentially overrides the default behaviour already provided by the crtc
helpers. Until rcar has at least proper detect support for VGA I'd just
kill this and use the connector force support (and manually adding the
right resolutions).

So just a return 0; here feels better imo.

> +
> +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 0000000..66b4d2d
> --- /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__ */
> diff --git a/include/linux/platform_data/rcar-du.h b/include/linux/platform_data/rcar-du.h
> new file mode 100644
> index 0000000..80587fd
> --- /dev/null
> +++ b/include/linux/platform_data/rcar-du.h
> @@ -0,0 +1,54 @@
> +/*
> + * rcar_du.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_H__
> +#define __RCAR_DU_H__
> +
> +#include <drm/drm_mode.h>
> +
> +enum rcar_du_encoder_type {
> +	RCAR_DU_ENCODER_UNUSED = 0,
> +	RCAR_DU_ENCODER_VGA,
> +	RCAR_DU_ENCODER_LVDS,
> +};
> +
> +struct rcar_du_panel_data {
> +	unsigned int width_mm;		/* Panel width in mm */
> +	unsigned int height_mm;		/* Panel height in mm */
> +	struct drm_mode_modeinfo mode;
> +};
> +
> +struct rcar_du_encoder_lvds_data {
> +	struct rcar_du_panel_data panel;
> +};
> +
> +struct rcar_du_encoder_vga_data {
> +	/* TODO: Add DDC information for EDID retrieval */
> +};
> +
> +struct rcar_du_encoder_data {
> +	enum rcar_du_encoder_type encoder;
> +	unsigned int output;
> +
> +	union {
> +		struct rcar_du_encoder_lvds_data lvds;
> +		struct rcar_du_encoder_vga_data vga;
> +	} u;
> +};
> +
> +struct rcar_du_platform_data {
> +	struct rcar_du_encoder_data *encoders;
> +	unsigned int num_encoders;
> +};
> +
> +#endif /* __RCAR_DU_H__ */
> -- 
> Regards,
> 
> Laurent Pinchart
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel
Laurent Pinchart June 4, 2013, 6:03 p.m. UTC | #2
Hi Daniel,

On Tuesday 04 June 2013 16:12:36 Daniel Vetter wrote:
> On Tue, Jun 04, 2013 at 04:53:40AM +0200, Laurent Pinchart wrote:
> > 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>
> 
> Ok, I've done a little review, and the driver looks rather nice.

Thank you.

> With a simpler driver like this the drm boilerplate sticks out more, so I've
> dropped a few grumblings about that. But I've also spotted 3 little things
> which imo should be fixed before merging. Comments inline below.
> 
> Cheers, Daniel

[snip]

> > 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 0000000..c66fa4c
> > --- /dev/null
> > +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c

[snip]

> > +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);
> > +	}
> > +}
> 
> You seem to be a prime user for atomic modeset stuff ;-) Have you looked
> already a bit into sensible additions for the crtc helpers to make that
> possible? Maybe a global modeset_prepare/commit hook?

Not yet. That's somewhere in my to-do list, but it's growing too long :-( I 
need to finish CDF first.

[snip]

> > +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.
> > +	 */
> 
> Should we add that to crtc helpers, instead of the current "just try to
> smash the old config on top of the ill-defined hw state after a failed
> modeset"?

It would probably make sense to add a rollback operation to undo the prepare 
operation, or maybe just a rollback/commit flag to the commit operation. We 
would still need to smash the old config back though, as the rollback 
operation shouldn't be expected to handle encoders and connectors.

While we're at it, shouldn't we make drivers report supported formats for the 
main frame buffer, like we do for planes ? That would allow catching format 
errors before calling the prepare operation.

> > +	rcar_du_put(rcdu);
> > +	return ret;
> > +}

[snip]

> > +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;
> > +	struct timeval vblanktime;
> > +	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;
> > +
> > +	event->event.sequence =
> > +		drm_vblank_count_and_time(dev, rcrtc->index, &vblanktime);
> > +	event->event.tv_sec = vblanktime.tv_sec;
> > +	event->event.tv_usec = vblanktime.tv_usec;
> > +
> > +	spin_lock_irqsave(&dev->event_lock, flags);
> > +	list_add_tail(&event->base.link, &event->base.file_priv->event_list);
> > +	wake_up_interruptible(&event->base.file_priv->event_wait);
> > +	spin_unlock_irqrestore(&dev->event_lock, flags);
> > +
> 
> This should use the drm_send_vblank_event helper.

What bothers me about drm_send_vblank_event() is that it calls 
drm_vblank_count_and_time() with the events lock unnecessarily held. I can 
live with that for now, I'll fix the driver to use the helper.

> > +	drm_vblank_put(dev, rcrtc->index);
> > +}

[snip]

> > 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 0000000..003b34e
> > --- /dev/null
> > +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c

[snip]

> > +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);
> > +}
> 
> Blergh, I hate our legacy vblank code which forces kms driver to jump
> through int pipe -> struct drm_crtc * hoops.

How would you like to fix it ? :-)

[snip]

> > 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 0000000..9c63f39
> > --- /dev/null
> > +++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c

[snip]

> > +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;
> 
> This looks strange. It should at least include
> 
> > +		encoder->possible_clones = 1 << i;
> 
> where i counts encoders to say that you can clone itself (userspace might
> get confused, haven't checked how throughout the modeset ddx is). But it
> sounds like rcar can clone encoders pretty freely (as long as they're
> using crtc 0), so maybe you want to use something like drm/i915 does?

The device has two outputs, 0 and 1. Output 0 can be driven by CRTC 0 only, 
and output 1 can be driven by CRTC 0 or CRTC 1.

> We smash all cloneable encoders into one groub with a
> intel_encoder->cloneable flag and then allow cloning any cloneable encoder
> to any other cloneable encoder with intel_encoder_clones in
> intel_display.c
> 
> possible_clones is a bit a ill-defined part of the kms api, but I think we
> still should strive for consistency. Maybe the modesetting ddx should also
> grow a warning if the possible_clones mask doesn't make too much sense.

I haven't been able to find an authoritative source of documentation regarding 
whether the possible_clones field should include the encoder itself. That 
should definitely be documented, I can fix the driver accordingly.

> > +	}
> > +
> > +	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;
> > +}
> 
> Hm, no fbdev legacy support or do I miss something?

Not yet. It's scheduled for a future update, for v3.12.

[snip]

> > 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 0000000..a65f81d
> > --- /dev/null
> > +++ b/drivers/gpu/drm/rcar-du/rcar_du_plane.c

[snip]

> > +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;
> > +}
> 
> All our set_property functions have the same ugly if-ladder, I guess we
> should look into reworking the interfaces so that the drm core can
> directly call into the relevant callback.
> 
> But that's certainly not something which needs to be fixed right away.

We have designed and implemented the control framework in V4L2 to solve the 
same issue. It turned out to be a pretty complex piece of code though.

> A slightly more important discussion imo is whether we should standardize
> plane/crtc properties a bit more. Personally I think we can wait a bit
> with that until we have more drivers and generic users ... Worst case we
> end up with a bit of backwards compat cruft in individual drivers.

I agree, but it should probably not be delayed for too much longer.

[snip]

> > 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 0000000..2e488dd
> > --- /dev/null
> > +++ b/drivers/gpu/drm/rcar-du/rcar_du_vga.c

[snip]

> > +static int rcar_du_vga_connector_get_modes(struct drm_connector
> > *connector)
> > +{
> > +	return drm_add_modes_noedid(connector, 1280, 768);
> > +}
> 
> This (and the dummy detect function below) looks a bit funny, since it
> essentially overrides the default behaviour already provided by the crtc
> helpers. Until rcar has at least proper detect support for VGA

I would add that but the DDC signals are not connected on the boards I have 
access to :-/

> I'd just kill this and use the connector force support (and manually adding
> the right resolutions).

Looks like that's a candidate for better documentation... How does force 
support work ?

> So just a return 0; here feels better imo.
Daniel Vetter June 4, 2013, 6:36 p.m. UTC | #3
On Tue, Jun 4, 2013 at 8:03 PM, Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
> Hi Daniel,
>
> On Tuesday 04 June 2013 16:12:36 Daniel Vetter wrote:
>> On Tue, Jun 04, 2013 at 04:53:40AM +0200, Laurent Pinchart wrote:

[snip]

>> Should we add that to crtc helpers, instead of the current "just try to
>> smash the old config on top of the ill-defined hw state after a failed
>> modeset"?
>
> It would probably make sense to add a rollback operation to undo the prepare
> operation, or maybe just a rollback/commit flag to the commit operation. We
> would still need to smash the old config back though, as the rollback
> operation shouldn't be expected to handle encoders and connectors.
>
> While we're at it, shouldn't we make drivers report supported formats for the
> main frame buffer, like we do for planes ? That would allow catching format
> errors before calling the prepare operation.

Yeah, I've noticed that one, too. I guess we could tackle that as part
of an eventual "make the implicit primary plane a bit more explict"
project. For now I'm not too offended by the duplication of checks.


>> This should use the drm_send_vblank_event helper.
>
> What bothers me about drm_send_vblank_event() is that it calls
> drm_vblank_count_and_time() with the events lock unnecessarily held. I can
> live with that for now, I'll fix the driver to use the helper.

Most other drivers protect a bit of other state with that lock, so
makes sense to hold it outside already. So not sure whether we should
optimize this much ...

>> > +   drm_vblank_put(dev, rcrtc->index);
>> > +}
>
> [snip]
>
>> > 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 0000000..003b34e
>> > --- /dev/null
>> > +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
>
> [snip]
>
>> > +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);
>> > +}
>>
>> Blergh, I hate our legacy vblank code which forces kms driver to jump
>> through int pipe -> struct drm_crtc * hoops.
>
> How would you like to fix it ? :-)

Haven't looked at the details, but the first step I have in mind is to
switch all drm core -> driver and driver -> vblank helper interfaces
from int pipe to struct drm_crtc * pointers for kms drivers. That
would allow us to implement at least sane locking for the vblank wait
ioctl (by grabbing the crtc mutex).

My plan was to split things by copy&pasting new kms functions and then
garbage-collecting all unnused features for the ums code (iirc no ums
driver ever supported more than 2 crtcs, vblank events or
high-precision timestamps).

Once that's in place we can look into more stuff. One of the things I
want to play with is support for hw timestamp and vblank counters
(also for pageflips). Then we don't have to enable the vblank
interrupt so often and more important should be able to turn it of
right away without loosing precision due to the potential vblank irq
vs. vblank irq off race.

>> where i counts encoders to say that you can clone itself (userspace might
>> get confused, haven't checked how throughout the modeset ddx is). But it
>> sounds like rcar can clone encoders pretty freely (as long as they're
>> using crtc 0), so maybe you want to use something like drm/i915 does?
>
> The device has two outputs, 0 and 1. Output 0 can be driven by CRTC 0 only,
> and output 1 can be driven by CRTC 0 or CRTC 1.

Ah, that explains it, I've missed the context that we only have 2
crtc/encoder pairs ;-)

>> We smash all cloneable encoders into one groub with a
>> intel_encoder->cloneable flag and then allow cloning any cloneable encoder
>> to any other cloneable encoder with intel_encoder_clones in
>> intel_display.c
>>
>> possible_clones is a bit a ill-defined part of the kms api, but I think we
>> still should strive for consistency. Maybe the modesetting ddx should also
>> grow a warning if the possible_clones mask doesn't make too much sense.
>
> I haven't been able to find an authoritative source of documentation regarding
> whether the possible_clones field should include the encoder itself. That
> should definitely be documented, I can fix the driver accordingly.

Yeah, sounds like something worth clarifying. I'd vote for the
self-clone bit to be set (I'm biased though, that's what i915 does). I
guess we could even enforce consistency by putting this into the drm
encoders.

Since the modesetting driver seems to not care too much I guess we can
fix that later on, imo not something to block merging rcar on.

[snip]

>> > +static int rcar_du_vga_connector_get_modes(struct drm_connector
>> > *connector)
>> > +{
>> > +   return drm_add_modes_noedid(connector, 1280, 768);
>> > +}
>>
>> This (and the dummy detect function below) looks a bit funny, since it
>> essentially overrides the default behaviour already provided by the crtc
>> helpers. Until rcar has at least proper detect support for VGA
>
> I would add that but the DDC signals are not connected on the boards I have
> access to :-/
>
>> I'd just kill this and use the connector force support (and manually adding
>> the right resolutions).
>
> Looks like that's a candidate for better documentation... How does force
> support work ?

Grep for DRM_FORCE_ON, iirc it can be set on the commandline, where
you can also force a specific mode. The best I could find wrt docs is
the kerneldoc for drm_mode_parse_command_line_for_connector. With a
bit more reading it looks like it's intermingled with the fbdev helper
code, but should be fairly easy to extract and used by your driver.

Cheers, Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
Laurent Pinchart June 5, 2013, 1:51 a.m. UTC | #4
Hi Daniel,

On Tuesday 04 June 2013 20:36:20 Daniel Vetter wrote:
> On Tue, Jun 4, 2013 at 8:03 PM, Laurent Pinchart wrote:
> > On Tuesday 04 June 2013 16:12:36 Daniel Vetter wrote:
> >> On Tue, Jun 04, 2013 at 04:53:40AM +0200, Laurent Pinchart wrote:
>
> [snip]
> 
> >> Should we add that to crtc helpers, instead of the current "just try to
> >> smash the old config on top of the ill-defined hw state after a failed
> >> modeset"?
> > 
> > It would probably make sense to add a rollback operation to undo the
> > prepare operation, or maybe just a rollback/commit flag to the commit
> > operation. We would still need to smash the old config back though, as
> > the rollback operation shouldn't be expected to handle encoders and
> > connectors.
> > 
> > While we're at it, shouldn't we make drivers report supported formats for
> > the main frame buffer, like we do for planes ? That would allow catching
> > format errors before calling the prepare operation.
> 
> Yeah, I've noticed that one, too. I guess we could tackle that as part
> of an eventual "make the implicit primary plane a bit more explict"
> project. For now I'm not too offended by the duplication of checks.

It would be nice to treat the primary plane as all the other planes. Several 
embedded display engines don't make the primary plane special and just paint 
the background with a plain color when the enabled planes don't cover the 
entire display.

> >> This should use the drm_send_vblank_event helper.
> > 
> > What bothers me about drm_send_vblank_event() is that it calls
> > drm_vblank_count_and_time() with the events lock unnecessarily held. I can
> > live with that for now, I'll fix the driver to use the helper.
> 
> Most other drivers protect a bit of other state with that lock, so
> makes sense to hold it outside already. So not sure whether we should
> optimize this much ...

Probably not :-)

> >> > +   drm_vblank_put(dev, rcrtc->index);
> >> > +}
> > 
> > [snip]
> > 
> >> > 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 0000000..003b34e
> >> > --- /dev/null
> >> > +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
> > 
> > [snip]
> > 
> >> > +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);
> >> > +}
> >> 
> >> Blergh, I hate our legacy vblank code which forces kms driver to jump
> >> through int pipe -> struct drm_crtc * hoops.
> > 
> > How would you like to fix it ? :-)
> 
> Haven't looked at the details, but the first step I have in mind is to
> switch all drm core -> driver and driver -> vblank helper interfaces from
> int pipe to struct drm_crtc * pointers for kms drivers. That would allow us
> to implement at least sane locking for the vblank wait ioctl (by grabbing
> the crtc mutex).
> 
> My plan was to split things by copy&pasting new kms functions and then
> garbage-collecting all unnused features for the ums code (iirc no ums driver
> ever supported more than 2 crtcs, vblank events or high-precision
> timestamps).
> 
> Once that's in place we can look into more stuff. One of the things I want
> to play with is support for hw timestamp and vblank counters (also for
> pageflips). Then we don't have to enable the vblank interrupt so often and
> more important should be able to turn it of right away without loosing
> precision due to the potential vblank irq vs. vblank irq off race.
> 
> >> where i counts encoders to say that you can clone itself (userspace might
> >> get confused, haven't checked how throughout the modeset ddx is). But it
> >> sounds like rcar can clone encoders pretty freely (as long as they're
> >> using crtc 0), so maybe you want to use something like drm/i915 does?
> > 
> > The device has two outputs, 0 and 1. Output 0 can be driven by CRTC 0
> > only, and output 1 can be driven by CRTC 0 or CRTC 1.
> 
> Ah, that explains it, I've missed the context that we only have 2
> crtc/encoder pairs ;-)

It wasn't particularly explicit :-)

> >> We smash all cloneable encoders into one groub with a
> >> intel_encoder->cloneable flag and then allow cloning any cloneable
> >> encoder to any other cloneable encoder with intel_encoder_clones in
> >> intel_display.c
> >> 
> >> possible_clones is a bit a ill-defined part of the kms api, but I think
> >> we still should strive for consistency. Maybe the modesetting ddx should
> >> also grow a warning if the possible_clones mask doesn't make too much
> >> sense.
> > 
> > I haven't been able to find an authoritative source of documentation
> > regarding whether the possible_clones field should include the encoder
> > itself. That should definitely be documented, I can fix the driver
> > accordingly.
>
> Yeah, sounds like something worth clarifying. I'd vote for the self-clone
> bit to be set (I'm biased though, that's what i915 does). I guess we could
> even enforce consistency by putting this into the drm encoders.

I don't have a strong opinion on the color of this particular bikeshed, but it 
would be nice to hear what other developers think.

> Since the modesetting driver seems to not care too much I guess we can
> fix that later on, imo not something to block merging rcar on.

OK, thanks. I'll fix that (and the documentation) later after we decide on 
whether an encoder can self-clone.

> [snip]
> 
> >> > +static int rcar_du_vga_connector_get_modes(struct drm_connector
> >> > *connector)
> >> > +{
> >> > +   return drm_add_modes_noedid(connector, 1280, 768);
> >> > +}
> >> 
> >> This (and the dummy detect function below) looks a bit funny, since it
> >> essentially overrides the default behaviour already provided by the crtc
> >> helpers. Until rcar has at least proper detect support for VGA
> > 
> > I would add that but the DDC signals are not connected on the boards I
> > have access to :-/
> > 
> >> I'd just kill this and use the connector force support (and manually
> >> adding the right resolutions).
> > 
> > Looks like that's a candidate for better documentation... How does force
> > support work ?
> 
> Grep for DRM_FORCE_ON, iirc it can be set on the commandline, where you can
> also force a specific mode. The best I could find wrt docs is the kerneldoc
> for drm_mode_parse_command_line_for_connector. With a bit more reading it
> looks like it's intermingled with the fbdev helper code, but should be
> fairly easy to extract and used by your driver.

It makes sense to force the connector state from command line, but I'm not 
sure if the same mechanism is the best solution here. As the driver has no way 
to know the connector state, the best we can do is guess what modes are 
supported. I can just return 0 in the get_modes handler, but then the core 
will not call drm_add_modes_noedid(), and modes will need to be added 
manually.

Is your point that for a board on which the VGA connector state can't be 
detected, the user should always be responsible for adding all the modes 
supported by the VGA monitor on the command line ?
Daniel Vetter June 5, 2013, 8:55 a.m. UTC | #5
On Wed, Jun 05, 2013 at 03:51:53AM +0200, Laurent Pinchart wrote:
> Hi Daniel,
> 
> On Tuesday 04 June 2013 20:36:20 Daniel Vetter wrote:
> > On Tue, Jun 4, 2013 at 8:03 PM, Laurent Pinchart wrote:
> > > On Tuesday 04 June 2013 16:12:36 Daniel Vetter wrote:
> > >> On Tue, Jun 04, 2013 at 04:53:40AM +0200, Laurent Pinchart wrote:

[snip]

> > >> > +static int rcar_du_vga_connector_get_modes(struct drm_connector
> > >> > *connector)
> > >> > +{
> > >> > +   return drm_add_modes_noedid(connector, 1280, 768);
> > >> > +}
> > >> 
> > >> This (and the dummy detect function below) looks a bit funny, since it
> > >> essentially overrides the default behaviour already provided by the crtc
> > >> helpers. Until rcar has at least proper detect support for VGA
> > > 
> > > I would add that but the DDC signals are not connected on the boards I
> > > have access to :-/
> > > 
> > >> I'd just kill this and use the connector force support (and manually
> > >> adding the right resolutions).
> > > 
> > > Looks like that's a candidate for better documentation... How does force
> > > support work ?
> > 
> > Grep for DRM_FORCE_ON, iirc it can be set on the commandline, where you can
> > also force a specific mode. The best I could find wrt docs is the kerneldoc
> > for drm_mode_parse_command_line_for_connector. With a bit more reading it
> > looks like it's intermingled with the fbdev helper code, but should be
> > fairly easy to extract and used by your driver.
> 
> It makes sense to force the connector state from command line, but I'm not 
> sure if the same mechanism is the best solution here. As the driver has no way 
> to know the connector state, the best we can do is guess what modes are 
> supported. I can just return 0 in the get_modes handler, but then the core 
> will not call drm_add_modes_noedid(), and modes will need to be added 
> manually.
> 
> Is your point that for a board on which the VGA connector state can't be 
> detected, the user should always be responsible for adding all the modes 
> supported by the VGA monitor on the command line ?

My point is that we already have both an established code for
connected outputs without EDID to add fallback modes and means to force
connectors to certain states. Your code here seems to reinvent that wheel,
so I wonder what we should/need to improve in the common code to suit your
needs. A few ideas:
- Untangling the connector forcing code from the fbdev helper so that you
  can use it.
- Exposing the connector state forcing through sysfs so that it's
  runtime-adjustable.
- Adding fallback modes for connectors in the unknonw state (imo too much
  risk in breaking something else).

Thinking about this some more I'd vote for the new sysfs file to expose
connector forcing at runtime. With that it'd boil down to 1024x756 vs.
1280x768 for the default fallback mode. And that could be fixed with the
EDID quirk support. Although that looks like it would benefit from a
per-connector sysfs file, too ;-)

Cheers, Daniel
Ville Syrjala June 5, 2013, 11:57 a.m. UTC | #6
On Wed, Jun 05, 2013 at 03:51:53AM +0200, Laurent Pinchart wrote:
> Hi Daniel,
> 
> On Tuesday 04 June 2013 20:36:20 Daniel Vetter wrote:
> > On Tue, Jun 4, 2013 at 8:03 PM, Laurent Pinchart wrote:
> > > On Tuesday 04 June 2013 16:12:36 Daniel Vetter wrote:
> > >> On Tue, Jun 04, 2013 at 04:53:40AM +0200, Laurent Pinchart wrote:
> >
> > [snip]
> > 
> > >> Should we add that to crtc helpers, instead of the current "just try to
> > >> smash the old config on top of the ill-defined hw state after a failed
> > >> modeset"?
> > > 
> > > It would probably make sense to add a rollback operation to undo the
> > > prepare operation, or maybe just a rollback/commit flag to the commit
> > > operation. We would still need to smash the old config back though, as
> > > the rollback operation shouldn't be expected to handle encoders and
> > > connectors.
> > > 
> > > While we're at it, shouldn't we make drivers report supported formats for
> > > the main frame buffer, like we do for planes ? That would allow catching
> > > format errors before calling the prepare operation.
> > 
> > Yeah, I've noticed that one, too. I guess we could tackle that as part
> > of an eventual "make the implicit primary plane a bit more explict"
> > project. For now I'm not too offended by the duplication of checks.
> 
> It would be nice to treat the primary plane as all the other planes. Several 
> embedded display engines don't make the primary plane special and just paint 
> the background with a plain color when the enabled planes don't cover the 
> entire display.

Same deal for some intel hardware (at least partially). Anyways, my
current plan is that we fix it for the atomic pageflip/modeset stuff.
Ie. I don't even want expose the CRTC scanout stuff in the new atomic
API.
Alex Deucher June 5, 2013, 1:10 p.m. UTC | #7
On Tue, Jun 4, 2013 at 9:51 PM, Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
> Hi Daniel,
>
> On Tuesday 04 June 2013 20:36:20 Daniel Vetter wrote:
>> On Tue, Jun 4, 2013 at 8:03 PM, Laurent Pinchart wrote:
>> > On Tuesday 04 June 2013 16:12:36 Daniel Vetter wrote:
>> >> On Tue, Jun 04, 2013 at 04:53:40AM +0200, Laurent Pinchart wrote:
>>
>> [snip]
>>
>> >> Should we add that to crtc helpers, instead of the current "just try to
>> >> smash the old config on top of the ill-defined hw state after a failed
>> >> modeset"?
>> >
>> > It would probably make sense to add a rollback operation to undo the
>> > prepare operation, or maybe just a rollback/commit flag to the commit
>> > operation. We would still need to smash the old config back though, as
>> > the rollback operation shouldn't be expected to handle encoders and
>> > connectors.
>> >
>> > While we're at it, shouldn't we make drivers report supported formats for
>> > the main frame buffer, like we do for planes ? That would allow catching
>> > format errors before calling the prepare operation.
>>
>> Yeah, I've noticed that one, too. I guess we could tackle that as part
>> of an eventual "make the implicit primary plane a bit more explict"
>> project. For now I'm not too offended by the duplication of checks.
>
> It would be nice to treat the primary plane as all the other planes. Several
> embedded display engines don't make the primary plane special and just paint
> the background with a plain color when the enabled planes don't cover the
> entire display.
>
>> >> This should use the drm_send_vblank_event helper.
>> >
>> > What bothers me about drm_send_vblank_event() is that it calls
>> > drm_vblank_count_and_time() with the events lock unnecessarily held. I can
>> > live with that for now, I'll fix the driver to use the helper.
>>
>> Most other drivers protect a bit of other state with that lock, so
>> makes sense to hold it outside already. So not sure whether we should
>> optimize this much ...
>
> Probably not :-)
>
>> >> > +   drm_vblank_put(dev, rcrtc->index);
>> >> > +}
>> >
>> > [snip]
>> >
>> >> > 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 0000000..003b34e
>> >> > --- /dev/null
>> >> > +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
>> >
>> > [snip]
>> >
>> >> > +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);
>> >> > +}
>> >>
>> >> Blergh, I hate our legacy vblank code which forces kms driver to jump
>> >> through int pipe -> struct drm_crtc * hoops.
>> >
>> > How would you like to fix it ? :-)
>>
>> Haven't looked at the details, but the first step I have in mind is to
>> switch all drm core -> driver and driver -> vblank helper interfaces from
>> int pipe to struct drm_crtc * pointers for kms drivers. That would allow us
>> to implement at least sane locking for the vblank wait ioctl (by grabbing
>> the crtc mutex).
>>
>> My plan was to split things by copy&pasting new kms functions and then
>> garbage-collecting all unnused features for the ums code (iirc no ums driver
>> ever supported more than 2 crtcs, vblank events or high-precision
>> timestamps).
>>
>> Once that's in place we can look into more stuff. One of the things I want
>> to play with is support for hw timestamp and vblank counters (also for
>> pageflips). Then we don't have to enable the vblank interrupt so often and
>> more important should be able to turn it of right away without loosing
>> precision due to the potential vblank irq vs. vblank irq off race.
>>
>> >> where i counts encoders to say that you can clone itself (userspace might
>> >> get confused, haven't checked how throughout the modeset ddx is). But it
>> >> sounds like rcar can clone encoders pretty freely (as long as they're
>> >> using crtc 0), so maybe you want to use something like drm/i915 does?
>> >
>> > The device has two outputs, 0 and 1. Output 0 can be driven by CRTC 0
>> > only, and output 1 can be driven by CRTC 0 or CRTC 1.
>>
>> Ah, that explains it, I've missed the context that we only have 2
>> crtc/encoder pairs ;-)
>
> It wasn't particularly explicit :-)
>
>> >> We smash all cloneable encoders into one groub with a
>> >> intel_encoder->cloneable flag and then allow cloning any cloneable
>> >> encoder to any other cloneable encoder with intel_encoder_clones in
>> >> intel_display.c
>> >>
>> >> possible_clones is a bit a ill-defined part of the kms api, but I think
>> >> we still should strive for consistency. Maybe the modesetting ddx should
>> >> also grow a warning if the possible_clones mask doesn't make too much
>> >> sense.
>> >
>> > I haven't been able to find an authoritative source of documentation
>> > regarding whether the possible_clones field should include the encoder
>> > itself. That should definitely be documented, I can fix the driver
>> > accordingly.
>>
>> Yeah, sounds like something worth clarifying. I'd vote for the self-clone
>> bit to be set (I'm biased though, that's what i915 does). I guess we could
>> even enforce consistency by putting this into the drm encoders.
>
> I don't have a strong opinion on the color of this particular bikeshed, but it
> would be nice to hear what other developers think.
>
>> Since the modesetting driver seems to not care too much I guess we can
>> fix that later on, imo not something to block merging rcar on.
>
> OK, thanks. I'll fix that (and the documentation) later after we decide on
> whether an encoder can self-clone.

To me at least, it doesn't make sense that an encoder can clone
itself.  If an encoder is already in use, trying to clone itself would
only lead to confusion and possible bugs (make sure some code path
doesn't try and reprogram the encoder again, etc.).

Alex


>
>> [snip]
>>
>> >> > +static int rcar_du_vga_connector_get_modes(struct drm_connector
>> >> > *connector)
>> >> > +{
>> >> > +   return drm_add_modes_noedid(connector, 1280, 768);
>> >> > +}
>> >>
>> >> This (and the dummy detect function below) looks a bit funny, since it
>> >> essentially overrides the default behaviour already provided by the crtc
>> >> helpers. Until rcar has at least proper detect support for VGA
>> >
>> > I would add that but the DDC signals are not connected on the boards I
>> > have access to :-/
>> >
>> >> I'd just kill this and use the connector force support (and manually
>> >> adding the right resolutions).
>> >
>> > Looks like that's a candidate for better documentation... How does force
>> > support work ?
>>
>> Grep for DRM_FORCE_ON, iirc it can be set on the commandline, where you can
>> also force a specific mode. The best I could find wrt docs is the kerneldoc
>> for drm_mode_parse_command_line_for_connector. With a bit more reading it
>> looks like it's intermingled with the fbdev helper code, but should be
>> fairly easy to extract and used by your driver.
>
> It makes sense to force the connector state from command line, but I'm not
> sure if the same mechanism is the best solution here. As the driver has no way
> to know the connector state, the best we can do is guess what modes are
> supported. I can just return 0 in the get_modes handler, but then the core
> will not call drm_add_modes_noedid(), and modes will need to be added
> manually.
>
> Is your point that for a board on which the VGA connector state can't be
> detected, the user should always be responsible for adding all the modes
> supported by the VGA monitor on the command line ?
>
> --
> Regards,
>
> Laurent Pinchart
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel
Daniel Vetter June 6, 2013, 1:12 p.m. UTC | #8
On Wed, Jun 5, 2013 at 3:10 PM, Alex Deucher <alexdeucher@gmail.com> wrote:
> To me at least, it doesn't make sense that an encoder can clone
> itself.  If an encoder is already in use, trying to clone itself would
> only lead to confusion and possible bugs (make sure some code path
> doesn't try and reprogram the encoder again, etc.).

For me the possible_clones mask is just the set of encoders which can
together share a crtc (presuming that crtc is indeed in all of the
possible_crtcs mask of each encoder). From that pov it makes imo sense
that a given encoder itself can always be with itself on the same crtc
;-)

Otoh setcrtc doesn't care one bit about encoders (the crtc helpers do
internally use them, but it's not interface). And the possible_clones
stuff is by far not enough to describe all hw restrictions. So tbh I
don't care which way we go (or whether we indeed keep on using this
much at all).
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
Alex Deucher June 6, 2013, 1:21 p.m. UTC | #9
On Thu, Jun 6, 2013 at 9:12 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Wed, Jun 5, 2013 at 3:10 PM, Alex Deucher <alexdeucher@gmail.com> wrote:
>> To me at least, it doesn't make sense that an encoder can clone
>> itself.  If an encoder is already in use, trying to clone itself would
>> only lead to confusion and possible bugs (make sure some code path
>> doesn't try and reprogram the encoder again, etc.).
>
> For me the possible_clones mask is just the set of encoders which can
> together share a crtc (presuming that crtc is indeed in all of the
> possible_crtcs mask of each encoder). From that pov it makes imo sense
> that a given encoder itself can always be with itself on the same crtc
> ;-)
>
> Otoh setcrtc doesn't care one bit about encoders (the crtc helpers do
> internally use them, but it's not interface). And the possible_clones
> stuff is by far not enough to describe all hw restrictions. So tbh I
> don't care which way we go (or whether we indeed keep on using this
> much at all).

Same.  I can go either way.

Alex

> -Daniel
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> +41 (0) 79 365 57 48 - http://blog.ffwll.ch
Laurent Pinchart June 7, 2013, 7:23 a.m. UTC | #10
On Thursday 06 June 2013 09:21:35 Alex Deucher wrote:
> On Thu, Jun 6, 2013 at 9:12 AM, Daniel Vetter wrote:
> > On Wed, Jun 5, 2013 at 3:10 PM, Alex Deucher wrote:
> >> To me at least, it doesn't make sense that an encoder can clone
> >> itself.  If an encoder is already in use, trying to clone itself would
> >> only lead to confusion and possible bugs (make sure some code path
> >> doesn't try and reprogram the encoder again, etc.).
> > 
> > For me the possible_clones mask is just the set of encoders which can
> > together share a crtc (presuming that crtc is indeed in all of the
> > possible_crtcs mask of each encoder). From that pov it makes imo sense
> > that a given encoder itself can always be with itself on the same crtc
> > ;-)
> > 
> > Otoh setcrtc doesn't care one bit about encoders (the crtc helpers do
> > internally use them, but it's not interface). And the possible_clones
> > stuff is by far not enough to describe all hw restrictions. So tbh I
> > don't care which way we go (or whether we indeed keep on using this
> > much at all).
> 
> Same.  I can go either way.

So what's the agreement ? :-)
Laurent Pinchart June 7, 2013, 7:44 a.m. UTC | #11
Hi Daniel,

On Wednesday 05 June 2013 10:55:05 Daniel Vetter wrote:
> On Wed, Jun 05, 2013 at 03:51:53AM +0200, Laurent Pinchart wrote:
> > On Tuesday 04 June 2013 20:36:20 Daniel Vetter wrote:
> > > On Tue, Jun 4, 2013 at 8:03 PM, Laurent Pinchart wrote:
> > > > On Tuesday 04 June 2013 16:12:36 Daniel Vetter wrote:
> > > >> On Tue, Jun 04, 2013 at 04:53:40AM +0200, Laurent Pinchart wrote:
> [snip]
> 
> > > >> > +static int rcar_du_vga_connector_get_modes(struct drm_connector
> > > >> > *connector)
> > > >> > +{
> > > >> > +   return drm_add_modes_noedid(connector, 1280, 768);
> > > >> > +}
> > > >> 
> > > >> This (and the dummy detect function below) looks a bit funny, since
> > > >> it essentially overrides the default behaviour already provided by
> > > >> the crtc helpers. Until rcar has at least proper detect support for
> > > >> VGA
> > > > 
> > > > I would add that but the DDC signals are not connected on the boards I
> > > > have access to :-/
> > > > 
> > > >> I'd just kill this and use the connector force support (and manually
> > > >> adding the right resolutions).
> > > > 
> > > > Looks like that's a candidate for better documentation... How does
> > > > force support work ?
> > > 
> > > Grep for DRM_FORCE_ON, iirc it can be set on the commandline, where you
> > > can also force a specific mode. The best I could find wrt docs is the
> > > kerneldoc for drm_mode_parse_command_line_for_connector. With a bit more
> > > reading it looks like it's intermingled with the fbdev helper code, but
> > > should be fairly easy to extract and used by your driver.
> > 
> > It makes sense to force the connector state from command line, but I'm not
> > sure if the same mechanism is the best solution here. As the driver has no
> > way to know the connector state, the best we can do is guess what modes
> > are supported. I can just return 0 in the get_modes handler, but then the
> > core will not call drm_add_modes_noedid(), and modes will need to be
> > added manually.
> > 
> > Is your point that for a board on which the VGA connector state can't be
> > detected, the user should always be responsible for adding all the modes
> > supported by the VGA monitor on the command line ?
> 
> My point is that we already have both an established code for connected
> outputs without EDID to add fallback modes and means to force connectors to
> certain states. Your code here seems to reinvent that wheel, so I wonder
> what we should/need to improve in the common code to suit your needs.

The currently available code might suit my needs, it might just be that I fail 
to see how to use it properly.

Regarding the "code for connected outputs without EDID to add fallback modes" 
you're referring to, is that 

        if (count == 0 && connector->status == connector_status_connected)
                count = drm_add_modes_noedid(connector, 1024, 768);

in drm_helper_probe_single_connector_modes() ? That function will only be 
called if the connector status is connector_status_connected. There are two 
ways to enforce that:

- returning connector_status_connected from the connector detect() operation, 
which seems to defeat the purpose of having connector_status_unknown 
completely.

- setting connector->force to DRM_FORCE_ON. Are drivers allowed to do so 
themselves at initialization time ? Once again that seems to defeat the 
purpose of connector_status_unknown.

> A few ideas:
> - Untangling the connector forcing code from the fbdev helper so that you
>   can use it.
> - Exposing the connector state forcing through sysfs so that it's
>   runtime-adjustable.

My main concern here is that fbcon won't be available if we delay setting 
force mode until userspace is ready..

> - Adding fallback modes for connectors in the unknonw state (imo too much
>   risk in breaking something else).

Could you please elaborate on what you thing it could break ?

> Thinking about this some more I'd vote for the new sysfs file to expose
> connector forcing at runtime. With that it'd boil down to 1024x756 vs.
> 1280x768 for the default fallback mode. And that could be fixed with the
> EDID quirk support. Although that looks like it would benefit from a
> per-connector sysfs file, too ;-)
Daniel Vetter June 7, 2013, 8:50 a.m. UTC | #12
On Fri, Jun 07, 2013 at 09:44:45AM +0200, Laurent Pinchart wrote:
> Hi Daniel,
> 
> On Wednesday 05 June 2013 10:55:05 Daniel Vetter wrote:
> > On Wed, Jun 05, 2013 at 03:51:53AM +0200, Laurent Pinchart wrote:
> > > On Tuesday 04 June 2013 20:36:20 Daniel Vetter wrote:
> > > > On Tue, Jun 4, 2013 at 8:03 PM, Laurent Pinchart wrote:
> > > > > On Tuesday 04 June 2013 16:12:36 Daniel Vetter wrote:
> > > > >> On Tue, Jun 04, 2013 at 04:53:40AM +0200, Laurent Pinchart wrote:
> > [snip]
> > 
> > > > >> > +static int rcar_du_vga_connector_get_modes(struct drm_connector
> > > > >> > *connector)
> > > > >> > +{
> > > > >> > +   return drm_add_modes_noedid(connector, 1280, 768);
> > > > >> > +}
> > > > >> 
> > > > >> This (and the dummy detect function below) looks a bit funny, since
> > > > >> it essentially overrides the default behaviour already provided by
> > > > >> the crtc helpers. Until rcar has at least proper detect support for
> > > > >> VGA
> > > > > 
> > > > > I would add that but the DDC signals are not connected on the boards I
> > > > > have access to :-/
> > > > > 
> > > > >> I'd just kill this and use the connector force support (and manually
> > > > >> adding the right resolutions).
> > > > > 
> > > > > Looks like that's a candidate for better documentation... How does
> > > > > force support work ?
> > > > 
> > > > Grep for DRM_FORCE_ON, iirc it can be set on the commandline, where you
> > > > can also force a specific mode. The best I could find wrt docs is the
> > > > kerneldoc for drm_mode_parse_command_line_for_connector. With a bit more
> > > > reading it looks like it's intermingled with the fbdev helper code, but
> > > > should be fairly easy to extract and used by your driver.
> > > 
> > > It makes sense to force the connector state from command line, but I'm not
> > > sure if the same mechanism is the best solution here. As the driver has no
> > > way to know the connector state, the best we can do is guess what modes
> > > are supported. I can just return 0 in the get_modes handler, but then the
> > > core will not call drm_add_modes_noedid(), and modes will need to be
> > > added manually.
> > > 
> > > Is your point that for a board on which the VGA connector state can't be
> > > detected, the user should always be responsible for adding all the modes
> > > supported by the VGA monitor on the command line ?
> > 
> > My point is that we already have both an established code for connected
> > outputs without EDID to add fallback modes and means to force connectors to
> > certain states. Your code here seems to reinvent that wheel, so I wonder
> > what we should/need to improve in the common code to suit your needs.
> 
> The currently available code might suit my needs, it might just be that I fail 
> to see how to use it properly.
> 
> Regarding the "code for connected outputs without EDID to add fallback modes" 
> you're referring to, is that 
> 
>         if (count == 0 && connector->status == connector_status_connected)
>                 count = drm_add_modes_noedid(connector, 1024, 768);
> 
> in drm_helper_probe_single_connector_modes() ? That function will only be 
> called if the connector status is connector_status_connected. There are two 
> ways to enforce that:
> 
> - returning connector_status_connected from the connector detect() operation, 
> which seems to defeat the purpose of having connector_status_unknown 
> completely.

We might want to add such a default mode also for unknown, I'm not sure.
Userspace policy is to first try to light up any connected outputs, and if
there's none try to light up any unknown outputs. Not sure whether
userspace (i.e. X) will automatically add a default mode. fbcon might also
handle this less gracefully.

Personally I'm ok with extending this to unknown, it shouldn't really
hurt (since we already try really hard not to leak unknown anywhere
visible).

> - setting connector->force to DRM_FORCE_ON. Are drivers allowed to do so 
> themselves at initialization time ? Once again that seems to defeat the 
> purpose of connector_status_unknown.

Atm you can set that with the kernel video= cmdline option, but only if
fbcon force this to be parsed. I think exposing ->force to userspace
somewhere in sysfs would make lots of sense. Drivers imo shouldn't ever
need to touch this. And there's a callback interface so that drivers can
intercept forced connector state, e.g. when they need to set up some stuff
which they otherwise would only do in their ->detect callback.

> > A few ideas:
> > - Untangling the connector forcing code from the fbdev helper so that you
> >   can use it.
> > - Exposing the connector state forcing through sysfs so that it's
> >   runtime-adjustable.
> 
> My main concern here is that fbcon won't be available if we delay setting 
> force mode until userspace is ready..

There's also a kernel option. Since we're talking about a VGA connector I
don't think we could do a hardwired quirk here.

> > - Adding fallback modes for connectors in the unknonw state (imo too much
> >   risk in breaking something else).
> 
> Could you please elaborate on what you thing it could break ?

Changed my mind ;-) Ajax recently said that X only looks at unknown
connectors if there's nothing better around, so if we stick to that policy
we should be good.

> > Thinking about this some more I'd vote for the new sysfs file to expose
> > connector forcing at runtime. With that it'd boil down to 1024x756 vs.
> > 1280x768 for the default fallback mode. And that could be fixed with the
> > EDID quirk support. Although that looks like it would benefit from a
> > per-connector sysfs file, too ;-)

Cheers, Daniel
Daniel Vetter June 7, 2013, 8:51 a.m. UTC | #13
On Fri, Jun 07, 2013 at 09:23:58AM +0200, Laurent Pinchart wrote:
> On Thursday 06 June 2013 09:21:35 Alex Deucher wrote:
> > On Thu, Jun 6, 2013 at 9:12 AM, Daniel Vetter wrote:
> > > On Wed, Jun 5, 2013 at 3:10 PM, Alex Deucher wrote:
> > >> To me at least, it doesn't make sense that an encoder can clone
> > >> itself.  If an encoder is already in use, trying to clone itself would
> > >> only lead to confusion and possible bugs (make sure some code path
> > >> doesn't try and reprogram the encoder again, etc.).
> > > 
> > > For me the possible_clones mask is just the set of encoders which can
> > > together share a crtc (presuming that crtc is indeed in all of the
> > > possible_crtcs mask of each encoder). From that pov it makes imo sense
> > > that a given encoder itself can always be with itself on the same crtc
> > > ;-)
> > > 
> > > Otoh setcrtc doesn't care one bit about encoders (the crtc helpers do
> > > internally use them, but it's not interface). And the possible_clones
> > > stuff is by far not enough to describe all hw restrictions. So tbh I
> > > don't care which way we go (or whether we indeed keep on using this
> > > much at all).
> > 
> > Same.  I can go either way.
> 
> So what's the agreement ? :-)

I think officially "meh". Keep your current code and we'll try to fix this
once it blows up somewhere for real ;-)
-Daniel
diff mbox

Patch

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index b16c50e..71ca63b 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 1ecbe5b..801bcaf 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 0000000..2eb7d23
--- /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 && ARCH_SHMOBILE
+	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 0000000..7333c00
--- /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 0000000..c66fa4c
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
@@ -0,0 +1,602 @@ 
+/*
+ * 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;
+	struct timeval vblanktime;
+	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;
+
+	event->event.sequence =
+		drm_vblank_count_and_time(dev, rcrtc->index, &vblanktime);
+	event->event.tv_sec = vblanktime.tv_sec;
+	event->event.tv_usec = vblanktime.tv_usec;
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	list_add_tail(&event->base.link, &event->base.file_priv->event_list);
+	wake_up_interruptible(&event->base.file_priv->event_wait);
+	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 0000000..2a0365b
--- /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 0000000..003b34e
--- /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 0000000..193cc59
--- /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 0000000..9c63f39
--- /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 0000000..e4d8db0
--- /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 0000000..7aefe72
--- /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 0000000..b47f832
--- /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 0000000..a65f81d
--- /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 0000000..5397dba
--- /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 0000000..69f21f1
--- /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 0000000..2e488dd
--- /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 drm_add_modes_noedid(connector, 1280, 768);
+}
+
+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 0000000..66b4d2d
--- /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__ */
diff --git a/include/linux/platform_data/rcar-du.h b/include/linux/platform_data/rcar-du.h
new file mode 100644
index 0000000..80587fd
--- /dev/null
+++ b/include/linux/platform_data/rcar-du.h
@@ -0,0 +1,54 @@ 
+/*
+ * rcar_du.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_H__
+#define __RCAR_DU_H__
+
+#include <drm/drm_mode.h>
+
+enum rcar_du_encoder_type {
+	RCAR_DU_ENCODER_UNUSED = 0,
+	RCAR_DU_ENCODER_VGA,
+	RCAR_DU_ENCODER_LVDS,
+};
+
+struct rcar_du_panel_data {
+	unsigned int width_mm;		/* Panel width in mm */
+	unsigned int height_mm;		/* Panel height in mm */
+	struct drm_mode_modeinfo mode;
+};
+
+struct rcar_du_encoder_lvds_data {
+	struct rcar_du_panel_data panel;
+};
+
+struct rcar_du_encoder_vga_data {
+	/* TODO: Add DDC information for EDID retrieval */
+};
+
+struct rcar_du_encoder_data {
+	enum rcar_du_encoder_type encoder;
+	unsigned int output;
+
+	union {
+		struct rcar_du_encoder_lvds_data lvds;
+		struct rcar_du_encoder_vga_data vga;
+	} u;
+};
+
+struct rcar_du_platform_data {
+	struct rcar_du_encoder_data *encoders;
+	unsigned int num_encoders;
+};
+
+#endif /* __RCAR_DU_H__ */