@@ -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
@@ -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) {
@@ -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__ */
@@ -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) {
@@ -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;
@@ -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;
@@ -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;
@@ -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
new file mode 100644
@@ -0,0 +1,792 @@
+/*
+ * rcar_du_wback.c -- R-Car Display Unit Write-Back
+ *
+ * Copyright (C) 2015 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/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);
+}
new file mode 100644
@@ -0,0 +1,102 @@
+/*
+ * rcar_du_wback.h -- R-Car Display Unit Write-Back
+ *
+ * Copyright (C) 2015 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_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__ */
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+renesas@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 :-)