summaryrefslogtreecommitdiffstats
path: root/drivers/media/test-drivers/vimc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/media/test-drivers/vimc
parentInitial commit. (diff)
downloadlinux-upstream.tar.xz
linux-upstream.zip
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/media/test-drivers/vimc')
-rw-r--r--drivers/media/test-drivers/vimc/Kconfig20
-rw-r--r--drivers/media/test-drivers/vimc/Makefile6
-rw-r--r--drivers/media/test-drivers/vimc/vimc-capture.c500
-rw-r--r--drivers/media/test-drivers/vimc/vimc-common.c400
-rw-r--r--drivers/media/test-drivers/vimc/vimc-common.h241
-rw-r--r--drivers/media/test-drivers/vimc/vimc-core.c455
-rw-r--r--drivers/media/test-drivers/vimc/vimc-debayer.c626
-rw-r--r--drivers/media/test-drivers/vimc/vimc-lens.c102
-rw-r--r--drivers/media/test-drivers/vimc/vimc-scaler.c444
-rw-r--r--drivers/media/test-drivers/vimc/vimc-sensor.c453
-rw-r--r--drivers/media/test-drivers/vimc/vimc-streamer.c238
-rw-r--r--drivers/media/test-drivers/vimc/vimc-streamer.h45
12 files changed, 3530 insertions, 0 deletions
diff --git a/drivers/media/test-drivers/vimc/Kconfig b/drivers/media/test-drivers/vimc/Kconfig
new file mode 100644
index 000000000..0d5169819
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_VIMC
+ tristate "Virtual Media Controller Driver (VIMC)"
+ depends on VIDEO_DEV
+ select FONT_SUPPORT
+ select FONT_8x16
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select VIDEOBUF2_VMALLOC
+ select VIDEOBUF2_DMA_CONTIG
+ select VIDEO_V4L2_TPG
+ help
+ Skeleton driver for Virtual Media Controller
+
+ This driver can be compared to the vivid driver for emulating
+ a media node that exposes a complex media topology. The topology
+ is hard coded for now but is meant to be highly configurable in
+ the future.
+
+ When in doubt, say N.
diff --git a/drivers/media/test-drivers/vimc/Makefile b/drivers/media/test-drivers/vimc/Makefile
new file mode 100644
index 000000000..9b9631562
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+vimc-y := vimc-core.o vimc-common.o vimc-streamer.o vimc-capture.o \
+ vimc-debayer.o vimc-scaler.o vimc-sensor.o vimc-lens.o
+
+obj-$(CONFIG_VIDEO_VIMC) += vimc.o
+
diff --git a/drivers/media/test-drivers/vimc/vimc-capture.c b/drivers/media/test-drivers/vimc/vimc-capture.c
new file mode 100644
index 000000000..aa944270e
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/vimc-capture.c
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * vimc-capture.c Virtual Media Controller Driver
+ *
+ * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com>
+ */
+
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "vimc-common.h"
+#include "vimc-streamer.h"
+
+struct vimc_capture_device {
+ struct vimc_ent_device ved;
+ struct video_device vdev;
+ struct v4l2_pix_format format;
+ struct vb2_queue queue;
+ struct list_head buf_list;
+ /*
+ * NOTE: in a real driver, a spin lock must be used to access the
+ * queue because the frames are generated from a hardware interruption
+ * and the isr is not allowed to sleep.
+ * Even if it is not necessary a spinlock in the vimc driver, we
+ * use it here as a code reference
+ */
+ spinlock_t qlock;
+ struct mutex lock;
+ u32 sequence;
+ struct vimc_stream stream;
+ struct media_pad pad;
+};
+
+static const struct v4l2_pix_format fmt_default = {
+ .width = 640,
+ .height = 480,
+ .pixelformat = V4L2_PIX_FMT_RGB24,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+};
+
+struct vimc_capture_buffer {
+ /*
+ * struct vb2_v4l2_buffer must be the first element
+ * the videobuf2 framework will allocate this struct based on
+ * buf_struct_size and use the first sizeof(struct vb2_buffer) bytes of
+ * memory as a vb2_buffer
+ */
+ struct vb2_v4l2_buffer vb2;
+ struct list_head list;
+};
+
+static int vimc_capture_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ strscpy(cap->driver, VIMC_PDEV_NAME, sizeof(cap->driver));
+ strscpy(cap->card, KBUILD_MODNAME, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info),
+ "platform:%s", VIMC_PDEV_NAME);
+
+ return 0;
+}
+
+static void vimc_capture_get_format(struct vimc_ent_device *ved,
+ struct v4l2_pix_format *fmt)
+{
+ struct vimc_capture_device *vcapture = container_of(ved, struct vimc_capture_device,
+ ved);
+
+ *fmt = vcapture->format;
+}
+
+static int vimc_capture_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct vimc_capture_device *vcapture = video_drvdata(file);
+
+ f->fmt.pix = vcapture->format;
+
+ return 0;
+}
+
+static int vimc_capture_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct v4l2_pix_format *format = &f->fmt.pix;
+ const struct vimc_pix_map *vpix;
+
+ format->width = clamp_t(u32, format->width, VIMC_FRAME_MIN_WIDTH,
+ VIMC_FRAME_MAX_WIDTH) & ~1;
+ format->height = clamp_t(u32, format->height, VIMC_FRAME_MIN_HEIGHT,
+ VIMC_FRAME_MAX_HEIGHT) & ~1;
+
+ /* Don't accept a pixelformat that is not on the table */
+ vpix = vimc_pix_map_by_pixelformat(format->pixelformat);
+ if (!vpix) {
+ format->pixelformat = fmt_default.pixelformat;
+ vpix = vimc_pix_map_by_pixelformat(format->pixelformat);
+ }
+ /* TODO: Add support for custom bytesperline values */
+ format->bytesperline = format->width * vpix->bpp;
+ format->sizeimage = format->bytesperline * format->height;
+
+ if (format->field == V4L2_FIELD_ANY)
+ format->field = fmt_default.field;
+
+ vimc_colorimetry_clamp(format);
+
+ if (format->colorspace == V4L2_COLORSPACE_DEFAULT)
+ format->colorspace = fmt_default.colorspace;
+
+ return 0;
+}
+
+static int vimc_capture_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct vimc_capture_device *vcapture = video_drvdata(file);
+ int ret;
+
+ /* Do not change the format while stream is on */
+ if (vb2_is_busy(&vcapture->queue))
+ return -EBUSY;
+
+ ret = vimc_capture_try_fmt_vid_cap(file, priv, f);
+ if (ret)
+ return ret;
+
+ dev_dbg(vcapture->ved.dev, "%s: format update: "
+ "old:%dx%d (0x%x, %d, %d, %d, %d) "
+ "new:%dx%d (0x%x, %d, %d, %d, %d)\n", vcapture->vdev.name,
+ /* old */
+ vcapture->format.width, vcapture->format.height,
+ vcapture->format.pixelformat, vcapture->format.colorspace,
+ vcapture->format.quantization, vcapture->format.xfer_func,
+ vcapture->format.ycbcr_enc,
+ /* new */
+ f->fmt.pix.width, f->fmt.pix.height,
+ f->fmt.pix.pixelformat, f->fmt.pix.colorspace,
+ f->fmt.pix.quantization, f->fmt.pix.xfer_func,
+ f->fmt.pix.ycbcr_enc);
+
+ vcapture->format = f->fmt.pix;
+
+ return 0;
+}
+
+static int vimc_capture_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ const struct vimc_pix_map *vpix;
+
+ if (f->mbus_code) {
+ if (f->index > 0)
+ return -EINVAL;
+
+ vpix = vimc_pix_map_by_code(f->mbus_code);
+ } else {
+ vpix = vimc_pix_map_by_index(f->index);
+ }
+
+ if (!vpix)
+ return -EINVAL;
+
+ f->pixelformat = vpix->pixelformat;
+
+ return 0;
+}
+
+static int vimc_capture_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *fsize)
+{
+ const struct vimc_pix_map *vpix;
+
+ if (fsize->index)
+ return -EINVAL;
+
+ /* Only accept code in the pix map table */
+ vpix = vimc_pix_map_by_code(fsize->pixel_format);
+ if (!vpix)
+ return -EINVAL;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
+ fsize->stepwise.min_width = VIMC_FRAME_MIN_WIDTH;
+ fsize->stepwise.max_width = VIMC_FRAME_MAX_WIDTH;
+ fsize->stepwise.min_height = VIMC_FRAME_MIN_HEIGHT;
+ fsize->stepwise.max_height = VIMC_FRAME_MAX_HEIGHT;
+ fsize->stepwise.step_width = 1;
+ fsize->stepwise.step_height = 1;
+
+ return 0;
+}
+
+static const struct v4l2_file_operations vimc_capture_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+};
+
+static const struct v4l2_ioctl_ops vimc_capture_ioctl_ops = {
+ .vidioc_querycap = vimc_capture_querycap,
+
+ .vidioc_g_fmt_vid_cap = vimc_capture_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vimc_capture_s_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vimc_capture_try_fmt_vid_cap,
+ .vidioc_enum_fmt_vid_cap = vimc_capture_enum_fmt_vid_cap,
+ .vidioc_enum_framesizes = vimc_capture_enum_framesizes,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+static void vimc_capture_return_all_buffers(struct vimc_capture_device *vcapture,
+ enum vb2_buffer_state state)
+{
+ struct vimc_capture_buffer *vbuf, *node;
+
+ spin_lock(&vcapture->qlock);
+
+ list_for_each_entry_safe(vbuf, node, &vcapture->buf_list, list) {
+ list_del(&vbuf->list);
+ vb2_buffer_done(&vbuf->vb2.vb2_buf, state);
+ }
+
+ spin_unlock(&vcapture->qlock);
+}
+
+static int vimc_capture_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct vimc_capture_device *vcapture = vb2_get_drv_priv(vq);
+ int ret;
+
+ vcapture->sequence = 0;
+
+ /* Start the media pipeline */
+ ret = video_device_pipeline_start(&vcapture->vdev, &vcapture->stream.pipe);
+ if (ret) {
+ vimc_capture_return_all_buffers(vcapture, VB2_BUF_STATE_QUEUED);
+ return ret;
+ }
+
+ ret = vimc_streamer_s_stream(&vcapture->stream, &vcapture->ved, 1);
+ if (ret) {
+ video_device_pipeline_stop(&vcapture->vdev);
+ vimc_capture_return_all_buffers(vcapture, VB2_BUF_STATE_QUEUED);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Stop the stream engine. Any remaining buffers in the stream queue are
+ * dequeued and passed on to the vb2 framework marked as STATE_ERROR.
+ */
+static void vimc_capture_stop_streaming(struct vb2_queue *vq)
+{
+ struct vimc_capture_device *vcapture = vb2_get_drv_priv(vq);
+
+ vimc_streamer_s_stream(&vcapture->stream, &vcapture->ved, 0);
+
+ /* Stop the media pipeline */
+ video_device_pipeline_stop(&vcapture->vdev);
+
+ /* Release all active buffers */
+ vimc_capture_return_all_buffers(vcapture, VB2_BUF_STATE_ERROR);
+}
+
+static void vimc_capture_buf_queue(struct vb2_buffer *vb2_buf)
+{
+ struct vimc_capture_device *vcapture = vb2_get_drv_priv(vb2_buf->vb2_queue);
+ struct vimc_capture_buffer *buf = container_of(vb2_buf,
+ struct vimc_capture_buffer,
+ vb2.vb2_buf);
+
+ spin_lock(&vcapture->qlock);
+ list_add_tail(&buf->list, &vcapture->buf_list);
+ spin_unlock(&vcapture->qlock);
+}
+
+static int vimc_capture_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
+ unsigned int *nplanes, unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct vimc_capture_device *vcapture = vb2_get_drv_priv(vq);
+
+ if (*nplanes)
+ return sizes[0] < vcapture->format.sizeimage ? -EINVAL : 0;
+ /* We don't support multiplanes for now */
+ *nplanes = 1;
+ sizes[0] = vcapture->format.sizeimage;
+
+ return 0;
+}
+
+static int vimc_capture_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vimc_capture_device *vcapture = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long size = vcapture->format.sizeimage;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ dev_err(vcapture->ved.dev, "%s: buffer too small (%lu < %lu)\n",
+ vcapture->vdev.name, vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct vb2_ops vimc_capture_qops = {
+ .start_streaming = vimc_capture_start_streaming,
+ .stop_streaming = vimc_capture_stop_streaming,
+ .buf_queue = vimc_capture_buf_queue,
+ .queue_setup = vimc_capture_queue_setup,
+ .buf_prepare = vimc_capture_buffer_prepare,
+ /*
+ * Since q->lock is set we can use the standard
+ * vb2_ops_wait_prepare/finish helper functions.
+ */
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static const struct media_entity_operations vimc_capture_mops = {
+ .link_validate = vimc_vdev_link_validate,
+};
+
+static void vimc_capture_release(struct vimc_ent_device *ved)
+{
+ struct vimc_capture_device *vcapture =
+ container_of(ved, struct vimc_capture_device, ved);
+
+ media_entity_cleanup(vcapture->ved.ent);
+ kfree(vcapture);
+}
+
+static void vimc_capture_unregister(struct vimc_ent_device *ved)
+{
+ struct vimc_capture_device *vcapture =
+ container_of(ved, struct vimc_capture_device, ved);
+
+ vb2_video_unregister_device(&vcapture->vdev);
+}
+
+static void *vimc_capture_process_frame(struct vimc_ent_device *ved,
+ const void *frame)
+{
+ struct vimc_capture_device *vcapture = container_of(ved, struct vimc_capture_device,
+ ved);
+ struct vimc_capture_buffer *vimc_buf;
+ void *vbuf;
+
+ spin_lock(&vcapture->qlock);
+
+ /* Get the first entry of the list */
+ vimc_buf = list_first_entry_or_null(&vcapture->buf_list,
+ typeof(*vimc_buf), list);
+ if (!vimc_buf) {
+ spin_unlock(&vcapture->qlock);
+ return ERR_PTR(-EAGAIN);
+ }
+
+ /* Remove this entry from the list */
+ list_del(&vimc_buf->list);
+
+ spin_unlock(&vcapture->qlock);
+
+ /* Fill the buffer */
+ vimc_buf->vb2.vb2_buf.timestamp = ktime_get_ns();
+ vimc_buf->vb2.sequence = vcapture->sequence++;
+ vimc_buf->vb2.field = vcapture->format.field;
+
+ vbuf = vb2_plane_vaddr(&vimc_buf->vb2.vb2_buf, 0);
+
+ memcpy(vbuf, frame, vcapture->format.sizeimage);
+
+ /* Set it as ready */
+ vb2_set_plane_payload(&vimc_buf->vb2.vb2_buf, 0,
+ vcapture->format.sizeimage);
+ vb2_buffer_done(&vimc_buf->vb2.vb2_buf, VB2_BUF_STATE_DONE);
+ return NULL;
+}
+
+static struct vimc_ent_device *vimc_capture_add(struct vimc_device *vimc,
+ const char *vcfg_name)
+{
+ struct v4l2_device *v4l2_dev = &vimc->v4l2_dev;
+ const struct vimc_pix_map *vpix;
+ struct vimc_capture_device *vcapture;
+ struct video_device *vdev;
+ struct vb2_queue *q;
+ int ret;
+
+ /* Allocate the vimc_capture_device struct */
+ vcapture = kzalloc(sizeof(*vcapture), GFP_KERNEL);
+ if (!vcapture)
+ return ERR_PTR(-ENOMEM);
+
+ /* Initialize the media entity */
+ vcapture->vdev.entity.name = vcfg_name;
+ vcapture->vdev.entity.function = MEDIA_ENT_F_IO_V4L;
+ vcapture->pad.flags = MEDIA_PAD_FL_SINK;
+ ret = media_entity_pads_init(&vcapture->vdev.entity,
+ 1, &vcapture->pad);
+ if (ret)
+ goto err_free_vcapture;
+
+ /* Initialize the lock */
+ mutex_init(&vcapture->lock);
+
+ /* Initialize the vb2 queue */
+ q = &vcapture->queue;
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ if (vimc_allocator == VIMC_ALLOCATOR_VMALLOC)
+ q->io_modes |= VB2_USERPTR;
+ q->drv_priv = vcapture;
+ q->buf_struct_size = sizeof(struct vimc_capture_buffer);
+ q->ops = &vimc_capture_qops;
+ q->mem_ops = vimc_allocator == VIMC_ALLOCATOR_DMA_CONTIG
+ ? &vb2_dma_contig_memops : &vb2_vmalloc_memops;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->min_buffers_needed = 2;
+ q->lock = &vcapture->lock;
+ q->dev = v4l2_dev->dev;
+
+ ret = vb2_queue_init(q);
+ if (ret) {
+ dev_err(vimc->mdev.dev, "%s: vb2 queue init failed (err=%d)\n",
+ vcfg_name, ret);
+ goto err_clean_m_ent;
+ }
+
+ /* Initialize buffer list and its lock */
+ INIT_LIST_HEAD(&vcapture->buf_list);
+ spin_lock_init(&vcapture->qlock);
+
+ /* Set default frame format */
+ vcapture->format = fmt_default;
+ vpix = vimc_pix_map_by_pixelformat(vcapture->format.pixelformat);
+ vcapture->format.bytesperline = vcapture->format.width * vpix->bpp;
+ vcapture->format.sizeimage = vcapture->format.bytesperline *
+ vcapture->format.height;
+
+ /* Fill the vimc_ent_device struct */
+ vcapture->ved.ent = &vcapture->vdev.entity;
+ vcapture->ved.process_frame = vimc_capture_process_frame;
+ vcapture->ved.vdev_get_format = vimc_capture_get_format;
+ vcapture->ved.dev = vimc->mdev.dev;
+
+ /* Initialize the video_device struct */
+ vdev = &vcapture->vdev;
+ vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING
+ | V4L2_CAP_IO_MC;
+ vdev->entity.ops = &vimc_capture_mops;
+ vdev->release = video_device_release_empty;
+ vdev->fops = &vimc_capture_fops;
+ vdev->ioctl_ops = &vimc_capture_ioctl_ops;
+ vdev->lock = &vcapture->lock;
+ vdev->queue = q;
+ vdev->v4l2_dev = v4l2_dev;
+ vdev->vfl_dir = VFL_DIR_RX;
+ strscpy(vdev->name, vcfg_name, sizeof(vdev->name));
+ video_set_drvdata(vdev, &vcapture->ved);
+
+ /* Register the video_device with the v4l2 and the media framework */
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret) {
+ dev_err(vimc->mdev.dev, "%s: video register failed (err=%d)\n",
+ vcapture->vdev.name, ret);
+ goto err_clean_m_ent;
+ }
+
+ return &vcapture->ved;
+
+err_clean_m_ent:
+ media_entity_cleanup(&vcapture->vdev.entity);
+err_free_vcapture:
+ kfree(vcapture);
+
+ return ERR_PTR(ret);
+}
+
+struct vimc_ent_type vimc_capture_type = {
+ .add = vimc_capture_add,
+ .unregister = vimc_capture_unregister,
+ .release = vimc_capture_release
+};
diff --git a/drivers/media/test-drivers/vimc/vimc-common.c b/drivers/media/test-drivers/vimc/vimc-common.c
new file mode 100644
index 000000000..7b27153c0
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/vimc-common.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * vimc-common.c Virtual Media Controller Driver
+ *
+ * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include "vimc-common.h"
+
+/*
+ * NOTE: non-bayer formats need to come first (necessary for enum_mbus_code
+ * in the scaler)
+ */
+static const struct vimc_pix_map vimc_pix_map_list[] = {
+ /* TODO: add all missing formats */
+
+ /* RGB formats */
+ {
+ .code = {
+ MEDIA_BUS_FMT_BGR888_1X24,
+ MEDIA_BUS_FMT_BGR888_3X8
+ },
+ .pixelformat = V4L2_PIX_FMT_BGR24,
+ .bpp = 3,
+ .bayer = false,
+ },
+ {
+ .code = {
+ MEDIA_BUS_FMT_RGB888_1X24,
+ MEDIA_BUS_FMT_RGB888_2X12_BE,
+ MEDIA_BUS_FMT_RGB888_2X12_LE,
+ MEDIA_BUS_FMT_RGB888_3X8,
+ MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,
+ MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA,
+ MEDIA_BUS_FMT_RGB888_1X32_PADHI,
+ MEDIA_BUS_FMT_GBR888_1X24
+ },
+ .pixelformat = V4L2_PIX_FMT_RGB24,
+ .bpp = 3,
+ .bayer = false,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_ARGB8888_1X32 },
+ .pixelformat = V4L2_PIX_FMT_ARGB32,
+ .bpp = 4,
+ .bayer = false,
+ },
+
+ /* Bayer formats */
+ {
+ .code = { MEDIA_BUS_FMT_SBGGR8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .bpp = 1,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SGBRG8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SGBRG8,
+ .bpp = 1,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SGRBG8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SGRBG8,
+ .bpp = 1,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SRGGB8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SRGGB8,
+ .bpp = 1,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SBGGR10_1X10 },
+ .pixelformat = V4L2_PIX_FMT_SBGGR10,
+ .bpp = 2,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SGBRG10_1X10 },
+ .pixelformat = V4L2_PIX_FMT_SGBRG10,
+ .bpp = 2,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SGRBG10_1X10 },
+ .pixelformat = V4L2_PIX_FMT_SGRBG10,
+ .bpp = 2,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SRGGB10_1X10 },
+ .pixelformat = V4L2_PIX_FMT_SRGGB10,
+ .bpp = 2,
+ .bayer = true,
+ },
+
+ /* 10bit raw bayer a-law compressed to 8 bits */
+ {
+ .code = { MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SBGGR10ALAW8,
+ .bpp = 1,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SGBRG10ALAW8,
+ .bpp = 1,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SGRBG10ALAW8,
+ .bpp = 1,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SRGGB10ALAW8,
+ .bpp = 1,
+ .bayer = true,
+ },
+
+ /* 10bit raw bayer DPCM compressed to 8 bits */
+ {
+ .code = { MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SBGGR10DPCM8,
+ .bpp = 1,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SGBRG10DPCM8,
+ .bpp = 1,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SGRBG10DPCM8,
+ .bpp = 1,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8 },
+ .pixelformat = V4L2_PIX_FMT_SRGGB10DPCM8,
+ .bpp = 1,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SBGGR12_1X12 },
+ .pixelformat = V4L2_PIX_FMT_SBGGR12,
+ .bpp = 2,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SGBRG12_1X12 },
+ .pixelformat = V4L2_PIX_FMT_SGBRG12,
+ .bpp = 2,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SGRBG12_1X12 },
+ .pixelformat = V4L2_PIX_FMT_SGRBG12,
+ .bpp = 2,
+ .bayer = true,
+ },
+ {
+ .code = { MEDIA_BUS_FMT_SRGGB12_1X12 },
+ .pixelformat = V4L2_PIX_FMT_SRGGB12,
+ .bpp = 2,
+ .bayer = true,
+ },
+};
+
+bool vimc_is_source(struct media_entity *ent)
+{
+ unsigned int i;
+
+ for (i = 0; i < ent->num_pads; i++)
+ if (ent->pads[i].flags & MEDIA_PAD_FL_SINK)
+ return false;
+ return true;
+}
+
+const struct vimc_pix_map *vimc_pix_map_by_index(unsigned int i)
+{
+ if (i >= ARRAY_SIZE(vimc_pix_map_list))
+ return NULL;
+
+ return &vimc_pix_map_list[i];
+}
+
+u32 vimc_mbus_code_by_index(unsigned int index)
+{
+ unsigned int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) {
+ for (j = 0; j < ARRAY_SIZE(vimc_pix_map_list[i].code); j++) {
+ if (!vimc_pix_map_list[i].code[j])
+ break;
+
+ if (!index)
+ return vimc_pix_map_list[i].code[j];
+ index--;
+ }
+ }
+ return 0;
+}
+
+const struct vimc_pix_map *vimc_pix_map_by_code(u32 code)
+{
+ unsigned int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) {
+ for (j = 0; j < ARRAY_SIZE(vimc_pix_map_list[i].code); j++) {
+ if (vimc_pix_map_list[i].code[j] == code)
+ return &vimc_pix_map_list[i];
+ }
+ }
+ return NULL;
+}
+
+const struct vimc_pix_map *vimc_pix_map_by_pixelformat(u32 pixelformat)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) {
+ if (vimc_pix_map_list[i].pixelformat == pixelformat)
+ return &vimc_pix_map_list[i];
+ }
+ return NULL;
+}
+
+static int vimc_get_pix_format(struct media_pad *pad,
+ struct v4l2_pix_format *fmt)
+{
+ if (is_media_entity_v4l2_subdev(pad->entity)) {
+ struct v4l2_subdev *sd =
+ media_entity_to_v4l2_subdev(pad->entity);
+ struct v4l2_subdev_format sd_fmt;
+ const struct vimc_pix_map *pix_map;
+ int ret;
+
+ sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ sd_fmt.pad = pad->index;
+
+ ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &sd_fmt);
+ if (ret)
+ return ret;
+
+ v4l2_fill_pix_format(fmt, &sd_fmt.format);
+ pix_map = vimc_pix_map_by_code(sd_fmt.format.code);
+ fmt->pixelformat = pix_map->pixelformat;
+ } else if (is_media_entity_v4l2_video_device(pad->entity)) {
+ struct video_device *vdev = container_of(pad->entity,
+ struct video_device,
+ entity);
+ struct vimc_ent_device *ved = video_get_drvdata(vdev);
+
+ if (!ved->vdev_get_format)
+ return -ENOIOCTLCMD;
+
+ ved->vdev_get_format(ved, fmt);
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int vimc_vdev_link_validate(struct media_link *link)
+{
+ struct v4l2_pix_format source_fmt, sink_fmt;
+ int ret;
+
+ ret = vimc_get_pix_format(link->source, &source_fmt);
+ if (ret)
+ return ret;
+
+ ret = vimc_get_pix_format(link->sink, &sink_fmt);
+ if (ret)
+ return ret;
+
+ pr_info("vimc link validate: "
+ "%s:src:%dx%d (0x%x, %d, %d, %d, %d) "
+ "%s:snk:%dx%d (0x%x, %d, %d, %d, %d)\n",
+ /* src */
+ link->source->entity->name,
+ source_fmt.width, source_fmt.height,
+ source_fmt.pixelformat, source_fmt.colorspace,
+ source_fmt.quantization, source_fmt.xfer_func,
+ source_fmt.ycbcr_enc,
+ /* sink */
+ link->sink->entity->name,
+ sink_fmt.width, sink_fmt.height,
+ sink_fmt.pixelformat, sink_fmt.colorspace,
+ sink_fmt.quantization, sink_fmt.xfer_func,
+ sink_fmt.ycbcr_enc);
+
+ /* The width, height and pixelformat must match. */
+ if (source_fmt.width != sink_fmt.width ||
+ source_fmt.height != sink_fmt.height ||
+ source_fmt.pixelformat != sink_fmt.pixelformat)
+ return -EPIPE;
+
+ /*
+ * The field order must match, or the sink field order must be NONE
+ * to support interlaced hardware connected to bridges that support
+ * progressive formats only.
+ */
+ if (source_fmt.field != sink_fmt.field &&
+ sink_fmt.field != V4L2_FIELD_NONE)
+ return -EPIPE;
+
+ /*
+ * If colorspace is DEFAULT, then assume all the colorimetry is also
+ * DEFAULT, return 0 to skip comparing the other colorimetry parameters
+ */
+ if (source_fmt.colorspace == V4L2_COLORSPACE_DEFAULT ||
+ sink_fmt.colorspace == V4L2_COLORSPACE_DEFAULT)
+ return 0;
+
+ /* Colorspace must match. */
+ if (source_fmt.colorspace != sink_fmt.colorspace)
+ return -EPIPE;
+
+ /* Colorimetry must match if they are not set to DEFAULT */
+ if (source_fmt.ycbcr_enc != V4L2_YCBCR_ENC_DEFAULT &&
+ sink_fmt.ycbcr_enc != V4L2_YCBCR_ENC_DEFAULT &&
+ source_fmt.ycbcr_enc != sink_fmt.ycbcr_enc)
+ return -EPIPE;
+
+ if (source_fmt.quantization != V4L2_QUANTIZATION_DEFAULT &&
+ sink_fmt.quantization != V4L2_QUANTIZATION_DEFAULT &&
+ source_fmt.quantization != sink_fmt.quantization)
+ return -EPIPE;
+
+ if (source_fmt.xfer_func != V4L2_XFER_FUNC_DEFAULT &&
+ sink_fmt.xfer_func != V4L2_XFER_FUNC_DEFAULT &&
+ source_fmt.xfer_func != sink_fmt.xfer_func)
+ return -EPIPE;
+
+ return 0;
+}
+
+static const struct media_entity_operations vimc_ent_sd_mops = {
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+int vimc_ent_sd_register(struct vimc_ent_device *ved,
+ struct v4l2_subdev *sd,
+ struct v4l2_device *v4l2_dev,
+ const char *const name,
+ u32 function,
+ u16 num_pads,
+ struct media_pad *pads,
+ const struct v4l2_subdev_ops *sd_ops)
+{
+ int ret;
+
+ /* Fill the vimc_ent_device struct */
+ ved->ent = &sd->entity;
+
+ /* Initialize the subdev */
+ v4l2_subdev_init(sd, sd_ops);
+ sd->entity.function = function;
+ sd->entity.ops = &vimc_ent_sd_mops;
+ sd->owner = THIS_MODULE;
+ strscpy(sd->name, name, sizeof(sd->name));
+ v4l2_set_subdevdata(sd, ved);
+
+ /* Expose this subdev to user space */
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ if (sd->ctrl_handler)
+ sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS;
+
+ /* Initialize the media entity */
+ ret = media_entity_pads_init(&sd->entity, num_pads, pads);
+ if (ret)
+ return ret;
+
+ /* Register the subdev with the v4l2 and the media framework */
+ ret = v4l2_device_register_subdev(v4l2_dev, sd);
+ if (ret) {
+ dev_err(v4l2_dev->dev,
+ "%s: subdev register failed (err=%d)\n",
+ name, ret);
+ goto err_clean_m_ent;
+ }
+
+ return 0;
+
+err_clean_m_ent:
+ media_entity_cleanup(&sd->entity);
+ return ret;
+}
diff --git a/drivers/media/test-drivers/vimc/vimc-common.h b/drivers/media/test-drivers/vimc/vimc-common.h
new file mode 100644
index 000000000..7641a101a
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/vimc-common.h
@@ -0,0 +1,241 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * vimc-common.h Virtual Media Controller Driver
+ *
+ * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com>
+ */
+
+#ifndef _VIMC_COMMON_H_
+#define _VIMC_COMMON_H_
+
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <media/media-device.h>
+#include <media/v4l2-device.h>
+
+#define VIMC_PDEV_NAME "vimc"
+
+/* VIMC-specific controls */
+#define VIMC_CID_VIMC_BASE (0x00f00000 | 0xf000)
+#define VIMC_CID_VIMC_CLASS (0x00f00000 | 1)
+#define VIMC_CID_TEST_PATTERN (VIMC_CID_VIMC_BASE + 0)
+#define VIMC_CID_MEAN_WIN_SIZE (VIMC_CID_VIMC_BASE + 1)
+#define VIMC_CID_OSD_TEXT_MODE (VIMC_CID_VIMC_BASE + 2)
+
+#define VIMC_FRAME_MAX_WIDTH 4096
+#define VIMC_FRAME_MAX_HEIGHT 2160
+#define VIMC_FRAME_MIN_WIDTH 16
+#define VIMC_FRAME_MIN_HEIGHT 16
+
+#define VIMC_FRAME_INDEX(lin, col, width, bpp) ((lin * width + col) * bpp)
+
+/* Source and sink pad checks */
+#define VIMC_IS_SRC(pad) (pad)
+#define VIMC_IS_SINK(pad) (!(pad))
+
+#define VIMC_PIX_FMT_MAX_CODES 8
+
+extern unsigned int vimc_allocator;
+
+enum vimc_allocator_type {
+ VIMC_ALLOCATOR_VMALLOC = 0,
+ VIMC_ALLOCATOR_DMA_CONTIG = 1,
+};
+
+/**
+ * vimc_colorimetry_clamp - Adjust colorimetry parameters
+ *
+ * @fmt: the pointer to struct v4l2_pix_format or
+ * struct v4l2_mbus_framefmt
+ *
+ * Entities must check if colorimetry given by the userspace is valid, if not
+ * then set them as DEFAULT
+ */
+#define vimc_colorimetry_clamp(fmt) \
+do { \
+ if ((fmt)->colorspace == V4L2_COLORSPACE_DEFAULT \
+ || (fmt)->colorspace > V4L2_COLORSPACE_DCI_P3) { \
+ (fmt)->colorspace = V4L2_COLORSPACE_DEFAULT; \
+ (fmt)->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; \
+ (fmt)->quantization = V4L2_QUANTIZATION_DEFAULT; \
+ (fmt)->xfer_func = V4L2_XFER_FUNC_DEFAULT; \
+ } \
+ if ((fmt)->ycbcr_enc > V4L2_YCBCR_ENC_SMPTE240M) \
+ (fmt)->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; \
+ if ((fmt)->quantization > V4L2_QUANTIZATION_LIM_RANGE) \
+ (fmt)->quantization = V4L2_QUANTIZATION_DEFAULT; \
+ if ((fmt)->xfer_func > V4L2_XFER_FUNC_SMPTE2084) \
+ (fmt)->xfer_func = V4L2_XFER_FUNC_DEFAULT; \
+} while (0)
+
+/**
+ * struct vimc_pix_map - maps media bus code with v4l2 pixel format
+ *
+ * @code: media bus format code defined by MEDIA_BUS_FMT_* macros
+ * @bpp: number of bytes each pixel occupies
+ * @pixelformat: pixel format defined by V4L2_PIX_FMT_* macros
+ * @bayer: true if this is a bayer format
+ *
+ * Struct which matches the MEDIA_BUS_FMT_* codes with the corresponding
+ * V4L2_PIX_FMT_* fourcc pixelformat and its bytes per pixel (bpp)
+ */
+struct vimc_pix_map {
+ unsigned int code[VIMC_PIX_FMT_MAX_CODES];
+ unsigned int bpp;
+ u32 pixelformat;
+ bool bayer;
+};
+
+/**
+ * struct vimc_ent_device - core struct that represents an entity in the
+ * topology
+ *
+ * @dev: a pointer of the device struct of the driver
+ * @ent: the pointer to struct media_entity for the node
+ * @process_frame: callback send a frame to that node
+ * @vdev_get_format: callback that returns the current format a pad, used
+ * only when is_media_entity_v4l2_video_device(ent) returns
+ * true
+ *
+ * Each node of the topology must create a vimc_ent_device struct. Depending on
+ * the node it will be of an instance of v4l2_subdev or video_device struct
+ * where both contains a struct media_entity.
+ * Those structures should embedded the vimc_ent_device struct through
+ * v4l2_set_subdevdata() and video_set_drvdata() respectively, allowing the
+ * vimc_ent_device struct to be retrieved from the corresponding struct
+ * media_entity
+ */
+struct vimc_ent_device {
+ struct device *dev;
+ struct media_entity *ent;
+ void * (*process_frame)(struct vimc_ent_device *ved,
+ const void *frame);
+ void (*vdev_get_format)(struct vimc_ent_device *ved,
+ struct v4l2_pix_format *fmt);
+};
+
+/**
+ * struct vimc_device - main device for vimc driver
+ *
+ * @pipe_cfg: pointer to the vimc pipeline configuration structure
+ * @ent_devs: array of vimc_ent_device pointers
+ * @mdev: the associated media_device parent
+ * @v4l2_dev: Internal v4l2 parent device
+ */
+struct vimc_device {
+ const struct vimc_pipeline_config *pipe_cfg;
+ struct vimc_ent_device **ent_devs;
+ struct media_device mdev;
+ struct v4l2_device v4l2_dev;
+};
+
+/**
+ * struct vimc_ent_type Structure for the callbacks of the entity types
+ *
+ *
+ * @add: initializes and registers
+ * vimc entity - called from vimc-core
+ * @unregister: unregisters vimc entity - called from vimc-core
+ * @release: releases vimc entity - called from the v4l2_dev
+ * release callback
+ */
+struct vimc_ent_type {
+ struct vimc_ent_device *(*add)(struct vimc_device *vimc,
+ const char *vcfg_name);
+ void (*unregister)(struct vimc_ent_device *ved);
+ void (*release)(struct vimc_ent_device *ved);
+};
+
+/**
+ * struct vimc_ent_config Structure which describes individual
+ * configuration for each entity
+ *
+ * @name: entity name
+ * @type: contain the callbacks of this entity type
+ *
+ */
+struct vimc_ent_config {
+ const char *name;
+ struct vimc_ent_type *type;
+};
+
+/**
+ * vimc_is_source - returns true if the entity has only source pads
+ *
+ * @ent: pointer to &struct media_entity
+ *
+ */
+bool vimc_is_source(struct media_entity *ent);
+
+extern struct vimc_ent_type vimc_sensor_type;
+extern struct vimc_ent_type vimc_debayer_type;
+extern struct vimc_ent_type vimc_scaler_type;
+extern struct vimc_ent_type vimc_capture_type;
+extern struct vimc_ent_type vimc_lens_type;
+
+/**
+ * vimc_pix_map_by_index - get vimc_pix_map struct by its index
+ *
+ * @i: index of the vimc_pix_map struct in vimc_pix_map_list
+ */
+const struct vimc_pix_map *vimc_pix_map_by_index(unsigned int i);
+
+/**
+ * vimc_mbus_code_by_index - get mbus code by its index
+ *
+ * @index: index of the mbus code in vimc_pix_map_list
+ *
+ * Returns 0 if no mbus code is found for the given index.
+ */
+u32 vimc_mbus_code_by_index(unsigned int index);
+
+/**
+ * vimc_pix_map_by_code - get vimc_pix_map struct by media bus code
+ *
+ * @code: media bus format code defined by MEDIA_BUS_FMT_* macros
+ */
+const struct vimc_pix_map *vimc_pix_map_by_code(u32 code);
+
+/**
+ * vimc_pix_map_by_pixelformat - get vimc_pix_map struct by v4l2 pixel format
+ *
+ * @pixelformat: pixel format defined by V4L2_PIX_FMT_* macros
+ */
+const struct vimc_pix_map *vimc_pix_map_by_pixelformat(u32 pixelformat);
+
+/**
+ * vimc_ent_sd_register - initialize and register a subdev node
+ *
+ * @ved: the vimc_ent_device struct to be initialize
+ * @sd: the v4l2_subdev struct to be initialize and registered
+ * @v4l2_dev: the v4l2 device to register the v4l2_subdev
+ * @name: name of the sub-device. Please notice that the name must be
+ * unique.
+ * @function: media entity function defined by MEDIA_ENT_F_* macros
+ * @num_pads: number of pads to initialize
+ * @pads: the array of pads of the entity, the caller should set the
+ * flags of the pads
+ * @sd_ops: pointer to &struct v4l2_subdev_ops.
+ *
+ * Helper function initialize and register the struct vimc_ent_device and struct
+ * v4l2_subdev which represents a subdev node in the topology
+ */
+int vimc_ent_sd_register(struct vimc_ent_device *ved,
+ struct v4l2_subdev *sd,
+ struct v4l2_device *v4l2_dev,
+ const char *const name,
+ u32 function,
+ u16 num_pads,
+ struct media_pad *pads,
+ const struct v4l2_subdev_ops *sd_ops);
+
+/**
+ * vimc_vdev_link_validate - validates a media link
+ *
+ * @link: pointer to &struct media_link
+ *
+ * This function calls validates if a media link is valid for streaming.
+ */
+int vimc_vdev_link_validate(struct media_link *link);
+
+#endif
diff --git a/drivers/media/test-drivers/vimc/vimc-core.c b/drivers/media/test-drivers/vimc/vimc-core.c
new file mode 100644
index 000000000..e82cfa5ff
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/vimc-core.c
@@ -0,0 +1,455 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * vimc-core.c Virtual Media Controller Driver
+ *
+ * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com>
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/font.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <media/media-device.h>
+#include <media/tpg/v4l2-tpg.h>
+#include <media/v4l2-device.h>
+
+#include "vimc-common.h"
+
+unsigned int vimc_allocator;
+module_param_named(allocator, vimc_allocator, uint, 0444);
+MODULE_PARM_DESC(allocator, " memory allocator selection, default is 0.\n"
+ "\t\t 0 == vmalloc\n"
+ "\t\t 1 == dma-contig");
+
+#define VIMC_MDEV_MODEL_NAME "VIMC MDEV"
+
+#define VIMC_DATA_LINK(src, srcpad, sink, sinkpad, link_flags) { \
+ .src_ent = src, \
+ .src_pad = srcpad, \
+ .sink_ent = sink, \
+ .sink_pad = sinkpad, \
+ .flags = link_flags, \
+}
+
+#define VIMC_ANCILLARY_LINK(primary, ancillary) { \
+ .primary_ent = primary, \
+ .ancillary_ent = ancillary \
+}
+
+/* Structure which describes data links between entities */
+struct vimc_data_link {
+ unsigned int src_ent;
+ u16 src_pad;
+ unsigned int sink_ent;
+ u16 sink_pad;
+ u32 flags;
+};
+
+/* Enum to improve clarity when defining vimc_data_links */
+enum vimc_data_link_ents {
+ SENSOR_A,
+ SENSOR_B,
+ DEBAYER_A,
+ DEBAYER_B,
+ RAW_CAPTURE_0,
+ RAW_CAPTURE_1,
+ RGB_YUV_INPUT,
+ SCALER,
+ RGB_YUV_CAPTURE,
+ LENS_A,
+ LENS_B,
+};
+
+/* Structure which describes ancillary links between entities */
+struct vimc_ancillary_link {
+ unsigned int primary_ent;
+ unsigned int ancillary_ent;
+};
+
+/* Structure which describes the whole topology */
+struct vimc_pipeline_config {
+ const struct vimc_ent_config *ents;
+ size_t num_ents;
+ const struct vimc_data_link *data_links;
+ size_t num_data_links;
+ const struct vimc_ancillary_link *ancillary_links;
+ size_t num_ancillary_links;
+};
+
+/* --------------------------------------------------------------------------
+ * Topology Configuration
+ */
+
+static struct vimc_ent_config ent_config[] = {
+ [SENSOR_A] = {
+ .name = "Sensor A",
+ .type = &vimc_sensor_type
+ },
+ [SENSOR_B] = {
+ .name = "Sensor B",
+ .type = &vimc_sensor_type
+ },
+ [DEBAYER_A] = {
+ .name = "Debayer A",
+ .type = &vimc_debayer_type
+ },
+ [DEBAYER_B] = {
+ .name = "Debayer B",
+ .type = &vimc_debayer_type
+ },
+ [RAW_CAPTURE_0] = {
+ .name = "Raw Capture 0",
+ .type = &vimc_capture_type
+ },
+ [RAW_CAPTURE_1] = {
+ .name = "Raw Capture 1",
+ .type = &vimc_capture_type
+ },
+ [RGB_YUV_INPUT] = {
+ /* TODO: change this to vimc-input when it is implemented */
+ .name = "RGB/YUV Input",
+ .type = &vimc_sensor_type
+ },
+ [SCALER] = {
+ .name = "Scaler",
+ .type = &vimc_scaler_type
+ },
+ [RGB_YUV_CAPTURE] = {
+ .name = "RGB/YUV Capture",
+ .type = &vimc_capture_type
+ },
+ [LENS_A] = {
+ .name = "Lens A",
+ .type = &vimc_lens_type
+ },
+ [LENS_B] = {
+ .name = "Lens B",
+ .type = &vimc_lens_type
+ },
+};
+
+static const struct vimc_data_link data_links[] = {
+ /* Link: Sensor A (Pad 0)->(Pad 0) Debayer A */
+ VIMC_DATA_LINK(SENSOR_A, 0, DEBAYER_A, 0,
+ MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
+ /* Link: Sensor A (Pad 0)->(Pad 0) Raw Capture 0 */
+ VIMC_DATA_LINK(SENSOR_A, 0, RAW_CAPTURE_0, 0,
+ MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
+ /* Link: Sensor B (Pad 0)->(Pad 0) Debayer B */
+ VIMC_DATA_LINK(SENSOR_B, 0, DEBAYER_B, 0,
+ MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
+ /* Link: Sensor B (Pad 0)->(Pad 0) Raw Capture 1 */
+ VIMC_DATA_LINK(SENSOR_B, 0, RAW_CAPTURE_1, 0,
+ MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
+ /* Link: Debayer A (Pad 1)->(Pad 0) Scaler */
+ VIMC_DATA_LINK(DEBAYER_A, 1, SCALER, 0, MEDIA_LNK_FL_ENABLED),
+ /* Link: Debayer B (Pad 1)->(Pad 0) Scaler */
+ VIMC_DATA_LINK(DEBAYER_B, 1, SCALER, 0, 0),
+ /* Link: RGB/YUV Input (Pad 0)->(Pad 0) Scaler */
+ VIMC_DATA_LINK(RGB_YUV_INPUT, 0, SCALER, 0, 0),
+ /* Link: Scaler (Pad 1)->(Pad 0) RGB/YUV Capture */
+ VIMC_DATA_LINK(SCALER, 1, RGB_YUV_CAPTURE, 0,
+ MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
+};
+
+static const struct vimc_ancillary_link ancillary_links[] = {
+ /* Link: Sensor A -> Lens A */
+ VIMC_ANCILLARY_LINK(0, 9),
+ /* Link: Sensor B -> Lens B */
+ VIMC_ANCILLARY_LINK(1, 10),
+};
+
+static struct vimc_pipeline_config pipe_cfg = {
+ .ents = ent_config,
+ .num_ents = ARRAY_SIZE(ent_config),
+ .data_links = data_links,
+ .num_data_links = ARRAY_SIZE(data_links),
+ .ancillary_links = ancillary_links,
+ .num_ancillary_links = ARRAY_SIZE(ancillary_links),
+};
+
+/* -------------------------------------------------------------------------- */
+
+static void vimc_rm_links(struct vimc_device *vimc)
+{
+ unsigned int i;
+
+ for (i = 0; i < vimc->pipe_cfg->num_ents; i++)
+ media_entity_remove_links(vimc->ent_devs[i]->ent);
+}
+
+static int vimc_create_links(struct vimc_device *vimc)
+{
+ unsigned int i;
+ int ret;
+
+ /* Initialize the links between entities */
+ for (i = 0; i < vimc->pipe_cfg->num_data_links; i++) {
+ const struct vimc_data_link *link = &vimc->pipe_cfg->data_links[i];
+
+ struct vimc_ent_device *ved_src =
+ vimc->ent_devs[link->src_ent];
+ struct vimc_ent_device *ved_sink =
+ vimc->ent_devs[link->sink_ent];
+
+ ret = media_create_pad_link(ved_src->ent, link->src_pad,
+ ved_sink->ent, link->sink_pad,
+ link->flags);
+ if (ret)
+ goto err_rm_links;
+ }
+
+ for (i = 0; i < vimc->pipe_cfg->num_ancillary_links; i++) {
+ const struct vimc_ancillary_link *link = &vimc->pipe_cfg->ancillary_links[i];
+
+ struct vimc_ent_device *ved_primary =
+ vimc->ent_devs[link->primary_ent];
+ struct vimc_ent_device *ved_ancillary =
+ vimc->ent_devs[link->ancillary_ent];
+ struct media_link *ret_link =
+ media_create_ancillary_link(ved_primary->ent, ved_ancillary->ent);
+
+ if (IS_ERR(ret_link)) {
+ ret = PTR_ERR(ret_link);
+ goto err_rm_links;
+ }
+ }
+
+ return 0;
+
+err_rm_links:
+ vimc_rm_links(vimc);
+ return ret;
+}
+
+static void vimc_release_subdevs(struct vimc_device *vimc)
+{
+ unsigned int i;
+
+ for (i = 0; i < vimc->pipe_cfg->num_ents; i++)
+ if (vimc->ent_devs[i])
+ vimc->pipe_cfg->ents[i].type->release(vimc->ent_devs[i]);
+}
+
+static void vimc_unregister_subdevs(struct vimc_device *vimc)
+{
+ unsigned int i;
+
+ for (i = 0; i < vimc->pipe_cfg->num_ents; i++)
+ if (vimc->ent_devs[i] && vimc->pipe_cfg->ents[i].type->unregister)
+ vimc->pipe_cfg->ents[i].type->unregister(vimc->ent_devs[i]);
+}
+
+static int vimc_add_subdevs(struct vimc_device *vimc)
+{
+ unsigned int i;
+
+ for (i = 0; i < vimc->pipe_cfg->num_ents; i++) {
+ dev_dbg(vimc->mdev.dev, "new entity for %s\n",
+ vimc->pipe_cfg->ents[i].name);
+ vimc->ent_devs[i] = vimc->pipe_cfg->ents[i].type->add(vimc,
+ vimc->pipe_cfg->ents[i].name);
+ if (IS_ERR(vimc->ent_devs[i])) {
+ int err = PTR_ERR(vimc->ent_devs[i]);
+
+ dev_err(vimc->mdev.dev, "adding entity %s failed (%d)\n",
+ vimc->pipe_cfg->ents[i].name, err);
+ vimc->ent_devs[i] = NULL;
+ vimc_unregister_subdevs(vimc);
+ vimc_release_subdevs(vimc);
+ return err;
+ }
+ }
+ return 0;
+}
+
+static void vimc_v4l2_dev_release(struct v4l2_device *v4l2_dev)
+{
+ struct vimc_device *vimc =
+ container_of(v4l2_dev, struct vimc_device, v4l2_dev);
+
+ vimc_release_subdevs(vimc);
+ media_device_cleanup(&vimc->mdev);
+ kfree(vimc->ent_devs);
+ kfree(vimc);
+}
+
+static int vimc_register_devices(struct vimc_device *vimc)
+{
+ int ret;
+
+ /* Register the v4l2 struct */
+ ret = v4l2_device_register(vimc->mdev.dev, &vimc->v4l2_dev);
+ if (ret) {
+ dev_err(vimc->mdev.dev,
+ "v4l2 device register failed (err=%d)\n", ret);
+ return ret;
+ }
+ /* allocate ent_devs */
+ vimc->ent_devs = kcalloc(vimc->pipe_cfg->num_ents,
+ sizeof(*vimc->ent_devs), GFP_KERNEL);
+ if (!vimc->ent_devs) {
+ ret = -ENOMEM;
+ goto err_v4l2_unregister;
+ }
+
+ /* Invoke entity config hooks to initialize and register subdevs */
+ ret = vimc_add_subdevs(vimc);
+ if (ret)
+ goto err_free_ent_devs;
+
+ /* Initialize links */
+ ret = vimc_create_links(vimc);
+ if (ret)
+ goto err_rm_subdevs;
+
+ /* Register the media device */
+ ret = media_device_register(&vimc->mdev);
+ if (ret) {
+ dev_err(vimc->mdev.dev,
+ "media device register failed (err=%d)\n", ret);
+ goto err_rm_subdevs;
+ }
+
+ /* Expose all subdev's nodes*/
+ ret = v4l2_device_register_subdev_nodes(&vimc->v4l2_dev);
+ if (ret) {
+ dev_err(vimc->mdev.dev,
+ "vimc subdev nodes registration failed (err=%d)\n",
+ ret);
+ goto err_mdev_unregister;
+ }
+
+ return 0;
+
+err_mdev_unregister:
+ media_device_unregister(&vimc->mdev);
+err_rm_subdevs:
+ vimc_unregister_subdevs(vimc);
+ vimc_release_subdevs(vimc);
+err_free_ent_devs:
+ kfree(vimc->ent_devs);
+err_v4l2_unregister:
+ v4l2_device_unregister(&vimc->v4l2_dev);
+
+ return ret;
+}
+
+static int vimc_probe(struct platform_device *pdev)
+{
+ const struct font_desc *font = find_font("VGA8x16");
+ struct vimc_device *vimc;
+ int ret;
+
+ dev_dbg(&pdev->dev, "probe");
+
+ if (!font) {
+ dev_err(&pdev->dev, "could not find font\n");
+ return -ENODEV;
+ }
+
+ tpg_set_font(font->data);
+
+ if (vimc_allocator == VIMC_ALLOCATOR_DMA_CONTIG)
+ dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+
+ vimc = kzalloc(sizeof(*vimc), GFP_KERNEL);
+ if (!vimc)
+ return -ENOMEM;
+
+ vimc->pipe_cfg = &pipe_cfg;
+
+ /* Link the media device within the v4l2_device */
+ vimc->v4l2_dev.mdev = &vimc->mdev;
+
+ /* Initialize media device */
+ strscpy(vimc->mdev.model, VIMC_MDEV_MODEL_NAME,
+ sizeof(vimc->mdev.model));
+ snprintf(vimc->mdev.bus_info, sizeof(vimc->mdev.bus_info),
+ "platform:%s", VIMC_PDEV_NAME);
+ vimc->mdev.dev = &pdev->dev;
+ media_device_init(&vimc->mdev);
+
+ ret = vimc_register_devices(vimc);
+ if (ret) {
+ media_device_cleanup(&vimc->mdev);
+ kfree(vimc);
+ return ret;
+ }
+ /*
+ * the release cb is set only after successful registration.
+ * if the registration fails, we release directly from probe
+ */
+
+ vimc->v4l2_dev.release = vimc_v4l2_dev_release;
+ platform_set_drvdata(pdev, vimc);
+ return 0;
+}
+
+static int vimc_remove(struct platform_device *pdev)
+{
+ struct vimc_device *vimc = platform_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "remove");
+
+ vimc_unregister_subdevs(vimc);
+ media_device_unregister(&vimc->mdev);
+ v4l2_device_unregister(&vimc->v4l2_dev);
+ v4l2_device_put(&vimc->v4l2_dev);
+
+ return 0;
+}
+
+static void vimc_dev_release(struct device *dev)
+{
+}
+
+static struct platform_device vimc_pdev = {
+ .name = VIMC_PDEV_NAME,
+ .dev.release = vimc_dev_release,
+};
+
+static struct platform_driver vimc_pdrv = {
+ .probe = vimc_probe,
+ .remove = vimc_remove,
+ .driver = {
+ .name = VIMC_PDEV_NAME,
+ },
+};
+
+static int __init vimc_init(void)
+{
+ int ret;
+
+ ret = platform_device_register(&vimc_pdev);
+ if (ret) {
+ dev_err(&vimc_pdev.dev,
+ "platform device registration failed (err=%d)\n", ret);
+ return ret;
+ }
+
+ ret = platform_driver_register(&vimc_pdrv);
+ if (ret) {
+ dev_err(&vimc_pdev.dev,
+ "platform driver registration failed (err=%d)\n", ret);
+ platform_device_unregister(&vimc_pdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit vimc_exit(void)
+{
+ platform_driver_unregister(&vimc_pdrv);
+
+ platform_device_unregister(&vimc_pdev);
+}
+
+module_init(vimc_init);
+module_exit(vimc_exit);
+
+MODULE_DESCRIPTION("Virtual Media Controller Driver (VIMC)");
+MODULE_AUTHOR("Helen Fornazier <helen.fornazier@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/test-drivers/vimc/vimc-debayer.c b/drivers/media/test-drivers/vimc/vimc-debayer.c
new file mode 100644
index 000000000..f671251fd
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/vimc-debayer.c
@@ -0,0 +1,626 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * vimc-debayer.c Virtual Media Controller Driver
+ *
+ * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com>
+ */
+
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/vmalloc.h>
+#include <linux/v4l2-mediabus.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-subdev.h>
+
+#include "vimc-common.h"
+
+enum vimc_debayer_rgb_colors {
+ VIMC_DEBAYER_RED = 0,
+ VIMC_DEBAYER_GREEN = 1,
+ VIMC_DEBAYER_BLUE = 2,
+};
+
+struct vimc_debayer_pix_map {
+ u32 code;
+ enum vimc_debayer_rgb_colors order[2][2];
+};
+
+struct vimc_debayer_device {
+ struct vimc_ent_device ved;
+ struct v4l2_subdev sd;
+ /* The active format */
+ struct v4l2_mbus_framefmt sink_fmt;
+ u32 src_code;
+ void (*set_rgb_src)(struct vimc_debayer_device *vdebayer,
+ unsigned int lin, unsigned int col,
+ unsigned int rgb[3]);
+ /* Values calculated when the stream starts */
+ u8 *src_frame;
+ const struct vimc_debayer_pix_map *sink_pix_map;
+ unsigned int sink_bpp;
+ unsigned int mean_win_size;
+ struct v4l2_ctrl_handler hdl;
+ struct media_pad pads[2];
+};
+
+static const struct v4l2_mbus_framefmt sink_fmt_default = {
+ .width = 640,
+ .height = 480,
+ .code = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+};
+
+static const u32 vimc_debayer_src_mbus_codes[] = {
+ MEDIA_BUS_FMT_GBR888_1X24,
+ MEDIA_BUS_FMT_BGR888_1X24,
+ MEDIA_BUS_FMT_BGR888_3X8,
+ MEDIA_BUS_FMT_RGB888_1X24,
+ MEDIA_BUS_FMT_RGB888_2X12_BE,
+ MEDIA_BUS_FMT_RGB888_2X12_LE,
+ MEDIA_BUS_FMT_RGB888_3X8,
+ MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,
+ MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA,
+ MEDIA_BUS_FMT_RGB888_1X32_PADHI,
+};
+
+static const struct vimc_debayer_pix_map vimc_debayer_pix_map_list[] = {
+ {
+ .code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .order = { { VIMC_DEBAYER_BLUE, VIMC_DEBAYER_GREEN },
+ { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_RED } }
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .order = { { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_BLUE },
+ { VIMC_DEBAYER_RED, VIMC_DEBAYER_GREEN } }
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .order = { { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_RED },
+ { VIMC_DEBAYER_BLUE, VIMC_DEBAYER_GREEN } }
+ },
+ {
+ .code = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .order = { { VIMC_DEBAYER_RED, VIMC_DEBAYER_GREEN },
+ { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_BLUE } }
+ },
+ {
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .order = { { VIMC_DEBAYER_BLUE, VIMC_DEBAYER_GREEN },
+ { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_RED } }
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .order = { { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_BLUE },
+ { VIMC_DEBAYER_RED, VIMC_DEBAYER_GREEN } }
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .order = { { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_RED },
+ { VIMC_DEBAYER_BLUE, VIMC_DEBAYER_GREEN } }
+ },
+ {
+ .code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .order = { { VIMC_DEBAYER_RED, VIMC_DEBAYER_GREEN },
+ { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_BLUE } }
+ },
+ {
+ .code = MEDIA_BUS_FMT_SBGGR12_1X12,
+ .order = { { VIMC_DEBAYER_BLUE, VIMC_DEBAYER_GREEN },
+ { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_RED } }
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGBRG12_1X12,
+ .order = { { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_BLUE },
+ { VIMC_DEBAYER_RED, VIMC_DEBAYER_GREEN } }
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .order = { { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_RED },
+ { VIMC_DEBAYER_BLUE, VIMC_DEBAYER_GREEN } }
+ },
+ {
+ .code = MEDIA_BUS_FMT_SRGGB12_1X12,
+ .order = { { VIMC_DEBAYER_RED, VIMC_DEBAYER_GREEN },
+ { VIMC_DEBAYER_GREEN, VIMC_DEBAYER_BLUE } }
+ },
+};
+
+static const struct vimc_debayer_pix_map *vimc_debayer_pix_map_by_code(u32 code)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(vimc_debayer_pix_map_list); i++)
+ if (vimc_debayer_pix_map_list[i].code == code)
+ return &vimc_debayer_pix_map_list[i];
+
+ return NULL;
+}
+
+static bool vimc_debayer_src_code_is_valid(u32 code)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(vimc_debayer_src_mbus_codes); i++)
+ if (vimc_debayer_src_mbus_codes[i] == code)
+ return true;
+
+ return false;
+}
+
+static int vimc_debayer_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state)
+{
+ struct vimc_debayer_device *vdebayer = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *mf;
+ unsigned int i;
+
+ mf = v4l2_subdev_get_try_format(sd, sd_state, 0);
+ *mf = sink_fmt_default;
+
+ for (i = 1; i < sd->entity.num_pads; i++) {
+ mf = v4l2_subdev_get_try_format(sd, sd_state, i);
+ *mf = sink_fmt_default;
+ mf->code = vdebayer->src_code;
+ }
+
+ return 0;
+}
+
+static int vimc_debayer_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (VIMC_IS_SRC(code->pad)) {
+ if (code->index >= ARRAY_SIZE(vimc_debayer_src_mbus_codes))
+ return -EINVAL;
+
+ code->code = vimc_debayer_src_mbus_codes[code->index];
+ } else {
+ if (code->index >= ARRAY_SIZE(vimc_debayer_pix_map_list))
+ return -EINVAL;
+
+ code->code = vimc_debayer_pix_map_list[code->index].code;
+ }
+
+ return 0;
+}
+
+static int vimc_debayer_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ if (fse->index)
+ return -EINVAL;
+
+ if (VIMC_IS_SINK(fse->pad)) {
+ const struct vimc_debayer_pix_map *vpix =
+ vimc_debayer_pix_map_by_code(fse->code);
+
+ if (!vpix)
+ return -EINVAL;
+ } else if (!vimc_debayer_src_code_is_valid(fse->code)) {
+ return -EINVAL;
+ }
+
+ fse->min_width = VIMC_FRAME_MIN_WIDTH;
+ fse->max_width = VIMC_FRAME_MAX_WIDTH;
+ fse->min_height = VIMC_FRAME_MIN_HEIGHT;
+ fse->max_height = VIMC_FRAME_MAX_HEIGHT;
+
+ return 0;
+}
+
+static int vimc_debayer_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vimc_debayer_device *vdebayer = v4l2_get_subdevdata(sd);
+
+ /* Get the current sink format */
+ fmt->format = fmt->which == V4L2_SUBDEV_FORMAT_TRY ?
+ *v4l2_subdev_get_try_format(sd, sd_state, 0) :
+ vdebayer->sink_fmt;
+
+ /* Set the right code for the source pad */
+ if (VIMC_IS_SRC(fmt->pad))
+ fmt->format.code = vdebayer->src_code;
+
+ return 0;
+}
+
+static void vimc_debayer_adjust_sink_fmt(struct v4l2_mbus_framefmt *fmt)
+{
+ const struct vimc_debayer_pix_map *vpix;
+
+ /* Don't accept a code that is not on the debayer table */
+ vpix = vimc_debayer_pix_map_by_code(fmt->code);
+ if (!vpix)
+ fmt->code = sink_fmt_default.code;
+
+ fmt->width = clamp_t(u32, fmt->width, VIMC_FRAME_MIN_WIDTH,
+ VIMC_FRAME_MAX_WIDTH) & ~1;
+ fmt->height = clamp_t(u32, fmt->height, VIMC_FRAME_MIN_HEIGHT,
+ VIMC_FRAME_MAX_HEIGHT) & ~1;
+
+ if (fmt->field == V4L2_FIELD_ANY)
+ fmt->field = sink_fmt_default.field;
+
+ vimc_colorimetry_clamp(fmt);
+}
+
+static int vimc_debayer_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vimc_debayer_device *vdebayer = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *sink_fmt;
+ u32 *src_code;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+ /* Do not change the format while stream is on */
+ if (vdebayer->src_frame)
+ return -EBUSY;
+
+ sink_fmt = &vdebayer->sink_fmt;
+ src_code = &vdebayer->src_code;
+ } else {
+ sink_fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
+ src_code = &v4l2_subdev_get_try_format(sd, sd_state, 1)->code;
+ }
+
+ /*
+ * Do not change the format of the source pad,
+ * it is propagated from the sink
+ */
+ if (VIMC_IS_SRC(fmt->pad)) {
+ u32 code = fmt->format.code;
+
+ fmt->format = *sink_fmt;
+
+ if (vimc_debayer_src_code_is_valid(code))
+ *src_code = code;
+
+ fmt->format.code = *src_code;
+ } else {
+ /* Set the new format in the sink pad */
+ vimc_debayer_adjust_sink_fmt(&fmt->format);
+
+ dev_dbg(vdebayer->ved.dev, "%s: sink format update: "
+ "old:%dx%d (0x%x, %d, %d, %d, %d) "
+ "new:%dx%d (0x%x, %d, %d, %d, %d)\n", vdebayer->sd.name,
+ /* old */
+ sink_fmt->width, sink_fmt->height, sink_fmt->code,
+ sink_fmt->colorspace, sink_fmt->quantization,
+ sink_fmt->xfer_func, sink_fmt->ycbcr_enc,
+ /* new */
+ fmt->format.width, fmt->format.height, fmt->format.code,
+ fmt->format.colorspace, fmt->format.quantization,
+ fmt->format.xfer_func, fmt->format.ycbcr_enc);
+
+ *sink_fmt = fmt->format;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops vimc_debayer_pad_ops = {
+ .init_cfg = vimc_debayer_init_cfg,
+ .enum_mbus_code = vimc_debayer_enum_mbus_code,
+ .enum_frame_size = vimc_debayer_enum_frame_size,
+ .get_fmt = vimc_debayer_get_fmt,
+ .set_fmt = vimc_debayer_set_fmt,
+};
+
+static void vimc_debayer_process_rgb_frame(struct vimc_debayer_device *vdebayer,
+ unsigned int lin,
+ unsigned int col,
+ unsigned int rgb[3])
+{
+ const struct vimc_pix_map *vpix;
+ unsigned int i, index;
+
+ vpix = vimc_pix_map_by_code(vdebayer->src_code);
+ index = VIMC_FRAME_INDEX(lin, col, vdebayer->sink_fmt.width, 3);
+ for (i = 0; i < 3; i++) {
+ switch (vpix->pixelformat) {
+ case V4L2_PIX_FMT_RGB24:
+ vdebayer->src_frame[index + i] = rgb[i];
+ break;
+ case V4L2_PIX_FMT_BGR24:
+ vdebayer->src_frame[index + i] = rgb[2 - i];
+ break;
+ }
+ }
+}
+
+static int vimc_debayer_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct vimc_debayer_device *vdebayer = v4l2_get_subdevdata(sd);
+
+ if (enable) {
+ const struct vimc_pix_map *vpix;
+ unsigned int frame_size;
+
+ if (vdebayer->src_frame)
+ return 0;
+
+ /* Calculate the frame size of the source pad */
+ vpix = vimc_pix_map_by_code(vdebayer->src_code);
+ frame_size = vdebayer->sink_fmt.width * vdebayer->sink_fmt.height *
+ vpix->bpp;
+
+ /* Save the bytes per pixel of the sink */
+ vpix = vimc_pix_map_by_code(vdebayer->sink_fmt.code);
+ vdebayer->sink_bpp = vpix->bpp;
+
+ /* Get the corresponding pixel map from the table */
+ vdebayer->sink_pix_map =
+ vimc_debayer_pix_map_by_code(vdebayer->sink_fmt.code);
+
+ /*
+ * Allocate the frame buffer. Use vmalloc to be able to
+ * allocate a large amount of memory
+ */
+ vdebayer->src_frame = vmalloc(frame_size);
+ if (!vdebayer->src_frame)
+ return -ENOMEM;
+
+ } else {
+ if (!vdebayer->src_frame)
+ return 0;
+
+ vfree(vdebayer->src_frame);
+ vdebayer->src_frame = NULL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_subdev_core_ops vimc_debayer_core_ops = {
+ .log_status = v4l2_ctrl_subdev_log_status,
+ .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops vimc_debayer_video_ops = {
+ .s_stream = vimc_debayer_s_stream,
+};
+
+static const struct v4l2_subdev_ops vimc_debayer_ops = {
+ .core = &vimc_debayer_core_ops,
+ .pad = &vimc_debayer_pad_ops,
+ .video = &vimc_debayer_video_ops,
+};
+
+static unsigned int vimc_debayer_get_val(const u8 *bytes,
+ const unsigned int n_bytes)
+{
+ unsigned int i;
+ unsigned int acc = 0;
+
+ for (i = 0; i < n_bytes; i++)
+ acc = acc + (bytes[i] << (8 * i));
+
+ return acc;
+}
+
+static void vimc_debayer_calc_rgb_sink(struct vimc_debayer_device *vdebayer,
+ const u8 *frame,
+ const unsigned int lin,
+ const unsigned int col,
+ unsigned int rgb[3])
+{
+ unsigned int i, seek, wlin, wcol;
+ unsigned int n_rgb[3] = {0, 0, 0};
+
+ for (i = 0; i < 3; i++)
+ rgb[i] = 0;
+
+ /*
+ * Calculate how many we need to subtract to get to the pixel in
+ * the top left corner of the mean window (considering the current
+ * pixel as the center)
+ */
+ seek = vdebayer->mean_win_size / 2;
+
+ /* Sum the values of the colors in the mean window */
+
+ dev_dbg(vdebayer->ved.dev,
+ "deb: %s: --- Calc pixel %dx%d, window mean %d, seek %d ---\n",
+ vdebayer->sd.name, lin, col, vdebayer->sink_fmt.height, seek);
+
+ /*
+ * Iterate through all the lines in the mean window, start
+ * with zero if the pixel is outside the frame and don't pass
+ * the height when the pixel is in the bottom border of the
+ * frame
+ */
+ for (wlin = seek > lin ? 0 : lin - seek;
+ wlin < lin + seek + 1 && wlin < vdebayer->sink_fmt.height;
+ wlin++) {
+
+ /*
+ * Iterate through all the columns in the mean window, start
+ * with zero if the pixel is outside the frame and don't pass
+ * the width when the pixel is in the right border of the
+ * frame
+ */
+ for (wcol = seek > col ? 0 : col - seek;
+ wcol < col + seek + 1 && wcol < vdebayer->sink_fmt.width;
+ wcol++) {
+ enum vimc_debayer_rgb_colors color;
+ unsigned int index;
+
+ /* Check which color this pixel is */
+ color = vdebayer->sink_pix_map->order[wlin % 2][wcol % 2];
+
+ index = VIMC_FRAME_INDEX(wlin, wcol,
+ vdebayer->sink_fmt.width,
+ vdebayer->sink_bpp);
+
+ dev_dbg(vdebayer->ved.dev,
+ "deb: %s: RGB CALC: frame index %d, win pos %dx%d, color %d\n",
+ vdebayer->sd.name, index, wlin, wcol, color);
+
+ /* Get its value */
+ rgb[color] = rgb[color] +
+ vimc_debayer_get_val(&frame[index],
+ vdebayer->sink_bpp);
+
+ /* Save how many values we already added */
+ n_rgb[color]++;
+
+ dev_dbg(vdebayer->ved.dev, "deb: %s: RGB CALC: val %d, n %d\n",
+ vdebayer->sd.name, rgb[color], n_rgb[color]);
+ }
+ }
+
+ /* Calculate the mean */
+ for (i = 0; i < 3; i++) {
+ dev_dbg(vdebayer->ved.dev,
+ "deb: %s: PRE CALC: %dx%d Color %d, val %d, n %d\n",
+ vdebayer->sd.name, lin, col, i, rgb[i], n_rgb[i]);
+
+ if (n_rgb[i])
+ rgb[i] = rgb[i] / n_rgb[i];
+
+ dev_dbg(vdebayer->ved.dev,
+ "deb: %s: FINAL CALC: %dx%d Color %d, val %d\n",
+ vdebayer->sd.name, lin, col, i, rgb[i]);
+ }
+}
+
+static void *vimc_debayer_process_frame(struct vimc_ent_device *ved,
+ const void *sink_frame)
+{
+ struct vimc_debayer_device *vdebayer =
+ container_of(ved, struct vimc_debayer_device, ved);
+
+ unsigned int rgb[3];
+ unsigned int i, j;
+
+ /* If the stream in this node is not active, just return */
+ if (!vdebayer->src_frame)
+ return ERR_PTR(-EINVAL);
+
+ for (i = 0; i < vdebayer->sink_fmt.height; i++)
+ for (j = 0; j < vdebayer->sink_fmt.width; j++) {
+ vimc_debayer_calc_rgb_sink(vdebayer, sink_frame, i, j, rgb);
+ vdebayer->set_rgb_src(vdebayer, i, j, rgb);
+ }
+
+ return vdebayer->src_frame;
+}
+
+static int vimc_debayer_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct vimc_debayer_device *vdebayer =
+ container_of(ctrl->handler, struct vimc_debayer_device, hdl);
+
+ switch (ctrl->id) {
+ case VIMC_CID_MEAN_WIN_SIZE:
+ vdebayer->mean_win_size = ctrl->val;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops vimc_debayer_ctrl_ops = {
+ .s_ctrl = vimc_debayer_s_ctrl,
+};
+
+static void vimc_debayer_release(struct vimc_ent_device *ved)
+{
+ struct vimc_debayer_device *vdebayer =
+ container_of(ved, struct vimc_debayer_device, ved);
+
+ v4l2_ctrl_handler_free(&vdebayer->hdl);
+ media_entity_cleanup(vdebayer->ved.ent);
+ kfree(vdebayer);
+}
+
+static const struct v4l2_ctrl_config vimc_debayer_ctrl_class = {
+ .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_WRITE_ONLY,
+ .id = VIMC_CID_VIMC_CLASS,
+ .name = "VIMC Controls",
+ .type = V4L2_CTRL_TYPE_CTRL_CLASS,
+};
+
+static const struct v4l2_ctrl_config vimc_debayer_ctrl_mean_win_size = {
+ .ops = &vimc_debayer_ctrl_ops,
+ .id = VIMC_CID_MEAN_WIN_SIZE,
+ .name = "Debayer Mean Window Size",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 25,
+ .step = 2,
+ .def = 3,
+};
+
+static struct vimc_ent_device *vimc_debayer_add(struct vimc_device *vimc,
+ const char *vcfg_name)
+{
+ struct v4l2_device *v4l2_dev = &vimc->v4l2_dev;
+ struct vimc_debayer_device *vdebayer;
+ int ret;
+
+ /* Allocate the vdebayer struct */
+ vdebayer = kzalloc(sizeof(*vdebayer), GFP_KERNEL);
+ if (!vdebayer)
+ return ERR_PTR(-ENOMEM);
+
+ /* Create controls: */
+ v4l2_ctrl_handler_init(&vdebayer->hdl, 2);
+ v4l2_ctrl_new_custom(&vdebayer->hdl, &vimc_debayer_ctrl_class, NULL);
+ v4l2_ctrl_new_custom(&vdebayer->hdl, &vimc_debayer_ctrl_mean_win_size, NULL);
+ vdebayer->sd.ctrl_handler = &vdebayer->hdl;
+ if (vdebayer->hdl.error) {
+ ret = vdebayer->hdl.error;
+ goto err_free_vdebayer;
+ }
+
+ /* Initialize ved and sd */
+ vdebayer->pads[0].flags = MEDIA_PAD_FL_SINK;
+ vdebayer->pads[1].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = vimc_ent_sd_register(&vdebayer->ved, &vdebayer->sd, v4l2_dev,
+ vcfg_name,
+ MEDIA_ENT_F_PROC_VIDEO_PIXEL_ENC_CONV, 2,
+ vdebayer->pads, &vimc_debayer_ops);
+ if (ret)
+ goto err_free_hdl;
+
+ vdebayer->ved.process_frame = vimc_debayer_process_frame;
+ vdebayer->ved.dev = vimc->mdev.dev;
+ vdebayer->mean_win_size = vimc_debayer_ctrl_mean_win_size.def;
+
+ /* Initialize the frame format */
+ vdebayer->sink_fmt = sink_fmt_default;
+ /*
+ * TODO: Add support for more output formats, we only support
+ * RGB888 for now
+ * NOTE: the src format is always the same as the sink, except
+ * for the code
+ */
+ vdebayer->src_code = MEDIA_BUS_FMT_RGB888_1X24;
+ vdebayer->set_rgb_src = vimc_debayer_process_rgb_frame;
+
+ return &vdebayer->ved;
+
+err_free_hdl:
+ v4l2_ctrl_handler_free(&vdebayer->hdl);
+err_free_vdebayer:
+ kfree(vdebayer);
+
+ return ERR_PTR(ret);
+}
+
+struct vimc_ent_type vimc_debayer_type = {
+ .add = vimc_debayer_add,
+ .release = vimc_debayer_release
+};
diff --git a/drivers/media/test-drivers/vimc/vimc-lens.c b/drivers/media/test-drivers/vimc/vimc-lens.c
new file mode 100644
index 000000000..3ce7f4b4d
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/vimc-lens.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * vimc-lens.c Virtual Media Controller Driver
+ * Copyright (C) 2022 Google, Inc
+ * Author: yunkec@google.com (Yunke Cao)
+ */
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-subdev.h>
+
+#include "vimc-common.h"
+
+#define VIMC_LENS_MAX_FOCUS_POS 1023
+#define VIMC_LENS_MAX_FOCUS_STEP 1
+
+struct vimc_lens_device {
+ struct vimc_ent_device ved;
+ struct v4l2_subdev sd;
+ struct v4l2_ctrl_handler hdl;
+ u32 focus_absolute;
+};
+
+static const struct v4l2_subdev_core_ops vimc_lens_core_ops = {
+ .log_status = v4l2_ctrl_subdev_log_status,
+ .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_ops vimc_lens_ops = {
+ .core = &vimc_lens_core_ops
+};
+
+static int vimc_lens_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct vimc_lens_device *vlens =
+ container_of(ctrl->handler, struct vimc_lens_device, hdl);
+ if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) {
+ vlens->focus_absolute = ctrl->val;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static const struct v4l2_ctrl_ops vimc_lens_ctrl_ops = {
+ .s_ctrl = vimc_lens_s_ctrl,
+};
+
+static struct vimc_ent_device *vimc_lens_add(struct vimc_device *vimc,
+ const char *vcfg_name)
+{
+ struct v4l2_device *v4l2_dev = &vimc->v4l2_dev;
+ struct vimc_lens_device *vlens;
+ int ret;
+
+ /* Allocate the vlens struct */
+ vlens = kzalloc(sizeof(*vlens), GFP_KERNEL);
+ if (!vlens)
+ return ERR_PTR(-ENOMEM);
+
+ v4l2_ctrl_handler_init(&vlens->hdl, 1);
+
+ v4l2_ctrl_new_std(&vlens->hdl, &vimc_lens_ctrl_ops,
+ V4L2_CID_FOCUS_ABSOLUTE, 0,
+ VIMC_LENS_MAX_FOCUS_POS, VIMC_LENS_MAX_FOCUS_STEP, 0);
+ vlens->sd.ctrl_handler = &vlens->hdl;
+ if (vlens->hdl.error) {
+ ret = vlens->hdl.error;
+ goto err_free_vlens;
+ }
+ vlens->ved.dev = vimc->mdev.dev;
+
+ ret = vimc_ent_sd_register(&vlens->ved, &vlens->sd, v4l2_dev,
+ vcfg_name, MEDIA_ENT_F_LENS, 0,
+ NULL, &vimc_lens_ops);
+ if (ret)
+ goto err_free_hdl;
+
+ return &vlens->ved;
+
+err_free_hdl:
+ v4l2_ctrl_handler_free(&vlens->hdl);
+err_free_vlens:
+ kfree(vlens);
+
+ return ERR_PTR(ret);
+}
+
+static void vimc_lens_release(struct vimc_ent_device *ved)
+{
+ struct vimc_lens_device *vlens =
+ container_of(ved, struct vimc_lens_device, ved);
+
+ v4l2_ctrl_handler_free(&vlens->hdl);
+ media_entity_cleanup(vlens->ved.ent);
+ kfree(vlens);
+}
+
+struct vimc_ent_type vimc_lens_type = {
+ .add = vimc_lens_add,
+ .release = vimc_lens_release
+};
diff --git a/drivers/media/test-drivers/vimc/vimc-scaler.c b/drivers/media/test-drivers/vimc/vimc-scaler.c
new file mode 100644
index 000000000..b671774e2
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/vimc-scaler.c
@@ -0,0 +1,444 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * vimc-scaler.c Virtual Media Controller Driver
+ *
+ * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com>
+ */
+
+#include <linux/moduleparam.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
+#include <linux/v4l2-mediabus.h>
+#include <media/v4l2-rect.h>
+#include <media/v4l2-subdev.h>
+
+#include "vimc-common.h"
+
+/* Pad identifier */
+enum vic_sca_pad {
+ VIMC_SCALER_SINK = 0,
+ VIMC_SCALER_SRC = 1,
+};
+
+#define VIMC_SCALER_FMT_WIDTH_DEFAULT 640
+#define VIMC_SCALER_FMT_HEIGHT_DEFAULT 480
+
+struct vimc_scaler_device {
+ struct vimc_ent_device ved;
+ struct v4l2_subdev sd;
+ struct v4l2_rect crop_rect;
+ /* Frame format for both sink and src pad */
+ struct v4l2_mbus_framefmt fmt[2];
+ /* Values calculated when the stream starts */
+ u8 *src_frame;
+ unsigned int bpp;
+ struct media_pad pads[2];
+};
+
+static const struct v4l2_mbus_framefmt fmt_default = {
+ .width = VIMC_SCALER_FMT_WIDTH_DEFAULT,
+ .height = VIMC_SCALER_FMT_HEIGHT_DEFAULT,
+ .code = MEDIA_BUS_FMT_RGB888_1X24,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+};
+
+static const struct v4l2_rect crop_rect_default = {
+ .width = VIMC_SCALER_FMT_WIDTH_DEFAULT,
+ .height = VIMC_SCALER_FMT_HEIGHT_DEFAULT,
+ .top = 0,
+ .left = 0,
+};
+
+static const struct v4l2_rect crop_rect_min = {
+ .width = VIMC_FRAME_MIN_WIDTH,
+ .height = VIMC_FRAME_MIN_HEIGHT,
+ .top = 0,
+ .left = 0,
+};
+
+static struct v4l2_rect
+vimc_scaler_get_crop_bound_sink(const struct v4l2_mbus_framefmt *sink_fmt)
+{
+ /* Get the crop bounds to clamp the crop rectangle correctly */
+ struct v4l2_rect r = {
+ .left = 0,
+ .top = 0,
+ .width = sink_fmt->width,
+ .height = sink_fmt->height,
+ };
+ return r;
+}
+
+static int vimc_scaler_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state)
+{
+ struct v4l2_mbus_framefmt *mf;
+ struct v4l2_rect *r;
+ unsigned int i;
+
+ for (i = 0; i < sd->entity.num_pads; i++) {
+ mf = v4l2_subdev_get_try_format(sd, sd_state, i);
+ *mf = fmt_default;
+ }
+
+ r = v4l2_subdev_get_try_crop(sd, sd_state, VIMC_SCALER_SINK);
+ *r = crop_rect_default;
+
+ return 0;
+}
+
+static int vimc_scaler_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ u32 mbus_code = vimc_mbus_code_by_index(code->index);
+ const struct vimc_pix_map *vpix;
+
+ if (!mbus_code)
+ return -EINVAL;
+
+ vpix = vimc_pix_map_by_code(mbus_code);
+
+ /* We don't support bayer format */
+ if (!vpix || vpix->bayer)
+ return -EINVAL;
+
+ code->code = mbus_code;
+
+ return 0;
+}
+
+static int vimc_scaler_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ const struct vimc_pix_map *vpix;
+
+ if (fse->index)
+ return -EINVAL;
+
+ /* Only accept code in the pix map table in non bayer format */
+ vpix = vimc_pix_map_by_code(fse->code);
+ if (!vpix || vpix->bayer)
+ return -EINVAL;
+
+ fse->min_width = VIMC_FRAME_MIN_WIDTH;
+ fse->min_height = VIMC_FRAME_MIN_HEIGHT;
+
+ fse->max_width = VIMC_FRAME_MAX_WIDTH;
+ fse->max_height = VIMC_FRAME_MAX_HEIGHT;
+
+ return 0;
+}
+
+static struct v4l2_mbus_framefmt *
+vimc_scaler_pad_format(struct vimc_scaler_device *vscaler,
+ struct v4l2_subdev_state *sd_state, u32 pad,
+ enum v4l2_subdev_format_whence which)
+{
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
+ return v4l2_subdev_get_try_format(&vscaler->sd, sd_state, pad);
+ else
+ return &vscaler->fmt[pad];
+}
+
+static struct v4l2_rect *
+vimc_scaler_pad_crop(struct vimc_scaler_device *vscaler,
+ struct v4l2_subdev_state *sd_state,
+ enum v4l2_subdev_format_whence which)
+{
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
+ return v4l2_subdev_get_try_crop(&vscaler->sd, sd_state,
+ VIMC_SCALER_SINK);
+ else
+ return &vscaler->crop_rect;
+}
+
+static int vimc_scaler_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *format)
+{
+ struct vimc_scaler_device *vscaler = v4l2_get_subdevdata(sd);
+
+ format->format = *vimc_scaler_pad_format(vscaler, sd_state, format->pad,
+ format->which);
+ return 0;
+}
+
+static int vimc_scaler_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *format)
+{
+ struct vimc_scaler_device *vscaler = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *fmt;
+
+ /* Do not change the active format while stream is on */
+ if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && vscaler->src_frame)
+ return -EBUSY;
+
+ fmt = vimc_scaler_pad_format(vscaler, sd_state, format->pad, format->which);
+
+ /*
+ * The media bus code and colorspace can only be changed on the sink
+ * pad, the source pad only follows.
+ */
+ if (format->pad == VIMC_SCALER_SINK) {
+ const struct vimc_pix_map *vpix;
+
+ /* Only accept code in the pix map table in non bayer format. */
+ vpix = vimc_pix_map_by_code(format->format.code);
+ if (vpix && !vpix->bayer)
+ fmt->code = format->format.code;
+ else
+ fmt->code = fmt_default.code;
+
+ /* Clamp the colorspace to valid values. */
+ fmt->colorspace = format->format.colorspace;
+ fmt->ycbcr_enc = format->format.ycbcr_enc;
+ fmt->quantization = format->format.quantization;
+ fmt->xfer_func = format->format.xfer_func;
+ vimc_colorimetry_clamp(fmt);
+ }
+
+ /* Clamp and align the width and height */
+ fmt->width = clamp_t(u32, format->format.width, VIMC_FRAME_MIN_WIDTH,
+ VIMC_FRAME_MAX_WIDTH) & ~1;
+ fmt->height = clamp_t(u32, format->format.height, VIMC_FRAME_MIN_HEIGHT,
+ VIMC_FRAME_MAX_HEIGHT) & ~1;
+
+ /*
+ * Propagate the sink pad format to the crop rectangle and the source
+ * pad.
+ */
+ if (format->pad == VIMC_SCALER_SINK) {
+ struct v4l2_mbus_framefmt *src_fmt;
+ struct v4l2_rect *crop;
+
+ crop = vimc_scaler_pad_crop(vscaler, sd_state, format->which);
+ crop->width = fmt->width;
+ crop->height = fmt->height;
+ crop->top = 0;
+ crop->left = 0;
+
+ src_fmt = vimc_scaler_pad_format(vscaler, sd_state, VIMC_SCALER_SRC,
+ format->which);
+ *src_fmt = *fmt;
+ }
+
+ format->format = *fmt;
+
+ return 0;
+}
+
+static int vimc_scaler_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct vimc_scaler_device *vscaler = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *sink_fmt;
+
+ if (VIMC_IS_SRC(sel->pad))
+ return -EINVAL;
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP:
+ sel->r = *vimc_scaler_pad_crop(vscaler, sd_state, sel->which);
+ break;
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ sink_fmt = vimc_scaler_pad_format(vscaler, sd_state, VIMC_SCALER_SINK,
+ sel->which);
+ sel->r = vimc_scaler_get_crop_bound_sink(sink_fmt);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void vimc_scaler_adjust_sink_crop(struct v4l2_rect *r,
+ const struct v4l2_mbus_framefmt *sink_fmt)
+{
+ const struct v4l2_rect sink_rect =
+ vimc_scaler_get_crop_bound_sink(sink_fmt);
+
+ /* Disallow rectangles smaller than the minimal one. */
+ v4l2_rect_set_min_size(r, &crop_rect_min);
+ v4l2_rect_map_inside(r, &sink_rect);
+}
+
+static int vimc_scaler_set_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct vimc_scaler_device *vscaler = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *sink_fmt;
+ struct v4l2_rect *crop_rect;
+
+ /* Only support setting the crop of the sink pad */
+ if (VIMC_IS_SRC(sel->pad) || sel->target != V4L2_SEL_TGT_CROP)
+ return -EINVAL;
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE && vscaler->src_frame)
+ return -EBUSY;
+
+ crop_rect = vimc_scaler_pad_crop(vscaler, sd_state, sel->which);
+ sink_fmt = vimc_scaler_pad_format(vscaler, sd_state, VIMC_SCALER_SINK,
+ sel->which);
+ vimc_scaler_adjust_sink_crop(&sel->r, sink_fmt);
+ *crop_rect = sel->r;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops vimc_scaler_pad_ops = {
+ .init_cfg = vimc_scaler_init_cfg,
+ .enum_mbus_code = vimc_scaler_enum_mbus_code,
+ .enum_frame_size = vimc_scaler_enum_frame_size,
+ .get_fmt = vimc_scaler_get_fmt,
+ .set_fmt = vimc_scaler_set_fmt,
+ .get_selection = vimc_scaler_get_selection,
+ .set_selection = vimc_scaler_set_selection,
+};
+
+static int vimc_scaler_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct vimc_scaler_device *vscaler = v4l2_get_subdevdata(sd);
+
+ if (enable) {
+ const struct vimc_pix_map *vpix;
+ unsigned int frame_size;
+
+ if (vscaler->src_frame)
+ return 0;
+
+ /* Save the bytes per pixel of the sink */
+ vpix = vimc_pix_map_by_code(vscaler->fmt[VIMC_SCALER_SINK].code);
+ vscaler->bpp = vpix->bpp;
+
+ /* Calculate the frame size of the source pad */
+ frame_size = vscaler->fmt[VIMC_SCALER_SRC].width
+ * vscaler->fmt[VIMC_SCALER_SRC].height * vscaler->bpp;
+
+ /* Allocate the frame buffer. Use vmalloc to be able to
+ * allocate a large amount of memory
+ */
+ vscaler->src_frame = vmalloc(frame_size);
+ if (!vscaler->src_frame)
+ return -ENOMEM;
+
+ } else {
+ if (!vscaler->src_frame)
+ return 0;
+
+ vfree(vscaler->src_frame);
+ vscaler->src_frame = NULL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_subdev_video_ops vimc_scaler_video_ops = {
+ .s_stream = vimc_scaler_s_stream,
+};
+
+static const struct v4l2_subdev_ops vimc_scaler_ops = {
+ .pad = &vimc_scaler_pad_ops,
+ .video = &vimc_scaler_video_ops,
+};
+
+static void vimc_scaler_fill_src_frame(const struct vimc_scaler_device *const vscaler,
+ const u8 *const sink_frame)
+{
+ const struct v4l2_mbus_framefmt *src_fmt = &vscaler->fmt[VIMC_SCALER_SRC];
+ const struct v4l2_rect *r = &vscaler->crop_rect;
+ unsigned int snk_width = vscaler->fmt[VIMC_SCALER_SINK].width;
+ unsigned int src_x, src_y;
+ u8 *walker = vscaler->src_frame;
+
+ /* Set each pixel at the src_frame to its sink_frame equivalent */
+ for (src_y = 0; src_y < src_fmt->height; src_y++) {
+ unsigned int snk_y, y_offset;
+
+ snk_y = (src_y * r->height) / src_fmt->height + r->top;
+ y_offset = snk_y * snk_width * vscaler->bpp;
+
+ for (src_x = 0; src_x < src_fmt->width; src_x++) {
+ unsigned int snk_x, x_offset, index;
+
+ snk_x = (src_x * r->width) / src_fmt->width + r->left;
+ x_offset = snk_x * vscaler->bpp;
+ index = y_offset + x_offset;
+ memcpy(walker, &sink_frame[index], vscaler->bpp);
+ walker += vscaler->bpp;
+ }
+ }
+}
+
+static void *vimc_scaler_process_frame(struct vimc_ent_device *ved,
+ const void *sink_frame)
+{
+ struct vimc_scaler_device *vscaler = container_of(ved, struct vimc_scaler_device,
+ ved);
+
+ /* If the stream in this node is not active, just return */
+ if (!vscaler->src_frame)
+ return ERR_PTR(-EINVAL);
+
+ vimc_scaler_fill_src_frame(vscaler, sink_frame);
+
+ return vscaler->src_frame;
+};
+
+static void vimc_scaler_release(struct vimc_ent_device *ved)
+{
+ struct vimc_scaler_device *vscaler =
+ container_of(ved, struct vimc_scaler_device, ved);
+
+ media_entity_cleanup(vscaler->ved.ent);
+ kfree(vscaler);
+}
+
+static struct vimc_ent_device *vimc_scaler_add(struct vimc_device *vimc,
+ const char *vcfg_name)
+{
+ struct v4l2_device *v4l2_dev = &vimc->v4l2_dev;
+ struct vimc_scaler_device *vscaler;
+ int ret;
+
+ /* Allocate the vscaler struct */
+ vscaler = kzalloc(sizeof(*vscaler), GFP_KERNEL);
+ if (!vscaler)
+ return ERR_PTR(-ENOMEM);
+
+ /* Initialize ved and sd */
+ vscaler->pads[VIMC_SCALER_SINK].flags = MEDIA_PAD_FL_SINK;
+ vscaler->pads[VIMC_SCALER_SRC].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = vimc_ent_sd_register(&vscaler->ved, &vscaler->sd, v4l2_dev,
+ vcfg_name,
+ MEDIA_ENT_F_PROC_VIDEO_SCALER, 2,
+ vscaler->pads, &vimc_scaler_ops);
+ if (ret) {
+ kfree(vscaler);
+ return ERR_PTR(ret);
+ }
+
+ vscaler->ved.process_frame = vimc_scaler_process_frame;
+ vscaler->ved.dev = vimc->mdev.dev;
+
+ /* Initialize the frame format */
+ vscaler->fmt[VIMC_SCALER_SINK] = fmt_default;
+ vscaler->fmt[VIMC_SCALER_SRC] = fmt_default;
+
+ /* Initialize the crop selection */
+ vscaler->crop_rect = crop_rect_default;
+
+ return &vscaler->ved;
+}
+
+struct vimc_ent_type vimc_scaler_type = {
+ .add = vimc_scaler_add,
+ .release = vimc_scaler_release
+};
diff --git a/drivers/media/test-drivers/vimc/vimc-sensor.c b/drivers/media/test-drivers/vimc/vimc-sensor.c
new file mode 100644
index 000000000..41a3dce2d
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/vimc-sensor.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * vimc-sensor.c Virtual Media Controller Driver
+ *
+ * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com>
+ */
+
+#include <linux/v4l2-mediabus.h>
+#include <linux/vmalloc.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-subdev.h>
+#include <media/tpg/v4l2-tpg.h>
+
+#include "vimc-common.h"
+
+enum vimc_sensor_osd_mode {
+ VIMC_SENSOR_OSD_SHOW_ALL = 0,
+ VIMC_SENSOR_OSD_SHOW_COUNTERS = 1,
+ VIMC_SENSOR_OSD_SHOW_NONE = 2
+};
+
+struct vimc_sensor_device {
+ struct vimc_ent_device ved;
+ struct v4l2_subdev sd;
+ struct tpg_data tpg;
+ u8 *frame;
+ enum vimc_sensor_osd_mode osd_value;
+ u64 start_stream_ts;
+ /* The active format */
+ struct v4l2_mbus_framefmt mbus_format;
+ struct v4l2_ctrl_handler hdl;
+ struct media_pad pad;
+};
+
+static const struct v4l2_mbus_framefmt fmt_default = {
+ .width = 640,
+ .height = 480,
+ .code = MEDIA_BUS_FMT_RGB888_1X24,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+};
+
+static int vimc_sensor_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state)
+{
+ unsigned int i;
+
+ for (i = 0; i < sd->entity.num_pads; i++) {
+ struct v4l2_mbus_framefmt *mf;
+
+ mf = v4l2_subdev_get_try_format(sd, sd_state, i);
+ *mf = fmt_default;
+ }
+
+ return 0;
+}
+
+static int vimc_sensor_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ u32 mbus_code = vimc_mbus_code_by_index(code->index);
+
+ if (!mbus_code)
+ return -EINVAL;
+
+ code->code = mbus_code;
+
+ return 0;
+}
+
+static int vimc_sensor_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ const struct vimc_pix_map *vpix;
+
+ if (fse->index)
+ return -EINVAL;
+
+ /* Only accept code in the pix map table */
+ vpix = vimc_pix_map_by_code(fse->code);
+ if (!vpix)
+ return -EINVAL;
+
+ fse->min_width = VIMC_FRAME_MIN_WIDTH;
+ fse->max_width = VIMC_FRAME_MAX_WIDTH;
+ fse->min_height = VIMC_FRAME_MIN_HEIGHT;
+ fse->max_height = VIMC_FRAME_MAX_HEIGHT;
+
+ return 0;
+}
+
+static int vimc_sensor_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vimc_sensor_device *vsensor =
+ container_of(sd, struct vimc_sensor_device, sd);
+
+ fmt->format = fmt->which == V4L2_SUBDEV_FORMAT_TRY ?
+ *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) :
+ vsensor->mbus_format;
+
+ return 0;
+}
+
+static void vimc_sensor_tpg_s_format(struct vimc_sensor_device *vsensor)
+{
+ const struct vimc_pix_map *vpix =
+ vimc_pix_map_by_code(vsensor->mbus_format.code);
+
+ tpg_reset_source(&vsensor->tpg, vsensor->mbus_format.width,
+ vsensor->mbus_format.height, vsensor->mbus_format.field);
+ tpg_s_bytesperline(&vsensor->tpg, 0, vsensor->mbus_format.width * vpix->bpp);
+ tpg_s_buf_height(&vsensor->tpg, vsensor->mbus_format.height);
+ tpg_s_fourcc(&vsensor->tpg, vpix->pixelformat);
+ /* TODO: add support for V4L2_FIELD_ALTERNATE */
+ tpg_s_field(&vsensor->tpg, vsensor->mbus_format.field, false);
+ tpg_s_colorspace(&vsensor->tpg, vsensor->mbus_format.colorspace);
+ tpg_s_ycbcr_enc(&vsensor->tpg, vsensor->mbus_format.ycbcr_enc);
+ tpg_s_quantization(&vsensor->tpg, vsensor->mbus_format.quantization);
+ tpg_s_xfer_func(&vsensor->tpg, vsensor->mbus_format.xfer_func);
+}
+
+static void vimc_sensor_adjust_fmt(struct v4l2_mbus_framefmt *fmt)
+{
+ const struct vimc_pix_map *vpix;
+
+ /* Only accept code in the pix map table */
+ vpix = vimc_pix_map_by_code(fmt->code);
+ if (!vpix)
+ fmt->code = fmt_default.code;
+
+ fmt->width = clamp_t(u32, fmt->width, VIMC_FRAME_MIN_WIDTH,
+ VIMC_FRAME_MAX_WIDTH) & ~1;
+ fmt->height = clamp_t(u32, fmt->height, VIMC_FRAME_MIN_HEIGHT,
+ VIMC_FRAME_MAX_HEIGHT) & ~1;
+
+ /* TODO: add support for V4L2_FIELD_ALTERNATE */
+ if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE)
+ fmt->field = fmt_default.field;
+
+ vimc_colorimetry_clamp(fmt);
+}
+
+static int vimc_sensor_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vimc_sensor_device *vsensor = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *mf;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+ /* Do not change the format while stream is on */
+ if (vsensor->frame)
+ return -EBUSY;
+
+ mf = &vsensor->mbus_format;
+ } else {
+ mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
+ }
+
+ /* Set the new format */
+ vimc_sensor_adjust_fmt(&fmt->format);
+
+ dev_dbg(vsensor->ved.dev, "%s: format update: "
+ "old:%dx%d (0x%x, %d, %d, %d, %d) "
+ "new:%dx%d (0x%x, %d, %d, %d, %d)\n", vsensor->sd.name,
+ /* old */
+ mf->width, mf->height, mf->code,
+ mf->colorspace, mf->quantization,
+ mf->xfer_func, mf->ycbcr_enc,
+ /* new */
+ fmt->format.width, fmt->format.height, fmt->format.code,
+ fmt->format.colorspace, fmt->format.quantization,
+ fmt->format.xfer_func, fmt->format.ycbcr_enc);
+
+ *mf = fmt->format;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops vimc_sensor_pad_ops = {
+ .init_cfg = vimc_sensor_init_cfg,
+ .enum_mbus_code = vimc_sensor_enum_mbus_code,
+ .enum_frame_size = vimc_sensor_enum_frame_size,
+ .get_fmt = vimc_sensor_get_fmt,
+ .set_fmt = vimc_sensor_set_fmt,
+};
+
+static void *vimc_sensor_process_frame(struct vimc_ent_device *ved,
+ const void *sink_frame)
+{
+ struct vimc_sensor_device *vsensor =
+ container_of(ved, struct vimc_sensor_device, ved);
+
+ const unsigned int line_height = 16;
+ u8 *basep[TPG_MAX_PLANES][2];
+ unsigned int line = 1;
+ char str[100];
+
+ tpg_fill_plane_buffer(&vsensor->tpg, 0, 0, vsensor->frame);
+ tpg_calc_text_basep(&vsensor->tpg, basep, 0, vsensor->frame);
+ switch (vsensor->osd_value) {
+ case VIMC_SENSOR_OSD_SHOW_ALL: {
+ const char *order = tpg_g_color_order(&vsensor->tpg);
+
+ tpg_gen_text(&vsensor->tpg, basep, line++ * line_height,
+ 16, order);
+ snprintf(str, sizeof(str),
+ "brightness %3d, contrast %3d, saturation %3d, hue %d ",
+ vsensor->tpg.brightness,
+ vsensor->tpg.contrast,
+ vsensor->tpg.saturation,
+ vsensor->tpg.hue);
+ tpg_gen_text(&vsensor->tpg, basep, line++ * line_height, 16, str);
+ snprintf(str, sizeof(str), "sensor size: %dx%d",
+ vsensor->mbus_format.width,
+ vsensor->mbus_format.height);
+ tpg_gen_text(&vsensor->tpg, basep, line++ * line_height, 16, str);
+ fallthrough;
+ }
+ case VIMC_SENSOR_OSD_SHOW_COUNTERS: {
+ unsigned int ms;
+
+ ms = div_u64(ktime_get_ns() - vsensor->start_stream_ts, 1000000);
+ snprintf(str, sizeof(str), "%02d:%02d:%02d:%03d",
+ (ms / (60 * 60 * 1000)) % 24,
+ (ms / (60 * 1000)) % 60,
+ (ms / 1000) % 60,
+ ms % 1000);
+ tpg_gen_text(&vsensor->tpg, basep, line++ * line_height, 16, str);
+ break;
+ }
+ case VIMC_SENSOR_OSD_SHOW_NONE:
+ default:
+ break;
+ }
+
+ return vsensor->frame;
+}
+
+static int vimc_sensor_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct vimc_sensor_device *vsensor =
+ container_of(sd, struct vimc_sensor_device, sd);
+
+ if (enable) {
+ const struct vimc_pix_map *vpix;
+ unsigned int frame_size;
+
+ vsensor->start_stream_ts = ktime_get_ns();
+
+ /* Calculate the frame size */
+ vpix = vimc_pix_map_by_code(vsensor->mbus_format.code);
+ frame_size = vsensor->mbus_format.width * vpix->bpp *
+ vsensor->mbus_format.height;
+
+ /*
+ * Allocate the frame buffer. Use vmalloc to be able to
+ * allocate a large amount of memory
+ */
+ vsensor->frame = vmalloc(frame_size);
+ if (!vsensor->frame)
+ return -ENOMEM;
+
+ /* configure the test pattern generator */
+ vimc_sensor_tpg_s_format(vsensor);
+
+ } else {
+
+ vfree(vsensor->frame);
+ vsensor->frame = NULL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_subdev_core_ops vimc_sensor_core_ops = {
+ .log_status = v4l2_ctrl_subdev_log_status,
+ .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops vimc_sensor_video_ops = {
+ .s_stream = vimc_sensor_s_stream,
+};
+
+static const struct v4l2_subdev_ops vimc_sensor_ops = {
+ .core = &vimc_sensor_core_ops,
+ .pad = &vimc_sensor_pad_ops,
+ .video = &vimc_sensor_video_ops,
+};
+
+static int vimc_sensor_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct vimc_sensor_device *vsensor =
+ container_of(ctrl->handler, struct vimc_sensor_device, hdl);
+
+ switch (ctrl->id) {
+ case VIMC_CID_TEST_PATTERN:
+ tpg_s_pattern(&vsensor->tpg, ctrl->val);
+ break;
+ case V4L2_CID_HFLIP:
+ tpg_s_hflip(&vsensor->tpg, ctrl->val);
+ break;
+ case V4L2_CID_VFLIP:
+ tpg_s_vflip(&vsensor->tpg, ctrl->val);
+ break;
+ case V4L2_CID_BRIGHTNESS:
+ tpg_s_brightness(&vsensor->tpg, ctrl->val);
+ break;
+ case V4L2_CID_CONTRAST:
+ tpg_s_contrast(&vsensor->tpg, ctrl->val);
+ break;
+ case V4L2_CID_HUE:
+ tpg_s_hue(&vsensor->tpg, ctrl->val);
+ break;
+ case V4L2_CID_SATURATION:
+ tpg_s_saturation(&vsensor->tpg, ctrl->val);
+ break;
+ case VIMC_CID_OSD_TEXT_MODE:
+ vsensor->osd_value = ctrl->val;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops vimc_sensor_ctrl_ops = {
+ .s_ctrl = vimc_sensor_s_ctrl,
+};
+
+static void vimc_sensor_release(struct vimc_ent_device *ved)
+{
+ struct vimc_sensor_device *vsensor =
+ container_of(ved, struct vimc_sensor_device, ved);
+
+ v4l2_ctrl_handler_free(&vsensor->hdl);
+ tpg_free(&vsensor->tpg);
+ media_entity_cleanup(vsensor->ved.ent);
+ kfree(vsensor);
+}
+
+/* Image Processing Controls */
+static const struct v4l2_ctrl_config vimc_sensor_ctrl_class = {
+ .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_WRITE_ONLY,
+ .id = VIMC_CID_VIMC_CLASS,
+ .name = "VIMC Controls",
+ .type = V4L2_CTRL_TYPE_CTRL_CLASS,
+};
+
+static const struct v4l2_ctrl_config vimc_sensor_ctrl_test_pattern = {
+ .ops = &vimc_sensor_ctrl_ops,
+ .id = VIMC_CID_TEST_PATTERN,
+ .name = "Test Pattern",
+ .type = V4L2_CTRL_TYPE_MENU,
+ .max = TPG_PAT_NOISE,
+ .qmenu = tpg_pattern_strings,
+};
+
+static const char * const vimc_ctrl_osd_mode_strings[] = {
+ "All",
+ "Counters Only",
+ "None",
+ NULL,
+};
+
+static const struct v4l2_ctrl_config vimc_sensor_ctrl_osd_mode = {
+ .ops = &vimc_sensor_ctrl_ops,
+ .id = VIMC_CID_OSD_TEXT_MODE,
+ .name = "Show Information",
+ .type = V4L2_CTRL_TYPE_MENU,
+ .max = ARRAY_SIZE(vimc_ctrl_osd_mode_strings) - 2,
+ .qmenu = vimc_ctrl_osd_mode_strings,
+};
+
+static struct vimc_ent_device *vimc_sensor_add(struct vimc_device *vimc,
+ const char *vcfg_name)
+{
+ struct v4l2_device *v4l2_dev = &vimc->v4l2_dev;
+ struct vimc_sensor_device *vsensor;
+ int ret;
+
+ /* Allocate the vsensor struct */
+ vsensor = kzalloc(sizeof(*vsensor), GFP_KERNEL);
+ if (!vsensor)
+ return ERR_PTR(-ENOMEM);
+
+ v4l2_ctrl_handler_init(&vsensor->hdl, 4);
+
+ v4l2_ctrl_new_custom(&vsensor->hdl, &vimc_sensor_ctrl_class, NULL);
+ v4l2_ctrl_new_custom(&vsensor->hdl, &vimc_sensor_ctrl_test_pattern, NULL);
+ v4l2_ctrl_new_custom(&vsensor->hdl, &vimc_sensor_ctrl_osd_mode, NULL);
+ v4l2_ctrl_new_std(&vsensor->hdl, &vimc_sensor_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(&vsensor->hdl, &vimc_sensor_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(&vsensor->hdl, &vimc_sensor_ctrl_ops,
+ V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
+ v4l2_ctrl_new_std(&vsensor->hdl, &vimc_sensor_ctrl_ops,
+ V4L2_CID_CONTRAST, 0, 255, 1, 128);
+ v4l2_ctrl_new_std(&vsensor->hdl, &vimc_sensor_ctrl_ops,
+ V4L2_CID_HUE, -128, 127, 1, 0);
+ v4l2_ctrl_new_std(&vsensor->hdl, &vimc_sensor_ctrl_ops,
+ V4L2_CID_SATURATION, 0, 255, 1, 128);
+ vsensor->sd.ctrl_handler = &vsensor->hdl;
+ if (vsensor->hdl.error) {
+ ret = vsensor->hdl.error;
+ goto err_free_vsensor;
+ }
+
+ /* Initialize the test pattern generator */
+ tpg_init(&vsensor->tpg, vsensor->mbus_format.width,
+ vsensor->mbus_format.height);
+ ret = tpg_alloc(&vsensor->tpg, VIMC_FRAME_MAX_WIDTH);
+ if (ret)
+ goto err_free_hdl;
+
+ /* Initialize ved and sd */
+ vsensor->pad.flags = MEDIA_PAD_FL_SOURCE;
+ ret = vimc_ent_sd_register(&vsensor->ved, &vsensor->sd, v4l2_dev,
+ vcfg_name,
+ MEDIA_ENT_F_CAM_SENSOR, 1, &vsensor->pad,
+ &vimc_sensor_ops);
+ if (ret)
+ goto err_free_tpg;
+
+ vsensor->ved.process_frame = vimc_sensor_process_frame;
+ vsensor->ved.dev = vimc->mdev.dev;
+
+ /* Initialize the frame format */
+ vsensor->mbus_format = fmt_default;
+
+ return &vsensor->ved;
+
+err_free_tpg:
+ tpg_free(&vsensor->tpg);
+err_free_hdl:
+ v4l2_ctrl_handler_free(&vsensor->hdl);
+err_free_vsensor:
+ kfree(vsensor);
+
+ return ERR_PTR(ret);
+}
+
+struct vimc_ent_type vimc_sensor_type = {
+ .add = vimc_sensor_add,
+ .release = vimc_sensor_release
+};
diff --git a/drivers/media/test-drivers/vimc/vimc-streamer.c b/drivers/media/test-drivers/vimc/vimc-streamer.c
new file mode 100644
index 000000000..807551a51
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/vimc-streamer.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * vimc-streamer.c Virtual Media Controller Driver
+ *
+ * Copyright (C) 2018 Lucas A. M. Magalhães <lucmaga@gmail.com>
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/freezer.h>
+#include <linux/kthread.h>
+
+#include "vimc-streamer.h"
+
+/**
+ * vimc_get_source_entity - get the entity connected with the first sink pad
+ *
+ * @ent: reference media_entity
+ *
+ * Helper function that returns the media entity containing the source pad
+ * linked with the first sink pad from the given media entity pad list.
+ *
+ * Return: The source pad or NULL, if it wasn't found.
+ */
+static struct media_entity *vimc_get_source_entity(struct media_entity *ent)
+{
+ struct media_pad *pad;
+ int i;
+
+ for (i = 0; i < ent->num_pads; i++) {
+ if (ent->pads[i].flags & MEDIA_PAD_FL_SOURCE)
+ continue;
+ pad = media_pad_remote_pad_first(&ent->pads[i]);
+ return pad ? pad->entity : NULL;
+ }
+ return NULL;
+}
+
+/**
+ * vimc_streamer_pipeline_terminate - Disable stream in all ved in stream
+ *
+ * @stream: the pointer to the stream structure with the pipeline to be
+ * disabled.
+ *
+ * Calls s_stream to disable the stream in each entity of the pipeline
+ *
+ */
+static void vimc_streamer_pipeline_terminate(struct vimc_stream *stream)
+{
+ struct vimc_ent_device *ved;
+ struct v4l2_subdev *sd;
+
+ while (stream->pipe_size) {
+ stream->pipe_size--;
+ ved = stream->ved_pipeline[stream->pipe_size];
+ stream->ved_pipeline[stream->pipe_size] = NULL;
+
+ if (!is_media_entity_v4l2_subdev(ved->ent))
+ continue;
+
+ sd = media_entity_to_v4l2_subdev(ved->ent);
+ v4l2_subdev_call(sd, video, s_stream, 0);
+ }
+}
+
+/**
+ * vimc_streamer_pipeline_init - Initializes the stream structure
+ *
+ * @stream: the pointer to the stream structure to be initialized
+ * @ved: the pointer to the vimc entity initializing the stream
+ *
+ * Initializes the stream structure. Walks through the entity graph to
+ * construct the pipeline used later on the streamer thread.
+ * Calls vimc_streamer_s_stream() to enable stream in all entities of
+ * the pipeline.
+ *
+ * Return: 0 if success, error code otherwise.
+ */
+static int vimc_streamer_pipeline_init(struct vimc_stream *stream,
+ struct vimc_ent_device *ved)
+{
+ struct media_entity *entity;
+ struct video_device *vdev;
+ struct v4l2_subdev *sd;
+ int ret = 0;
+
+ stream->pipe_size = 0;
+ while (stream->pipe_size < VIMC_STREAMER_PIPELINE_MAX_SIZE) {
+ if (!ved) {
+ vimc_streamer_pipeline_terminate(stream);
+ return -EINVAL;
+ }
+ stream->ved_pipeline[stream->pipe_size++] = ved;
+
+ if (is_media_entity_v4l2_subdev(ved->ent)) {
+ sd = media_entity_to_v4l2_subdev(ved->ent);
+ ret = v4l2_subdev_call(sd, video, s_stream, 1);
+ if (ret && ret != -ENOIOCTLCMD) {
+ dev_err(ved->dev, "subdev_call error %s\n",
+ ved->ent->name);
+ vimc_streamer_pipeline_terminate(stream);
+ return ret;
+ }
+ }
+
+ entity = vimc_get_source_entity(ved->ent);
+ /* Check if the end of the pipeline was reached */
+ if (!entity) {
+ /* the first entity of the pipe should be source only */
+ if (!vimc_is_source(ved->ent)) {
+ dev_err(ved->dev,
+ "first entity in the pipe '%s' is not a source\n",
+ ved->ent->name);
+ vimc_streamer_pipeline_terminate(stream);
+ return -EPIPE;
+ }
+ return 0;
+ }
+
+ /* Get the next device in the pipeline */
+ if (is_media_entity_v4l2_subdev(entity)) {
+ sd = media_entity_to_v4l2_subdev(entity);
+ ved = v4l2_get_subdevdata(sd);
+ } else {
+ vdev = container_of(entity,
+ struct video_device,
+ entity);
+ ved = video_get_drvdata(vdev);
+ }
+ }
+
+ vimc_streamer_pipeline_terminate(stream);
+ return -EINVAL;
+}
+
+/**
+ * vimc_streamer_thread - Process frames through the pipeline
+ *
+ * @data: vimc_stream struct of the current stream
+ *
+ * From the source to the sink, gets a frame from each subdevice and send to
+ * the next one of the pipeline at a fixed framerate.
+ *
+ * Return:
+ * Always zero (created as ``int`` instead of ``void`` to comply with
+ * kthread API).
+ */
+static int vimc_streamer_thread(void *data)
+{
+ struct vimc_stream *stream = data;
+ u8 *frame = NULL;
+ int i;
+
+ set_freezable();
+
+ for (;;) {
+ try_to_freeze();
+ if (kthread_should_stop())
+ break;
+
+ for (i = stream->pipe_size - 1; i >= 0; i--) {
+ frame = stream->ved_pipeline[i]->process_frame(
+ stream->ved_pipeline[i], frame);
+ if (!frame || IS_ERR(frame))
+ break;
+ }
+ //wait for 60hz
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(HZ / 60);
+ }
+
+ return 0;
+}
+
+/**
+ * vimc_streamer_s_stream - Start/stop the streaming on the media pipeline
+ *
+ * @stream: the pointer to the stream structure of the current stream
+ * @ved: pointer to the vimc entity of the entity of the stream
+ * @enable: flag to determine if stream should start/stop
+ *
+ * When starting, check if there is no ``stream->kthread`` allocated. This
+ * should indicate that a stream is already running. Then, it initializes the
+ * pipeline, creates and runs a kthread to consume buffers through the pipeline.
+ * When stopping, analogously check if there is a stream running, stop the
+ * thread and terminates the pipeline.
+ *
+ * Return: 0 if success, error code otherwise.
+ */
+int vimc_streamer_s_stream(struct vimc_stream *stream,
+ struct vimc_ent_device *ved,
+ int enable)
+{
+ int ret;
+
+ if (!stream || !ved)
+ return -EINVAL;
+
+ if (enable) {
+ if (stream->kthread)
+ return 0;
+
+ ret = vimc_streamer_pipeline_init(stream, ved);
+ if (ret)
+ return ret;
+
+ stream->kthread = kthread_run(vimc_streamer_thread, stream,
+ "vimc-streamer thread");
+
+ if (IS_ERR(stream->kthread)) {
+ ret = PTR_ERR(stream->kthread);
+ dev_err(ved->dev, "kthread_run failed with %d\n", ret);
+ vimc_streamer_pipeline_terminate(stream);
+ stream->kthread = NULL;
+ return ret;
+ }
+
+ } else {
+ if (!stream->kthread)
+ return 0;
+
+ ret = kthread_stop(stream->kthread);
+ /*
+ * kthread_stop returns -EINTR in cases when streamon was
+ * immediately followed by streamoff, and the thread didn't had
+ * a chance to run. Ignore errors to stop the stream in the
+ * pipeline.
+ */
+ if (ret)
+ dev_dbg(ved->dev, "kthread_stop returned '%d'\n", ret);
+
+ stream->kthread = NULL;
+
+ vimc_streamer_pipeline_terminate(stream);
+ }
+
+ return 0;
+}
diff --git a/drivers/media/test-drivers/vimc/vimc-streamer.h b/drivers/media/test-drivers/vimc/vimc-streamer.h
new file mode 100644
index 000000000..3bb6731b8
--- /dev/null
+++ b/drivers/media/test-drivers/vimc/vimc-streamer.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * vimc-streamer.h Virtual Media Controller Driver
+ *
+ * Copyright (C) 2018 Lucas A. M. Magalhães <lucmaga@gmail.com>
+ *
+ */
+
+#ifndef _VIMC_STREAMER_H_
+#define _VIMC_STREAMER_H_
+
+#include <media/media-device.h>
+
+#include "vimc-common.h"
+
+#define VIMC_STREAMER_PIPELINE_MAX_SIZE 16
+
+/**
+ * struct vimc_stream - struct that represents a stream in the pipeline
+ *
+ * @pipe: the media pipeline object associated with this stream
+ * @ved_pipeline: array containing all the entities participating in the
+ * stream. The order is from a video device (usually a
+ * capture device) where stream_on was called, to the
+ * entity generating the first base image to be
+ * processed in the pipeline.
+ * @pipe_size: size of @ved_pipeline
+ * @kthread: thread that generates the frames of the stream.
+ *
+ * When the user call stream_on in a video device, struct vimc_stream is
+ * used to keep track of all entities and subdevices that generates and
+ * process frames for the stream.
+ */
+struct vimc_stream {
+ struct media_pipeline pipe;
+ struct vimc_ent_device *ved_pipeline[VIMC_STREAMER_PIPELINE_MAX_SIZE];
+ unsigned int pipe_size;
+ struct task_struct *kthread;
+};
+
+int vimc_streamer_s_stream(struct vimc_stream *stream,
+ struct vimc_ent_device *ved,
+ int enable);
+
+#endif //_VIMC_STREAMER_H_