summaryrefslogtreecommitdiff
path: root/plugins/v4l2
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/v4l2')
-rw-r--r--plugins/v4l2/Makefile22
-rw-r--r--plugins/v4l2/v4l2.c721
-rw-r--r--plugins/v4l2/v4l2.h289
-rw-r--r--plugins/v4l2/xlnx-v4l2.c417
4 files changed, 1449 insertions, 0 deletions
diff --git a/plugins/v4l2/Makefile b/plugins/v4l2/Makefile
new file mode 100644
index 0000000..fcce631
--- /dev/null
+++ b/plugins/v4l2/Makefile
@@ -0,0 +1,22 @@
+AR := $(CROSS_COMPILE)ar
+CC := $(CROSS_COMPILE)gcc
+CFLAGS := -O2 -W -Wall -Wno-unused-parameter -I$(KDIR)/usr/include -I$(topdir)/3rdparty/media-ctl/include -I$(topdir)/include -I. -fPIC
+LDFLAGS := -L$(topdir)/3rdparty/media-ctl
+LIBS := -lmediactl
+
+OBJECTS := xlnx-v4l2.o
+TARGET := xlnx-v4l2.so
+
+%.o : %.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+all: $(TARGET)
+
+$(TARGET): $(OBJECTS)
+ $(CC) -o $@ -shared $^
+
+clean:
+ -$(RM) *.o
+ -$(RM) $(TARGET)
+
+install:
diff --git a/plugins/v4l2/v4l2.c b/plugins/v4l2/v4l2.c
new file mode 100644
index 0000000..c0fdd54
--- /dev/null
+++ b/plugins/v4l2/v4l2.c
@@ -0,0 +1,721 @@
+/*
+ * Xilinx Video Library - Generic V4L2 support
+ *
+ * Copyright (C) 2014-2016 Ideas on board Oy
+ *
+ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/select.h>
+#include <sys/time.h>
+
+#include <linux/videodev2.h>
+
+#include <xlnx-buffers.h>
+#include <xlnx-list.h>
+#include <xlnx-tools.h>
+
+#include "v4l2.h"
+
+#ifndef V4L2_BUF_FLAG_ERROR
+#define V4L2_BUF_FLAG_ERROR 0x0040
+#endif
+
+struct v4l2_ival_desc {
+ struct v4l2_fract min;
+ struct v4l2_fract max;
+ struct v4l2_fract step;
+
+ struct list_entry list;
+};
+
+struct v4l2_frame_desc {
+ unsigned int min_width;
+ unsigned int min_height;
+ unsigned int max_width;
+ unsigned int max_height;
+ unsigned int step_width;
+ unsigned int step_height;
+
+ struct list_entry list;
+ struct list_entry ivals;
+};
+
+struct v4l2_format_desc {
+ unsigned int pixelformat;
+
+ struct list_entry list;
+ struct list_entry frames;
+};
+
+/* -----------------------------------------------------------------------------
+ * Formats enumeration
+ */
+
+static int
+v4l2_enum_frame_intervals(struct v4l2_device *dev, struct v4l2_format_desc *format,
+ struct v4l2_frame_desc *frame)
+{
+ struct v4l2_ival_desc *ival;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; ; ++i) {
+ struct v4l2_frmivalenum ivalenum;
+
+ memset(&ivalenum, 0, sizeof ivalenum);
+ ivalenum.index = i;
+ ivalenum.pixel_format = format->pixelformat;
+ ivalenum.width = frame->min_width;
+ ivalenum.height = frame->min_height;
+ ret = ioctl(dev->fd, VIDIOC_ENUM_FRAMEINTERVALS, &ivalenum);
+ if (ret < 0)
+ break;
+
+ if (i != ivalenum.index)
+ printf("Warning: driver returned wrong ival index "
+ "%u.\n", ivalenum.index);
+ if (format->pixelformat != ivalenum.pixel_format)
+ printf("Warning: driver returned wrong ival pixel "
+ "format %08x.\n", ivalenum.pixel_format);
+ if (frame->min_width != ivalenum.width)
+ printf("Warning: driver returned wrong ival width "
+ "%u.\n", ivalenum.width);
+ if (frame->min_height != ivalenum.height)
+ printf("Warning: driver returned wrong ival height "
+ "%u.\n", ivalenum.height);
+
+ ival = malloc(sizeof *ival);
+ if (ival == NULL)
+ return -ENOMEM;
+
+ memset(ival, 0, sizeof *ival);
+
+ switch (ivalenum.type) {
+ case V4L2_FRMIVAL_TYPE_DISCRETE:
+ ival->min = ivalenum.discrete;
+ ival->max = ivalenum.discrete;
+ ival->step.numerator = 1;
+ ival->step.denominator = 1;
+ break;
+
+ case V4L2_FRMIVAL_TYPE_STEPWISE:
+ ival->min = ivalenum.stepwise.min;
+ ival->max = ivalenum.stepwise.max;
+ ival->step = ivalenum.stepwise.step;
+ break;
+
+ default:
+ printf("Error: driver returned invalid frame ival "
+ "type %u\n", ivalenum.type);
+ return -EINVAL;
+ }
+
+ list_append(&ival->list, &frame->ivals);
+ }
+
+ return 0;
+}
+
+static int
+v4l2_enum_frame_sizes(struct v4l2_device *dev, struct v4l2_format_desc *format)
+{
+ struct v4l2_frame_desc *frame;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; ; ++i) {
+ struct v4l2_frmsizeenum frmenum;
+
+ memset(&frmenum, 0, sizeof frmenum);
+ frmenum.index = i;
+ frmenum.pixel_format = format->pixelformat;
+
+ ret = ioctl(dev->fd, VIDIOC_ENUM_FRAMESIZES, &frmenum);
+ if (ret < 0)
+ break;
+
+ if (i != frmenum.index)
+ printf("Warning: driver returned wrong frame index "
+ "%u.\n", frmenum.index);
+ if (format->pixelformat != frmenum.pixel_format)
+ printf("Warning: driver returned wrong frame pixel "
+ "format %08x.\n", frmenum.pixel_format);
+
+ frame = malloc(sizeof *frame);
+ if (frame == NULL)
+ return -ENOMEM;
+
+ memset(frame, 0, sizeof *frame);
+
+ list_init(&frame->ivals);
+ frame->step_width = 1;
+ frame->step_height = 1;
+
+ switch (frmenum.type) {
+ case V4L2_FRMSIZE_TYPE_DISCRETE:
+ frame->min_width = frmenum.discrete.width;
+ frame->min_height = frmenum.discrete.height;
+ frame->max_width = frmenum.discrete.width;
+ frame->max_height = frmenum.discrete.height;
+ break;
+
+ case V4L2_FRMSIZE_TYPE_STEPWISE:
+ frame->step_width = frmenum.stepwise.step_width;
+ frame->step_height = frmenum.stepwise.step_height;
+ case V4L2_FRMSIZE_TYPE_CONTINUOUS:
+ frame->min_width = frmenum.stepwise.min_width;
+ frame->min_height = frmenum.stepwise.min_height;
+ frame->max_width = frmenum.stepwise.max_width;
+ frame->max_height = frmenum.stepwise.max_height;
+ break;
+
+ default:
+ printf("Error: driver returned invalid frame size "
+ "type %u\n", frmenum.type);
+ return -EINVAL;
+ }
+
+ list_append(&frame->list, &format->frames);
+
+ ret = v4l2_enum_frame_intervals(dev, format, frame);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+static int v4l2_enum_formats(struct v4l2_device *dev)
+{
+ struct v4l2_format_desc *format;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; ; ++i) {
+ struct v4l2_fmtdesc fmtenum;
+
+ memset(&fmtenum, 0, sizeof fmtenum);
+ fmtenum.index = i;
+ fmtenum.type = dev->type;
+
+ ret = ioctl(dev->fd, VIDIOC_ENUM_FMT, &fmtenum);
+ if (ret < 0)
+ break;
+
+ if (i != fmtenum.index)
+ printf("Warning: driver returned wrong format index "
+ "%u.\n", fmtenum.index);
+ if (dev->type != fmtenum.type)
+ printf("Warning: driver returned wrong format type "
+ "%u.\n", fmtenum.type);
+
+ format = malloc(sizeof *format);
+ if (format == NULL)
+ return -ENOMEM;
+
+ memset(format, 0, sizeof *format);
+
+ list_init(&format->frames);
+ format->pixelformat = fmtenum.pixelformat;
+
+ list_append(&format->list, &dev->formats);
+
+ ret = v4l2_enum_frame_sizes(dev, format);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Open/close
+ */
+
+struct v4l2_device *v4l2_open(const char *devname)
+{
+ struct v4l2_device *dev;
+ struct v4l2_capability cap;
+ int ret;
+
+ dev = malloc(sizeof *dev);
+ if (dev == NULL)
+ return NULL;
+
+ memset(dev, 0, sizeof *dev);
+ dev->fd = -1;
+ dev->name = strdup(devname);
+ list_init(&dev->formats);
+
+ dev->fd = open(devname, O_RDWR | O_NONBLOCK);
+ if (dev->fd < 0) {
+ printf("Error opening device %s: %d.\n", devname, errno);
+ v4l2_close(dev);
+ return NULL;
+ }
+
+ memset(&cap, 0, sizeof cap);
+ ret = ioctl(dev->fd, VIDIOC_QUERYCAP, &cap);
+ if (ret < 0) {
+ printf("Error opening device %s: unable to query "
+ "device.\n", devname);
+ v4l2_close(dev);
+ return NULL;
+ }
+
+ if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
+ dev->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ else if (cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)
+ dev->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ else {
+ printf("Error opening device %s: neither video capture "
+ "nor video output supported.\n", devname);
+ v4l2_close(dev);
+ return NULL;
+ }
+
+ ret = v4l2_enum_formats(dev);
+ if (ret < 0) {
+ printf("Error opening device %s: unable to enumerate "
+ "formats.\n", devname);
+ v4l2_close(dev);
+ return NULL;
+ }
+
+ printf("Device %s opened: %s (%s).\n", devname, cap.card, cap.bus_info);
+
+ return dev;
+}
+
+void v4l2_close(struct v4l2_device *dev)
+{
+ struct v4l2_format_desc *format, *next_fmt;
+ struct v4l2_frame_desc *frame, *next_frm;
+ struct v4l2_ival_desc *ival, *next_ival;
+
+ if (dev == NULL)
+ return;
+
+ list_for_each_entry_safe(format, next_fmt, &dev->formats, list) {
+ list_for_each_entry_safe(frame, next_frm, &format->frames, list) {
+ list_for_each_entry_safe(ival, next_ival, &frame->ivals, list) {
+ free(ival);
+ }
+ free(frame);
+ }
+ free(format);
+ }
+
+ free(dev->name);
+ close(dev->fd);
+}
+
+/* -----------------------------------------------------------------------------
+ * Controls
+ */
+
+int v4l2_get_control(struct v4l2_device *dev, unsigned int id, int32_t *value)
+{
+ struct v4l2_control ctrl;
+ int ret;
+
+ ctrl.id = id;
+
+ ret = ioctl(dev->fd, VIDIOC_G_CTRL, &ctrl);
+ if (ret < 0) {
+ printf("%s: unable to get control (%d).\n", dev->name, errno);
+ return -errno;
+ }
+
+ *value = ctrl.value;
+ return 0;
+}
+
+int v4l2_set_control(struct v4l2_device *dev, unsigned int id, int32_t *value)
+{
+ struct v4l2_control ctrl;
+ int ret;
+
+ ctrl.id = id;
+ ctrl.value = *value;
+
+ ret = ioctl(dev->fd, VIDIOC_S_CTRL, &ctrl);
+ if (ret < 0) {
+ printf("%s: unable to set control (%d).\n", dev->name, errno);
+ return -errno;
+ }
+
+ *value = ctrl.value;
+ return 0;
+}
+
+int v4l2_get_controls(struct v4l2_device *dev, unsigned int count,
+ struct v4l2_ext_control *ctrls)
+{
+ struct v4l2_ext_controls controls;
+ int ret;
+
+ memset(&controls, 0, sizeof controls);
+ controls.count = count;
+ controls.controls = ctrls;
+
+ ret = ioctl(dev->fd, VIDIOC_G_EXT_CTRLS, &controls);
+ if (ret < 0)
+ printf("%s: unable to get multiple controls (%d).\n", dev->name,
+ errno);
+
+ return ret;
+}
+
+int v4l2_set_controls(struct v4l2_device *dev, unsigned int count,
+ struct v4l2_ext_control *ctrls)
+{
+ struct v4l2_ext_controls controls;
+ int ret;
+
+ memset(&controls, 0, sizeof controls);
+ controls.count = count;
+ controls.controls = ctrls;
+
+ ret = ioctl(dev->fd, VIDIOC_S_EXT_CTRLS, &controls);
+ if (ret < 0)
+ printf("%s: unable to set multiple controls (%d).\n", dev->name,
+ errno);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Formats and frame rates
+ */
+
+int v4l2_get_crop(struct v4l2_device *dev, struct v4l2_rect *rect)
+{
+ struct v4l2_crop crop;
+ int ret;
+
+ memset(&crop, 0, sizeof crop);
+ crop.type = dev->type;
+
+ ret = ioctl(dev->fd, VIDIOC_G_CROP, &crop);
+ if (ret < 0) {
+ printf("%s: unable to get crop rectangle (%d).\n", dev->name,
+ errno);
+ return -errno;
+ }
+
+ dev->crop = crop.c;
+ *rect = crop.c;
+
+ return 0;
+}
+
+int v4l2_set_crop(struct v4l2_device *dev, struct v4l2_rect *rect)
+{
+ struct v4l2_crop crop;
+ int ret;
+
+ memset(&crop, 0, sizeof crop);
+ crop.type = dev->type;
+ crop.c = *rect;
+
+ ret = ioctl(dev->fd, VIDIOC_S_CROP, &crop);
+ if (ret < 0) {
+ printf("%s: unable to set crop rectangle (%d).\n", dev->name,
+ errno);
+ return -errno;
+ }
+
+ dev->crop = crop.c;
+ *rect = crop.c;
+
+ return 0;
+}
+
+int v4l2_get_format(struct v4l2_device *dev, struct v4l2_pix_format *format)
+{
+ struct v4l2_format fmt;
+ int ret;
+
+ memset(&fmt, 0, sizeof fmt);
+ fmt.type = dev->type;
+
+ ret = ioctl(dev->fd, VIDIOC_G_FMT, &fmt);
+ if (ret < 0) {
+ printf("%s: unable to get format (%d).\n", dev->name, errno);
+ return -errno;
+ }
+
+ dev->format = fmt.fmt.pix;
+ *format = fmt.fmt.pix;
+
+ return 0;
+}
+
+int v4l2_set_format(struct v4l2_device *dev, struct v4l2_pix_format *format)
+{
+ struct v4l2_format fmt;
+ int ret;
+
+ memset(&fmt, 0, sizeof fmt);
+ fmt.type = dev->type;
+ fmt.fmt.pix.width = format->width;
+ fmt.fmt.pix.height = format->height;
+ fmt.fmt.pix.pixelformat = format->pixelformat;
+ fmt.fmt.pix.field = V4L2_FIELD_ANY;
+
+ ret = ioctl(dev->fd, VIDIOC_S_FMT, &fmt);
+ if (ret < 0) {
+ printf("%s: unable to set format (%d).\n", dev->name, errno);
+ return -errno;
+ }
+
+ dev->format = fmt.fmt.pix;
+ *format = fmt.fmt.pix;
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Buffers management
+ */
+
+int v4l2_alloc_buffers(struct v4l2_device *dev,
+ struct xlnx_video_buffers_pool *pool,
+ enum v4l2_memory memtype)
+{
+ struct v4l2_requestbuffers rb;
+ struct v4l2_buffer buf;
+ unsigned int i;
+ int ret;
+
+ dev->memtype = memtype;
+ dev->pool = pool;
+
+ memset(&rb, 0, sizeof rb);
+ rb.count = pool->nbufs;
+ rb.type = dev->type;
+ rb.memory = memtype;
+
+ ret = ioctl(dev->fd, VIDIOC_REQBUFS, &rb);
+ if (ret < 0) {
+ printf("%s: unable to request buffers (%d).\n", dev->name,
+ errno);
+ ret = -errno;
+ goto done;
+ }
+
+ if (rb.count > pool->nbufs) {
+ printf("%s: driver needs more buffers (%u) than available (%u).\n",
+ dev->name, rb.count, pool->nbufs);
+ ret = -E2BIG;
+ goto done;
+ }
+
+ dev->nbufs = rb.count;
+
+ printf("%s: %u buffers requested.\n", dev->name, rb.count);
+
+ /* Map the buffers. */
+ for (i = 0; i < rb.count; ++i) {
+ memset(&buf, 0, sizeof buf);
+ buf.index = i;
+ buf.type = dev->type;
+ buf.memory = memtype;
+ ret = ioctl(dev->fd, VIDIOC_QUERYBUF, &buf);
+ if (ret < 0) {
+ printf("%s: unable to query buffer %u (%d).\n",
+ dev->name, i, errno);
+ ret = -errno;
+ goto done;
+ }
+
+ switch (memtype) {
+ case V4L2_MEMORY_MMAP:
+ pool->buffers[i].mem = mmap(0, buf.length, PROT_READ | PROT_WRITE,
+ MAP_SHARED, dev->fd, buf.m.offset);
+ if (pool->buffers[i].mem == MAP_FAILED) {
+ printf("%s: unable to map buffer %u (%d)\n",
+ dev->name, i, errno);
+ ret = -errno;
+ goto done;
+ }
+ pool->buffers[i].size = buf.length;
+ printf("%s: buffer %u mapped at address %p.\n",
+ dev->name, i, pool->buffers[i].mem);
+ break;
+
+ case V4L2_MEMORY_USERPTR:
+ if (pool->buffers[i].size < buf.length) {
+ printf("%s: buffer %u too small (%u bytes required, "
+ "%u bytes available.\n", dev->name, i,
+ buf.length, pool->buffers[i].size);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ printf("%s: buffer %u valid.\n", dev->name, i);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ ret = 0;
+
+done:
+ if (ret < 0)
+ v4l2_free_buffers(dev);
+
+ return ret;
+}
+
+int v4l2_free_buffers(struct v4l2_device *dev)
+{
+ struct v4l2_requestbuffers rb;
+ unsigned int i;
+ int ret;
+
+ if (dev->pool == NULL)
+ return 0;
+
+ if (dev->memtype == V4L2_MEMORY_MMAP) {
+ for (i = 0; i < dev->nbufs; ++i) {
+ struct xlnx_video_buffer *buffer = &dev->pool->buffers[i];
+
+ if (buffer->mem == NULL)
+ continue;
+
+ ret = munmap(buffer->mem, buffer->size);
+ if (ret < 0) {
+ printf("%s: unable to unmap buffer %u (%d)\n",
+ dev->name, i, errno);
+ return -errno;
+ }
+
+ buffer->mem = NULL;
+ buffer->size = 0;
+ }
+ }
+
+ memset(&rb, 0, sizeof rb);
+ rb.count = 0;
+ rb.type = dev->type;
+ rb.memory = dev->memtype;
+
+ ret = ioctl(dev->fd, VIDIOC_REQBUFS, &rb);
+ if (ret < 0) {
+ printf("%s: unable to release buffers (%d)\n", dev->name,
+ errno);
+ return -errno;
+ }
+
+ dev->nbufs = 0;
+ dev->pool = NULL;
+
+ return 0;
+}
+
+int v4l2_dequeue_buffer(struct v4l2_device *dev, struct xlnx_video_buffer *buffer)
+{
+ struct v4l2_buffer buf;
+ int ret;
+
+ memset(&buf, 0, sizeof buf);
+ buf.type = dev->type;
+ buf.memory = dev->memtype;
+
+ ret = ioctl(dev->fd, VIDIOC_DQBUF, &buf);
+ if (ret < 0) {
+ printf("%s: unable to dequeue buffer index %u/%u (%d)\n",
+ dev->name, buf.index, dev->nbufs, errno);
+ return -errno;
+ }
+
+ *buffer = dev->pool->buffers[buf.index];
+ buffer->bytesused = buf.bytesused;
+ buffer->timestamp = buf.timestamp;
+ buffer->error = !!(buf.flags & V4L2_BUF_FLAG_ERROR);
+
+ return 0;
+}
+
+int v4l2_queue_buffer(struct v4l2_device *dev, struct xlnx_video_buffer *buffer)
+{
+ struct v4l2_buffer buf;
+ int ret;
+
+ if (buffer->index >= dev->nbufs)
+ return -EINVAL;
+
+ memset(&buf, 0, sizeof buf);
+ buf.index = buffer->index;
+ buf.type = dev->type;
+ buf.memory = dev->memtype;
+
+ if (dev->memtype == V4L2_MEMORY_USERPTR)
+ buf.m.userptr = (unsigned long)dev->pool->buffers[buffer->index].mem;
+
+ buf.length = dev->pool->buffers[buffer->index].size;
+
+ if (dev->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ buf.bytesused = buffer->bytesused;
+
+ ret = ioctl(dev->fd, VIDIOC_QBUF, &buf);
+ if (ret < 0) {
+ printf("%s: unable to queue buffer index %u/%u (%d)\n",
+ dev->name, buf.index, dev->nbufs, errno);
+ return -errno;
+ }
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Stream management
+ */
+
+int v4l2_stream_on(struct v4l2_device *dev)
+{
+ int type = dev->type;
+ int ret;
+
+ ret = ioctl(dev->fd, VIDIOC_STREAMON, &type);
+ if (ret < 0)
+ return -errno;
+
+ return 0;
+}
+
+int v4l2_stream_off(struct v4l2_device *dev)
+{
+ int type = dev->type;
+ int ret;
+
+ ret = ioctl(dev->fd, VIDIOC_STREAMOFF, &type);
+ if (ret < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/plugins/v4l2/v4l2.h b/plugins/v4l2/v4l2.h
new file mode 100644
index 0000000..c2b683a
--- /dev/null
+++ b/plugins/v4l2/v4l2.h
@@ -0,0 +1,289 @@
+/*
+ * Xilinx Video Library - Generic V4L2 support
+ *
+ * Copyright (C) 2014-2016 Ideas on board Oy
+ *
+ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+#ifndef __V4L2_H
+#define __V4L2_H
+
+#include <linux/videodev2.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <xlnx-list.h>
+
+struct xlnx_video_buffers_pool;
+struct xlnx_video_buffer;
+
+struct v4l2_device
+{
+ int fd;
+ char *name;
+
+ enum v4l2_buf_type type;
+ enum v4l2_memory memtype;
+
+ struct list_entry formats;
+ struct v4l2_pix_format format;
+ struct v4l2_rect crop;
+
+ unsigned int nbufs;
+ struct xlnx_video_buffers_pool *pool;
+};
+
+/*
+ * v4l2_open - Open a V4L2 device
+ * @devname: Name (including path) of the device node
+ *
+ * Open the V4L2 device referenced by @devname for video capture or display in
+ * non-blocking mode.
+ *
+ * If the device can be opened, query its capabilities and enumerates frame
+ * formats, sizes and intervals.
+ *
+ * Return a pointer to a newly allocated v4l2_device structure instance on
+ * success and NULL on failure. The returned pointer must be freed with
+ * v4l2_close when the device isn't needed anymore.
+ */
+struct v4l2_device *v4l2_open(const char *devname);
+
+/*
+ * v4l2_close - Close a V4L2 device
+ * @dev: Device instance
+ *
+ * Close the device instance given as argument and free allocated resources.
+ * Access to the device instance is forbidden after this function returns.
+ */
+void v4l2_close(struct v4l2_device *dev);
+
+/*
+ * v4l2_get_format - Retrieve the current pixel format
+ * @dev: Device instance
+ * @format: Pixel format structure to be filled
+ *
+ * Query the device to retrieve the current pixel format and frame size and fill
+ * the @format structure.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_get_format(struct v4l2_device *dev, struct v4l2_pix_format *format);
+
+/*
+ * v4l2_set_format - Set the pixel format
+ * @dev: Device instance
+ * @format: Pixel format structure to be set
+ *
+ * Set the pixel format and frame size stored in @format. The device can modify
+ * the requested format and size, in which case the @format structure will be
+ * updated to reflect the modified settings.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_set_format(struct v4l2_device *dev, struct v4l2_pix_format *format);
+
+/*
+ * v4l2_get_crop - Retrieve the current crop rectangle
+ * @dev: Device instance
+ * @rect: Crop rectangle structure to be filled
+ *
+ * Query the device to retrieve the current crop rectangle and fill the @rect
+ * structure.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_get_crop(struct v4l2_device *dev, struct v4l2_rect *rect);
+
+/*
+ * v4l2_set_crop - Set the crop rectangle
+ * @dev: Device instance
+ * @rect: Crop rectangle structure to be set
+ *
+ * Set the crop rectangle stored in @rect. The device can modify the requested
+ * rectangle, in which case the @rect structure will be updated to reflect the
+ * modified settings.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_set_crop(struct v4l2_device *dev, struct v4l2_rect *rect);
+
+/*
+ * v4l2_alloc_buffers - Allocate buffers for video frames
+ * @dev: Device instance
+ * @pool: Buffers pool
+ * @memtype: Type of buffers
+ *
+ * Request the driver to allocate the number of buffers in the pool. The driver
+ * can modify the number of buffers depending on its needs. The number of
+ * allocated buffers will be stored in the @dev::nbufs field.
+ *
+ * When @memtype is set to V4L2_MEMORY_MMAP the buffers are allocated by the
+ * driver and mapped to userspace. When @memtype is set to V4L2_MEMORY_USERPTR
+ * the driver only allocates buffer objects and relies on the application to
+ * provide memory storage for video frames.
+ *
+ * Upon successful allocation the @dev::pool field points to the buffers pool.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_alloc_buffers(struct v4l2_device *dev,
+ struct xlnx_video_buffers_pool *pool,
+ enum v4l2_memory memtype);
+
+/*
+ * v4l2_free_buffers - Free buffers
+ * @dev: Device instance
+ *
+ * Free buffers previously allocated by v4l2_alloc_buffers(). If the buffers
+ * have been allocated with the V4L2_MEMORY_USERPTR memory type only the buffer
+ * objects are freed, and the caller is responsible for freeing the video frames
+ * memory if required.
+ *
+ * When successful this function sets the @dev::nbufs field to zero and the
+ * @dev::pool field to NULL.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_free_buffers(struct v4l2_device *dev);
+
+/*
+ * v4l2_queue_buffer - Queue a buffer for video capture/output
+ * @dev: Device instance
+ * @buffer: Buffer to be queued
+ *
+ * Queue the buffer identified by @buffer for video capture or output, depending
+ * on the device type.
+ *
+ * The caller must initialize the @buffer::index field with the index of the
+ * buffer to be queued. The index is zero-based and must be lower than the
+ * number of allocated buffers.
+ *
+ * For V4L2_MEMORY_USERPTR buffers, the caller must initialize the @buffer::mem
+ * field with the address of the video frame memory, and the @buffer:length
+ * field with its size in bytes. For optimal performances the address and length
+ * should be identical between v4l2_queue_buffer() calls for a given buffer
+ * index.
+ *
+ * For video output, the caller must initialize the @buffer::bytesused field
+ * with the size of video data. The value should differ from the buffer length
+ * for variable-size video formats only.
+ *
+ * Upon successful return the buffer ownership is transferred to the driver. The
+ * caller must not touch video memory for that buffer before calling
+ * v4l2_dequeue_buffer(). Attempting to queue an already queued buffer will
+ * fail.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_queue_buffer(struct v4l2_device *dev, struct xlnx_video_buffer *buffer);
+
+/*
+ * v4l2_dequeue_buffer - Dequeue the next buffer
+ * @dev: Device instance
+ * @buffer: Dequeued buffer data to be filled
+ *
+ * Dequeue the next buffer processed by the driver and fill all fields in
+ * @buffer.
+ *
+ * This function does not block. If no buffer is ready it will return
+ * immediately with -EAGAIN.
+ *
+ * If an error occured during video capture or display, the @buffer::error field
+ * is set to true. Depending on the device the video data can be partly
+ * corrupted or complete garbage.
+ *
+ * Once dequeued the buffer ownership is transferred to the caller. Video memory
+ * for that buffer can be safely read from and written to.
+ *
+ * Return 0 on success or a negative error code on failure. An error that
+ * results in @buffer:error being set is not considered as a failure condition
+ * for the purpose of the return value.
+ */
+int v4l2_dequeue_buffer(struct v4l2_device *dev, struct xlnx_video_buffer *buffer);
+
+/*
+ * v4l2_stream_on - Start video streaming
+ * @dev: Device instance
+ *
+ * Start video capture or output on the device. For video output devices at
+ * least one buffer must be queued before starting the stream.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_stream_on(struct v4l2_device *dev);
+
+/*
+ * v4l2_stream_off - Stop video streaming
+ * @dev: Device instance
+ *
+ * Stop video capture or output on the device. Upon successful return ownership
+ * of all buffers is returned to the caller.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_stream_off(struct v4l2_device *dev);
+
+/*
+ * v4l2_get_control - Read the value of a control
+ * @dev: Device instance
+ * @id: Control ID
+ * @value: Control value to be filled
+ *
+ * Retrieve the current value of control @id and store it in @value.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_get_control(struct v4l2_device *dev, unsigned int id, int32_t *value);
+
+/*
+ * v4l2_set_control - Write the value of a control
+ * @dev: Device instance
+ * @id: Control ID
+ * @value: Control value
+ *
+ * Set control @id to @value. The device is allowed to modify the requested
+ * value, in which case @value is updated to the modified value.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_set_control(struct v4l2_device *dev, unsigned int id, int32_t *value);
+
+/*
+ * v4l2_get_controls - Read the value of multiple controls
+ * @dev: Device instance
+ * @count: Number of controls
+ * @ctrls: Controls to be read
+ *
+ * Retrieve the current value of controls identified by @ctrls.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_get_controls(struct v4l2_device *dev, unsigned int count,
+ struct v4l2_ext_control *ctrls);
+
+/*
+ * v4l2_set_controls - Write the value of multiple controls
+ * @dev: Device instance
+ * @count: Number of controls
+ * @ctrls: Controls to be written
+ *
+ * Set controls identified by @ctrls. The device is allowed to modify the
+ * requested values, in which case @ctrls is updated to the modified value.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+int v4l2_set_controls(struct v4l2_device *dev, unsigned int count,
+ struct v4l2_ext_control *ctrls);
+
+#endif
diff --git a/plugins/v4l2/xlnx-v4l2.c b/plugins/v4l2/xlnx-v4l2.c
new file mode 100644
index 0000000..c307041
--- /dev/null
+++ b/plugins/v4l2/xlnx-v4l2.c
@@ -0,0 +1,417 @@
+/*
+ * Xilinx Video Library - V4L2 plugin
+ *
+ * Copyright (C) 2014-2016 Ideas on board Oy
+ *
+ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#if 0
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <v4l2.h>
+#include <v4l2subdev.h>
+
+#include <xlnx-list.h>
+#include <xlnx-tools.h>
+#include <xlnx-video.h>
+
+/*static inline struct xlnx_video_node *
+to_xlnx_video_node(struct xlnx_video_entity *e) {
+ return container_of(e, struct xlnx_video_node, entity);
+}*/
+
+/* -----------------------------------------------------------------------------
+ * Helper functions
+ */
+
+/*
+ * struct xlnx_video_format_info - Media bus format information
+ * @code: V4L2 media bus format code
+ * @pixelformat: V4L2 pixel format FCC identifier
+ */
+struct xlnx_video_format_info {
+ enum v4l2_mbus_pixelcode code;
+ __u32 pixelformat;
+};
+
+static const struct xlnx_video_format_info formats[] = {
+ { MEDIA_BUS_FMT_RGB888_1X32_PADHI, V4L2_PIX_FMT_BGR32, },
+ { MEDIA_BUS_FMT_UYVY8_1X16, V4L2_PIX_FMT_YUYV, },
+ { MEDIA_BUS_FMT_VUY8_1X24, V4L2_PIX_FMT_YUV444, },
+ { MEDIA_BUS_FMT_SRGGB8_1X8, V4L2_PIX_FMT_SGRBG8, },
+ { MEDIA_BUS_FMT_SGRBG8_1X8, V4L2_PIX_FMT_SGRBG8, },
+ { MEDIA_BUS_FMT_SGBRG8_1X8, V4L2_PIX_FMT_SGBRG8, },
+ { MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_PIX_FMT_SBGGR8, },
+};
+
+static __u32 mbus_to_pix(enum v4l2_mbus_pixelcode code)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(formats); ++i) {
+ if (formats[i].code == code)
+ return formats[i].pixelformat;
+ }
+
+ return 0;
+}
+
+static enum v4l2_mbus_pixelcode pix_to_mbus(__u32 pixelformat)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(formats); ++i) {
+ if (formats[i].pixelformat == pixelformat)
+ return formats[i].code;
+ }
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Video streaming
+ */
+
+int xlnx_video_component_set_format(struct xlnx_video_component *xvcomp,
+ struct v4l2_mbus_framefmt *fmt)
+{
+ struct v4l2_mbus_framefmt format;
+ struct v4l2_pix_format v4l2_fmt;
+ struct xlnx_video_entity *source = NULL;
+ struct xlnx_video_entity *sink;
+ struct xlnx_video_node *video;
+ struct media_entity_pad *pad;
+ int ret;
+
+ /* Configure formats. Start from the input and propagate the format
+ * through the component.
+ */
+ format = *initial_fmt;
+
+ list_for_each_entry(sink, &xvcomp->entities, list) {
+ if (source == NULL) {
+ /* Configure the first entity in the component with the
+ * initial format.
+ */
+ pad = sink->source.link->source;
+
+ if ((sink->info->type & MEDIA_ENT_TYPE_MASK) == MEDIA_ENT_T_V4L2_SUBDEV) {
+ ret = v4l2_subdev_set_format(sink->entity, &format, 0,
+ V4L2_SUBDEV_FORMAT_ACTIVE);
+ if (ret < 0) {
+ printf("error: set format on component input failed.\n");
+ return ret;
+ }
+ } else if (sink->info->type == MEDIA_ENT_T_DEVNODE_V4L) {
+ video = to_xlnx_video_node(sink);
+
+ memset(&v4l2_fmt, 0, sizeof v4l2_fmt);
+ v4l2_fmt.pixelformat = mbus_to_pix(format.code);
+ v4l2_fmt.width = format.width;
+ v4l2_fmt.height = format.height;
+
+ ret = v4l2_set_format(video->video, &v4l2_fmt);
+ if (ret < 0) {
+ printf("error: set format failed on %s.\n",
+ sink->info->name);
+ return ret;
+ }
+
+ format.code = pix_to_mbus(v4l2_fmt.pixelformat);
+ format.width = v4l2_fmt.width;
+ format.height = v4l2_fmt.height;
+
+ video->format = format;
+ }
+
+ source = sink;
+ continue;
+ }
+
+ if ((source->info->type & MEDIA_ENT_TYPE_MASK) == MEDIA_ENT_T_V4L2_SUBDEV) {
+ pad = source->source.link->source;
+
+ /* Try to force the output format code onto the source pad. */
+ ret = v4l2_subdev_get_format(pad->entity, &format, pad->index,
+ V4L2_SUBDEV_FORMAT_ACTIVE);
+ if (ret < 0) {
+ printf("error: set format failed on %s:%u.\n",
+ pad->entity->info->name, pad->index);
+ return ret;
+ }
+
+ if (sink->last)
+ format = *fmt;
+ else
+ format.code = fmt->code;
+
+ ret = v4l2_subdev_set_format(pad->entity, &format, pad->index,
+ V4L2_SUBDEV_FORMAT_ACTIVE);
+ if (ret < 0) {
+ printf("error: set format failed on %s:%u.\n",
+ pad->entity->info.name, pad->index);
+ return ret;
+ }
+
+ if (sink->last)
+ *fmt = format;
+ }
+
+ source->source.format = format;
+
+ if ((sink->info->type & MEDIA_ENT_TYPE_MASK) == MEDIA_ENT_T_V4L2_SUBDEV) {
+ /* Propagate the format to the link target. */
+ pad = sink->sink.link->sink;
+
+ ret = v4l2_subdev_set_format(pad->entity, &format, pad->index,
+ V4L2_SUBDEV_FORMAT_ACTIVE);
+ if (ret < 0) {
+ printf("error: set format failed on %s:%u.\n",
+ pad->entity->info.name, pad->index);
+ return ret;
+ }
+ } else if (sink->info->type == MEDIA_ENT_T_DEVNODE_V4L) {
+ video = to_xlnx_video_node(sink);
+
+ /* Set the capture format on the capture or output video
+ * node. If the connected source is a pool, v4l2_fmt
+ * already contains the format that has been setup on
+ * the associated capture video node.
+ */
+ memset(&v4l2_fmt, 0, sizeof v4l2_fmt);
+ v4l2_fmt.pixelformat = mbus_to_pix(format.code);
+ v4l2_fmt.width = format.width;
+ v4l2_fmt.height = format.height;
+
+ ret = v4l2_set_format(video->video, &v4l2_fmt);
+ if (ret < 0) {
+ printf("error: set format failed on %s.\n",
+ sink->info->name);
+ return ret;
+ }
+
+ format.code = pix_to_mbus(v4l2_fmt.pixelformat);
+ format.width = v4l2_fmt.width;
+ format.height = v4l2_fmt.height;
+
+ video->format = format;
+ }
+
+ sink->sink.format = format;
+
+ source = sink;
+ }
+
+ return 0;
+}
+
+static int xlnx_video_validate_entity(struct media_entity *entity,
+ const struct media_link **linkp)
+{
+ const struct media_link *link_source = NULL;
+ const struct media_link *link_sink = NULL;
+ const struct media_entity_desc *info;
+ unsigned int num_links;
+ unsigned int i;
+
+ info = media_entity_get_info(entity);
+
+ if (info->type == MEDIA_ENT_T_DEVNODE_V4L) {
+ const struct media_link *link;
+
+ /* Video nodes must have a single pad and a single link. */
+ if (info->pads != 1)
+ return -EINVAL;
+
+ if (media_entity_get_links_count(entity) != 1)
+ return -EINVAL;
+
+ link = media_entity_get_link(entity, 0);
+ *linkp = link->sink->entity == entity ? link : NULL;
+ return 0;
+ }
+
+ if ((info->type & MEDIA_ENT_TYPE_MASK) != MEDIA_ENT_T_V4L2_SUBDEV)
+ return -EINVAL;
+
+ /*
+ * Only components with linear internal pipelines are supported, the
+ * entity must have a single connected source and a single connected
+ * sink.
+ */
+ num_links = media_entity_get_links_count(entity);
+
+ for (i = 0; i < num_links; ++i) {
+ const struct media_link *link = media_entity_get_link(entity, i);
+
+ /*
+ * Components with configurable internal pipelines are not
+ * supported.
+ */
+ if (!(link->flags & MEDIA_LNK_FL_IMMUTABLE))
+ return -EINVAL;
+
+ if (!(link->flags & MEDIA_LNK_FL_ENABLED))
+ continue;
+
+ if (link->source->entity == entity) {
+ if (link_source && link_source != link)
+ return -EINVAL;
+ link_source = link;
+ } else {
+ if (link_sink && link_sink != link)
+ return -EINVAL;
+ link_sink = link;
+ }
+ }
+
+ *linkp = link_sink;
+ return 0;
+}
+
+static int xlnx_video_scan_component(struct xlnx_video_component *xvcomp)
+{
+ struct media_entity *entity;
+
+ entity = xvcomp->output->entity;
+
+ while (1) {
+ const struct media_link *link;
+ int ret;
+
+ ret = xlnx_video_validate_entity(entity, &link);
+ if (ret < 0)
+ return ret;
+
+ if (link == NULL) {
+ const struct media_entity_desc *info;
+
+ info = media_entity_get_info(entity);
+ if (info->type == MEDIA_ENT_T_DEVNODE_V4L)
+ xvcomp->input.entity = entity;
+ break;
+ }
+
+ entity = link->source->entity;
+ }
+
+ return 0;
+}
+
+static int xlnx_video_scan_media(struct xlnx_video *xv, struct media_device *media)
+{
+}
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <linux/media-bus-format.h>
+#include <linux/videodev2.h>
+#include <linux/v4l2-subdev.h>
+
+#include <mediactl.h>
+
+#include <xlnx-plugin.h>
+
+static const struct xlnx_video_plugin_info v4l2_plugin_info = {
+ .name = "V4L2 plugin",
+ .type = XLNX_VIDEO_PLUGIN_TYPE_HARDWARE,
+};
+
+static const struct xlnx_video_plugin_info *v4l2_info(void)
+{
+ return &v4l2_plugin_info;
+}
+
+/*
+ * \brief Xilinx V4L2 video node
+ */
+struct v4l2_video_node {
+ /* V4L2 device */
+ struct v4l2_device *video;
+#if 0
+ /* Current video format */
+ struct v4l2_mbus_framefmt format;
+ struct xlnx_video_buffers_pool *pool;
+ /* Number of buffers queued to the driver */
+ unsigned int queued;
+ /* Whether video capture is running on the device */
+ bool running;
+#endif
+};
+
+struct v4l2_component {
+ struct xlnx_video_component *xvcomp;
+
+ struct v4l2_video_node *input;
+ struct v4l2_video_node *output;
+};
+
+static struct xlnx_video_component *v4l2_scan(struct xlnx_video *xv,
+ struct media_device *mdev)
+{
+ struct media_entity *entity;
+ unsigned int num_entities;
+ unsigned int i;
+
+ num_entities = media_get_entities_count(mdev);
+ if (num_entities == 0)
+ return 0;
+
+ for (i = 0; i < num_entities; ++i) {
+ const struct media_entity_desc *info;
+ const struct media_pad *pad;
+ struct v4l2_component *xvcomp;
+ int ret;
+
+ entity = media_get_entity(mdev, i);
+ info = media_entity_get_info(entity);
+
+ if (info->type != MEDIA_ENT_T_DEVNODE_V4L ||
+ info->pads != 1)
+ continue;
+
+ pad = media_entity_get_pad(entity, 0);
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ continue;
+
+ xvcomp = calloc(1, sizeof *xvcomp);
+ if (!xvcomp)
+ return -ENOMEM;
+
+ list_init(&xvcomp->entities);
+ xvcomp->xv = xv;
+ xvcomp->output.entity = entity;
+
+ ret = xlnx_video_scan_component(xvcomp);
+ if (ret < 0) {
+ free(xvcomp);
+ return ret;
+ }
+
+ list_append(&xv->components, &xvcomp->list);
+ xv->num_components++;
+ }
+
+ return 0;
+}
+
+EXPORT_XLNX_VIDEO_PLUGIN(v4l2) = {
+ .info = v4l2_info,
+ .scan = v4l2_scan,
+};