media: stm32-dcmipp: STM32 DCMIPP camera interface driver
authorHugues Fruchet <hugues.fruchet@foss.st.com>
Mon, 27 Nov 2023 17:08:17 +0000 (18:08 +0100)
committerHans Verkuil <hverkuil-cisco@xs4all.nl>
Mon, 4 Dec 2023 09:37:47 +0000 (10:37 +0100)
This V4L2 subdev driver enables Digital Camera Memory Interface
Pixel Processor(DCMIPP) of STMicroelectronics STM32 SoC series.

Signed-off-by: Hugues Fruchet <hugues.fruchet@foss.st.com>
Signed-off-by: Alain Volmat <alain.volmat@foss.st.com>
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
[hverkuil: remove empty line at end of source]

drivers/media/platform/st/stm32/Kconfig
drivers/media/platform/st/stm32/Makefile
drivers/media/platform/st/stm32/stm32-dcmipp/Makefile [new file with mode: 0644]
drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c [new file with mode: 0644]
drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c [new file with mode: 0644]
drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.c [new file with mode: 0644]
drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h [new file with mode: 0644]
drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c [new file with mode: 0644]
drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-parallel.c [new file with mode: 0644]

index b22dd4753496b493dcd952073bfb00e586e12a3f..9df9a2a177280c2e473c1312fc257154817bc2bc 100644 (file)
@@ -16,6 +16,22 @@ config VIDEO_STM32_DCMI
          To compile this driver as a module, choose M here: the module
          will be called stm32-dcmi.
 
+config VIDEO_STM32_DCMIPP
+       tristate "STM32 Digital Camera Memory Interface Pixel Processor (DCMIPP) support"
+       depends on V4L_PLATFORM_DRIVERS
+       depends on VIDEO_DEV
+       depends on ARCH_STM32 || COMPILE_TEST
+       select MEDIA_CONTROLLER
+       select VIDEOBUF2_DMA_CONTIG
+       select VIDEO_V4L2_SUBDEV_API
+       select V4L2_FWNODE
+       help
+         This module makes the STM32 Digital Camera Memory Interface
+         Pixel Processor (DCMIPP) available as a v4l2 device.
+
+         To compile this driver as a module, choose M here: the module
+         will be called stm32-dcmipp.
+
 # Mem2mem drivers
 config VIDEO_STM32_DMA2D
        tristate "STM32 Chrom-Art Accelerator (DMA2D)"
index 896ef98a73ab8b786e6cac565ad23d4511a33483..7ed8297b9b1913b04eb07015f847491aa80ba529 100644 (file)
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32-dcmi.o
+obj-$(CONFIG_VIDEO_STM32_DCMIPP) += stm32-dcmipp/
 stm32-dma2d-objs := dma2d/dma2d.o dma2d/dma2d-hw.o
 obj-$(CONFIG_VIDEO_STM32_DMA2D) += stm32-dma2d.o
diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile b/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile
new file mode 100644 (file)
index 0000000..8920d93
--- /dev/null
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+stm32-dcmipp-y := dcmipp-core.o dcmipp-common.o dcmipp-parallel.o dcmipp-byteproc.o dcmipp-bytecap.o
+
+obj-$(CONFIG_VIDEO_STM32_DCMIPP) += stm32-dcmipp.o
diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c
new file mode 100644 (file)
index 0000000..0cd9b08
--- /dev/null
@@ -0,0 +1,956 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
+ *
+ * Copyright (C) STMicroelectronics SA 2023
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
+ *          Alain Volmat <alain.volmat@foss.st.com>
+ *          for STMicroelectronics.
+ */
+
+#include <linux/iopoll.h>
+#include <linux/pm_runtime.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "dcmipp-common.h"
+
+#define DCMIPP_PRSR            0x1f8
+#define DCMIPP_CMIER           0x3f0
+#define DCMIPP_CMIER_P0FRAMEIE BIT(9)
+#define DCMIPP_CMIER_P0VSYNCIE BIT(10)
+#define DCMIPP_CMIER_P0OVRIE   BIT(15)
+#define DCMIPP_CMIER_P0ALL     (DCMIPP_CMIER_P0VSYNCIE |\
+                                DCMIPP_CMIER_P0FRAMEIE |\
+                                DCMIPP_CMIER_P0OVRIE)
+#define DCMIPP_CMSR1           0x3f4
+#define DCMIPP_CMSR2           0x3f8
+#define DCMIPP_CMSR2_P0FRAMEF  BIT(9)
+#define DCMIPP_CMSR2_P0VSYNCF  BIT(10)
+#define DCMIPP_CMSR2_P0OVRF    BIT(15)
+#define DCMIPP_CMFCR           0x3fc
+#define DCMIPP_P0FSCR          0x404
+#define DCMIPP_P0FSCR_PIPEN    BIT(31)
+#define DCMIPP_P0FCTCR         0x500
+#define DCMIPP_P0FCTCR_CPTREQ  BIT(3)
+#define DCMIPP_P0DCCNTR                0x5b0
+#define DCMIPP_P0DCLMTR                0x5b4
+#define DCMIPP_P0DCLMTR_ENABLE BIT(31)
+#define DCMIPP_P0DCLMTR_LIMIT_MASK     GENMASK(23, 0)
+#define DCMIPP_P0PPM0AR1       0x5c4
+#define DCMIPP_P0SR            0x5f8
+#define DCMIPP_P0SR_CPTACT     BIT(23)
+
+struct dcmipp_bytecap_pix_map {
+       unsigned int code;
+       u32 pixelformat;
+};
+
+#define PIXMAP_MBUS_PFMT(mbus, fmt)                    \
+       {                                               \
+               .code = MEDIA_BUS_FMT_##mbus,           \
+               .pixelformat = V4L2_PIX_FMT_##fmt       \
+       }
+
+static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_pix_map_list[] = {
+       PIXMAP_MBUS_PFMT(RGB565_2X8_LE, RGB565),
+       PIXMAP_MBUS_PFMT(YUYV8_2X8, YUYV),
+       PIXMAP_MBUS_PFMT(YVYU8_2X8, YVYU),
+       PIXMAP_MBUS_PFMT(UYVY8_2X8, UYVY),
+       PIXMAP_MBUS_PFMT(VYUY8_2X8, VYUY),
+       PIXMAP_MBUS_PFMT(Y8_1X8, GREY),
+       PIXMAP_MBUS_PFMT(SBGGR8_1X8, SBGGR8),
+       PIXMAP_MBUS_PFMT(SGBRG8_1X8, SGBRG8),
+       PIXMAP_MBUS_PFMT(SGRBG8_1X8, SGRBG8),
+       PIXMAP_MBUS_PFMT(SRGGB8_1X8, SRGGB8),
+       PIXMAP_MBUS_PFMT(JPEG_1X8, JPEG),
+};
+
+static const struct dcmipp_bytecap_pix_map *
+dcmipp_bytecap_pix_map_by_pixelformat(u32 pixelformat)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(dcmipp_bytecap_pix_map_list); i++) {
+               if (dcmipp_bytecap_pix_map_list[i].pixelformat == pixelformat)
+                       return &dcmipp_bytecap_pix_map_list[i];
+       }
+
+       return NULL;
+}
+
+struct dcmipp_buf {
+       struct vb2_v4l2_buffer  vb;
+       bool                    prepared;
+       dma_addr_t              addr;
+       size_t                  size;
+       struct list_head        list;
+};
+
+enum dcmipp_state {
+       DCMIPP_STOPPED = 0,
+       DCMIPP_WAIT_FOR_BUFFER,
+       DCMIPP_RUNNING,
+};
+
+struct dcmipp_bytecap_device {
+       struct dcmipp_ent_device ved;
+       struct video_device vdev;
+       struct device *dev;
+       struct v4l2_pix_format format;
+       struct vb2_queue queue;
+       struct list_head buffers;
+       /*
+        * Protects concurrent calls of buf queue / irq handler
+        * and buffer handling related variables / lists
+        */
+       spinlock_t irqlock;
+       /* mutex used as vdev and queue lock */
+       struct mutex lock;
+       u32 sequence;
+       struct media_pipeline pipe;
+       struct v4l2_subdev *s_subdev;
+
+       enum dcmipp_state state;
+
+       /*
+        * DCMIPP driver is handling 2 buffers
+        * active: buffer into which DCMIPP is currently writing into
+        * next: buffer given to the DCMIPP and which will become
+        *       automatically active on next VSYNC
+        */
+       struct dcmipp_buf *active, *next;
+
+       void __iomem *regs;
+
+       u32 cmier;
+       u32 cmsr2;
+
+       struct {
+               u32 errors;
+               u32 limit;
+               u32 overrun;
+               u32 buffers;
+               u32 vsync;
+               u32 frame;
+               u32 it;
+               u32 underrun;
+               u32 nactive;
+       } count;
+};
+
+static const struct v4l2_pix_format fmt_default = {
+       .width = DCMIPP_FMT_WIDTH_DEFAULT,
+       .height = DCMIPP_FMT_HEIGHT_DEFAULT,
+       .pixelformat = V4L2_PIX_FMT_RGB565,
+       .field = V4L2_FIELD_NONE,
+       .bytesperline = DCMIPP_FMT_WIDTH_DEFAULT * 2,
+       .sizeimage = DCMIPP_FMT_WIDTH_DEFAULT * DCMIPP_FMT_HEIGHT_DEFAULT * 2,
+       .colorspace = DCMIPP_COLORSPACE_DEFAULT,
+       .ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT,
+       .quantization = DCMIPP_QUANTIZATION_DEFAULT,
+       .xfer_func = DCMIPP_XFER_FUNC_DEFAULT,
+};
+
+static int dcmipp_bytecap_querycap(struct file *file, void *priv,
+                                  struct v4l2_capability *cap)
+{
+       strscpy(cap->driver, DCMIPP_PDEV_NAME, sizeof(cap->driver));
+       strscpy(cap->card, KBUILD_MODNAME, sizeof(cap->card));
+
+       return 0;
+}
+
+static int dcmipp_bytecap_g_fmt_vid_cap(struct file *file, void *priv,
+                                       struct v4l2_format *f)
+{
+       struct dcmipp_bytecap_device *vcap = video_drvdata(file);
+
+       f->fmt.pix = vcap->format;
+
+       return 0;
+}
+
+static int dcmipp_bytecap_try_fmt_vid_cap(struct file *file, void *priv,
+                                         struct v4l2_format *f)
+{
+       struct dcmipp_bytecap_device *vcap = video_drvdata(file);
+       struct v4l2_pix_format *format = &f->fmt.pix;
+       const struct dcmipp_bytecap_pix_map *vpix;
+       u32 in_w, in_h;
+
+       /* Don't accept a pixelformat that is not on the table */
+       vpix = dcmipp_bytecap_pix_map_by_pixelformat(format->pixelformat);
+       if (!vpix)
+               format->pixelformat = fmt_default.pixelformat;
+
+       /* Adjust width & height */
+       in_w = format->width;
+       in_h = format->height;
+       v4l_bound_align_image(&format->width, DCMIPP_FRAME_MIN_WIDTH,
+                             DCMIPP_FRAME_MAX_WIDTH, 0, &format->height,
+                             DCMIPP_FRAME_MIN_HEIGHT, DCMIPP_FRAME_MAX_HEIGHT,
+                             0, 0);
+       if (format->width != in_w || format->height != in_h)
+               dev_dbg(vcap->dev, "resolution updated: %dx%d -> %dx%d\n",
+                       in_w, in_h, format->width, format->height);
+
+       if (format->pixelformat == V4L2_PIX_FMT_JPEG) {
+               format->bytesperline = format->width;
+               format->sizeimage = format->bytesperline * format->height;
+       } else {
+               v4l2_fill_pixfmt(format, format->pixelformat,
+                                format->width, format->height);
+       }
+
+       if (format->field == V4L2_FIELD_ANY)
+               format->field = fmt_default.field;
+
+       dcmipp_colorimetry_clamp(format);
+
+       return 0;
+}
+
+static int dcmipp_bytecap_s_fmt_vid_cap(struct file *file, void *priv,
+                                       struct v4l2_format *f)
+{
+       struct dcmipp_bytecap_device *vcap = video_drvdata(file);
+       int ret;
+
+       /* Do not change the format while stream is on */
+       if (vb2_is_busy(&vcap->queue))
+               return -EBUSY;
+
+       ret = dcmipp_bytecap_try_fmt_vid_cap(file, priv, f);
+       if (ret)
+               return ret;
+
+       dev_dbg(vcap->dev, "%s: format update: old:%ux%u (0x%p4cc, %u, %u, %u, %u) new:%ux%d (0x%p4cc, %u, %u, %u, %u)\n",
+               vcap->vdev.name,
+               /* old */
+               vcap->format.width, vcap->format.height,
+               &vcap->format.pixelformat, vcap->format.colorspace,
+               vcap->format.quantization, vcap->format.xfer_func,
+               vcap->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);
+
+       vcap->format = f->fmt.pix;
+
+       return 0;
+}
+
+static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv,
+                                          struct v4l2_fmtdesc *f)
+{
+       const struct dcmipp_bytecap_pix_map *vpix;
+       unsigned int index = f->index;
+       unsigned int i;
+
+       if (f->mbus_code) {
+               /*
+                * If a media bus code is specified, only enumerate formats
+                * compatible with it.
+                */
+               for (i = 0; i < ARRAY_SIZE(dcmipp_bytecap_pix_map_list); i++) {
+                       vpix = &dcmipp_bytecap_pix_map_list[i];
+                       if (vpix->code != f->mbus_code)
+                               continue;
+
+                       if (index == 0)
+                               break;
+
+                       index--;
+               }
+
+               if (i == ARRAY_SIZE(dcmipp_bytecap_pix_map_list))
+                       return -EINVAL;
+       } else {
+               /* Otherwise, enumerate all formats. */
+               if (f->index >= ARRAY_SIZE(dcmipp_bytecap_pix_map_list))
+                       return -EINVAL;
+
+               vpix = &dcmipp_bytecap_pix_map_list[f->index];
+       }
+
+       f->pixelformat = vpix->pixelformat;
+
+       return 0;
+}
+
+static int dcmipp_bytecap_enum_framesizes(struct file *file, void *fh,
+                                         struct v4l2_frmsizeenum *fsize)
+{
+       const struct dcmipp_bytecap_pix_map *vpix;
+
+       if (fsize->index)
+               return -EINVAL;
+
+       /* Only accept code in the pix map table */
+       vpix = dcmipp_bytecap_pix_map_by_pixelformat(fsize->pixel_format);
+       if (!vpix)
+               return -EINVAL;
+
+       fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
+       fsize->stepwise.min_width = DCMIPP_FRAME_MIN_WIDTH;
+       fsize->stepwise.max_width = DCMIPP_FRAME_MAX_WIDTH;
+       fsize->stepwise.min_height = DCMIPP_FRAME_MIN_HEIGHT;
+       fsize->stepwise.max_height = DCMIPP_FRAME_MAX_HEIGHT;
+       fsize->stepwise.step_width = 1;
+       fsize->stepwise.step_height = 1;
+
+       return 0;
+}
+
+static const struct v4l2_file_operations dcmipp_bytecap_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 dcmipp_bytecap_ioctl_ops = {
+       .vidioc_querycap = dcmipp_bytecap_querycap,
+
+       .vidioc_g_fmt_vid_cap = dcmipp_bytecap_g_fmt_vid_cap,
+       .vidioc_s_fmt_vid_cap = dcmipp_bytecap_s_fmt_vid_cap,
+       .vidioc_try_fmt_vid_cap = dcmipp_bytecap_try_fmt_vid_cap,
+       .vidioc_enum_fmt_vid_cap = dcmipp_bytecap_enum_fmt_vid_cap,
+       .vidioc_enum_framesizes = dcmipp_bytecap_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 int dcmipp_pipeline_s_stream(struct dcmipp_bytecap_device *vcap,
+                                   int state)
+{
+       struct media_pad *pad;
+       int ret;
+
+       /*
+        * Get source subdev - since link is IMMUTABLE, pointer is cached
+        * within the dcmipp_bytecap_device structure
+        */
+       if (!vcap->s_subdev) {
+               pad = media_pad_remote_pad_first(&vcap->vdev.entity.pads[0]);
+               if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+                       return -EINVAL;
+               vcap->s_subdev = media_entity_to_v4l2_subdev(pad->entity);
+       }
+
+       ret = v4l2_subdev_call(vcap->s_subdev, video, s_stream, state);
+       if (ret < 0) {
+               dev_err(vcap->dev, "failed to %s streaming (%d)\n",
+                       state ? "start" : "stop", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static void dcmipp_start_capture(struct dcmipp_bytecap_device *vcap,
+                                struct dcmipp_buf *buf)
+{
+       /* Set buffer address */
+       reg_write(vcap, DCMIPP_P0PPM0AR1, buf->addr);
+
+       /* Set buffer size */
+       reg_write(vcap, DCMIPP_P0DCLMTR, DCMIPP_P0DCLMTR_ENABLE |
+                 ((buf->size / 4) & DCMIPP_P0DCLMTR_LIMIT_MASK));
+
+       /* Capture request */
+       reg_set(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ);
+}
+
+static void dcmipp_bytecap_all_buffers_done(struct dcmipp_bytecap_device *vcap,
+                                           enum vb2_buffer_state state)
+{
+       struct dcmipp_buf *buf, *node;
+
+       list_for_each_entry_safe(buf, node, &vcap->buffers, list) {
+               list_del_init(&buf->list);
+               vb2_buffer_done(&buf->vb.vb2_buf, state);
+       }
+}
+
+static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq,
+                                         unsigned int count)
+{
+       struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq);
+       struct media_entity *entity = &vcap->vdev.entity;
+       struct dcmipp_buf *buf;
+       int ret;
+
+       vcap->sequence = 0;
+       memset(&vcap->count, 0, sizeof(vcap->count));
+
+       ret = pm_runtime_resume_and_get(vcap->dev);
+       if (ret < 0) {
+               dev_err(vcap->dev, "%s: Failed to start streaming, cannot get sync (%d)\n",
+                       __func__, ret);
+               goto err_buffer_done;
+       }
+
+       ret = media_pipeline_start(entity->pads, &vcap->pipe);
+       if (ret) {
+               dev_dbg(vcap->dev, "%s: Failed to start streaming, media pipeline start error (%d)\n",
+                       __func__, ret);
+               goto err_pm_put;
+       }
+
+       ret = dcmipp_pipeline_s_stream(vcap, 1);
+       if (ret)
+               goto err_media_pipeline_stop;
+
+       spin_lock_irq(&vcap->irqlock);
+
+       /* Enable pipe at the end of programming */
+       reg_set(vcap, DCMIPP_P0FSCR, DCMIPP_P0FSCR_PIPEN);
+
+       /*
+        * vb2 framework guarantee that we have at least 'min_buffers_needed'
+        * buffers in the list at this moment
+        */
+       vcap->next = list_first_entry(&vcap->buffers, typeof(*buf), list);
+       dev_dbg(vcap->dev, "Start with next [%d] %p phy=%pad\n",
+               vcap->next->vb.vb2_buf.index, vcap->next, &vcap->next->addr);
+
+       dcmipp_start_capture(vcap, vcap->next);
+
+       /* Enable interruptions */
+       vcap->cmier |= DCMIPP_CMIER_P0ALL;
+       reg_set(vcap, DCMIPP_CMIER, vcap->cmier);
+
+       vcap->state = DCMIPP_RUNNING;
+
+       spin_unlock_irq(&vcap->irqlock);
+
+       return 0;
+
+err_media_pipeline_stop:
+       media_pipeline_stop(entity->pads);
+err_pm_put:
+       pm_runtime_put(vcap->dev);
+err_buffer_done:
+       spin_lock_irq(&vcap->irqlock);
+       /*
+        * Return all buffers to vb2 in QUEUED state.
+        * This will give ownership back to userspace
+        */
+       dcmipp_bytecap_all_buffers_done(vcap, VB2_BUF_STATE_QUEUED);
+       vcap->active = NULL;
+       spin_unlock_irq(&vcap->irqlock);
+
+       return ret;
+}
+
+static void dcmipp_dump_status(struct dcmipp_bytecap_device *vcap)
+{
+       struct device *dev = vcap->dev;
+
+       dev_dbg(dev, "[DCMIPP_PRSR]  =%#10.8x\n", reg_read(vcap, DCMIPP_PRSR));
+       dev_dbg(dev, "[DCMIPP_P0SR] =%#10.8x\n", reg_read(vcap, DCMIPP_P0SR));
+       dev_dbg(dev, "[DCMIPP_P0DCCNTR]=%#10.8x\n",
+               reg_read(vcap, DCMIPP_P0DCCNTR));
+       dev_dbg(dev, "[DCMIPP_CMSR1] =%#10.8x\n", reg_read(vcap, DCMIPP_CMSR1));
+       dev_dbg(dev, "[DCMIPP_CMSR2] =%#10.8x\n", reg_read(vcap, DCMIPP_CMSR2));
+}
+
+/*
+ * 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 dcmipp_bytecap_stop_streaming(struct vb2_queue *vq)
+{
+       struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq);
+       int ret;
+       u32 status;
+
+       dcmipp_pipeline_s_stream(vcap, 0);
+
+       /* Stop the media pipeline */
+       media_pipeline_stop(vcap->vdev.entity.pads);
+
+       /* Disable interruptions */
+       reg_clear(vcap, DCMIPP_CMIER, vcap->cmier);
+
+       /* Stop capture */
+       reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ);
+
+       /* Wait until CPTACT become 0 */
+       ret = readl_relaxed_poll_timeout(vcap->regs + DCMIPP_P0SR, status,
+                                        !(status & DCMIPP_P0SR_CPTACT),
+                                        20 * USEC_PER_MSEC,
+                                        1000 * USEC_PER_MSEC);
+       if (ret)
+               dev_warn(vcap->dev, "Timeout when stopping\n");
+
+       /* Disable pipe */
+       reg_clear(vcap, DCMIPP_P0FSCR, DCMIPP_P0FSCR_PIPEN);
+
+       spin_lock_irq(&vcap->irqlock);
+
+       /* Return all queued buffers to vb2 in ERROR state */
+       dcmipp_bytecap_all_buffers_done(vcap, VB2_BUF_STATE_ERROR);
+       INIT_LIST_HEAD(&vcap->buffers);
+
+       vcap->active = NULL;
+       vcap->state = DCMIPP_STOPPED;
+
+       spin_unlock_irq(&vcap->irqlock);
+
+       dcmipp_dump_status(vcap);
+
+       pm_runtime_put(vcap->dev);
+
+       if (vcap->count.errors)
+               dev_warn(vcap->dev, "Some errors found while streaming: errors=%d (overrun=%d, limit=%d, nactive=%d), underrun=%d, buffers=%d\n",
+                        vcap->count.errors, vcap->count.overrun,
+                        vcap->count.limit, vcap->count.nactive,
+                        vcap->count.underrun, vcap->count.buffers);
+}
+
+static int dcmipp_bytecap_buf_prepare(struct vb2_buffer *vb)
+{
+       struct dcmipp_bytecap_device *vcap =  vb2_get_drv_priv(vb->vb2_queue);
+       struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+       struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb);
+       unsigned long size;
+
+       size = vcap->format.sizeimage;
+
+       if (vb2_plane_size(vb, 0) < size) {
+               dev_err(vcap->dev, "%s data will not fit into plane (%lu < %lu)\n",
+                       __func__, vb2_plane_size(vb, 0), size);
+               return -EINVAL;
+       }
+
+       vb2_set_plane_payload(vb, 0, size);
+
+       if (!buf->prepared) {
+               /* Get memory addresses */
+               buf->addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+               buf->size = vb2_plane_size(&buf->vb.vb2_buf, 0);
+               buf->prepared = true;
+
+               vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size);
+
+               dev_dbg(vcap->dev, "Setup [%d] phy=%pad size=%zu\n",
+                       vb->index, &buf->addr, buf->size);
+       }
+
+       return 0;
+}
+
+static void dcmipp_bytecap_buf_queue(struct vb2_buffer *vb2_buf)
+{
+       struct dcmipp_bytecap_device *vcap =
+               vb2_get_drv_priv(vb2_buf->vb2_queue);
+       struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2_buf);
+       struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb);
+
+       dev_dbg(vcap->dev, "Queue [%d] %p phy=%pad\n", buf->vb.vb2_buf.index,
+               buf, &buf->addr);
+
+       spin_lock_irq(&vcap->irqlock);
+       list_add_tail(&buf->list, &vcap->buffers);
+
+       if (vcap->state == DCMIPP_WAIT_FOR_BUFFER) {
+               vcap->next = buf;
+               dev_dbg(vcap->dev, "Restart with next [%d] %p phy=%pad\n",
+                       buf->vb.vb2_buf.index, buf, &buf->addr);
+
+               dcmipp_start_capture(vcap, buf);
+
+               vcap->state = DCMIPP_RUNNING;
+       }
+
+       spin_unlock_irq(&vcap->irqlock);
+}
+
+static int dcmipp_bytecap_queue_setup(struct vb2_queue *vq,
+                                     unsigned int *nbuffers,
+                                     unsigned int *nplanes,
+                                     unsigned int sizes[],
+                                     struct device *alloc_devs[])
+{
+       struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq);
+       unsigned int size;
+
+       size = vcap->format.sizeimage;
+
+       /* Make sure the image size is large enough */
+       if (*nplanes)
+               return sizes[0] < vcap->format.sizeimage ? -EINVAL : 0;
+
+       *nplanes = 1;
+       sizes[0] = vcap->format.sizeimage;
+
+       dev_dbg(vcap->dev, "Setup queue, count=%d, size=%d\n",
+               *nbuffers, size);
+
+       return 0;
+}
+
+static int dcmipp_bytecap_buf_init(struct vb2_buffer *vb)
+{
+       struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+       struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb);
+
+       INIT_LIST_HEAD(&buf->list);
+
+       return 0;
+}
+
+static const struct vb2_ops dcmipp_bytecap_qops = {
+       .start_streaming        = dcmipp_bytecap_start_streaming,
+       .stop_streaming         = dcmipp_bytecap_stop_streaming,
+       .buf_init               = dcmipp_bytecap_buf_init,
+       .buf_prepare            = dcmipp_bytecap_buf_prepare,
+       .buf_queue              = dcmipp_bytecap_buf_queue,
+       .queue_setup            = dcmipp_bytecap_queue_setup,
+       /*
+        * 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 void dcmipp_bytecap_release(struct video_device *vdev)
+{
+       struct dcmipp_bytecap_device *vcap =
+               container_of(vdev, struct dcmipp_bytecap_device, vdev);
+
+       dcmipp_pads_cleanup(vcap->ved.pads);
+       mutex_destroy(&vcap->lock);
+
+       kfree(vcap);
+}
+
+void dcmipp_bytecap_ent_release(struct dcmipp_ent_device *ved)
+{
+       struct dcmipp_bytecap_device *vcap =
+               container_of(ved, struct dcmipp_bytecap_device, ved);
+
+       media_entity_cleanup(ved->ent);
+       vb2_video_unregister_device(&vcap->vdev);
+}
+
+static void dcmipp_buffer_done(struct dcmipp_bytecap_device *vcap,
+                              struct dcmipp_buf *buf,
+                              size_t bytesused,
+                              int err)
+{
+       struct vb2_v4l2_buffer *vbuf;
+
+       list_del_init(&buf->list);
+
+       vbuf = &buf->vb;
+
+       vbuf->sequence = vcap->sequence++;
+       vbuf->field = V4L2_FIELD_NONE;
+       vbuf->vb2_buf.timestamp = ktime_get_ns();
+       vb2_set_plane_payload(&vbuf->vb2_buf, 0, bytesused);
+       vb2_buffer_done(&vbuf->vb2_buf,
+                       err ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
+       dev_dbg(vcap->dev, "Done  [%d] %p phy=%pad\n", buf->vb.vb2_buf.index,
+               buf, &buf->addr);
+       vcap->count.buffers++;
+}
+
+/* irqlock must be held */
+static void
+dcmipp_bytecap_set_next_frame_or_stop(struct dcmipp_bytecap_device *vcap)
+{
+       if (!vcap->next && list_is_singular(&vcap->buffers)) {
+               /*
+                * If there is no available buffer (none or a single one in the
+                * list while two are expected), stop the capture (effective
+                * for next frame). On-going frame capture will continue until
+                * FRAME END but no further capture will be done.
+                */
+               reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ);
+
+               dev_dbg(vcap->dev, "Capture restart is deferred to next buffer queueing\n");
+               vcap->next = NULL;
+               vcap->state = DCMIPP_WAIT_FOR_BUFFER;
+               return;
+       }
+
+       /* If we don't have buffer yet, pick the one after active */
+       if (!vcap->next)
+               vcap->next = list_next_entry(vcap->active, list);
+
+       /*
+        * Set buffer address
+        * This register is shadowed and will be taken into
+        * account on next VSYNC (start of next frame)
+        */
+       reg_write(vcap, DCMIPP_P0PPM0AR1, vcap->next->addr);
+       dev_dbg(vcap->dev, "Write [%d] %p phy=%pad\n",
+               vcap->next->vb.vb2_buf.index, vcap->next, &vcap->next->addr);
+}
+
+/* irqlock must be held */
+static void dcmipp_bytecap_process_frame(struct dcmipp_bytecap_device *vcap,
+                                        size_t bytesused)
+{
+       int err = 0;
+       struct dcmipp_buf *buf = vcap->active;
+
+       if (!buf) {
+               vcap->count.nactive++;
+               vcap->count.errors++;
+               return;
+       }
+
+       if (bytesused > buf->size) {
+               dev_dbg(vcap->dev, "frame larger than expected (%zu > %zu)\n",
+                       bytesused, buf->size);
+               /* Clip to buffer size and return buffer to V4L2 in error */
+               bytesused = buf->size;
+               vcap->count.limit++;
+               vcap->count.errors++;
+               err = -EOVERFLOW;
+       }
+
+       dcmipp_buffer_done(vcap, buf, bytesused, err);
+       vcap->active = NULL;
+}
+
+static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg)
+{
+       struct dcmipp_bytecap_device *vcap =
+                       container_of(arg, struct dcmipp_bytecap_device, ved);
+       size_t bytesused = 0;
+       u32 cmsr2;
+
+       spin_lock_irq(&vcap->irqlock);
+
+       cmsr2 = vcap->cmsr2 & vcap->cmier;
+
+       /*
+        * If we have an overrun, a frame-end will probably not be generated,
+        * in that case the active buffer will be recycled as next buffer by
+        * the VSYNC handler
+        */
+       if (cmsr2 & DCMIPP_CMSR2_P0OVRF) {
+               vcap->count.errors++;
+               vcap->count.overrun++;
+       }
+
+       if (cmsr2 & DCMIPP_CMSR2_P0FRAMEF) {
+               vcap->count.frame++;
+
+               /* Read captured buffer size */
+               bytesused = reg_read(vcap, DCMIPP_P0DCCNTR);
+               dcmipp_bytecap_process_frame(vcap, bytesused);
+       }
+
+       if (cmsr2 & DCMIPP_CMSR2_P0VSYNCF) {
+               vcap->count.vsync++;
+               if (vcap->state == DCMIPP_WAIT_FOR_BUFFER) {
+                       vcap->count.underrun++;
+                       goto out;
+               }
+
+               /*
+                * On VSYNC, the previously set next buffer is going to become
+                * active thanks to the shadowing mechanism of the DCMIPP. In
+                * most of the cases, since a FRAMEEND has already come,
+                * pointer next is NULL since active is reset during the
+                * FRAMEEND handling. However, in case of framerate adjustment,
+                * there are more VSYNC than FRAMEEND. Thus we recycle the
+                * active (but not used) buffer and put it back into next.
+                */
+               swap(vcap->active, vcap->next);
+               dcmipp_bytecap_set_next_frame_or_stop(vcap);
+       }
+
+out:
+       spin_unlock_irq(&vcap->irqlock);
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t dcmipp_bytecap_irq_callback(int irq, void *arg)
+{
+       struct dcmipp_bytecap_device *vcap =
+                       container_of(arg, struct dcmipp_bytecap_device, ved);
+
+       /* Store interrupt status register */
+       vcap->cmsr2 = reg_read(vcap, DCMIPP_CMSR2) & vcap->cmier;
+       vcap->count.it++;
+
+       /* Clear interrupt */
+       reg_write(vcap, DCMIPP_CMFCR, vcap->cmsr2);
+
+       return IRQ_WAKE_THREAD;
+}
+
+static int dcmipp_bytecap_link_validate(struct media_link *link)
+{
+       struct media_entity *entity = link->sink->entity;
+       struct video_device *vd = media_entity_to_video_device(entity);
+       struct dcmipp_bytecap_device *vcap = container_of(vd,
+                                       struct dcmipp_bytecap_device, vdev);
+       struct v4l2_subdev *source_sd =
+               media_entity_to_v4l2_subdev(link->source->entity);
+       struct v4l2_subdev_format source_fmt = {
+               .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+               .pad = link->source->index,
+       };
+       const struct dcmipp_bytecap_pix_map *vpix;
+       int ret;
+
+       ret = v4l2_subdev_call(source_sd, pad, get_fmt, NULL, &source_fmt);
+       if (ret < 0)
+               return 0;
+
+       if (source_fmt.format.width != vcap->format.width ||
+           source_fmt.format.height != vcap->format.height) {
+               dev_err(vcap->dev, "Wrong width or height %ux%u (%ux%u expected)\n",
+                       vcap->format.width, vcap->format.height,
+                       source_fmt.format.width, source_fmt.format.height);
+               return -EINVAL;
+       }
+
+       vpix = dcmipp_bytecap_pix_map_by_pixelformat(vcap->format.pixelformat);
+       if (source_fmt.format.code != vpix->code) {
+               dev_err(vcap->dev, "Wrong mbus_code 0x%x, (0x%x expected)\n",
+                       vpix->code, source_fmt.format.code);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static const struct media_entity_operations dcmipp_bytecap_entity_ops = {
+       .link_validate = dcmipp_bytecap_link_validate,
+};
+
+struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev,
+                                                 const char *entity_name,
+                                                 struct v4l2_device *v4l2_dev,
+                                                 void __iomem *regs)
+{
+       struct dcmipp_bytecap_device *vcap;
+       struct video_device *vdev;
+       struct vb2_queue *q;
+       const unsigned long pad_flag = MEDIA_PAD_FL_SINK;
+       int ret = 0;
+
+       /* Allocate the dcmipp_bytecap_device struct */
+       vcap = kzalloc(sizeof(*vcap), GFP_KERNEL);
+       if (!vcap)
+               return ERR_PTR(-ENOMEM);
+
+       /* Allocate the pads */
+       vcap->ved.pads = dcmipp_pads_init(1, &pad_flag);
+       if (IS_ERR(vcap->ved.pads)) {
+               ret = PTR_ERR(vcap->ved.pads);
+               goto err_free_vcap;
+       }
+
+       /* Initialize the media entity */
+       vcap->vdev.entity.name = entity_name;
+       vcap->vdev.entity.function = MEDIA_ENT_F_IO_V4L;
+       vcap->vdev.entity.ops = &dcmipp_bytecap_entity_ops;
+       ret = media_entity_pads_init(&vcap->vdev.entity, 1, vcap->ved.pads);
+       if (ret)
+               goto err_clean_pads;
+
+       /* Initialize the lock */
+       mutex_init(&vcap->lock);
+
+       /* Initialize the vb2 queue */
+       q = &vcap->queue;
+       q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       q->io_modes = VB2_MMAP | VB2_DMABUF;
+       q->lock = &vcap->lock;
+       q->drv_priv = vcap;
+       q->buf_struct_size = sizeof(struct dcmipp_buf);
+       q->ops = &dcmipp_bytecap_qops;
+       q->mem_ops = &vb2_dma_contig_memops;
+       q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+       q->min_buffers_needed = 1;
+       q->dev = dev;
+
+       /* DCMIPP requires 16 bytes aligned buffers */
+       ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32) & ~0x0f);
+       if (ret) {
+               dev_err(dev, "Failed to set DMA mask\n");
+               goto err_mutex_destroy;
+       }
+
+       ret = vb2_queue_init(q);
+       if (ret) {
+               dev_err(dev, "%s: vb2 queue init failed (err=%d)\n",
+                       entity_name, ret);
+               goto err_clean_m_ent;
+       }
+
+       /* Initialize buffer list and its lock */
+       INIT_LIST_HEAD(&vcap->buffers);
+       spin_lock_init(&vcap->irqlock);
+
+       /* Set default frame format */
+       vcap->format = fmt_default;
+
+       /* Fill the dcmipp_ent_device struct */
+       vcap->ved.ent = &vcap->vdev.entity;
+       vcap->ved.handler = dcmipp_bytecap_irq_callback;
+       vcap->ved.thread_fn = dcmipp_bytecap_irq_thread;
+       vcap->dev = dev;
+       vcap->regs = regs;
+
+       /* Initialize the video_device struct */
+       vdev = &vcap->vdev;
+       vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
+                           V4L2_CAP_IO_MC;
+       vdev->release = dcmipp_bytecap_release;
+       vdev->fops = &dcmipp_bytecap_fops;
+       vdev->ioctl_ops = &dcmipp_bytecap_ioctl_ops;
+       vdev->lock = &vcap->lock;
+       vdev->queue = q;
+       vdev->v4l2_dev = v4l2_dev;
+       strscpy(vdev->name, entity_name, sizeof(vdev->name));
+       video_set_drvdata(vdev, &vcap->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(dev, "%s: video register failed (err=%d)\n",
+                       vcap->vdev.name, ret);
+               goto err_clean_m_ent;
+       }
+
+       return &vcap->ved;
+
+err_clean_m_ent:
+       media_entity_cleanup(&vcap->vdev.entity);
+err_mutex_destroy:
+       mutex_destroy(&vcap->lock);
+err_clean_pads:
+       dcmipp_pads_cleanup(vcap->ved.pads);
+err_free_vcap:
+       kfree(vcap);
+
+       return ERR_PTR(ret);
+}
diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c
new file mode 100644 (file)
index 0000000..5a361ad
--- /dev/null
@@ -0,0 +1,565 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
+ *
+ * Copyright (C) STMicroelectronics SA 2023
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
+ *          Alain Volmat <alain.volmat@foss.st.com>
+ *          for STMicroelectronics.
+ */
+
+#include <linux/vmalloc.h>
+#include <linux/v4l2-mediabus.h>
+#include <media/v4l2-rect.h>
+#include <media/v4l2-subdev.h>
+
+#include "dcmipp-common.h"
+
+#define DCMIPP_P0FCTCR 0x500
+#define DCMIPP_P0FCTCR_FRATE_MASK      GENMASK(1, 0)
+#define DCMIPP_P0SCSTR 0x504
+#define DCMIPP_P0SCSTR_HSTART_SHIFT    0
+#define DCMIPP_P0SCSTR_VSTART_SHIFT    16
+#define DCMIPP_P0SCSZR 0x508
+#define DCMIPP_P0SCSZR_ENABLE          BIT(31)
+#define DCMIPP_P0SCSZR_HSIZE_SHIFT     0
+#define DCMIPP_P0SCSZR_VSIZE_SHIFT     16
+#define DCMIPP_P0PPCR  0x5c0
+#define DCMIPP_P0PPCR_BSM_1_2          0x1
+#define DCMIPP_P0PPCR_BSM_1_4          0x2
+#define DCMIPP_P0PPCR_BSM_2_4          0x3
+#define DCMIPP_P0PPCR_BSM_MASK         GENMASK(8, 7)
+#define DCMIPP_P0PPCR_BSM_SHIFT                0x7
+#define DCMIPP_P0PPCR_LSM              BIT(10)
+#define DCMIPP_P0PPCR_OELS             BIT(11)
+
+#define IS_SINK(pad) (!(pad))
+#define IS_SRC(pad)  ((pad))
+
+struct dcmipp_byteproc_pix_map {
+       unsigned int code;
+       unsigned int bpp;
+};
+
+#define PIXMAP_MBUS_BPP(mbus, byteperpixel)            \
+       {                                               \
+               .code = MEDIA_BUS_FMT_##mbus,           \
+               .bpp = byteperpixel,                    \
+       }
+static const struct dcmipp_byteproc_pix_map dcmipp_byteproc_pix_map_list[] = {
+       PIXMAP_MBUS_BPP(RGB565_2X8_LE, 2),
+       PIXMAP_MBUS_BPP(YUYV8_2X8, 2),
+       PIXMAP_MBUS_BPP(YVYU8_2X8, 2),
+       PIXMAP_MBUS_BPP(UYVY8_2X8, 2),
+       PIXMAP_MBUS_BPP(VYUY8_2X8, 2),
+       PIXMAP_MBUS_BPP(Y8_1X8, 1),
+       PIXMAP_MBUS_BPP(SBGGR8_1X8, 1),
+       PIXMAP_MBUS_BPP(SGBRG8_1X8, 1),
+       PIXMAP_MBUS_BPP(SGRBG8_1X8, 1),
+       PIXMAP_MBUS_BPP(SRGGB8_1X8, 1),
+       PIXMAP_MBUS_BPP(JPEG_1X8, 1),
+};
+
+static const struct dcmipp_byteproc_pix_map *
+dcmipp_byteproc_pix_map_by_code(u32 code)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(dcmipp_byteproc_pix_map_list); i++) {
+               if (dcmipp_byteproc_pix_map_list[i].code == code)
+                       return &dcmipp_byteproc_pix_map_list[i];
+       }
+
+       return NULL;
+}
+
+struct dcmipp_byteproc_device {
+       struct dcmipp_ent_device ved;
+       struct v4l2_subdev sd;
+       struct device *dev;
+       void __iomem *regs;
+       bool streaming;
+};
+
+static const struct v4l2_mbus_framefmt fmt_default = {
+       .width = DCMIPP_FMT_WIDTH_DEFAULT,
+       .height = DCMIPP_FMT_HEIGHT_DEFAULT,
+       .code = MEDIA_BUS_FMT_RGB565_2X8_LE,
+       .field = V4L2_FIELD_NONE,
+       .colorspace = DCMIPP_COLORSPACE_DEFAULT,
+       .ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT,
+       .quantization = DCMIPP_QUANTIZATION_DEFAULT,
+       .xfer_func = DCMIPP_XFER_FUNC_DEFAULT,
+};
+
+static const struct v4l2_rect crop_min = {
+       .width = DCMIPP_FRAME_MIN_WIDTH,
+       .height = DCMIPP_FRAME_MIN_HEIGHT,
+       .top = 0,
+       .left = 0,
+};
+
+static void dcmipp_byteproc_adjust_crop(struct v4l2_rect *r,
+                                       struct v4l2_rect *compose)
+{
+       /* Disallow rectangles smaller than the minimal one. */
+       v4l2_rect_set_min_size(r, &crop_min);
+       v4l2_rect_map_inside(r, compose);
+}
+
+static void dcmipp_byteproc_adjust_compose(struct v4l2_rect *r,
+                                          const struct v4l2_mbus_framefmt *fmt)
+{
+       r->top = 0;
+       r->left = 0;
+
+       /* Compose is not possible for JPEG or Bayer formats */
+       if (fmt->code == MEDIA_BUS_FMT_JPEG_1X8 ||
+           fmt->code == MEDIA_BUS_FMT_SBGGR8_1X8 ||
+           fmt->code == MEDIA_BUS_FMT_SGBRG8_1X8 ||
+           fmt->code == MEDIA_BUS_FMT_SGRBG8_1X8 ||
+           fmt->code == MEDIA_BUS_FMT_SRGGB8_1X8) {
+               r->width = fmt->width;
+               r->height = fmt->height;
+               return;
+       }
+
+       /* Adjust height - we can only perform 1/2 decimation */
+       if (r->height <= (fmt->height / 2))
+               r->height = fmt->height / 2;
+       else
+               r->height = fmt->height;
+
+       /* Adjust width /2 or /4 for 8bits formats and /2 for 16bits formats */
+       if (fmt->code == MEDIA_BUS_FMT_Y8_1X8 && r->width <= (fmt->width / 4))
+               r->width = fmt->width / 4;
+       else if (r->width <= (fmt->width / 2))
+               r->width = fmt->width / 2;
+       else
+               r->width = fmt->width;
+}
+
+static void dcmipp_byteproc_adjust_fmt(struct v4l2_mbus_framefmt *fmt)
+{
+       const struct dcmipp_byteproc_pix_map *vpix;
+
+       /* Only accept code in the pix map table */
+       vpix = dcmipp_byteproc_pix_map_by_code(fmt->code);
+       if (!vpix)
+               fmt->code = fmt_default.code;
+
+       fmt->width = clamp_t(u32, fmt->width, DCMIPP_FRAME_MIN_WIDTH,
+                            DCMIPP_FRAME_MAX_WIDTH) & ~1;
+       fmt->height = clamp_t(u32, fmt->height, DCMIPP_FRAME_MIN_HEIGHT,
+                             DCMIPP_FRAME_MAX_HEIGHT) & ~1;
+
+       if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE)
+               fmt->field = fmt_default.field;
+
+       dcmipp_colorimetry_clamp(fmt);
+}
+
+static int dcmipp_byteproc_init_state(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;
+               struct v4l2_rect *r;
+
+               mf = v4l2_subdev_state_get_format(sd_state, i);
+               *mf = fmt_default;
+
+               if (IS_SINK(i))
+                       r = v4l2_subdev_state_get_compose(sd_state, i);
+               else
+                       r = v4l2_subdev_state_get_crop(sd_state, i);
+
+               r->top = 0;
+               r->left = 0;
+               r->width = DCMIPP_FMT_WIDTH_DEFAULT;
+               r->height = DCMIPP_FMT_HEIGHT_DEFAULT;
+       }
+
+       return 0;
+}
+
+static int
+dcmipp_byteproc_enum_mbus_code(struct v4l2_subdev *sd,
+                              struct v4l2_subdev_state *sd_state,
+                              struct v4l2_subdev_mbus_code_enum *code)
+{
+       const struct dcmipp_byteproc_pix_map *vpix;
+       struct v4l2_mbus_framefmt *sink_fmt;
+
+       if (IS_SINK(code->pad)) {
+               if (code->index >= ARRAY_SIZE(dcmipp_byteproc_pix_map_list))
+                       return -EINVAL;
+               vpix = &dcmipp_byteproc_pix_map_list[code->index];
+               code->code = vpix->code;
+       } else {
+               /* byteproc doesn't support transformation on format */
+               if (code->index > 0)
+                       return -EINVAL;
+
+               sink_fmt = v4l2_subdev_state_get_format(sd_state, 0);
+               code->code = sink_fmt->code;
+       }
+
+       return 0;
+}
+
+static int
+dcmipp_byteproc_enum_frame_size(struct v4l2_subdev *sd,
+                               struct v4l2_subdev_state *sd_state,
+                               struct v4l2_subdev_frame_size_enum *fse)
+{
+       struct v4l2_rect *compose;
+
+       if (fse->index)
+               return -EINVAL;
+
+       fse->min_width = DCMIPP_FRAME_MIN_WIDTH;
+       fse->min_height = DCMIPP_FRAME_MIN_HEIGHT;
+
+       if (IS_SINK(fse->pad)) {
+               fse->max_width = DCMIPP_FRAME_MAX_WIDTH;
+               fse->max_height = DCMIPP_FRAME_MAX_HEIGHT;
+       } else {
+               compose = v4l2_subdev_state_get_compose(sd_state, 0);
+               fse->max_width = compose->width;
+               fse->max_height = compose->height;
+       }
+
+       return 0;
+}
+
+static int dcmipp_byteproc_set_fmt(struct v4l2_subdev *sd,
+                                  struct v4l2_subdev_state *sd_state,
+                                  struct v4l2_subdev_format *fmt)
+{
+       struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
+       struct v4l2_mbus_framefmt *mf;
+       struct v4l2_rect *crop, *compose;
+
+       if (byteproc->streaming)
+               return -EBUSY;
+
+       mf = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+
+       crop = v4l2_subdev_state_get_crop(sd_state, 1);
+       compose = v4l2_subdev_state_get_compose(sd_state, 0);
+
+       if (IS_SRC(fmt->pad)) {
+               fmt->format = *v4l2_subdev_state_get_format(sd_state, 0);
+               fmt->format.width = crop->width;
+               fmt->format.height = crop->height;
+       } else {
+               dcmipp_byteproc_adjust_fmt(&fmt->format);
+               crop->top = 0;
+               crop->left = 0;
+               crop->width = fmt->format.width;
+               crop->height = fmt->format.height;
+               *compose = *crop;
+               /* Set the same format on SOURCE pad as well */
+               *v4l2_subdev_state_get_format(sd_state, 1) = fmt->format;
+       }
+       *mf = fmt->format;
+
+       return 0;
+}
+
+static int dcmipp_byteproc_get_selection(struct v4l2_subdev *sd,
+                                        struct v4l2_subdev_state *sd_state,
+                                        struct v4l2_subdev_selection *s)
+{
+       struct v4l2_mbus_framefmt *sink_fmt;
+       struct v4l2_rect *crop, *compose;
+
+       /*
+        * In the HW, the decimation block is located prior to the crop hence:
+        * Compose is done on the sink pad
+        * Crop is done on the src pad
+        */
+       if (IS_SINK(s->pad) &&
+           (s->target == V4L2_SEL_TGT_CROP ||
+            s->target == V4L2_SEL_TGT_CROP_BOUNDS ||
+            s->target == V4L2_SEL_TGT_CROP_DEFAULT))
+               return -EINVAL;
+
+       if (IS_SRC(s->pad) &&
+           (s->target == V4L2_SEL_TGT_COMPOSE ||
+            s->target == V4L2_SEL_TGT_COMPOSE_BOUNDS ||
+            s->target == V4L2_SEL_TGT_COMPOSE_DEFAULT))
+               return -EINVAL;
+
+       sink_fmt = v4l2_subdev_state_get_format(sd_state, 0);
+       crop = v4l2_subdev_state_get_crop(sd_state, 1);
+       compose = v4l2_subdev_state_get_compose(sd_state, 0);
+
+       switch (s->target) {
+       case V4L2_SEL_TGT_CROP:
+               s->r = *crop;
+               break;
+       case V4L2_SEL_TGT_CROP_BOUNDS:
+       case V4L2_SEL_TGT_CROP_DEFAULT:
+               s->r = *compose;
+               break;
+       case V4L2_SEL_TGT_COMPOSE:
+               s->r = *compose;
+               break;
+       case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+       case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+               s->r.top = 0;
+               s->r.left = 0;
+               s->r.width = sink_fmt->width;
+               s->r.height = sink_fmt->height;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int dcmipp_byteproc_set_selection(struct v4l2_subdev *sd,
+                                        struct v4l2_subdev_state *sd_state,
+                                        struct v4l2_subdev_selection *s)
+{
+       struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
+       struct v4l2_mbus_framefmt *mf;
+       struct v4l2_rect *crop, *compose;
+
+       /*
+        * In the HW, the decimation block is located prior to the crop hence:
+        * Compose is done on the sink pad
+        * Crop is done on the src pad
+        */
+       if ((s->target == V4L2_SEL_TGT_CROP ||
+            s->target == V4L2_SEL_TGT_CROP_BOUNDS ||
+            s->target == V4L2_SEL_TGT_CROP_DEFAULT) && IS_SINK(s->pad))
+               return -EINVAL;
+
+       if ((s->target == V4L2_SEL_TGT_COMPOSE ||
+            s->target == V4L2_SEL_TGT_COMPOSE_BOUNDS ||
+            s->target == V4L2_SEL_TGT_COMPOSE_DEFAULT) && IS_SRC(s->pad))
+               return -EINVAL;
+
+       crop = v4l2_subdev_state_get_crop(sd_state, 1);
+       compose = v4l2_subdev_state_get_compose(sd_state, 0);
+
+       switch (s->target) {
+       case V4L2_SEL_TGT_CROP:
+               dcmipp_byteproc_adjust_crop(&s->r, compose);
+
+               *crop = s->r;
+               mf = v4l2_subdev_state_get_format(sd_state, 1);
+               mf->width = s->r.width;
+               mf->height = s->r.height;
+
+               dev_dbg(byteproc->dev, "s_selection: crop %ux%u@(%u,%u)\n",
+                       crop->width, crop->height, crop->left, crop->top);
+               break;
+       case V4L2_SEL_TGT_COMPOSE:
+               mf = v4l2_subdev_state_get_format(sd_state, 0);
+               dcmipp_byteproc_adjust_compose(&s->r, mf);
+               *compose = s->r;
+               *crop = s->r;
+
+               mf = v4l2_subdev_state_get_format(sd_state, 1);
+               mf->width = s->r.width;
+               mf->height = s->r.height;
+
+               dev_dbg(byteproc->dev, "s_selection: compose %ux%u@(%u,%u)\n",
+                       compose->width, compose->height,
+                       compose->left, compose->top);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static const struct v4l2_subdev_pad_ops dcmipp_byteproc_pad_ops = {
+       .enum_mbus_code         = dcmipp_byteproc_enum_mbus_code,
+       .enum_frame_size        = dcmipp_byteproc_enum_frame_size,
+       .get_fmt                = v4l2_subdev_get_fmt,
+       .set_fmt                = dcmipp_byteproc_set_fmt,
+       .get_selection          = dcmipp_byteproc_get_selection,
+       .set_selection          = dcmipp_byteproc_set_selection,
+};
+
+static int dcmipp_byteproc_configure_scale_crop
+                       (struct dcmipp_byteproc_device *byteproc)
+{
+       const struct dcmipp_byteproc_pix_map *vpix;
+       struct v4l2_subdev_state *state;
+       struct v4l2_mbus_framefmt *sink_fmt;
+       u32 hprediv, vprediv;
+       struct v4l2_rect *compose, *crop;
+       u32 val = 0;
+
+       state = v4l2_subdev_lock_and_get_active_state(&byteproc->sd);
+       sink_fmt = v4l2_subdev_state_get_format(state, 0);
+       compose = v4l2_subdev_state_get_compose(state, 0);
+       crop = v4l2_subdev_state_get_crop(state, 1);
+       v4l2_subdev_unlock_state(state);
+
+       /* find output format bpp */
+       vpix = dcmipp_byteproc_pix_map_by_code(sink_fmt->code);
+       if (!vpix)
+               return -EINVAL;
+
+       /* clear decimation/crop */
+       reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_BSM_MASK);
+       reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_LSM);
+       reg_write(byteproc, DCMIPP_P0SCSTR, 0);
+       reg_write(byteproc, DCMIPP_P0SCSZR, 0);
+
+       /* Ignore decimation/crop with JPEG */
+       if (vpix->code == MEDIA_BUS_FMT_JPEG_1X8)
+               return 0;
+
+       /* decimation */
+       hprediv = sink_fmt->width / compose->width;
+       if (hprediv == 4)
+               val |= DCMIPP_P0PPCR_BSM_1_4 << DCMIPP_P0PPCR_BSM_SHIFT;
+       else if ((vpix->code == MEDIA_BUS_FMT_Y8_1X8) && (hprediv == 2))
+               val |= DCMIPP_P0PPCR_BSM_1_2 << DCMIPP_P0PPCR_BSM_SHIFT;
+       else if (hprediv == 2)
+               val |= DCMIPP_P0PPCR_BSM_2_4 << DCMIPP_P0PPCR_BSM_SHIFT;
+
+       vprediv = sink_fmt->height / compose->height;
+       if (vprediv == 2)
+               val |= DCMIPP_P0PPCR_LSM | DCMIPP_P0PPCR_OELS;
+
+       /* decimate using bytes and lines skipping */
+       if (val) {
+               reg_set(byteproc, DCMIPP_P0PPCR, val);
+
+               dev_dbg(byteproc->dev, "decimate to %dx%d [prediv=%dx%d]\n",
+                       compose->width, compose->height,
+                       hprediv, vprediv);
+       }
+
+       dev_dbg(byteproc->dev, "crop to %dx%d\n", crop->width, crop->height);
+
+       /* expressed in 32-bits words on X axis, lines on Y axis */
+       reg_write(byteproc, DCMIPP_P0SCSTR,
+                 (((crop->left * vpix->bpp) / 4) <<
+                  DCMIPP_P0SCSTR_HSTART_SHIFT) |
+                 (crop->top << DCMIPP_P0SCSTR_VSTART_SHIFT));
+       reg_write(byteproc, DCMIPP_P0SCSZR,
+                 DCMIPP_P0SCSZR_ENABLE |
+                 (((crop->width * vpix->bpp) / 4) <<
+                  DCMIPP_P0SCSZR_HSIZE_SHIFT) |
+                 (crop->height << DCMIPP_P0SCSZR_VSIZE_SHIFT));
+
+       return 0;
+}
+
+static int dcmipp_byteproc_s_stream(struct v4l2_subdev *sd, int enable)
+{
+       struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
+       struct v4l2_subdev *s_subdev;
+       struct media_pad *pad;
+       int ret = 0;
+
+       /* Get source subdev */
+       pad = media_pad_remote_pad_first(&sd->entity.pads[0]);
+       if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+               return -EINVAL;
+       s_subdev = media_entity_to_v4l2_subdev(pad->entity);
+
+       if (enable) {
+               ret = dcmipp_byteproc_configure_scale_crop(byteproc);
+               if (ret)
+                       return ret;
+
+               ret = v4l2_subdev_call(s_subdev, video, s_stream, enable);
+               if (ret < 0) {
+                       dev_err(byteproc->dev,
+                               "failed to start source subdev streaming (%d)\n",
+                               ret);
+                       return ret;
+               }
+       } else {
+               ret = v4l2_subdev_call(s_subdev, video, s_stream, enable);
+               if (ret < 0) {
+                       dev_err(byteproc->dev,
+                               "failed to stop source subdev streaming (%d)\n",
+                               ret);
+                       return ret;
+               }
+       }
+
+       byteproc->streaming = enable;
+
+       return 0;
+}
+
+static const struct v4l2_subdev_video_ops dcmipp_byteproc_video_ops = {
+       .s_stream = dcmipp_byteproc_s_stream,
+};
+
+static const struct v4l2_subdev_ops dcmipp_byteproc_ops = {
+       .pad = &dcmipp_byteproc_pad_ops,
+       .video = &dcmipp_byteproc_video_ops,
+};
+
+static void dcmipp_byteproc_release(struct v4l2_subdev *sd)
+{
+       struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
+
+       kfree(byteproc);
+}
+
+static const struct v4l2_subdev_internal_ops dcmipp_byteproc_int_ops = {
+       .init_state = dcmipp_byteproc_init_state,
+       .release = dcmipp_byteproc_release,
+};
+
+void dcmipp_byteproc_ent_release(struct dcmipp_ent_device *ved)
+{
+       struct dcmipp_byteproc_device *byteproc =
+                       container_of(ved, struct dcmipp_byteproc_device, ved);
+
+       dcmipp_ent_sd_unregister(ved, &byteproc->sd);
+}
+
+struct dcmipp_ent_device *
+dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name,
+                        struct v4l2_device *v4l2_dev, void __iomem *regs)
+{
+       struct dcmipp_byteproc_device *byteproc;
+       const unsigned long pads_flag[] = {
+               MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE,
+       };
+       int ret;
+
+       /* Allocate the byteproc struct */
+       byteproc = kzalloc(sizeof(*byteproc), GFP_KERNEL);
+       if (!byteproc)
+               return ERR_PTR(-ENOMEM);
+
+       byteproc->regs = regs;
+
+       /* Initialize ved and sd */
+       ret = dcmipp_ent_sd_register(&byteproc->ved, &byteproc->sd,
+                                    v4l2_dev, entity_name,
+                                    MEDIA_ENT_F_PROC_VIDEO_SCALER,
+                                    ARRAY_SIZE(pads_flag), pads_flag,
+                                    &dcmipp_byteproc_int_ops,
+                                    &dcmipp_byteproc_ops,
+                                    NULL, NULL);
+       if (ret) {
+               kfree(byteproc);
+               return ERR_PTR(ret);
+       }
+
+       byteproc->dev = dev;
+
+       return &byteproc->ved;
+}
diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.c
new file mode 100644 (file)
index 0000000..562933e
--- /dev/null
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
+ *
+ * Copyright (C) STMicroelectronics SA 2023
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
+ *          Alain Volmat <alain.volmat@foss.st.com>
+ *          for STMicroelectronics.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include "dcmipp-common.h"
+
+/* Helper function to allocate and initialize pads */
+struct media_pad *dcmipp_pads_init(u16 num_pads, const unsigned long *pads_flags)
+{
+       struct media_pad *pads;
+       unsigned int i;
+
+       /* Allocate memory for the pads */
+       pads = kcalloc(num_pads, sizeof(*pads), GFP_KERNEL);
+       if (!pads)
+               return ERR_PTR(-ENOMEM);
+
+       /* Initialize the pads */
+       for (i = 0; i < num_pads; i++) {
+               pads[i].index = i;
+               pads[i].flags = pads_flags[i];
+       }
+
+       return pads;
+}
+
+static const struct media_entity_operations dcmipp_entity_ops = {
+       .link_validate = v4l2_subdev_link_validate,
+};
+
+int dcmipp_ent_sd_register(struct dcmipp_ent_device *ved,
+                          struct v4l2_subdev *sd,
+                          struct v4l2_device *v4l2_dev,
+                          const char *const name,
+                          u32 function,
+                          u16 num_pads,
+                          const unsigned long *pads_flag,
+                          const struct v4l2_subdev_internal_ops *sd_int_ops,
+                          const struct v4l2_subdev_ops *sd_ops,
+                          irq_handler_t handler,
+                          irq_handler_t thread_fn)
+{
+       int ret;
+
+       /* Allocate the pads. Should be released from the sd_int_op release */
+       ved->pads = dcmipp_pads_init(num_pads, pads_flag);
+       if (IS_ERR(ved->pads))
+               return PTR_ERR(ved->pads);
+
+       /* Fill the dcmipp_ent_device struct */
+       ved->ent = &sd->entity;
+
+       /* Initialize the subdev */
+       v4l2_subdev_init(sd, sd_ops);
+       sd->internal_ops = sd_int_ops;
+       sd->entity.function = function;
+       sd->entity.ops = &dcmipp_entity_ops;
+       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, ved->pads);
+       if (ret)
+               goto err_clean_pads;
+
+       ret = v4l2_subdev_init_finalize(sd);
+       if (ret < 0)
+               goto err_clean_m_ent;
+
+       /* 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;
+       }
+
+       ved->handler = handler;
+       ved->thread_fn = thread_fn;
+
+       return 0;
+
+err_clean_m_ent:
+       media_entity_cleanup(&sd->entity);
+err_clean_pads:
+       dcmipp_pads_cleanup(ved->pads);
+       return ret;
+}
+
+void
+dcmipp_ent_sd_unregister(struct dcmipp_ent_device *ved, struct v4l2_subdev *sd)
+{
+       media_entity_cleanup(ved->ent);
+       v4l2_device_unregister_subdev(sd);
+}
diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h
new file mode 100644 (file)
index 0000000..7a7cf43
--- /dev/null
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
+ *
+ * Copyright (C) STMicroelectronics SA 2023
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
+ *          Alain Volmat <alain.volmat@foss.st.com>
+ *          for STMicroelectronics.
+ */
+
+#ifndef _DCMIPP_COMMON_H_
+#define _DCMIPP_COMMON_H_
+
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <media/media-device.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#define DCMIPP_PDEV_NAME "dcmipp"
+
+#define DCMIPP_FRAME_MAX_WIDTH 4096
+#define DCMIPP_FRAME_MAX_HEIGHT 2160
+#define DCMIPP_FRAME_MIN_WIDTH 16
+#define DCMIPP_FRAME_MIN_HEIGHT 16
+
+#define DCMIPP_FMT_WIDTH_DEFAULT  640
+#define DCMIPP_FMT_HEIGHT_DEFAULT 480
+
+#define DCMIPP_COLORSPACE_DEFAULT      V4L2_COLORSPACE_REC709
+#define DCMIPP_YCBCR_ENC_DEFAULT       V4L2_YCBCR_ENC_DEFAULT
+#define DCMIPP_QUANTIZATION_DEFAULT    V4L2_QUANTIZATION_DEFAULT
+#define DCMIPP_XFER_FUNC_DEFAULT       V4L2_XFER_FUNC_DEFAULT
+
+/**
+ * dcmipp_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 dcmipp_colorimetry_clamp(fmt)                                  \
+do {                                                                   \
+       if ((fmt)->colorspace == V4L2_COLORSPACE_DEFAULT ||             \
+           (fmt)->colorspace > V4L2_COLORSPACE_DCI_P3) {               \
+               (fmt)->colorspace = DCMIPP_COLORSPACE_DEFAULT;          \
+               (fmt)->ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT;            \
+               (fmt)->quantization = DCMIPP_QUANTIZATION_DEFAULT;      \
+               (fmt)->xfer_func = DCMIPP_XFER_FUNC_DEFAULT;            \
+       }                                                               \
+       if ((fmt)->ycbcr_enc > V4L2_YCBCR_ENC_SMPTE240M)                \
+               (fmt)->ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT;            \
+       if ((fmt)->quantization > V4L2_QUANTIZATION_LIM_RANGE)          \
+               (fmt)->quantization = DCMIPP_QUANTIZATION_DEFAULT;      \
+       if ((fmt)->xfer_func > V4L2_XFER_FUNC_SMPTE2084)                \
+               (fmt)->xfer_func = DCMIPP_XFER_FUNC_DEFAULT;            \
+} while (0)
+
+/**
+ * struct dcmipp_ent_device - core struct that represents a node in the topology
+ *
+ * @ent:               the pointer to struct media_entity for the node
+ * @pads:              the list of pads of the node
+ * @bus:               struct v4l2_mbus_config_parallel describing input bus
+ * @bus_type:          type of input bus (parallel or BT656)
+ * @handler:           irq handler dedicated to the subdev
+ * @handler_ret:       value returned by the irq handler
+ * @thread_fn:         threaded irq handler
+ *
+ * The DCMIPP provides a single IRQ line and a IRQ status registers for all
+ * subdevs, hence once the main irq handler (registered at probe time) is
+ * called, it will chain calls to the irq handler of each the subdevs of the
+ * pipelines, using the handler/handler_ret/thread_fn variables.
+ *
+ * Each node of the topology must create a dcmipp_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 dcmipp_ent_device struct through
+ * v4l2_set_subdevdata() and video_set_drvdata() respectivaly, allowing the
+ * dcmipp_ent_device struct to be retrieved from the corresponding struct
+ * media_entity
+ */
+struct dcmipp_ent_device {
+       struct media_entity *ent;
+       struct media_pad *pads;
+
+       /* Parallel input device */
+       struct v4l2_mbus_config_parallel bus;
+       enum v4l2_mbus_type bus_type;
+       irq_handler_t handler;
+       irqreturn_t handler_ret;
+       irq_handler_t thread_fn;
+};
+
+/**
+ * dcmipp_pads_init - initialize pads
+ *
+ * @num_pads:  number of pads to initialize
+ * @pads_flags:        flags to use in each pad
+ *
+ * Helper functions to allocate/initialize pads
+ */
+struct media_pad *dcmipp_pads_init(u16 num_pads,
+                                  const unsigned long *pads_flags);
+
+/**
+ * dcmipp_pads_cleanup - free pads
+ *
+ * @pads: pointer to the pads
+ *
+ * Helper function to free the pads initialized with dcmipp_pads_init
+ */
+static inline void dcmipp_pads_cleanup(struct media_pad *pads)
+{
+       kfree(pads);
+}
+
+/**
+ * dcmipp_ent_sd_register - initialize and register a subdev node
+ *
+ * @ved:       the dcmipp_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_flag: flags to use in each pad
+ * @sd_int_ops:        pointer to &struct v4l2_subdev_internal_ops
+ * @sd_ops:    pointer to &struct v4l2_subdev_ops.
+ * @handler:   func pointer of the irq handler
+ * @thread_fn: func pointer of the threaded irq handler
+ *
+ * Helper function initialize and register the struct dcmipp_ent_device and
+ * struct v4l2_subdev which represents a subdev node in the topology
+ */
+int dcmipp_ent_sd_register(struct dcmipp_ent_device *ved,
+                          struct v4l2_subdev *sd,
+                          struct v4l2_device *v4l2_dev,
+                          const char *const name,
+                          u32 function,
+                          u16 num_pads,
+                          const unsigned long *pads_flag,
+                          const struct v4l2_subdev_internal_ops *sd_int_ops,
+                          const struct v4l2_subdev_ops *sd_ops,
+                          irq_handler_t handler,
+                          irq_handler_t thread_fn);
+
+/**
+ * dcmipp_ent_sd_unregister - cleanup and unregister a subdev node
+ *
+ * @ved:       the dcmipp_ent_device struct to be cleaned up
+ * @sd:                the v4l2_subdev struct to be unregistered
+ *
+ * Helper function cleanup and unregister the struct dcmipp_ent_device and
+ * struct v4l2_subdev which represents a subdev node in the topology
+ */
+void dcmipp_ent_sd_unregister(struct dcmipp_ent_device *ved,
+                             struct v4l2_subdev *sd);
+
+#define reg_write(device, reg, val) \
+       (__reg_write((device)->dev, (device)->regs, (reg), (val)))
+#define reg_read(device, reg) \
+       (__reg_read((device)->dev, (device)->regs, (reg)))
+#define reg_set(device, reg, mask) \
+       (__reg_set((device)->dev, (device)->regs, (reg), (mask)))
+#define reg_clear(device, reg, mask) \
+       (__reg_clear((device)->dev, (device)->regs, (reg), (mask)))
+
+static inline u32 __reg_read(struct device *dev, void __iomem *base, u32 reg)
+{
+       u32 val = readl_relaxed(base + reg);
+
+       dev_dbg(dev, "RD 0x%x %#10.8x\n", reg, val);
+       return val;
+}
+
+static inline void __reg_write(struct device *dev, void __iomem *base, u32 reg,
+                              u32 val)
+{
+       dev_dbg(dev, "WR 0x%x %#10.8x\n", reg, val);
+       writel_relaxed(val, base + reg);
+}
+
+static inline void __reg_set(struct device *dev, void __iomem *base, u32 reg,
+                            u32 mask)
+{
+       dev_dbg(dev, "SET 0x%x %#10.8x\n", reg, mask);
+       __reg_write(dev, base, reg, readl_relaxed(base + reg) | mask);
+}
+
+static inline void __reg_clear(struct device *dev, void __iomem *base, u32 reg,
+                              u32 mask)
+{
+       dev_dbg(dev, "CLR 0x%x %#10.8x\n", reg, mask);
+       __reg_write(dev, base, reg, readl_relaxed(base + reg) & ~mask);
+}
+
+/* DCMIPP subdev init / release entry points */
+struct dcmipp_ent_device *dcmipp_par_ent_init(struct device *dev,
+                                             const char *entity_name,
+                                             struct v4l2_device *v4l2_dev,
+                                             void __iomem *regs);
+void dcmipp_par_ent_release(struct dcmipp_ent_device *ved);
+struct dcmipp_ent_device *
+dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name,
+                        struct v4l2_device *v4l2_dev, void __iomem *regs);
+void dcmipp_byteproc_ent_release(struct dcmipp_ent_device *ved);
+struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev,
+                                                 const char *entity_name,
+                                                 struct v4l2_device *v4l2_dev,
+                                                 void __iomem *regs);
+void dcmipp_bytecap_ent_release(struct dcmipp_ent_device *ved);
+
+#endif
diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c
new file mode 100644 (file)
index 0000000..32c6619
--- /dev/null
@@ -0,0 +1,604 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
+ *
+ * Copyright (C) STMicroelectronics SA 2023
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
+ *          Alain Volmat <alain.volmat@foss.st.com>
+ *          for STMicroelectronics.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/reset.h>
+#include <media/media-device.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#include "dcmipp-common.h"
+
+#define DCMIPP_MDEV_MODEL_NAME "DCMIPP MDEV"
+
+#define DCMIPP_ENT_LINK(src, srcpad, sink, sinkpad, link_flags) {      \
+       .src_ent = src,                                         \
+       .src_pad = srcpad,                                      \
+       .sink_ent = sink,                                       \
+       .sink_pad = sinkpad,                                    \
+       .flags = link_flags,                                    \
+}
+
+struct dcmipp_device {
+       /* The platform device */
+       struct platform_device          pdev;
+       struct device                   *dev;
+
+       /* Hardware resources */
+       void __iomem                    *regs;
+       struct clk                      *kclk;
+
+       /* The pipeline configuration */
+       const struct dcmipp_pipeline_config     *pipe_cfg;
+
+       /* The Associated media_device parent */
+       struct media_device             mdev;
+
+       /* Internal v4l2 parent device*/
+       struct v4l2_device              v4l2_dev;
+
+       /* Entities */
+       struct dcmipp_ent_device        **entity;
+
+       struct v4l2_async_notifier      notifier;
+};
+
+static inline struct dcmipp_device *
+notifier_to_dcmipp(struct v4l2_async_notifier *n)
+{
+       return container_of(n, struct dcmipp_device, notifier);
+}
+
+/* Structure which describes individual configuration for each entity */
+struct dcmipp_ent_config {
+       const char *name;
+       struct dcmipp_ent_device *(*init)
+               (struct device *dev, const char *entity_name,
+                struct v4l2_device *v4l2_dev, void __iomem *regs);
+       void (*release)(struct dcmipp_ent_device *ved);
+};
+
+/* Structure which describes links between entities */
+struct dcmipp_ent_link {
+       unsigned int src_ent;
+       u16 src_pad;
+       unsigned int sink_ent;
+       u16 sink_pad;
+       u32 flags;
+};
+
+/* Structure which describes the whole topology */
+struct dcmipp_pipeline_config {
+       const struct dcmipp_ent_config *ents;
+       size_t num_ents;
+       const struct dcmipp_ent_link *links;
+       size_t num_links;
+};
+
+/* --------------------------------------------------------------------------
+ * Topology Configuration
+ */
+
+static const struct dcmipp_ent_config stm32mp13_ent_config[] = {
+       {
+               .name = "dcmipp_parallel",
+               .init = dcmipp_par_ent_init,
+               .release = dcmipp_par_ent_release,
+       },
+       {
+               .name = "dcmipp_dump_postproc",
+               .init = dcmipp_byteproc_ent_init,
+               .release = dcmipp_byteproc_ent_release,
+       },
+       {
+               .name = "dcmipp_dump_capture",
+               .init = dcmipp_bytecap_ent_init,
+               .release = dcmipp_bytecap_ent_release,
+       },
+};
+
+#define ID_PARALLEL 0
+#define ID_DUMP_BYTEPROC 1
+#define ID_DUMP_CAPTURE 2
+
+static const struct dcmipp_ent_link stm32mp13_ent_links[] = {
+       DCMIPP_ENT_LINK(ID_PARALLEL,      1, ID_DUMP_BYTEPROC, 0,
+                       MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
+       DCMIPP_ENT_LINK(ID_DUMP_BYTEPROC, 1, ID_DUMP_CAPTURE,  0,
+                       MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
+};
+
+static const struct dcmipp_pipeline_config stm32mp13_pipe_cfg = {
+       .ents           = stm32mp13_ent_config,
+       .num_ents       = ARRAY_SIZE(stm32mp13_ent_config),
+       .links          = stm32mp13_ent_links,
+       .num_links      = ARRAY_SIZE(stm32mp13_ent_links)
+};
+
+#define LINK_FLAG_TO_STR(f) ((f) == 0 ? "" :\
+                            (f) == MEDIA_LNK_FL_ENABLED ? "ENABLED" :\
+                            (f) == MEDIA_LNK_FL_IMMUTABLE ? "IMMUTABLE" :\
+                            (f) == (MEDIA_LNK_FL_ENABLED |\
+                                    MEDIA_LNK_FL_IMMUTABLE) ?\
+                                       "ENABLED, IMMUTABLE" :\
+                            "UNKNOWN")
+
+static int dcmipp_create_links(struct dcmipp_device *dcmipp)
+{
+       unsigned int i;
+       int ret;
+
+       /* Initialize the links between entities */
+       for (i = 0; i < dcmipp->pipe_cfg->num_links; i++) {
+               const struct dcmipp_ent_link *link =
+                       &dcmipp->pipe_cfg->links[i];
+               struct dcmipp_ent_device *ved_src =
+                       dcmipp->entity[link->src_ent];
+               struct dcmipp_ent_device *ved_sink =
+                       dcmipp->entity[link->sink_ent];
+
+               dev_dbg(dcmipp->dev, "Create link \"%s\":%d -> %d:\"%s\" [%s]\n",
+                       dcmipp->pipe_cfg->ents[link->src_ent].name,
+                       link->src_pad, link->sink_pad,
+                       dcmipp->pipe_cfg->ents[link->sink_ent].name,
+                       LINK_FLAG_TO_STR(link->flags));
+
+               ret = media_create_pad_link(ved_src->ent, link->src_pad,
+                                           ved_sink->ent, link->sink_pad,
+                                           link->flags);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int dcmipp_graph_init(struct dcmipp_device *dcmipp);
+
+static int dcmipp_create_subdevs(struct dcmipp_device *dcmipp)
+{
+       int ret, i;
+
+       /* Call all subdev inits */
+       for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) {
+               const char *name = dcmipp->pipe_cfg->ents[i].name;
+
+               dev_dbg(dcmipp->dev, "add subdev %s\n", name);
+               dcmipp->entity[i] =
+                       dcmipp->pipe_cfg->ents[i].init(dcmipp->dev, name,
+                                                      &dcmipp->v4l2_dev,
+                                                      dcmipp->regs);
+               if (IS_ERR(dcmipp->entity[i])) {
+                       dev_err(dcmipp->dev, "failed to init subdev %s\n",
+                               name);
+                       ret = PTR_ERR(dcmipp->entity[i]);
+                       goto err_init_entity;
+               }
+       }
+
+       /* Initialize links */
+       ret = dcmipp_create_links(dcmipp);
+       if (ret)
+               goto err_init_entity;
+
+       ret = dcmipp_graph_init(dcmipp);
+       if (ret < 0)
+               goto err_init_entity;
+
+       return 0;
+
+err_init_entity:
+       while (i > 0)
+               dcmipp->pipe_cfg->ents[i - 1].release(dcmipp->entity[i - 1]);
+       return ret;
+}
+
+static const struct of_device_id dcmipp_of_match[] = {
+       { .compatible = "st,stm32mp13-dcmipp", .data = &stm32mp13_pipe_cfg },
+       { /* end node */ },
+};
+MODULE_DEVICE_TABLE(of, dcmipp_of_match);
+
+static irqreturn_t dcmipp_irq_thread(int irq, void *arg)
+{
+       struct dcmipp_device *dcmipp = arg;
+       struct dcmipp_ent_device *ved;
+       unsigned int i;
+
+       /* Call irq thread of each entities of pipeline */
+       for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) {
+               ved = dcmipp->entity[i];
+               if (ved->thread_fn && ved->handler_ret == IRQ_WAKE_THREAD)
+                       ved->thread_fn(irq, ved);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t dcmipp_irq_callback(int irq, void *arg)
+{
+       struct dcmipp_device *dcmipp = arg;
+       struct dcmipp_ent_device *ved;
+       irqreturn_t ret = IRQ_HANDLED;
+       unsigned int i;
+
+       /* Call irq handler of each entities of pipeline */
+       for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) {
+               ved = dcmipp->entity[i];
+               if (ved->handler)
+                       ved->handler_ret = ved->handler(irq, ved);
+               else if (ved->thread_fn)
+                       ved->handler_ret = IRQ_WAKE_THREAD;
+               else
+                       ved->handler_ret = IRQ_HANDLED;
+               if (ved->handler_ret != IRQ_HANDLED)
+                       ret = ved->handler_ret;
+       }
+
+       return ret;
+}
+
+static int dcmipp_graph_notify_bound(struct v4l2_async_notifier *notifier,
+                                    struct v4l2_subdev *subdev,
+                                    struct v4l2_async_connection *asd)
+{
+       struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier);
+       unsigned int ret;
+       int src_pad;
+       struct dcmipp_ent_device *sink;
+       struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_PARALLEL };
+       struct fwnode_handle *ep;
+
+       dev_dbg(dcmipp->dev, "Subdev \"%s\" bound\n", subdev->name);
+
+       /*
+        * Link this sub-device to DCMIPP, it could be
+        * a parallel camera sensor or a CSI-2 to parallel bridge
+        */
+       src_pad = media_entity_get_fwnode_pad(&subdev->entity,
+                                             subdev->fwnode,
+                                             MEDIA_PAD_FL_SOURCE);
+
+       /* Get bus characteristics from devicetree */
+       ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dcmipp->dev), 0, 0,
+                                            FWNODE_GRAPH_ENDPOINT_NEXT);
+       if (!ep) {
+               dev_err(dcmipp->dev, "Could not find the endpoint\n");
+               return -ENODEV;
+       }
+
+       /* Check for parallel bus-type first, then bt656 */
+       ret = v4l2_fwnode_endpoint_parse(ep, &vep);
+       if (ret) {
+               vep.bus_type = V4L2_MBUS_BT656;
+               ret = v4l2_fwnode_endpoint_parse(ep, &vep);
+               if (ret) {
+                       dev_err(dcmipp->dev, "Could not parse the endpoint\n");
+                       fwnode_handle_put(ep);
+                       return ret;
+               }
+       }
+
+       fwnode_handle_put(ep);
+
+       if (vep.bus.parallel.bus_width == 0) {
+               dev_err(dcmipp->dev, "Invalid parallel interface bus-width\n");
+               return -ENODEV;
+       }
+
+       /* Only 8 bits bus width supported with BT656 bus */
+       if (vep.bus_type == V4L2_MBUS_BT656 &&
+           vep.bus.parallel.bus_width != 8) {
+               dev_err(dcmipp->dev, "BT656 bus conflicts with %u bits bus width (8 bits required)\n",
+                       vep.bus.parallel.bus_width);
+               return -ENODEV;
+       }
+
+       /* Parallel input device detected, connect it to parallel subdev */
+       sink = dcmipp->entity[ID_PARALLEL];
+       sink->bus.flags = vep.bus.parallel.flags;
+       sink->bus.bus_width = vep.bus.parallel.bus_width;
+       sink->bus.data_shift = vep.bus.parallel.data_shift;
+       sink->bus_type = vep.bus_type;
+       ret = media_create_pad_link(&subdev->entity, src_pad, sink->ent, 0,
+                                   MEDIA_LNK_FL_IMMUTABLE |
+                                   MEDIA_LNK_FL_ENABLED);
+       if (ret) {
+               dev_err(dcmipp->dev, "Failed to create media pad link with subdev \"%s\"\n",
+                       subdev->name);
+               return ret;
+       }
+
+       dev_dbg(dcmipp->dev, "DCMIPP is now linked to \"%s\"\n", subdev->name);
+
+       return 0;
+}
+
+static void dcmipp_graph_notify_unbind(struct v4l2_async_notifier *notifier,
+                                      struct v4l2_subdev *sd,
+                                      struct v4l2_async_connection *asd)
+{
+       struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier);
+
+       dev_dbg(dcmipp->dev, "Removing %s\n", sd->name);
+}
+
+static int dcmipp_graph_notify_complete(struct v4l2_async_notifier *notifier)
+{
+       struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier);
+       int ret;
+
+       /* Register the media device */
+       ret = media_device_register(&dcmipp->mdev);
+       if (ret) {
+               dev_err(dcmipp->mdev.dev,
+                       "media device register failed (err=%d)\n", ret);
+               return ret;
+       }
+
+       /* Expose all subdev's nodes*/
+       ret = v4l2_device_register_subdev_nodes(&dcmipp->v4l2_dev);
+       if (ret) {
+               dev_err(dcmipp->mdev.dev,
+                       "dcmipp subdev nodes registration failed (err=%d)\n",
+                       ret);
+               media_device_unregister(&dcmipp->mdev);
+               return ret;
+       }
+
+       dev_dbg(dcmipp->dev, "Notify complete !\n");
+
+       return 0;
+}
+
+static const struct v4l2_async_notifier_operations dcmipp_graph_notify_ops = {
+       .bound = dcmipp_graph_notify_bound,
+       .unbind = dcmipp_graph_notify_unbind,
+       .complete = dcmipp_graph_notify_complete,
+};
+
+static int dcmipp_graph_init(struct dcmipp_device *dcmipp)
+{
+       struct v4l2_async_connection *asd;
+       struct fwnode_handle *ep;
+       int ret;
+
+       ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dcmipp->dev), 0, 0,
+                                            FWNODE_GRAPH_ENDPOINT_NEXT);
+       if (!ep) {
+               dev_err(dcmipp->dev, "Failed to get next endpoint\n");
+               return -EINVAL;
+       }
+
+       v4l2_async_nf_init(&dcmipp->notifier, &dcmipp->v4l2_dev);
+
+       asd = v4l2_async_nf_add_fwnode_remote(&dcmipp->notifier, ep,
+                                             struct v4l2_async_connection);
+
+       fwnode_handle_put(ep);
+
+       if (IS_ERR(asd)) {
+               dev_err(dcmipp->dev, "Failed to add fwnode remote subdev\n");
+               return PTR_ERR(asd);
+       }
+
+       dcmipp->notifier.ops = &dcmipp_graph_notify_ops;
+
+       ret = v4l2_async_nf_register(&dcmipp->notifier);
+       if (ret < 0) {
+               dev_err(dcmipp->dev, "Failed to register notifier\n");
+               v4l2_async_nf_cleanup(&dcmipp->notifier);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int dcmipp_probe(struct platform_device *pdev)
+{
+       struct dcmipp_device *dcmipp;
+       struct clk *kclk;
+       const struct dcmipp_pipeline_config *pipe_cfg;
+       struct reset_control *rstc;
+       int irq;
+       int ret;
+
+       dcmipp = devm_kzalloc(&pdev->dev, sizeof(*dcmipp), GFP_KERNEL);
+       if (!dcmipp)
+               return -ENOMEM;
+
+       dcmipp->dev = &pdev->dev;
+
+       pipe_cfg = device_get_match_data(dcmipp->dev);
+       if (!pipe_cfg) {
+               dev_err(&pdev->dev, "Can't get device data\n");
+               return -ENODEV;
+       }
+       dcmipp->pipe_cfg = pipe_cfg;
+
+       platform_set_drvdata(pdev, dcmipp);
+
+       /* Get hardware resources from devicetree */
+       rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+       if (IS_ERR(rstc))
+               return dev_err_probe(&pdev->dev, PTR_ERR(rstc),
+                                    "Could not get reset control\n");
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq <= 0) {
+               if (irq != -EPROBE_DEFER)
+                       dev_err(&pdev->dev, "Could not get irq\n");
+               return irq ? irq : -ENXIO;
+       }
+
+       dcmipp->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+       if (IS_ERR(dcmipp->regs)) {
+               dev_err(&pdev->dev, "Could not map registers\n");
+               return PTR_ERR(dcmipp->regs);
+       }
+
+       ret = devm_request_threaded_irq(&pdev->dev, irq, dcmipp_irq_callback,
+                                       dcmipp_irq_thread, IRQF_ONESHOT,
+                                       dev_name(&pdev->dev), dcmipp);
+       if (ret) {
+               dev_err(&pdev->dev, "Unable to request irq %d\n", irq);
+               return ret;
+       }
+
+       /* Reset device */
+       ret = reset_control_assert(rstc);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to assert the reset line\n");
+               return ret;
+       }
+
+       usleep_range(3000, 5000);
+
+       ret = reset_control_deassert(rstc);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to deassert the reset line\n");
+               return ret;
+       }
+
+       kclk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(kclk))
+               return dev_err_probe(&pdev->dev, PTR_ERR(kclk),
+                                    "Unable to get kclk\n");
+       dcmipp->kclk = kclk;
+
+       dcmipp->entity = devm_kcalloc(&pdev->dev, dcmipp->pipe_cfg->num_ents,
+                                     sizeof(*dcmipp->entity), GFP_KERNEL);
+       if (!dcmipp->entity)
+               return -ENOMEM;
+
+       /* Register the v4l2 struct */
+       ret = v4l2_device_register(&pdev->dev, &dcmipp->v4l2_dev);
+       if (ret) {
+               dev_err(&pdev->dev,
+                       "v4l2 device register failed (err=%d)\n", ret);
+               return ret;
+       }
+
+       /* Link the media device within the v4l2_device */
+       dcmipp->v4l2_dev.mdev = &dcmipp->mdev;
+
+       /* Initialize media device */
+       strscpy(dcmipp->mdev.model, DCMIPP_MDEV_MODEL_NAME,
+               sizeof(dcmipp->mdev.model));
+       dcmipp->mdev.dev = &pdev->dev;
+       media_device_init(&dcmipp->mdev);
+
+       /* Initialize subdevs */
+       ret = dcmipp_create_subdevs(dcmipp);
+       if (ret) {
+               media_device_cleanup(&dcmipp->mdev);
+               v4l2_device_unregister(&dcmipp->v4l2_dev);
+               return ret;
+       }
+
+       pm_runtime_enable(dcmipp->dev);
+
+       dev_info(&pdev->dev, "Probe done");
+
+       return 0;
+}
+
+static int dcmipp_remove(struct platform_device *pdev)
+{
+       struct dcmipp_device *dcmipp = platform_get_drvdata(pdev);
+       unsigned int i;
+
+       pm_runtime_disable(&pdev->dev);
+
+       v4l2_async_nf_unregister(&dcmipp->notifier);
+       v4l2_async_nf_cleanup(&dcmipp->notifier);
+
+       for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++)
+               dcmipp->pipe_cfg->ents[i].release(dcmipp->entity[i]);
+
+       media_device_unregister(&dcmipp->mdev);
+       media_device_cleanup(&dcmipp->mdev);
+
+       v4l2_device_unregister(&dcmipp->v4l2_dev);
+
+       return 0;
+}
+
+static int dcmipp_runtime_suspend(struct device *dev)
+{
+       struct dcmipp_device *dcmipp = dev_get_drvdata(dev);
+
+       clk_disable_unprepare(dcmipp->kclk);
+
+       return 0;
+}
+
+static int dcmipp_runtime_resume(struct device *dev)
+{
+       struct dcmipp_device *dcmipp = dev_get_drvdata(dev);
+       int ret;
+
+       ret = clk_prepare_enable(dcmipp->kclk);
+       if (ret)
+               dev_err(dev, "%s: Failed to prepare_enable kclk\n", __func__);
+
+       return ret;
+}
+
+static int dcmipp_suspend(struct device *dev)
+{
+       /* disable clock */
+       pm_runtime_force_suspend(dev);
+
+       /* change pinctrl state */
+       pinctrl_pm_select_sleep_state(dev);
+
+       return 0;
+}
+
+static int dcmipp_resume(struct device *dev)
+{
+       /* restore pinctl default state */
+       pinctrl_pm_select_default_state(dev);
+
+       /* clock enable */
+       pm_runtime_force_resume(dev);
+
+       return 0;
+}
+
+static const struct dev_pm_ops dcmipp_pm_ops = {
+       SYSTEM_SLEEP_PM_OPS(dcmipp_suspend, dcmipp_resume)
+       RUNTIME_PM_OPS(dcmipp_runtime_suspend, dcmipp_runtime_resume, NULL)
+};
+
+static struct platform_driver dcmipp_pdrv = {
+       .probe          = dcmipp_probe,
+       .remove         = dcmipp_remove,
+       .driver         = {
+               .name   = DCMIPP_PDEV_NAME,
+               .of_match_table = dcmipp_of_match,
+               .pm = pm_ptr(&dcmipp_pm_ops),
+       },
+};
+
+module_platform_driver(dcmipp_pdrv);
+
+MODULE_AUTHOR("Hugues Fruchet <hugues.fruchet@foss.st.com>");
+MODULE_AUTHOR("Alain Volmat <alain.volmat@foss.st.com>");
+MODULE_DESCRIPTION("STMicroelectronics STM32 Digital Camera Memory Interface with Pixel Processor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-parallel.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-parallel.c
new file mode 100644 (file)
index 0000000..62c5c33
--- /dev/null
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
+ *
+ * Copyright (C) STMicroelectronics SA 2023
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
+ *          Alain Volmat <alain.volmat@foss.st.com>
+ *          for STMicroelectronics.
+ */
+
+#include <linux/v4l2-mediabus.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-subdev.h>
+
+#include "dcmipp-common.h"
+
+#define DCMIPP_PRCR    0x104
+#define DCMIPP_PRCR_FORMAT_SHIFT       16
+#define DCMIPP_PRCR_FORMAT_YUV422      0x1e
+#define DCMIPP_PRCR_FORMAT_RGB565      0x22
+#define DCMIPP_PRCR_FORMAT_RAW8                0x2a
+#define DCMIPP_PRCR_FORMAT_G8          0x4a
+#define DCMIPP_PRCR_FORMAT_BYTE_STREAM 0x5a
+#define DCMIPP_PRCR_ESS                        BIT(4)
+#define DCMIPP_PRCR_PCKPOL             BIT(5)
+#define DCMIPP_PRCR_HSPOL              BIT(6)
+#define DCMIPP_PRCR_VSPOL              BIT(7)
+#define DCMIPP_PRCR_ENABLE             BIT(14)
+#define DCMIPP_PRCR_SWAPCYCLES         BIT(25)
+
+#define DCMIPP_PRESCR  0x108
+#define DCMIPP_PRESUR  0x10c
+
+#define IS_SINK(pad) (!(pad))
+#define IS_SRC(pad)  ((pad))
+
+struct dcmipp_par_pix_map {
+       unsigned int code_sink;
+       unsigned int code_src;
+       u8 prcr_format;
+       u8 prcr_swapcycles;
+};
+
+#define PIXMAP_SINK_SRC_PRCR_SWAP(sink, src, prcr, swap)       \
+       {                                                       \
+               .code_sink = MEDIA_BUS_FMT_##sink,              \
+               .code_src = MEDIA_BUS_FMT_##src,                \
+               .prcr_format = DCMIPP_PRCR_FORMAT_##prcr,       \
+               .prcr_swapcycles = swap,                        \
+       }
+static const struct dcmipp_par_pix_map dcmipp_par_pix_map_list[] = {
+       /* RGB565 */
+       PIXMAP_SINK_SRC_PRCR_SWAP(RGB565_2X8_LE, RGB565_2X8_LE, RGB565, 1),
+       PIXMAP_SINK_SRC_PRCR_SWAP(RGB565_2X8_BE, RGB565_2X8_LE, RGB565, 0),
+       /* YUV422 */
+       PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, YUYV8_2X8, YUV422, 1),
+       PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, UYVY8_2X8, YUV422, 0),
+       PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, UYVY8_2X8, YUV422, 1),
+       PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, YUYV8_2X8, YUV422, 0),
+       PIXMAP_SINK_SRC_PRCR_SWAP(YVYU8_2X8, YVYU8_2X8, YUV422, 1),
+       PIXMAP_SINK_SRC_PRCR_SWAP(VYUY8_2X8, VYUY8_2X8, YUV422, 1),
+       /* GREY */
+       PIXMAP_SINK_SRC_PRCR_SWAP(Y8_1X8, Y8_1X8, G8, 0),
+       /* Raw Bayer */
+       PIXMAP_SINK_SRC_PRCR_SWAP(SBGGR8_1X8, SBGGR8_1X8, RAW8, 0),
+       PIXMAP_SINK_SRC_PRCR_SWAP(SGBRG8_1X8, SGBRG8_1X8, RAW8, 0),
+       PIXMAP_SINK_SRC_PRCR_SWAP(SGRBG8_1X8, SGRBG8_1X8, RAW8, 0),
+       PIXMAP_SINK_SRC_PRCR_SWAP(SRGGB8_1X8, SRGGB8_1X8, RAW8, 0),
+       /* JPEG */
+       PIXMAP_SINK_SRC_PRCR_SWAP(JPEG_1X8, JPEG_1X8, BYTE_STREAM, 0),
+};
+
+/*
+ * Search through the pix_map table, skipping two consecutive entry with the
+ * same code
+ */
+static inline const struct dcmipp_par_pix_map *dcmipp_par_pix_map_by_index
+                                               (unsigned int index,
+                                                unsigned int pad)
+{
+       unsigned int i = 0;
+       u32 prev_code = 0, cur_code;
+
+       while (i < ARRAY_SIZE(dcmipp_par_pix_map_list)) {
+               if (IS_SRC(pad))
+                       cur_code = dcmipp_par_pix_map_list[i].code_src;
+               else
+                       cur_code = dcmipp_par_pix_map_list[i].code_sink;
+
+               if (cur_code == prev_code) {
+                       i++;
+                       continue;
+               }
+               prev_code = cur_code;
+
+               if (index == 0)
+                       break;
+               i++;
+               index--;
+       }
+
+       if (i >= ARRAY_SIZE(dcmipp_par_pix_map_list))
+               return NULL;
+
+       return &dcmipp_par_pix_map_list[i];
+}
+
+static inline const struct dcmipp_par_pix_map *dcmipp_par_pix_map_by_code
+                                       (u32 code_sink, u32 code_src)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(dcmipp_par_pix_map_list); i++) {
+               if ((dcmipp_par_pix_map_list[i].code_sink == code_sink &&
+                    dcmipp_par_pix_map_list[i].code_src == code_src) ||
+                   (dcmipp_par_pix_map_list[i].code_sink == code_src &&
+                    dcmipp_par_pix_map_list[i].code_src == code_sink) ||
+                   (dcmipp_par_pix_map_list[i].code_sink == code_sink &&
+                    code_src == 0) ||
+                   (code_sink == 0 &&
+                    dcmipp_par_pix_map_list[i].code_src == code_src))
+                       return &dcmipp_par_pix_map_list[i];
+       }
+       return NULL;
+}
+
+struct dcmipp_par_device {
+       struct dcmipp_ent_device ved;
+       struct v4l2_subdev sd;
+       struct device *dev;
+       void __iomem *regs;
+       bool streaming;
+};
+
+static const struct v4l2_mbus_framefmt fmt_default = {
+       .width = DCMIPP_FMT_WIDTH_DEFAULT,
+       .height = DCMIPP_FMT_HEIGHT_DEFAULT,
+       .code = MEDIA_BUS_FMT_RGB565_2X8_LE,
+       .field = V4L2_FIELD_NONE,
+       .colorspace = DCMIPP_COLORSPACE_DEFAULT,
+       .ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT,
+       .quantization = DCMIPP_QUANTIZATION_DEFAULT,
+       .xfer_func = DCMIPP_XFER_FUNC_DEFAULT,
+};
+
+static int dcmipp_par_init_state(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_state_get_format(sd_state, i);
+               *mf = fmt_default;
+       }
+
+       return 0;
+}
+
+static int dcmipp_par_enum_mbus_code(struct v4l2_subdev *sd,
+                                    struct v4l2_subdev_state *sd_state,
+                                    struct v4l2_subdev_mbus_code_enum *code)
+{
+       const struct dcmipp_par_pix_map *vpix =
+               dcmipp_par_pix_map_by_index(code->index, code->pad);
+
+       if (!vpix)
+               return -EINVAL;
+
+       code->code = IS_SRC(code->pad) ? vpix->code_src : vpix->code_sink;
+
+       return 0;
+}
+
+static int dcmipp_par_enum_frame_size(struct v4l2_subdev *sd,
+                                     struct v4l2_subdev_state *sd_state,
+                                     struct v4l2_subdev_frame_size_enum *fse)
+{
+       const struct dcmipp_par_pix_map *vpix;
+
+       if (fse->index)
+               return -EINVAL;
+
+       /* Only accept code in the pix map table */
+       vpix = dcmipp_par_pix_map_by_code(IS_SINK(fse->pad) ? fse->code : 0,
+                                         IS_SRC(fse->pad) ? fse->code : 0);
+       if (!vpix)
+               return -EINVAL;
+
+       fse->min_width = DCMIPP_FRAME_MIN_WIDTH;
+       fse->max_width = DCMIPP_FRAME_MAX_WIDTH;
+       fse->min_height = DCMIPP_FRAME_MIN_HEIGHT;
+       fse->max_height = DCMIPP_FRAME_MAX_HEIGHT;
+
+       return 0;
+}
+
+static void dcmipp_par_adjust_fmt(struct dcmipp_par_device *par,
+                                 struct v4l2_mbus_framefmt *fmt, __u32 pad)
+{
+       const struct dcmipp_par_pix_map *vpix;
+
+       /* Only accept code in the pix map table */
+       vpix = dcmipp_par_pix_map_by_code(IS_SINK(pad) ? fmt->code : 0,
+                                         IS_SRC(pad) ? fmt->code : 0);
+       if (!vpix)
+               fmt->code = fmt_default.code;
+
+       /* Exclude JPEG if BT656 bus is selected */
+       if (vpix && vpix->code_sink == MEDIA_BUS_FMT_JPEG_1X8 &&
+           par->ved.bus_type == V4L2_MBUS_BT656)
+               fmt->code = fmt_default.code;
+
+       fmt->width = clamp_t(u32, fmt->width, DCMIPP_FRAME_MIN_WIDTH,
+                            DCMIPP_FRAME_MAX_WIDTH) & ~1;
+       fmt->height = clamp_t(u32, fmt->height, DCMIPP_FRAME_MIN_HEIGHT,
+                             DCMIPP_FRAME_MAX_HEIGHT) & ~1;
+
+       if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE)
+               fmt->field = fmt_default.field;
+
+       dcmipp_colorimetry_clamp(fmt);
+}
+
+static int dcmipp_par_set_fmt(struct v4l2_subdev *sd,
+                             struct v4l2_subdev_state *sd_state,
+                             struct v4l2_subdev_format *fmt)
+{
+       struct dcmipp_par_device *par = v4l2_get_subdevdata(sd);
+       struct v4l2_mbus_framefmt *mf;
+
+       if (par->streaming)
+               return -EBUSY;
+
+       mf = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+
+       /* Set the new format */
+       dcmipp_par_adjust_fmt(par, &fmt->format, fmt->pad);
+
+       dev_dbg(par->dev, "%s: format update: old:%dx%d (0x%x, %d, %d, %d, %d) new:%dx%d (0x%x, %d, %d, %d, %d)\n",
+               par->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;
+
+       /* When setting the sink format, report that format on the src pad */
+       if (IS_SINK(fmt->pad)) {
+               mf = v4l2_subdev_state_get_format(sd_state, 1);
+               *mf = fmt->format;
+               dcmipp_par_adjust_fmt(par, mf, 1);
+       }
+
+       return 0;
+}
+
+static const struct v4l2_subdev_pad_ops dcmipp_par_pad_ops = {
+       .enum_mbus_code         = dcmipp_par_enum_mbus_code,
+       .enum_frame_size        = dcmipp_par_enum_frame_size,
+       .get_fmt                = v4l2_subdev_get_fmt,
+       .set_fmt                = dcmipp_par_set_fmt,
+};
+
+static int dcmipp_par_configure(struct dcmipp_par_device *par)
+{
+       u32 val = 0;
+       const struct dcmipp_par_pix_map *vpix;
+       struct v4l2_subdev_state *state;
+       struct v4l2_mbus_framefmt *sink_fmt;
+       struct v4l2_mbus_framefmt *src_fmt;
+
+       /* Set vertical synchronization polarity */
+       if (par->ved.bus.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+               val |= DCMIPP_PRCR_VSPOL;
+
+       /* Set horizontal synchronization polarity */
+       if (par->ved.bus.flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+               val |= DCMIPP_PRCR_HSPOL;
+
+       /* Set pixel clock polarity */
+       if (par->ved.bus.flags & V4L2_MBUS_PCLK_SAMPLE_RISING)
+               val |= DCMIPP_PRCR_PCKPOL;
+
+       /*
+        * BT656 embedded synchronisation bus mode.
+        *
+        * Default SAV/EAV mode is supported here with default codes
+        * SAV=0xff000080 & EAV=0xff00009d.
+        * With DCMIPP this means LSC=SAV=0x80 & LEC=EAV=0x9d.
+        */
+       if (par->ved.bus_type == V4L2_MBUS_BT656) {
+               val |= DCMIPP_PRCR_ESS;
+
+               /* Unmask all codes */
+               reg_write(par, DCMIPP_PRESUR, 0xffffffff);/* FEC:LEC:LSC:FSC */
+
+               /* Trig on LSC=0x80 & LEC=0x9d codes, ignore FSC and FEC */
+               reg_write(par, DCMIPP_PRESCR, 0xff9d80ff);/* FEC:LEC:LSC:FSC */
+       }
+
+       /* Set format */
+       state = v4l2_subdev_lock_and_get_active_state(&par->sd);
+       sink_fmt = v4l2_subdev_state_get_format(state, 0);
+       src_fmt = v4l2_subdev_state_get_format(state, 1);
+       v4l2_subdev_unlock_state(state);
+
+       vpix = dcmipp_par_pix_map_by_code(sink_fmt->code, src_fmt->code);
+       if (!vpix) {
+               dev_err(par->dev, "Invalid sink/src format configuration\n");
+               return -EINVAL;
+       }
+
+       val |= vpix->prcr_format << DCMIPP_PRCR_FORMAT_SHIFT;
+
+       /* swap cycles */
+       if (vpix->prcr_swapcycles)
+               val |= DCMIPP_PRCR_SWAPCYCLES;
+
+       reg_write(par, DCMIPP_PRCR, val);
+
+       return 0;
+}
+
+static int dcmipp_par_s_stream(struct v4l2_subdev *sd, int enable)
+{
+       struct dcmipp_par_device *par =
+                               container_of(sd, struct dcmipp_par_device, sd);
+       struct v4l2_subdev *s_subdev;
+       struct media_pad *pad;
+       int ret = 0;
+
+       /* Get source subdev */
+       pad = media_pad_remote_pad_first(&sd->entity.pads[0]);
+       if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+               return -EINVAL;
+       s_subdev = media_entity_to_v4l2_subdev(pad->entity);
+
+       if (enable) {
+               ret = dcmipp_par_configure(par);
+               if (ret)
+                       return ret;
+
+               /* Enable parallel interface */
+               reg_set(par, DCMIPP_PRCR, DCMIPP_PRCR_ENABLE);
+
+               ret = v4l2_subdev_call(s_subdev, video, s_stream, enable);
+               if (ret < 0) {
+                       dev_err(par->dev,
+                               "failed to start source subdev streaming (%d)\n",
+                               ret);
+                       return ret;
+               }
+       } else {
+               ret = v4l2_subdev_call(s_subdev, video, s_stream, enable);
+               if (ret < 0) {
+                       dev_err(par->dev,
+                               "failed to stop source subdev streaming (%d)\n",
+                               ret);
+                       return ret;
+               }
+
+               /* Disable parallel interface */
+               reg_clear(par, DCMIPP_PRCR, DCMIPP_PRCR_ENABLE);
+       }
+
+       par->streaming = enable;
+
+       return ret;
+}
+
+static const struct v4l2_subdev_video_ops dcmipp_par_video_ops = {
+       .s_stream = dcmipp_par_s_stream,
+};
+
+static const struct v4l2_subdev_ops dcmipp_par_ops = {
+       .pad = &dcmipp_par_pad_ops,
+       .video = &dcmipp_par_video_ops,
+};
+
+static void dcmipp_par_release(struct v4l2_subdev *sd)
+{
+       struct dcmipp_par_device *par =
+                               container_of(sd, struct dcmipp_par_device, sd);
+
+       kfree(par);
+}
+
+static const struct v4l2_subdev_internal_ops dcmipp_par_int_ops = {
+       .init_state = dcmipp_par_init_state,
+       .release = dcmipp_par_release,
+};
+
+void dcmipp_par_ent_release(struct dcmipp_ent_device *ved)
+{
+       struct dcmipp_par_device *par =
+                       container_of(ved, struct dcmipp_par_device, ved);
+
+       dcmipp_ent_sd_unregister(ved, &par->sd);
+}
+
+struct dcmipp_ent_device *dcmipp_par_ent_init(struct device *dev,
+                                             const char *entity_name,
+                                             struct v4l2_device *v4l2_dev,
+                                             void __iomem *regs)
+{
+       struct dcmipp_par_device *par;
+       const unsigned long pads_flag[] = {
+               MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE,
+       };
+       int ret;
+
+       /* Allocate the par struct */
+       par = kzalloc(sizeof(*par), GFP_KERNEL);
+       if (!par)
+               return ERR_PTR(-ENOMEM);
+
+       par->regs = regs;
+
+       /* Initialize ved and sd */
+       ret = dcmipp_ent_sd_register(&par->ved, &par->sd, v4l2_dev,
+                                    entity_name, MEDIA_ENT_F_VID_IF_BRIDGE,
+                                    ARRAY_SIZE(pads_flag), pads_flag,
+                                    &dcmipp_par_int_ops, &dcmipp_par_ops,
+                                    NULL, NULL);
+       if (ret) {
+               kfree(par);
+               return ERR_PTR(ret);
+       }
+
+       par->dev = dev;
+
+       return &par->ved;
+}