The R-Car DU supports writing back the display unit output to memory.
Add support for that feature using a V4L2 device.

Signed-off-by: Laurent Pinchart <laurent.pinchart+rene...@ideasonboard.com>
---
 drivers/gpu/drm/rcar-du/Makefile        |   3 +-
 drivers/gpu/drm/rcar-du/rcar_du_crtc.c  |  47 +-
 drivers/gpu/drm/rcar-du/rcar_du_crtc.h  |   7 +
 drivers/gpu/drm/rcar-du/rcar_du_drv.c   |   8 +
 drivers/gpu/drm/rcar-du/rcar_du_drv.h   |   4 +
 drivers/gpu/drm/rcar-du/rcar_du_kms.c   |   8 +-
 drivers/gpu/drm/rcar-du/rcar_du_kms.h   |   5 +
 drivers/gpu/drm/rcar-du/rcar_du_regs.h  |   4 +
 drivers/gpu/drm/rcar-du/rcar_du_wback.c | 792 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/rcar-du/rcar_du_wback.h | 102 ++++
 10 files changed, 969 insertions(+), 11 deletions(-)
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_wback.c
 create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_wback.h

Hello,

This is (to my knowledge) the first attempt to use V4L2 as a capture API for
the write-back feature or a DRM/KMS device.

Overall the implementation isn't very intrusive and keeps the V4L2 API
implementation pretty much in a single source file. I'm quite happy with the
architecture, let's now see how the cross-subsystem review will affect that
mood :-)

diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile
index 05de1c4097af..65050435cbb9 100644
--- a/drivers/gpu/drm/rcar-du/Makefile
+++ b/drivers/gpu/drm/rcar-du/Makefile
@@ -5,7 +5,8 @@ rcar-du-drm-y := rcar_du_crtc.o \
                 rcar_du_kms.o \
                 rcar_du_lvdscon.o \
                 rcar_du_plane.o \
-                rcar_du_vgacon.o
+                rcar_du_vgacon.o \
+                rcar_du_wback.o
 
 rcar-du-drm-$(CONFIG_DRM_RCAR_HDMI)    += rcar_du_hdmicon.o \
                                           rcar_du_hdmienc.o
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c 
b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
index 9e72133bb64b..479f14886ec1 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
@@ -28,6 +28,7 @@
 #include "rcar_du_kms.h"
 #include "rcar_du_plane.h"
 #include "rcar_du_regs.h"
+#include "rcar_du_wback.h"
 
 static u32 rcar_du_crtc_read(struct rcar_du_crtc *rcrtc, u32 reg)
 {
@@ -68,7 +69,7 @@ static void rcar_du_crtc_clr_set(struct rcar_du_crtc *rcrtc, 
u32 reg,
        rcar_du_write(rcdu, rcrtc->mmio_offset + reg, (value & ~clr) | set);
 }
 
-static int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc)
+int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc)
 {
        int ret;
 
@@ -93,7 +94,7 @@ error_clock:
        return ret;
 }
 
-static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
+void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
 {
        rcar_du_group_put(rcrtc->group);
 
@@ -173,6 +174,13 @@ static void rcar_du_crtc_set_display_timing(struct 
rcar_du_crtc *rcrtc)
 
        rcar_du_crtc_write(rcrtc, DESR,  mode->htotal - mode->hsync_start);
        rcar_du_crtc_write(rcrtc, DEWR,  mode->hdisplay);
+
+       /* Program the raster interrupt offset (used by the writeback state
+        * machine) to generate an interrupt as far as possible from the start
+        * of vertical blanking.
+        */
+       rcar_du_crtc_write(rcrtc, RINTOFSR,
+                          max(mode->crtc_vdisplay - mode->crtc_vtotal / 2, 1));
 }
 
 void rcar_du_crtc_route_output(struct drm_crtc *crtc,
@@ -511,9 +519,17 @@ static const struct drm_crtc_helper_funcs 
crtc_helper_funcs = {
        .atomic_flush = rcar_du_crtc_atomic_flush,
 };
 
+static void rcar_du_crtc_destroy(struct drm_crtc *crtc)
+{
+       struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+
+       rcar_du_wback_cleanup_crtc(rcrtc);
+       drm_crtc_cleanup(crtc);
+}
+
 static const struct drm_crtc_funcs crtc_funcs = {
        .reset = drm_atomic_helper_crtc_reset,
-       .destroy = drm_crtc_cleanup,
+       .destroy = rcar_du_crtc_destroy,
        .set_config = drm_atomic_helper_set_config,
        .page_flip = drm_atomic_helper_page_flip,
        .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
@@ -527,19 +543,20 @@ static const struct drm_crtc_funcs crtc_funcs = {
 static irqreturn_t rcar_du_crtc_irq(int irq, void *arg)
 {
        struct rcar_du_crtc *rcrtc = arg;
-       irqreturn_t ret = IRQ_NONE;
        u32 status;
 
        status = rcar_du_crtc_read(rcrtc, DSSR);
+
+       rcar_du_wback_irq(&rcrtc->wback, status);
+
        rcar_du_crtc_write(rcrtc, DSRCR, status & DSRCR_MASK);
 
        if (status & DSSR_FRM) {
                drm_handle_vblank(rcrtc->crtc.dev, rcrtc->index);
                rcar_du_crtc_finish_page_flip(rcrtc);
-               ret = IRQ_HANDLED;
        }
 
-       return ret;
+       return status & (DSSR_FRM | DSSR_RINT) ? IRQ_HANDLED : IRQ_NONE;
 }
 
 /* 
-----------------------------------------------------------------------------
@@ -626,9 +643,27 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, 
unsigned int index)
                return ret;
        }
 
+       ret = rcar_du_wback_init_crtc(rcrtc);
+       if (ret < 0) {
+               dev_err(rcdu->dev,
+                       "write-back initialization failed for CRTC %u\n",
+                       index);
+               return ret;
+       }
+
        return 0;
 }
 
+void rcar_du_crtc_enable_rint(struct rcar_du_crtc *rcrtc, bool enable)
+{
+       if (enable) {
+               rcar_du_crtc_write(rcrtc, DSRCR, DSRCR_RICL);
+               rcar_du_crtc_set(rcrtc, DIER, DIER_RIE);
+       } else {
+               rcar_du_crtc_clr(rcrtc, DIER, DIER_RIE);
+       }
+}
+
 void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable)
 {
        if (enable) {
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h 
b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
index 5d9aa9b33769..84ee5e3eb2d2 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
@@ -20,6 +20,8 @@
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
 
+#include "rcar_du_wback.h"
+
 struct rcar_du_group;
 
 struct rcar_du_crtc {
@@ -38,6 +40,7 @@ struct rcar_du_crtc {
        bool enabled;
 
        struct rcar_du_group *group;
+       struct rcar_du_wback wback;
 };
 
 #define to_rcar_crtc(c)        container_of(c, struct rcar_du_crtc, crtc)
@@ -52,6 +55,7 @@ enum rcar_du_output {
 };
 
 int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index);
+void rcar_du_crtc_enable_rint(struct rcar_du_crtc *rcrtc, bool enable);
 void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable);
 void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc,
                                   struct drm_file *file);
@@ -61,4 +65,7 @@ void rcar_du_crtc_resume(struct rcar_du_crtc *rcrtc);
 void rcar_du_crtc_route_output(struct drm_crtc *crtc,
                               enum rcar_du_output output);
 
+int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc);
+void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc);
+
 #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
index 1d9e4f8568ae..c7f94948153a 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
@@ -139,6 +139,8 @@ static int rcar_du_unload(struct drm_device *dev)
        drm_mode_config_cleanup(dev);
        drm_vblank_cleanup(dev);
 
+       rcar_du_wback_cleanup(rcdu);
+
        dev->irq_enabled = 0;
        dev->dev_private = NULL;
 
@@ -187,6 +189,12 @@ static int rcar_du_load(struct drm_device *dev, unsigned 
long flags)
                goto done;
        }
 
+       ret = rcar_du_wback_init(rcdu);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to initialize write-back\n");
+               goto done;
+       }
+
        /* DRM/KMS objects */
        ret = rcar_du_modeset_init(rcdu);
        if (ret < 0) {
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h 
b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
index c7c538dd2e68..b95f6de1c3bb 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
@@ -17,6 +17,8 @@
 #include <linux/kernel.h>
 #include <linux/wait.h>
 
+#include <media/v4l2-device.h>
+
 #include "rcar_du_crtc.h"
 #include "rcar_du_group.h"
 
@@ -73,6 +75,8 @@ struct rcar_du_device {
        struct device *dev;
        const struct rcar_du_device_info *info;
 
+       struct v4l2_device v4l2_dev;
+
        void __iomem *mmio;
 
        struct drm_device *ddev;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c 
b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
index fb052bca574f..b1ff8050210d 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_kms.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
@@ -681,10 +681,10 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
 
        drm_mode_config_init(dev);
 
-       dev->mode_config.min_width = 0;
-       dev->mode_config.min_height = 0;
-       dev->mode_config.max_width = 4095;
-       dev->mode_config.max_height = 2047;
+       dev->mode_config.min_width = RCAR_DU_MIN_WIDTH;
+       dev->mode_config.min_height = RCAR_DU_MIN_HEIGHT;
+       dev->mode_config.max_width = RCAR_DU_MAX_WIDTH;
+       dev->mode_config.max_height = RCAR_DU_MAX_HEIGHT;
        dev->mode_config.funcs = &rcar_du_mode_config_funcs;
 
        rcdu->num_crtcs = rcdu->info->num_crtcs;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.h 
b/drivers/gpu/drm/rcar-du/rcar_du_kms.h
index 07951d5fe38b..b772dc515c7a 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_kms.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.h
@@ -21,6 +21,11 @@ struct drm_device;
 struct drm_mode_create_dumb;
 struct rcar_du_device;
 
+#define RCAR_DU_MIN_WIDTH              1
+#define RCAR_DU_MAX_WIDTH              4095
+#define RCAR_DU_MIN_HEIGHT             1
+#define RCAR_DU_MAX_HEIGHT             2047
+
 struct rcar_du_format_info {
        u32 fourcc;
        unsigned int bpp;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_regs.h 
b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
index 70fcbc471ebd..8dd510b5d34b 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_regs.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
@@ -430,6 +430,10 @@
  */
 
 #define DCMR                   0x0c100
+#define DCMR_CODE              (0x7790 << 16)
+#define DCMR_DCAR(a)           ((a) << 8)
+#define DCMR_DCDF              (1 << 0)
+
 #define DCMWR                  0x0c104
 #define DCSAR                  0x0c120
 #define DCMLR                  0x0c150
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_wback.c 
b/drivers/gpu/drm/rcar-du/rcar_du_wback.c
new file mode 100644
index 000000000000..573313f0f6f2
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_wback.c
@@ -0,0 +1,792 @@
+/*
+ * rcar_du_wback.c  --  R-Car Display Unit Write-Back
+ *
+ * Copyright (C) 2015 Renesas Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinch...@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/slab.h>
+
+#include <media/v4l2-fh.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "rcar_du_crtc.h"
+#include "rcar_du_drv.h"
+#include "rcar_du_kms.h"
+#include "rcar_du_regs.h"
+#include "rcar_du_wback.h"
+
+#define RCAR_DU_WBACK_DEF_FORMAT       V4L2_PIX_FMT_RGB565
+
+static void rcar_du_wback_write(struct rcar_du_wback *wback, u32 reg, u32 data)
+{
+       rcar_du_write(wback->dev, wback->mmio_offset + reg, data);
+}
+
+static void rcar_du_wback_write_dcpcr(struct rcar_du_wback *wback, u32 data)
+{
+       u32 addr = wback->crtc->group->mmio_offset + DCPCR;
+       unsigned int shift = (wback->crtc->index & 1) ? 8 : 0;
+       u32 dcpcr;
+
+       dcpcr = rcar_du_read(wback->dev, addr) & (0xff << shift);
+       dcpcr |= data << (shift);
+       rcar_du_write(wback->dev, addr, DCPCR_CODE | dcpcr);
+}
+
+/* 
-----------------------------------------------------------------------------
+ * Format Helpers
+ */
+
+struct rcar_du_wback_format_info {
+       u32 fourcc;
+       const char *description;
+       unsigned int bpp;
+};
+
+static const struct rcar_du_wback_format_info rcar_du_wback_formats[] = {
+       {
+               .fourcc = V4L2_PIX_FMT_RGB565,
+               .description = "RGB565",
+               .bpp = 16,
+       }, {
+               .fourcc = V4L2_PIX_FMT_RGB555,
+               .description = "RGB555",
+               .bpp = 16,
+       }, {
+               .fourcc = V4L2_PIX_FMT_XBGR32,
+               .description = "XBGR 8:8:8:8",
+               .bpp = 32,
+       },
+};
+
+const struct rcar_du_wback_format_info *rcar_du_wback_format_info(u32 fourcc)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(rcar_du_wback_formats); ++i) {
+               if (rcar_du_wback_formats[i].fourcc == fourcc)
+                       return &rcar_du_wback_formats[i];
+       }
+
+       return NULL;
+}
+
+static int __rcar_du_wback_try_format(struct rcar_du_wback *wback,
+                                     struct v4l2_pix_format_mplane *pix,
+                                     const struct rcar_du_wback_format_info 
**fmtinfo)
+{
+       const struct rcar_du_wback_format_info *info;
+       unsigned int align = 16;
+       unsigned int bpl;
+
+       /* Retrieve format information and select the default format if the
+        * requested format isn't supported.
+        */
+       info = rcar_du_wback_format_info(pix->pixelformat);
+       if (info == NULL)
+               info = rcar_du_wback_format_info(RCAR_DU_WBACK_DEF_FORMAT);
+
+       pix->pixelformat = info->fourcc;
+       pix->colorspace = V4L2_COLORSPACE_SRGB;
+       pix->field = V4L2_FIELD_NONE;
+       memset(pix->reserved, 0, sizeof(pix->reserved));
+
+       /* Clamp the width and height. */
+       pix->width = clamp_t(unsigned int, pix->width, RCAR_DU_MIN_WIDTH,
+                            RCAR_DU_MAX_WIDTH);
+       pix->height = clamp_t(unsigned int, pix->height, RCAR_DU_MIN_HEIGHT,
+                             RCAR_DU_MAX_HEIGHT);
+
+       /* Compute and clamp the stride and image size. The line stride must be
+        * a multiple of 16 pixels.
+        */
+       pix->num_planes = 1;
+
+       bpl = round_up(pix->plane_fmt[0].bytesperline * 8 / info->bpp, align);
+       bpl = clamp(bpl, round_up(pix->width, align), round_down(4096U, align));
+
+       pix->plane_fmt[0].bytesperline = bpl * info->bpp / 8;
+       pix->plane_fmt[0].sizeimage = pix->plane_fmt[0].bytesperline
+                                   * pix->height;
+
+       if (fmtinfo)
+               *fmtinfo = info;
+
+       return 0;
+}
+
+static bool
+rcar_du_wback_format_adjust(struct rcar_du_wback *wback,
+                           const struct v4l2_pix_format_mplane *format,
+                           struct v4l2_pix_format_mplane *adjust)
+{
+       *adjust = *format;
+       __rcar_du_wback_try_format(wback, adjust, NULL);
+
+       if (format->width != adjust->width ||
+           format->height != adjust->height ||
+           format->pixelformat != adjust->pixelformat ||
+           format->num_planes != adjust->num_planes)
+               return false;
+
+       if (format->plane_fmt[0].bytesperline !=
+           adjust->plane_fmt[0].bytesperline)
+               return false;
+
+       adjust->plane_fmt[0].sizeimage =
+               max(adjust->plane_fmt[0].sizeimage,
+                   format->plane_fmt[0].sizeimage);
+
+       return true;
+}
+
+/* 
-----------------------------------------------------------------------------
+ * State Machine
+ */
+
+static bool rcar_du_wback_vblank_miss(struct rcar_du_wback *wback)
+{
+       return !!(rcar_du_read(wback->dev, wback->crtc->mmio_offset + DSSR) &
+                 DSSR_FRM);
+}
+
+static void rcar_du_wback_complete_buffer(struct rcar_du_wback *wback)
+{
+       struct rcar_du_wback_buffer *buf = wback->active_buffer;
+
+       if (buf && buf->state == RCAR_DU_WBACK_BUFFER_STATE_DONE) {
+               /* The previously active capture buffer has been released,
+                * complete it.
+                */
+               buf->buf.v4l2_buf.sequence = wback->sequence;
+               v4l2_get_timestamp(&buf->buf.v4l2_buf.timestamp);
+               vb2_set_plane_payload(&buf->buf, 0, buf->length);
+               vb2_buffer_done(&buf->buf, VB2_BUF_STATE_DONE);
+       }
+
+       if (list_empty(&wback->irqqueue))
+               return;
+
+       buf = list_first_entry(&wback->irqqueue, struct rcar_du_wback_buffer,
+                              queue);
+       if (buf->state == RCAR_DU_WBACK_BUFFER_STATE_ACTIVE) {
+               /* The next buffer has been activated, remove it from the wait
+                * list and store it as the active buffer.
+                */
+               wback->active_buffer = buf;
+               list_del(&buf->queue);
+       }
+}
+
+static void rcar_du_wback_vblank(struct rcar_du_wback *wback)
+{
+       switch (wback->state) {
+       case RCAR_DU_WBACK_STATE_RUNNING:
+               rcar_du_wback_complete_buffer(wback);
+               wback->sequence++;
+               break;
+
+       case RCAR_DU_WBACK_STATE_STOP_WAIT:
+               /* Capture is now stopped, wake waiters. */
+               wback->state = RCAR_DU_WBACK_STATE_STOPPED;
+               wake_up(&wback->wait);
+               break;
+
+       case RCAR_DU_WBACK_STATE_STOPPING:
+       case RCAR_DU_WBACK_STATE_STOPPED:
+               break;
+       }
+}
+
+static void rcar_du_wback_activate_next_buffer(struct rcar_du_wback *wback)
+{
+       struct rcar_du_wback_buffer *buf;
+       u32 dcpcr;
+
+       /* If there's no buffer waiting keep the current buffer active and bail
+        * out.
+        */
+       if (list_empty(&wback->irqqueue))
+               return;
+
+       /* Activate the next buffer. */
+       buf = list_first_entry(&wback->irqqueue, struct rcar_du_wback_buffer,
+                              queue);
+
+       rcar_du_wback_write(wback, DCSAR, buf->addr);
+       dcpcr = wback->fmtinfo->fourcc == V4L2_PIX_FMT_RGB555
+             ? DCPCR_CDF : 0;
+       rcar_du_wback_write_dcpcr(wback, dcpcr | DCPCR_DCE);
+
+       /* If we miss vertical blanking start, there's no way to know whether
+        * the DU will capture the frame to the previous or next buffer. Assume
+        * the worst case, reuse the currently active buffer for capture and try
+        * again next time.
+        */
+       if (rcar_du_wback_vblank_miss(wback))
+               return;
+
+       /* The new buffer has been successfully activated, mark it as active and
+        * mark the previous active buffer as done.
+        */
+       buf->state = RCAR_DU_WBACK_BUFFER_STATE_ACTIVE;
+       if (wback->active_buffer)
+               wback->active_buffer->state = RCAR_DU_WBACK_BUFFER_STATE_DONE;
+}
+
+static void rcar_du_wback_rint(struct rcar_du_wback *wback)
+{
+       switch (wback->state) {
+       case RCAR_DU_WBACK_STATE_RUNNING:
+               /* Program the DU to capture the next frame to the next queued
+                * buffer.
+                */
+               rcar_du_wback_activate_next_buffer(wback);
+               break;
+
+       case RCAR_DU_WBACK_STATE_STOPPING:
+               /* Stop capture. The setting will only take effect at the next
+                * frame start. The delay can't be shortened by stopping capture
+                * in the stop_streaming handler as it could race with the frame
+                * interrupt and waiting for two frames would anyway be
+                * required.
+                *
+                * If we miss the beginning of vertical blanking we need to wait
+                * for an extra frame.
+                */
+               rcar_du_wback_write_dcpcr(wback, 0);
+               if (!rcar_du_wback_vblank_miss(wback))
+                       wback->state = RCAR_DU_WBACK_STATE_STOP_WAIT;
+               break;
+
+       case RCAR_DU_WBACK_STATE_STOP_WAIT:
+       case RCAR_DU_WBACK_STATE_STOPPED:
+               break;
+       }
+}
+
+/*
+ * The DU doesn't have write-back interrupts. We can use the frame interrupt
+ * (triggered at the start of the vertical blanking period) to be notified when
+ * a frame has been captured and reprogram the capture engine. The process is
+ * inherently racy though, as missing the end of vertical blanking would leave
+ * the capture running using the previously configured buffer. As this can't be
+ * avoided, we at least need to detect that condition.
+ *
+ * The hardware synchronizes shadow register writes to the beginning of a 
frame.
+ * There's unfortunately no corresponding interrupt, so we can't detect frame
+ * start misses. Instead, we need to reprogram the capture engine before the
+ * start of vertical blanking (using the raster interrupt as a trigger), and 
use
+ * the frame interrupt to detect misses. That's suboptimal as missing vertical
+ * blanking start doesn't mean we miss vertical blanking end, but that's the
+ * best procedure found so far.
+ */
+void rcar_du_wback_irq(struct rcar_du_wback *wback, u32 status)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&wback->irqlock, flags);
+
+       if (status & DSSR_RINT)
+               rcar_du_wback_rint(wback);
+
+       if (status & DSSR_FRM)
+               rcar_du_wback_vblank(wback);
+
+       spin_unlock_irqrestore(&wback->irqlock, flags);
+}
+
+/* 
-----------------------------------------------------------------------------
+ * videobuf2 Queue Operations
+ */
+
+/* Return all queued buffers to videobuf2 in the requested state. */
+static void rcar_du_wback_return_buffers(struct rcar_du_wback *wback,
+                                        enum vb2_buffer_state state)
+{
+       /* There's no need to take the irqlock spinlock as the state is set to
+        * stopped, the interrupt handlers will not touch the buffers, and the
+        * videobuf2 API calls are serialized with the queue mutex.
+        */
+       while (!list_empty(&wback->irqqueue)) {
+               struct rcar_du_wback_buffer *buf;
+
+               buf = list_first_entry(&wback->irqqueue,
+                                      struct rcar_du_wback_buffer, queue);
+               list_del(&buf->queue);
+               vb2_buffer_done(&buf->buf, state);
+       }
+
+       if (wback->active_buffer) {
+               vb2_buffer_done(&wback->active_buffer->buf, state);
+               wback->active_buffer = NULL;
+       }
+}
+
+static int
+rcar_du_wback_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
+                    unsigned int *nbuffers, unsigned int *nplanes,
+                    unsigned int sizes[], void *alloc_ctxs[])
+{
+       struct rcar_du_wback *wback = vb2_get_drv_priv(vq);
+       const struct v4l2_pix_format_mplane *format;
+       struct v4l2_pix_format_mplane pix_mp;
+
+       if (fmt) {
+               /* Make sure the format is valid and adjust the sizeimage field
+                * if needed.
+                */
+               if (!rcar_du_wback_format_adjust(wback, &fmt->fmt.pix_mp,
+                                                &pix_mp))
+                       return -EINVAL;
+
+               format = &pix_mp;
+       } else {
+               format = &wback->format;
+       }
+
+       *nplanes = 1;
+       sizes[0] = format->plane_fmt[0].sizeimage;
+       alloc_ctxs[0] = wback->alloc_ctx;
+
+       return 0;
+}
+
+static int rcar_du_wback_buffer_prepare(struct vb2_buffer *vb)
+{
+       struct rcar_du_wback *wback = vb2_get_drv_priv(vb->vb2_queue);
+       struct rcar_du_wback_buffer *buf = to_rcar_du_wback_buffer(vb);
+       const struct v4l2_pix_format_mplane *format = &wback->format;
+
+       if (vb->num_planes < format->num_planes)
+               return -EINVAL;
+
+       buf->addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+       buf->length = vb2_plane_size(vb, 0);
+
+       if (buf->length < format->plane_fmt[0].sizeimage)
+               return -EINVAL;
+
+       return 0;
+}
+
+static void rcar_du_wback_buffer_queue(struct vb2_buffer *vb)
+{
+       struct rcar_du_wback *wback = vb2_get_drv_priv(vb->vb2_queue);
+       struct rcar_du_wback_buffer *buf = to_rcar_du_wback_buffer(vb);
+       unsigned long flags;
+
+       spin_lock_irqsave(&wback->irqlock, flags);
+       buf->state = RCAR_DU_WBACK_BUFFER_STATE_WAIT;
+       list_add_tail(&buf->queue, &wback->irqqueue);
+       spin_unlock_irqrestore(&wback->irqlock, flags);
+}
+
+static int rcar_du_wback_start_streaming(struct vb2_queue *vq,
+                                        unsigned int count)
+{
+       struct rcar_du_wback *wback = vb2_get_drv_priv(vq);
+       const struct drm_display_mode *mode;
+       unsigned long flags;
+       u32 dcmr;
+       u32 mwr;
+
+       wback->sequence = 0;
+
+       /* Verify that the configured format matches the output of the connected
+        * subdev.
+        */
+       mode = &wback->crtc->crtc.state->adjusted_mode;
+       if (wback->format.height != mode->vdisplay ||
+           wback->format.width != mode->hdisplay) {
+               rcar_du_wback_return_buffers(wback, VB2_BUF_STATE_QUEUED);
+               return -EINVAL;
+       }
+
+       /* Setup the capture registers. While documentation states that the
+        * memory pitch is expressed in pixels, this seems to be only true in
+        * practice for 16bpp formats. 32bpp formats requires multiplying the
+        * pitch by two, effectively leading to bytesperline / 2 for every
+        * format.
+        */
+       mwr = wback->format.plane_fmt[0].bytesperline / 2;
+       dcmr = wback->fmtinfo->bpp == 32 ? DCMR_DCDF : 0;
+
+       rcar_du_wback_write(wback, DCMWR, mwr);
+       rcar_du_wback_write(wback, DCMLR, 0);
+       rcar_du_wback_write(wback, DCMR, DCMR_CODE | dcmr);
+
+       /* Set the state to started and enable the interrupts. The interrupt-
+        * driven state machine will take care of starting the hardware.
+        */
+       spin_lock_irqsave(&wback->irqlock, flags);
+       wback->state = RCAR_DU_WBACK_STATE_RUNNING;
+       spin_unlock_irqrestore(&wback->irqlock, flags);
+
+       drm_vblank_get(wback->dev->ddev, wback->crtc->index);
+       rcar_du_crtc_enable_rint(wback->crtc, true);
+
+       return 0;
+}
+
+static void rcar_du_wback_stop_streaming(struct vb2_queue *vq)
+{
+       struct rcar_du_wback *wback = vb2_get_drv_priv(vq);
+       unsigned long flags;
+       int ret;
+
+       /* Stop write-back. As the hardware can't be force-stopped in the middle
+        * of a frame, set the state to stopping and let the interrupt handlers
+        * manage the state machine. There's no need to stop the hardware here
+        * as this would be racing the interrupt handlers which would need to
+        * wait one extra frame anyway.
+        */
+       spin_lock_irqsave(&wback->irqlock, flags);
+       wback->state = RCAR_DU_WBACK_STATE_STOPPING;
+       spin_unlock_irqrestore(&wback->irqlock, flags);
+
+       ret = wait_event_timeout(wback->wait,
+                                wback->state == RCAR_DU_WBACK_STATE_STOPPED,
+                                msecs_to_jiffies(1000));
+       if (wback->state != RCAR_DU_WBACK_STATE_STOPPED)
+               dev_err(wback->dev->dev, "pipeline stop timeout\n");
+
+       drm_vblank_put(wback->dev->ddev, wback->crtc->index);
+       rcar_du_crtc_enable_rint(wback->crtc, false);
+
+       /* Hand back all queued buffers to videobuf2. */
+       rcar_du_wback_return_buffers(wback, VB2_BUF_STATE_ERROR);
+}
+
+static struct vb2_ops rcar_du_wback_queue_qops = {
+       .queue_setup = rcar_du_wback_queue_setup,
+       .buf_prepare = rcar_du_wback_buffer_prepare,
+       .buf_queue = rcar_du_wback_buffer_queue,
+       .wait_prepare = vb2_ops_wait_prepare,
+       .wait_finish = vb2_ops_wait_finish,
+       .start_streaming = rcar_du_wback_start_streaming,
+       .stop_streaming = rcar_du_wback_stop_streaming,
+};
+
+/* 
-----------------------------------------------------------------------------
+ * V4L2 ioctls
+ */
+
+static int
+rcar_du_wback_querycap(struct file *file, void *fh, struct v4l2_capability 
*cap)
+{
+       struct v4l2_fh *vfh = file->private_data;
+       struct rcar_du_wback *wback = to_rcar_du_wback(vfh->vdev);
+
+       cap->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING;
+       cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+       strlcpy(cap->driver, "rcdu", sizeof(cap->driver));
+       strlcpy(cap->card, wback->video.name, sizeof(cap->card));
+       snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+                dev_name(wback->dev->dev));
+
+       return 0;
+}
+
+static int
+rcar_du_wback_enum_formats(struct file *file, void *fh,
+                          struct v4l2_fmtdesc *fmt)
+{
+       const struct rcar_du_wback_format_info *info;
+
+       if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+               return -EINVAL;
+
+       if (fmt->index >= ARRAY_SIZE(rcar_du_wback_formats))
+               return -EINVAL;
+
+       info = &rcar_du_wback_formats[fmt->index];
+
+       strlcpy(fmt->description, info->description, sizeof(fmt->description));
+       fmt->pixelformat = info->fourcc;
+
+       return 0;
+}
+
+static int
+rcar_du_wback_get_format(struct file *file, void *fh, struct v4l2_format *fmt)
+{
+       struct v4l2_fh *vfh = file->private_data;
+       struct rcar_du_wback *wback = to_rcar_du_wback(vfh->vdev);
+
+       if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+               return -EINVAL;
+
+       mutex_lock(&wback->lock);
+       fmt->fmt.pix_mp = wback->format;
+       mutex_unlock(&wback->lock);
+
+       return 0;
+}
+
+static int
+rcar_du_wback_try_format(struct file *file, void *fh, struct v4l2_format *fmt)
+{
+       struct v4l2_fh *vfh = file->private_data;
+       struct rcar_du_wback *wback = to_rcar_du_wback(vfh->vdev);
+
+       if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+               return -EINVAL;
+
+       return __rcar_du_wback_try_format(wback, &fmt->fmt.pix_mp, NULL);
+}
+
+static int
+rcar_du_wback_set_format(struct file *file, void *fh, struct v4l2_format *fmt)
+{
+       struct v4l2_fh *vfh = file->private_data;
+       struct rcar_du_wback *wback = to_rcar_du_wback(vfh->vdev);
+       const struct rcar_du_wback_format_info *info;
+       int ret;
+
+       if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+               return -EINVAL;
+
+       ret = __rcar_du_wback_try_format(wback, &fmt->fmt.pix_mp, &info);
+       if (ret < 0)
+               return ret;
+
+       mutex_lock(&wback->lock);
+
+       if (vb2_is_busy(&wback->queue)) {
+               ret = -EBUSY;
+               goto done;
+       }
+
+       wback->format = fmt->fmt.pix_mp;
+       wback->fmtinfo = info;
+
+done:
+       mutex_unlock(&wback->lock);
+       return ret;
+}
+
+static int rcar_du_wback_streamon(struct file *file, void *priv,
+                                 enum v4l2_buf_type i)
+{
+       struct video_device *vdev = video_devdata(file);
+       struct rcar_du_wback *wback = to_rcar_du_wback(vdev);
+       int ret;
+
+       /* Take to DRM modeset lock to ensure that the CRTC mode won't change
+        * behind our back while we verify the format when starting the video
+        * stream.
+        */
+       drm_modeset_lock_all(wback->dev->ddev);
+       ret = vb2_ioctl_streamon(file, priv, i);
+       drm_modeset_unlock_all(wback->dev->ddev);
+
+       return ret;
+}
+
+static int rcar_du_wback_streamoff(struct file *file, void *priv,
+                                  enum v4l2_buf_type i)
+{
+       struct video_device *vdev = video_devdata(file);
+       struct rcar_du_wback *wback = to_rcar_du_wback(vdev);
+       int ret;
+
+       drm_modeset_lock_all(wback->dev->ddev);
+       ret = vb2_ioctl_streamoff(file, priv, i);
+       drm_modeset_unlock_all(wback->dev->ddev);
+
+       return ret;
+}
+
+static const struct v4l2_ioctl_ops rcar_du_wback_ioctl_ops = {
+       .vidioc_querycap                = rcar_du_wback_querycap,
+       .vidioc_enum_fmt_vid_cap_mplane = rcar_du_wback_enum_formats,
+       .vidioc_g_fmt_vid_cap_mplane    = rcar_du_wback_get_format,
+       .vidioc_s_fmt_vid_cap_mplane    = rcar_du_wback_set_format,
+       .vidioc_try_fmt_vid_cap_mplane  = rcar_du_wback_try_format,
+       .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
+       .vidioc_querybuf                = vb2_ioctl_querybuf,
+       .vidioc_qbuf                    = vb2_ioctl_qbuf,
+       .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
+       .vidioc_create_bufs             = vb2_ioctl_create_bufs,
+       .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
+       .vidioc_streamon                = rcar_du_wback_streamon,
+       .vidioc_streamoff               = rcar_du_wback_streamoff,
+       .vidioc_expbuf                  = vb2_ioctl_expbuf,
+};
+
+/* 
-----------------------------------------------------------------------------
+ * V4L2 File Operations
+ */
+
+static int rcar_du_wback_open(struct file *file)
+{
+       struct rcar_du_wback *wback = video_drvdata(file);
+       struct v4l2_fh *vfh;
+       int ret;
+
+       vfh = kzalloc(sizeof(*vfh), GFP_KERNEL);
+       if (vfh == NULL)
+               return -ENOMEM;
+
+       v4l2_fh_init(vfh, &wback->video);
+       v4l2_fh_add(vfh);
+
+       file->private_data = vfh;
+
+       ret = rcar_du_crtc_get(wback->crtc);
+       if (ret < 0) {
+               v4l2_fh_del(vfh);
+               kfree(vfh);
+       }
+
+       return ret;
+}
+
+static int rcar_du_wback_release(struct file *file)
+{
+       struct rcar_du_wback *wback = video_drvdata(file);
+       struct v4l2_fh *vfh = file->private_data;
+
+       mutex_lock(&wback->lock);
+       if (wback->queue.owner == vfh) {
+               vb2_queue_release(&wback->queue);
+               wback->queue.owner = NULL;
+       }
+       mutex_unlock(&wback->lock);
+
+       rcar_du_crtc_put(wback->crtc);
+
+       v4l2_fh_release(file);
+
+       file->private_data = NULL;
+
+       return 0;
+}
+
+static struct v4l2_file_operations rcar_du_wback_fops = {
+       .owner = THIS_MODULE,
+       .unlocked_ioctl = video_ioctl2,
+       .open = rcar_du_wback_open,
+       .release = rcar_du_wback_release,
+       .poll = vb2_fop_poll,
+       .mmap = vb2_fop_mmap,
+};
+
+/* 
-----------------------------------------------------------------------------
+ * Initialization and Cleanup
+ */
+
+int rcar_du_wback_init_crtc(struct rcar_du_crtc *rcrtc)
+{
+       struct rcar_du_wback *wback = &rcrtc->wback;
+       int ret;
+
+       mutex_init(&wback->lock);
+       spin_lock_init(&wback->irqlock);
+       INIT_LIST_HEAD(&wback->irqqueue);
+       init_waitqueue_head(&wback->wait);
+       wback->state = RCAR_DU_WBACK_STATE_STOPPED;
+
+       wback->dev = rcrtc->group->dev;
+       wback->crtc = rcrtc;
+
+       wback->mmio_offset = rcrtc->group->mmio_offset
+                          + 0x100 * (rcrtc->index % 2);
+
+       /* Initialize the media entity... */
+       wback->pad.flags = MEDIA_PAD_FL_SINK;
+
+       ret = media_entity_init(&wback->video.entity, 1, &wback->pad, 0);
+       if (ret < 0)
+               return ret;
+
+       /* ... and the format ... */
+       wback->fmtinfo = rcar_du_wback_format_info(RCAR_DU_WBACK_DEF_FORMAT);
+       wback->format.pixelformat = wback->fmtinfo->fourcc;
+       wback->format.colorspace = V4L2_COLORSPACE_SRGB;
+       wback->format.field = V4L2_FIELD_NONE;
+       wback->format.width = 0;
+       wback->format.height = 0;
+       wback->format.num_planes = 1;
+       wback->format.plane_fmt[0].bytesperline =
+               wback->format.width * wback->fmtinfo->bpp / 8;
+       wback->format.plane_fmt[0].sizeimage =
+               wback->format.plane_fmt[0].bytesperline * wback->format.height;
+
+       /* ... and the wback node... */
+       wback->video.v4l2_dev = &wback->dev->v4l2_dev;
+       wback->video.fops = &rcar_du_wback_fops;
+       snprintf(wback->video.name, sizeof(wback->video.name),
+                "CRTC %u capture", rcrtc->index);
+       wback->video.vfl_type = VFL_TYPE_GRABBER;
+       wback->video.vfl_dir = VFL_DIR_RX;
+       wback->video.release = video_device_release_empty;
+       wback->video.ioctl_ops = &rcar_du_wback_ioctl_ops;
+
+       video_set_drvdata(&wback->video, wback);
+
+       /* ... and the buffers queue... */
+       wback->alloc_ctx = vb2_dma_contig_init_ctx(wback->dev->dev);
+       if (IS_ERR(wback->alloc_ctx))
+               goto error;
+
+       wback->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+       wback->queue.io_modes = VB2_MMAP | VB2_DMABUF;
+       wback->queue.lock = &wback->lock;
+       wback->queue.drv_priv = wback;
+       wback->queue.buf_struct_size = sizeof(struct rcar_du_wback_buffer);
+       wback->queue.ops = &rcar_du_wback_queue_qops;
+       wback->queue.mem_ops = &vb2_dma_contig_memops;
+       wback->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
+                                    | V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
+       ret = vb2_queue_init(&wback->queue);
+       if (ret < 0) {
+               dev_err(wback->dev->dev, "failed to initialize vb2 queue\n");
+               goto error;
+       }
+
+       /* ... and register the video device. */
+       wback->video.queue = &wback->queue;
+       ret = video_register_device(&wback->video, VFL_TYPE_GRABBER, -1);
+       if (ret < 0) {
+               dev_err(wback->dev->dev, "failed to register video device\n");
+               goto error;
+       }
+
+       return 0;
+
+error:
+       vb2_dma_contig_cleanup_ctx(wback->alloc_ctx);
+       rcar_du_wback_cleanup_crtc(rcrtc);
+       return ret;
+}
+
+void rcar_du_wback_cleanup_crtc(struct rcar_du_crtc *rcrtc)
+{
+       struct rcar_du_wback *wback = &rcrtc->wback;
+
+       video_unregister_device(&wback->video);
+
+       vb2_dma_contig_cleanup_ctx(wback->alloc_ctx);
+       media_entity_cleanup(&wback->video.entity);
+}
+
+int rcar_du_wback_init(struct rcar_du_device *rcdu)
+{
+       return v4l2_device_register(rcdu->dev, &rcdu->v4l2_dev);
+}
+
+void rcar_du_wback_cleanup(struct rcar_du_device *rcdu)
+{
+       v4l2_device_unregister(&rcdu->v4l2_dev);
+}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_wback.h 
b/drivers/gpu/drm/rcar-du/rcar_du_wback.h
new file mode 100644
index 000000000000..529132812aa6
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_du_wback.h
@@ -0,0 +1,102 @@
+/*
+ * rcar_du_wback.h  --  R-Car Display Unit Write-Back
+ *
+ * Copyright (C) 2015 Renesas Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinch...@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_WBACK_H__
+#define __RCAR_DU_WBACK_H__
+
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include <media/media-entity.h>
+#include <media/v4l2-dev.h>
+#include <media/videobuf2-core.h>
+
+struct rcar_du_crtc;
+struct rcar_du_device;
+struct rcar_du_wback;
+struct rcar_du_wback_format_info;
+
+/*
+ * WAIT: The buffer is waiting to be queued to the hardware
+ * ACTIVE: The buffer is being used as the capture target
+ * DONE: The buffer is full and has been released as the capture target
+ */
+enum rcar_du_wback_buffer_state {
+       RCAR_DU_WBACK_BUFFER_STATE_WAIT,
+       RCAR_DU_WBACK_BUFFER_STATE_ACTIVE,
+       RCAR_DU_WBACK_BUFFER_STATE_DONE,
+};
+
+struct rcar_du_wback_buffer {
+       struct vb2_buffer buf;
+       struct list_head queue;
+       unsigned length;
+       dma_addr_t addr;
+       enum rcar_du_wback_buffer_state state;
+};
+
+static inline struct rcar_du_wback_buffer *
+to_rcar_du_wback_buffer(struct vb2_buffer *vb)
+{
+       return container_of(vb, struct rcar_du_wback_buffer, buf);
+}
+
+enum rcar_du_wback_state {
+       RCAR_DU_WBACK_STATE_STOPPED,
+       RCAR_DU_WBACK_STATE_RUNNING,
+       RCAR_DU_WBACK_STATE_STOPPING,
+       RCAR_DU_WBACK_STATE_STOP_WAIT,
+};
+
+struct rcar_du_wback {
+       struct rcar_du_device *dev;
+       struct rcar_du_crtc *crtc;
+
+       unsigned int mmio_offset;
+
+       struct video_device video;
+       enum v4l2_buf_type type;
+       struct media_pad pad;
+
+       struct mutex lock;
+       struct v4l2_pix_format_mplane format;
+       const struct rcar_du_wback_format_info *fmtinfo;
+
+       struct vb2_queue queue;
+       void *alloc_ctx;
+
+       wait_queue_head_t wait;
+       spinlock_t irqlock;     /* Protects irqqueue, active_buffer, sequence 
and state. */
+       enum rcar_du_wback_state state;
+       struct list_head irqqueue;
+       struct rcar_du_wback_buffer *active_buffer;
+       unsigned int sequence;
+};
+
+static inline struct rcar_du_wback *to_rcar_du_wback(struct video_device *vdev)
+{
+       return container_of(vdev, struct rcar_du_wback, video);
+}
+
+int rcar_du_wback_init_crtc(struct rcar_du_crtc *rcrtc);
+void rcar_du_wback_cleanup_crtc(struct rcar_du_crtc *rcrtc);
+
+int rcar_du_wback_init(struct rcar_du_device *rcdu);
+void rcar_du_wback_cleanup(struct rcar_du_device *rcdu);
+
+void rcar_du_wback_irq(struct rcar_du_wback *wback, u32 status);
+
+#endif /* __RCAR_DU_WBACK_H__ */
-- 
Regards,

Laurent Pinchart

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to