diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/media/test-drivers/vimc | |
parent | Initial commit. (diff) | |
download | linux-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/Kconfig | 20 | ||||
-rw-r--r-- | drivers/media/test-drivers/vimc/Makefile | 6 | ||||
-rw-r--r-- | drivers/media/test-drivers/vimc/vimc-capture.c | 500 | ||||
-rw-r--r-- | drivers/media/test-drivers/vimc/vimc-common.c | 400 | ||||
-rw-r--r-- | drivers/media/test-drivers/vimc/vimc-common.h | 241 | ||||
-rw-r--r-- | drivers/media/test-drivers/vimc/vimc-core.c | 455 | ||||
-rw-r--r-- | drivers/media/test-drivers/vimc/vimc-debayer.c | 626 | ||||
-rw-r--r-- | drivers/media/test-drivers/vimc/vimc-lens.c | 102 | ||||
-rw-r--r-- | drivers/media/test-drivers/vimc/vimc-scaler.c | 444 | ||||
-rw-r--r-- | drivers/media/test-drivers/vimc/vimc-sensor.c | 453 | ||||
-rw-r--r-- | drivers/media/test-drivers/vimc/vimc-streamer.c | 238 | ||||
-rw-r--r-- | drivers/media/test-drivers/vimc/vimc-streamer.h | 45 |
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_ |