summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDong Aisheng <aisheng.dong@nxp.com>2021-11-30 14:58:50 +0800
committerDong Aisheng <aisheng.dong@nxp.com>2021-11-30 14:58:50 +0800
commit075645ca57a3eee7c3941ad354f8576916ae381a (patch)
treee4b3fd37274163f968ed3f19f14b64eb07fece57
parenta7452bee56d246e8b719a25d8132ad31a00fce95 (diff)
parenta88cdadf0ed49e392beaee6a90674a3fcbc10ac0 (diff)
Merge remote-tracking branch 'origin/capture/isi' into capture/next
* origin/capture/isi: (92 commits) LF-4830 media: isi_m2m: fix gstreamer pipeline can not work on second open LF-4561: media: isi: Fix nullptr when isi-cap is built as module LF-4315 media: isi: fix the null point panic LF-4238 media: isi: fix the null point kernel panic LF-4405: media: imx: isi-core: fix KASAN report global-out-of-bounds issue ...
-rw-r--r--Documentation/devicetree/bindings/media/imx8-isi.txt33
-rw-r--r--drivers/staging/media/Makefile2
-rw-r--r--drivers/staging/media/imx/Kconfig28
-rw-r--r--drivers/staging/media/imx/Makefile9
-rw-r--r--drivers/staging/media/imx/imx8-isi-cap.c1832
-rw-r--r--drivers/staging/media/imx/imx8-isi-core.c885
-rw-r--r--drivers/staging/media/imx/imx8-isi-core.h457
-rw-r--r--drivers/staging/media/imx/imx8-isi-fmt.c85
-rw-r--r--drivers/staging/media/imx/imx8-isi-fmt.h12
-rw-r--r--drivers/staging/media/imx/imx8-isi-hw.c829
-rw-r--r--drivers/staging/media/imx/imx8-isi-hw.h485
-rw-r--r--drivers/staging/media/imx/imx8-isi-m2m.c1375
12 files changed, 6031 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/media/imx8-isi.txt b/Documentation/devicetree/bindings/media/imx8-isi.txt
new file mode 100644
index 000000000000..7739121f0ca6
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/imx8-isi.txt
@@ -0,0 +1,33 @@
+NXP Image Sensor Interface
+========================
+
+The Image Sensor Interface (ISI) is used to obtain the image data for
+processing in its pipeline channels. Each pipeline processes the image
+line from a configured source and performs one or more functions that
+are configured by software, such as down scaling, color space conversion,
+de-interlacing, alpha insertion, cropping and rotation (horizontal and
+vertical). The processed image is stored into programmable memory locations.
+
+Required properties:
+- compatible: should be "fsl,imx8-isi", where SoC can be one of imx8qxp, imx8qm
+- reg: the register base and size for the device registers
+- interrupts: the ISI interrupt, high level active
+- clock-names: should be "per"
+- clocks: the ISI AXI clock
+- interface: specify ISI input, virtual channel and output,
+ <Input MIPI_VCx Output>
+ Input : 0-DC0, 1-DC1, 2-MIPI CSI0, 3-MIPI CSI1, 4-HDMI, 5-MEM
+ VCx : 0-VC0, 1-VC1, 2-VC2, 3-VC3, MIPI CSI only
+ Output: 0-DC0, 1-DC1, 2-MEM
+
+Example:
+ isi_0: isi@58100000 {
+ compatible = "fsl,imx8-isi";
+ reg = <0x58100000 0x10000>;
+ interrupts = <GIC_SPI 297 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&gic>;
+ clocks = <&img_lpcg IMX_IMG_LPCG_PDMA0_CLK>;
+ clock-names = "per";
+ power-domains = <&pd IMX_SC_R_ISI_CH0>;
+ interface = <2 0 2>;
+ };
diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile
index 5b5afc5b03a0..7acc2a0b84dd 100644
--- a/drivers/staging/media/Makefile
+++ b/drivers/staging/media/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_INTEL_ATOMISP) += atomisp/
-obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx/
+obj-$(CONFIG_VIDEO_IMX_CAPTURE) += imx/
obj-$(CONFIG_VIDEO_MESON_VDEC) += meson/vdec/
obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/
obj-$(CONFIG_VIDEO_ROCKCHIP_VDEC) += rkvdec/
diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig
index 271ca4ddb15a..57835016134a 100644
--- a/drivers/staging/media/imx/Kconfig
+++ b/drivers/staging/media/imx/Kconfig
@@ -63,6 +63,34 @@ config IMX8_MIPI_CSI2_SAM
help
Enable support for video4linux MIPI CSI2 Samsung driver for
i.MX8MN platform.
+
+config IMX8_ISI_HW
+ tristate "IMX8 Image Sensor Interface hardware driver"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ default y
+ help
+ ISI hardware driver is used to export functions to config
+ ISI registers and it is shared by isi capture and mem2mem
+ driver
+
+config IMX8_ISI_CORE
+ tristate "IMX8 Image Sensor Interface Core Driver"
+ depends on IMX8_ISI_CAPTURE && IMX8_ISI_M2M
+ default y
+
+config IMX8_ISI_CAPTURE
+ tristate "IMX8 Image Sensor Interface Capture Device Driver"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ depends on IMX8_ISI_HW
+ select VIDEOBUF2_DMA_CONTIG
+ default y
+
+config IMX8_ISI_M2M
+ tristate "IMX8 Image Sensor Interface Memory to Memory Device Driver"
+ select V4L2_MEM2MEM_DEV
+ depends on IMX8_ISI_HW
+ default y
+
endmenu
source "drivers/staging/media/imx/hdmirx/Kconfig"
diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile
index a924af590c3f..74db6bbe0af6 100644
--- a/drivers/staging/media/imx/Makefile
+++ b/drivers/staging/media/imx/Makefile
@@ -8,6 +8,10 @@ imx6-media-objs := imx-media-dev.o imx-media-internal-sd.o \
imx6-media-csi-objs := imx-media-csi.o imx-media-fim.o
+imx8-capture-objs := imx8-isi-core.o
+
+imx8-isi-capture-objs := imx8-isi-fmt.o imx8-isi-cap.o
+imx8-isi-mem2mem-objs := imx8-isi-fmt.o imx8-isi-m2m.o
obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-media-common.o
obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-media.o
@@ -19,5 +23,10 @@ obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o
obj-$(CONFIG_VIDEO_IMX7_CSI) += imx8mq-mipi-csi2.o
obj-$(CONFIG_IMX8_MIPI_CSI2) += imx8-mipi-csi2.o
obj-$(CONFIG_IMX8_MIPI_CSI2_SAM) += imx8-mipi-csi2-sam.o
+obj-$(CONFIG_IMX8_CAPTURE_DRIVER) += imx8-capture.o
+obj-$(CONFIG_IMX8_ISI_CORE) += imx8-capture.o
+obj-$(CONFIG_IMX8_ISI_CAPTURE) += imx8-isi-capture.o
+obj-$(CONFIG_IMX8_ISI_M2M) += imx8-isi-mem2mem.o
+obj-$(CONFIG_IMX8_ISI_HW) += imx8-isi-hw.o
obj-y += hdmirx/
diff --git a/drivers/staging/media/imx/imx8-isi-cap.c b/drivers/staging/media/imx/imx8-isi-cap.c
new file mode 100644
index 000000000000..4a919cc8d52f
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-cap.c
@@ -0,0 +1,1832 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * V4L2 Capture ISI subdev driver for i.MX8QXP/QM platform
+ *
+ * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
+ * used to process image from camera sensor to memory or DC
+ *
+ * Copyright 2019-2021 NXP
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/pm_runtime.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/of_graph.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "imx8-isi-hw.h"
+#include "imx8-common.h"
+#include "imx8-isi-fmt.h"
+
+#define sd_to_cap_dev(ptr) container_of(ptr, struct mxc_isi_cap_dev, sd)
+static int mxc_isi_cap_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type);
+
+/*
+ * Pixel link input format
+ */
+struct mxc_isi_fmt mxc_isi_src_formats[] = {
+ {
+ .name = "RGB32",
+ .fourcc = V4L2_PIX_FMT_RGB32,
+ .depth = { 32 },
+ .memplanes = 1,
+ .colplanes = 1,
+ }, {
+ .name = "YUV32 (X-Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUV32,
+ .depth = { 32 },
+ .memplanes = 1,
+ .colplanes = 1,
+ }
+};
+
+struct mxc_isi_fmt *mxc_isi_get_format(unsigned int index)
+{
+ return &mxc_isi_out_formats[index];
+}
+
+/*
+ * lookup mxc_isi color format by fourcc or media bus format
+ */
+struct mxc_isi_fmt *mxc_isi_find_format(const u32 *pixelformat,
+ const u32 *mbus_code, int index)
+{
+ struct mxc_isi_fmt *fmt, *def_fmt = NULL;
+ unsigned int i;
+ int id = 0;
+
+ if (index >= (int)mxc_isi_out_formats_size)
+ return NULL;
+
+ for (i = 0; i < mxc_isi_out_formats_size; i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (pixelformat && fmt->fourcc == *pixelformat)
+ return fmt;
+ if (mbus_code && fmt->mbus_code == *mbus_code)
+ return fmt;
+ if (index == id)
+ def_fmt = fmt;
+ id++;
+ }
+ return def_fmt;
+}
+
+struct mxc_isi_fmt *mxc_isi_get_src_fmt(struct v4l2_subdev_format *sd_fmt)
+{
+ u32 index;
+
+ /* two fmt RGB32 and YUV444 from pixellink */
+ if (sd_fmt->format.code == MEDIA_BUS_FMT_YUYV8_1X16 ||
+ sd_fmt->format.code == MEDIA_BUS_FMT_YVYU8_2X8 ||
+ sd_fmt->format.code == MEDIA_BUS_FMT_AYUV8_1X32 ||
+ sd_fmt->format.code == MEDIA_BUS_FMT_UYVY8_2X8 ||
+ sd_fmt->format.code == MEDIA_BUS_FMT_YUYV8_2X8)
+ index = 1;
+ else
+ index = 0;
+ return &mxc_isi_src_formats[index];
+}
+
+static inline struct mxc_isi_buffer *to_isi_buffer(struct vb2_v4l2_buffer *v4l2_buf)
+{
+ return container_of(v4l2_buf, struct mxc_isi_buffer, v4l2_buf);
+}
+
+/*
+ * mxc_isi_pipeline_enable() - Enable streaming on a pipeline
+ */
+static int mxc_isi_pipeline_enable(struct mxc_isi_cap_dev *isi_cap, bool enable)
+{
+ struct device *dev = &isi_cap->pdev->dev;
+ struct media_entity *entity = &isi_cap->vdev.entity;
+ struct media_device *mdev = entity->graph_obj.mdev;
+ struct media_graph graph;
+ struct v4l2_subdev *subdev;
+ int ret = 0;
+
+ mutex_lock(&mdev->graph_mutex);
+
+ ret = media_graph_walk_init(&graph, entity->graph_obj.mdev);
+ if (ret) {
+ mutex_unlock(&mdev->graph_mutex);
+ return ret;
+ }
+ media_graph_walk_start(&graph, entity);
+
+ while ((entity = media_graph_walk_next(&graph))) {
+ if (!entity) {
+ dev_dbg(dev, "entity is NULL\n");
+ continue;
+ }
+
+ if (!is_media_entity_v4l2_subdev(entity)) {
+ dev_dbg(dev, "%s is no v4l2 subdev\n", entity->name);
+ continue;
+ }
+
+ subdev = media_entity_to_v4l2_subdev(entity);
+ if (!subdev) {
+ dev_dbg(dev, "%s subdev is NULL\n", entity->name);
+ continue;
+ }
+
+ ret = v4l2_subdev_call(subdev, video, s_stream, enable);
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
+ dev_err(dev, "subdev %s s_stream failed\n", subdev->name);
+ break;
+ }
+ }
+ mutex_unlock(&mdev->graph_mutex);
+ media_graph_walk_cleanup(&graph);
+
+ return ret;
+}
+
+static int mxc_isi_update_buf_paddr(struct mxc_isi_buffer *buf, int memplanes)
+{
+ struct frame_addr *paddr = &buf->paddr;
+ struct vb2_buffer *vb2 = &buf->v4l2_buf.vb2_buf;
+
+ paddr->cb = 0;
+ paddr->cr = 0;
+
+ switch (memplanes) {
+ case 3:
+ paddr->cr = vb2_dma_contig_plane_dma_addr(vb2, 2);
+ fallthrough;
+ case 2:
+ paddr->cb = vb2_dma_contig_plane_dma_addr(vb2, 1);
+ fallthrough;
+ case 1:
+ paddr->y = vb2_dma_contig_plane_dma_addr(vb2, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi)
+{
+ struct mxc_isi_cap_dev *isi_cap = mxc_isi->isi_cap;
+ struct device *dev = &isi_cap->pdev->dev;
+ struct mxc_isi_buffer *buf;
+ struct vb2_buffer *vb2;
+
+ if (list_empty(&isi_cap->out_active)) {
+ dev_warn(dev, "trying to access empty active list\n");
+ return;
+ }
+
+ buf = list_first_entry(&isi_cap->out_active, struct mxc_isi_buffer, list);
+
+ /*
+ * Skip frame when buffer number is not match ISI trigger
+ * buffer
+ */
+ if ((is_buf_active(mxc_isi, 1) && buf->id == MXC_ISI_BUF1) ||
+ (is_buf_active(mxc_isi, 2) && buf->id == MXC_ISI_BUF2)) {
+ dev_dbg(dev, "status=0x%x id=%d\n", mxc_isi->status, buf->id);
+ return;
+ }
+
+ if (buf->discard) {
+ list_move_tail(isi_cap->out_active.next, &isi_cap->out_discard);
+ } else {
+ vb2 = &buf->v4l2_buf.vb2_buf;
+ list_del_init(&buf->list);
+ buf->v4l2_buf.vb2_buf.timestamp = ktime_get_ns();
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_DONE);
+ }
+
+ isi_cap->frame_count++;
+
+ if (list_empty(&isi_cap->out_pending)) {
+ if (list_empty(&isi_cap->out_discard)) {
+ dev_warn(dev, "trying to access empty discard list\n");
+ return;
+ }
+
+ buf = list_first_entry(&isi_cap->out_discard,
+ struct mxc_isi_buffer, list);
+ buf->v4l2_buf.sequence = isi_cap->frame_count;
+ mxc_isi_channel_set_outbuf(mxc_isi, buf);
+ list_move_tail(isi_cap->out_discard.next, &isi_cap->out_active);
+ return;
+ }
+
+ /* ISI channel output buffer */
+ buf = list_first_entry(&isi_cap->out_pending, struct mxc_isi_buffer, list);
+ buf->v4l2_buf.sequence = isi_cap->frame_count;
+ mxc_isi_channel_set_outbuf(mxc_isi, buf);
+ vb2 = &buf->v4l2_buf.vb2_buf;
+ vb2->state = VB2_BUF_STATE_ACTIVE;
+ list_move_tail(isi_cap->out_pending.next, &isi_cap->out_active);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_cap_frame_write_done);
+
+static int cap_vb2_queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers,
+ unsigned int *num_planes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q);
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ struct mxc_isi_fmt *fmt = dst_f->fmt;
+ unsigned long wh;
+ int i;
+
+ if (!fmt)
+ return -EINVAL;
+
+ if (*num_planes) {
+ if (*num_planes != fmt->memplanes)
+ return -EINVAL;
+
+ for (i = 0; i < *num_planes; i++)
+ if (sizes[i] < dst_f->sizeimage[i])
+ return -EINVAL;
+ }
+
+ for (i = 0; i < fmt->memplanes; i++)
+ alloc_devs[i] = &isi_cap->pdev->dev;
+
+ wh = dst_f->width * dst_f->height;
+
+ *num_planes = fmt->memplanes;
+
+ for (i = 0; i < fmt->memplanes; i++) {
+ unsigned int size = (wh * fmt->depth[i]) / 8;
+
+ if (i == 1 && fmt->fourcc == V4L2_PIX_FMT_NV12)
+ size >>= 1;
+ sizes[i] = max_t(u32, size, dst_f->sizeimage[i]);
+ }
+ dev_dbg(&isi_cap->pdev->dev, "%s, buf_n=%d, size=%d\n",
+ __func__, *num_buffers, sizes[0]);
+
+ return 0;
+}
+
+static int cap_vb2_buffer_prepare(struct vb2_buffer *vb2)
+{
+ struct vb2_queue *q = vb2->vb2_queue;
+ struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q);
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ int i;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ if (!isi_cap->dst_f.fmt)
+ return -EINVAL;
+
+ for (i = 0; i < dst_f->fmt->memplanes; i++) {
+ unsigned long size = dst_f->sizeimage[i];
+
+ if (vb2_plane_size(vb2, i) < size) {
+ v4l2_err(&isi_cap->vdev,
+ "User buffer too small (%ld < %ld)\n",
+ vb2_plane_size(vb2, i), size);
+
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb2, i, size);
+ }
+
+ return 0;
+}
+
+static void cap_vb2_buffer_queue(struct vb2_buffer *vb2)
+{
+ struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb2);
+ struct mxc_isi_buffer *buf = to_isi_buffer(v4l2_buf);
+ struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(vb2->vb2_queue);
+ unsigned long flags;
+
+ spin_lock_irqsave(&isi_cap->slock, flags);
+
+ mxc_isi_update_buf_paddr(buf, isi_cap->dst_f.fmt->mdataplanes);
+ list_add_tail(&buf->list, &isi_cap->out_pending);
+
+ spin_unlock_irqrestore(&isi_cap->slock, flags);
+}
+
+static int cap_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ struct mxc_isi_buffer *buf;
+ struct vb2_buffer *vb2;
+ unsigned long flags;
+ int i, j;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ if (count < 2)
+ return -ENOBUFS;
+
+ if (!mxc_isi)
+ return -EINVAL;
+
+ /* Create a buffer for discard operation */
+ for (i = 0; i < isi_cap->pix.num_planes; i++) {
+ isi_cap->discard_size[i] = isi_cap->dst_f.sizeimage[i];
+ isi_cap->discard_buffer[i] =
+ dma_alloc_coherent(&isi_cap->pdev->dev,
+ PAGE_ALIGN(isi_cap->discard_size[i]),
+ &isi_cap->discard_buffer_dma[i],
+ GFP_DMA | GFP_KERNEL);
+ if (!isi_cap->discard_buffer[i]) {
+ for (j = 0; j < i; j++) {
+ dma_free_coherent(&isi_cap->pdev->dev,
+ PAGE_ALIGN(isi_cap->discard_size[j]),
+ isi_cap->discard_buffer[j],
+ isi_cap->discard_buffer_dma[j]);
+ dev_err(&isi_cap->pdev->dev,
+ "alloc dma buffer(%d) fail\n", j);
+ }
+ return -ENOMEM;
+ }
+ dev_dbg(&isi_cap->pdev->dev,
+ "%s: num_plane=%d discard_size=%d discard_buffer=%p\n"
+ , __func__, i,
+ PAGE_ALIGN((int)isi_cap->discard_size[i]),
+ isi_cap->discard_buffer[i]);
+ }
+
+ spin_lock_irqsave(&isi_cap->slock, flags);
+
+ /* add two list member to out_discard list head */
+ isi_cap->buf_discard[0].discard = true;
+ list_add_tail(&isi_cap->buf_discard[0].list, &isi_cap->out_discard);
+
+ isi_cap->buf_discard[1].discard = true;
+ list_add_tail(&isi_cap->buf_discard[1].list, &isi_cap->out_discard);
+
+ /* ISI channel output buffer 1 */
+ buf = list_first_entry(&isi_cap->out_discard, struct mxc_isi_buffer, list);
+ buf->v4l2_buf.sequence = 0;
+ vb2 = &buf->v4l2_buf.vb2_buf;
+ vb2->state = VB2_BUF_STATE_ACTIVE;
+ mxc_isi_channel_set_outbuf(mxc_isi, buf);
+ list_move_tail(isi_cap->out_discard.next, &isi_cap->out_active);
+
+ /* ISI channel output buffer 2 */
+ buf = list_first_entry(&isi_cap->out_pending, struct mxc_isi_buffer, list);
+ buf->v4l2_buf.sequence = 1;
+ vb2 = &buf->v4l2_buf.vb2_buf;
+ vb2->state = VB2_BUF_STATE_ACTIVE;
+ mxc_isi_channel_set_outbuf(mxc_isi, buf);
+ list_move_tail(isi_cap->out_pending.next, &isi_cap->out_active);
+
+ /* Clear frame count */
+ isi_cap->frame_count = 1;
+ spin_unlock_irqrestore(&isi_cap->slock, flags);
+
+ return 0;
+}
+
+static void cap_vb2_stop_streaming(struct vb2_queue *q)
+{
+ struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ struct mxc_isi_buffer *buf;
+ unsigned long flags;
+ int i;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ mxc_isi_channel_disable(mxc_isi);
+
+ spin_lock_irqsave(&isi_cap->slock, flags);
+
+ while (!list_empty(&isi_cap->out_active)) {
+ buf = list_entry(isi_cap->out_active.next,
+ struct mxc_isi_buffer, list);
+ list_del_init(&buf->list);
+ if (buf->discard)
+ continue;
+
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ while (!list_empty(&isi_cap->out_pending)) {
+ buf = list_entry(isi_cap->out_pending.next,
+ struct mxc_isi_buffer, list);
+ list_del_init(&buf->list);
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ while (!list_empty(&isi_cap->out_discard)) {
+ buf = list_entry(isi_cap->out_discard.next,
+ struct mxc_isi_buffer, list);
+ list_del_init(&buf->list);
+ }
+
+ INIT_LIST_HEAD(&isi_cap->out_active);
+ INIT_LIST_HEAD(&isi_cap->out_pending);
+ INIT_LIST_HEAD(&isi_cap->out_discard);
+
+ spin_unlock_irqrestore(&isi_cap->slock, flags);
+
+ for (i = 0; i < isi_cap->pix.num_planes; i++)
+ dma_free_coherent(&isi_cap->pdev->dev,
+ PAGE_ALIGN(isi_cap->discard_size[i]),
+ isi_cap->discard_buffer[i],
+ isi_cap->discard_buffer_dma[i]);
+}
+
+static struct vb2_ops mxc_cap_vb2_qops = {
+ .queue_setup = cap_vb2_queue_setup,
+ .buf_prepare = cap_vb2_buffer_prepare,
+ .buf_queue = cap_vb2_buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = cap_vb2_start_streaming,
+ .stop_streaming = cap_vb2_stop_streaming,
+};
+
+/*
+ * V4L2 controls handling
+ */
+static inline struct mxc_isi_cap_dev *ctrl_to_isi_cap(struct v4l2_ctrl *ctrl)
+{
+ return container_of(ctrl->handler, struct mxc_isi_cap_dev, ctrls.handler);
+}
+
+static int mxc_isi_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct mxc_isi_cap_dev *isi_cap = ctrl_to_isi_cap(ctrl);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ unsigned long flags;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE)
+ return 0;
+
+ spin_lock_irqsave(&mxc_isi->slock, flags);
+
+ switch (ctrl->id) {
+ case V4L2_CID_HFLIP:
+ if (ctrl->val < 0)
+ return -EINVAL;
+ mxc_isi->hflip = (ctrl->val > 0) ? 1 : 0;
+ break;
+
+ case V4L2_CID_VFLIP:
+ if (ctrl->val < 0)
+ return -EINVAL;
+ mxc_isi->vflip = (ctrl->val > 0) ? 1 : 0;
+ break;
+
+ case V4L2_CID_ALPHA_COMPONENT:
+ if (ctrl->val < 0 || ctrl->val > 255)
+ return -EINVAL;
+ mxc_isi->alpha = ctrl->val;
+ mxc_isi->alphaen = 1;
+ break;
+
+ default:
+ dev_err(&isi_cap->pdev->dev,
+ "%s: Not support %d CID\n", __func__, ctrl->id);
+ return -EINVAL;
+ }
+
+ spin_unlock_irqrestore(&mxc_isi->slock, flags);
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops mxc_isi_ctrl_ops = {
+ .s_ctrl = mxc_isi_s_ctrl,
+};
+
+int mxc_isi_ctrls_create(struct mxc_isi_cap_dev *isi_cap)
+{
+ struct mxc_isi_ctrls *ctrls = &isi_cap->ctrls;
+ struct v4l2_ctrl_handler *handler = &ctrls->handler;
+
+ if (isi_cap->ctrls.ready)
+ return 0;
+
+ v4l2_ctrl_handler_init(handler, 4);
+
+ ctrls->hflip = v4l2_ctrl_new_std(handler, &mxc_isi_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+ ctrls->vflip = v4l2_ctrl_new_std(handler, &mxc_isi_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+ ctrls->alpha = v4l2_ctrl_new_std(handler, &mxc_isi_ctrl_ops,
+ V4L2_CID_ALPHA_COMPONENT,
+ 0, 0xff, 1, 0);
+
+ if (!handler->error)
+ ctrls->ready = true;
+
+ return handler->error;
+}
+
+void mxc_isi_ctrls_delete(struct mxc_isi_cap_dev *isi_cap)
+{
+ struct mxc_isi_ctrls *ctrls = &isi_cap->ctrls;
+
+ if (ctrls->ready) {
+ v4l2_ctrl_handler_free(&ctrls->handler);
+ ctrls->ready = false;
+ ctrls->alpha = NULL;
+ }
+}
+
+static struct media_pad
+*mxc_isi_get_remote_source_pad(struct v4l2_subdev *subdev)
+{
+ struct media_pad *sink_pad, *source_pad;
+ int i;
+
+ while (1) {
+ source_pad = NULL;
+ for (i = 0; i < subdev->entity.num_pads; i++) {
+ sink_pad = &subdev->entity.pads[i];
+
+ if (sink_pad->flags & MEDIA_PAD_FL_SINK) {
+ source_pad = media_entity_remote_pad(sink_pad);
+ if (source_pad)
+ break;
+ }
+ }
+ /* return first pad point in the loop */
+ return source_pad;
+ }
+
+ if (i == subdev->entity.num_pads)
+ v4l2_err(subdev, "(%d): No remote pad found!\n", __LINE__);
+
+ return NULL;
+}
+
+static struct v4l2_subdev *mxc_get_remote_subdev(struct v4l2_subdev *subdev,
+ const char * const label)
+{
+ struct media_pad *source_pad;
+ struct v4l2_subdev *sen_sd;
+
+ /* Get remote source pad */
+ source_pad = mxc_isi_get_remote_source_pad(subdev);
+ if (!source_pad) {
+ v4l2_err(subdev, "%s, No remote pad found!\n", label);
+ return NULL;
+ }
+
+ /* Get remote source pad subdev */
+ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity);
+ if (!sen_sd) {
+ v4l2_err(subdev, "%s, No remote subdev found!\n", label);
+ return NULL;
+ }
+
+ return sen_sd;
+}
+
+static bool is_entity_link_setup(struct mxc_isi_cap_dev *isi_cap)
+{
+ struct video_device *vdev = &isi_cap->vdev;
+ struct v4l2_subdev *csi_sd, *sen_sd;
+
+ if (!vdev->entity.num_links || !isi_cap->sd.entity.num_links)
+ return false;
+
+ csi_sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!csi_sd || !csi_sd->entity.num_links)
+ return false;
+
+ /* No sensor subdev for hdmi rx */
+ if (strstr(csi_sd->name, "hdmi"))
+ return true;
+
+ sen_sd = mxc_get_remote_subdev(csi_sd, __func__);
+ if (!sen_sd || !sen_sd->entity.num_links)
+ return false;
+
+ return true;
+}
+
+static int isi_cap_fmt_init(struct mxc_isi_cap_dev *isi_cap)
+{
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ struct mxc_isi_frame *src_f = &isi_cap->src_f;
+ struct v4l2_subdev_format src_fmt;
+ struct v4l2_subdev *src_sd;
+ int i, ret;
+
+ src_sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!src_sd) {
+ v4l2_err(&isi_cap->sd, "get remote subdev fail!\n");
+ return -EINVAL;
+ }
+
+ memset(&src_fmt, 0, sizeof(src_fmt));
+ src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ ret = v4l2_subdev_call(src_sd, pad, get_fmt, NULL, &src_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
+ v4l2_err(&isi_cap->sd, "get remote fmt fail!\n");
+ return ret;
+ }
+
+ if (dst_f->width == 0 || dst_f->height == 0)
+ set_frame_bounds(dst_f, src_fmt.format.width, src_fmt.format.height);
+
+ if (!dst_f->fmt)
+ dst_f->fmt = &mxc_isi_out_formats[0];
+
+ for (i = 0; i < dst_f->fmt->memplanes; i++) {
+ if (dst_f->bytesperline[i] == 0)
+ dst_f->bytesperline[i] = dst_f->width * dst_f->fmt->depth[i] >> 3;
+ if (dst_f->sizeimage[i] == 0)
+ dst_f->sizeimage[i] = dst_f->bytesperline[i] * dst_f->height;
+ }
+
+ if (!src_f->fmt)
+ memcpy(src_f, dst_f, sizeof(*dst_f));
+ return 0;
+}
+
+
+static int mxc_isi_capture_open(struct file *file)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ struct device *dev = &isi_cap->pdev->dev;
+ int ret = -EBUSY;
+
+ mutex_lock(&isi_cap->lock);
+ isi_cap->is_link_setup = is_entity_link_setup(isi_cap);
+ if (!isi_cap->is_link_setup) {
+ mutex_unlock(&isi_cap->lock);
+ return 0;
+ }
+ mutex_unlock(&isi_cap->lock);
+
+ if (mxc_isi->m2m_enabled || isi_cap->is_streaming[isi_cap->id]) {
+ dev_err(dev, "ISI channel[%d] is busy\n", isi_cap->id);
+ return ret;
+ }
+
+ mutex_lock(&isi_cap->lock);
+ ret = v4l2_fh_open(file);
+ if (ret) {
+ mutex_unlock(&isi_cap->lock);
+ return ret;
+ }
+ mutex_unlock(&isi_cap->lock);
+
+ pm_runtime_get_sync(dev);
+
+ mutex_lock(&isi_cap->lock);
+ ret = isi_cap_fmt_init(isi_cap);
+ mutex_unlock(&isi_cap->lock);
+
+ /* increase usage count for ISI channel */
+ mutex_lock(&mxc_isi->lock);
+ atomic_inc(&mxc_isi->usage_count);
+ mxc_isi->cap_enabled = true;
+ mutex_unlock(&mxc_isi->lock);
+
+ return ret;
+}
+
+static int mxc_isi_capture_release(struct file *file)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct video_device *vdev = video_devdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ struct device *dev = &isi_cap->pdev->dev;
+ struct vb2_queue *q = vdev->queue;
+ int ret = -1;
+
+ if (!isi_cap->is_link_setup)
+ return 0;
+
+ if (isi_cap->is_streaming[isi_cap->id])
+ mxc_isi_cap_streamoff(file, NULL, q->type);
+
+ mutex_lock(&isi_cap->lock);
+ ret = _vb2_fop_release(file, NULL);
+ if (ret) {
+ dev_err(dev, "%s fail\n", __func__);
+ mutex_unlock(&isi_cap->lock);
+ goto label;
+ }
+ mutex_unlock(&isi_cap->lock);
+
+ if (atomic_read(&mxc_isi->usage_count) > 0 &&
+ atomic_dec_and_test(&mxc_isi->usage_count))
+ mxc_isi_channel_deinit(mxc_isi);
+
+label:
+ mutex_lock(&mxc_isi->lock);
+ mxc_isi->cap_enabled = false;
+ mutex_unlock(&mxc_isi->lock);
+
+ pm_runtime_put(dev);
+ return (ret) ? ret : 0;
+}
+
+static const struct v4l2_file_operations mxc_isi_capture_fops = {
+ .owner = THIS_MODULE,
+ .open = mxc_isi_capture_open,
+ .release = mxc_isi_capture_release,
+ .poll = vb2_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+};
+
+/*
+ * The video node ioctl operations
+ */
+static int mxc_isi_cap_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+
+ strlcpy(cap->driver, MXC_ISI_CAPTURE, sizeof(cap->driver));
+ strlcpy(cap->card, MXC_ISI_CAPTURE, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s.%d",
+ dev_name(&isi_cap->pdev->dev), isi_cap->id);
+
+ cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+ return 0;
+}
+
+static int mxc_isi_cap_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_fmt *fmt;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+ if (f->index >= (int)mxc_isi_out_formats_size)
+ return -EINVAL;
+
+ fmt = &mxc_isi_out_formats[f->index];
+
+ strncpy(f->description, fmt->name, sizeof(f->description) - 1);
+
+ f->pixelformat = fmt->fourcc;
+
+ return 0;
+}
+
+static int mxc_isi_cap_g_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ int i;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ pix->width = dst_f->o_width;
+ pix->height = dst_f->o_height;
+ pix->field = V4L2_FIELD_NONE;
+ pix->pixelformat = dst_f->fmt->fourcc;
+ pix->colorspace = V4L2_COLORSPACE_SRGB;
+ pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
+ pix->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ pix->num_planes = dst_f->fmt->memplanes;
+
+ for (i = 0; i < pix->num_planes; ++i) {
+ pix->plane_fmt[i].bytesperline = dst_f->bytesperline[i];
+ pix->plane_fmt[i].sizeimage = dst_f->sizeimage[i];
+ }
+
+ return 0;
+}
+
+static int mxc_isi_cap_try_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_fmt *fmt;
+ int bpl;
+ int i;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ for (i = 0; i < mxc_isi_out_formats_size; i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= mxc_isi_out_formats_size) {
+ v4l2_err(&isi_cap->sd, "format(%.4s) is not support!\n",
+ (char *)&pix->pixelformat);
+ return -EINVAL;
+ }
+
+ if (pix->width <= 0 || pix->height <= 0) {
+ v4l2_err(&isi_cap->sd, "%s, W/H=(%d, %d) is not valid\n"
+ , __func__, pix->width, pix->height);
+ return -EINVAL;
+ }
+
+ if (pix->width > ISI_4K)
+ pix->width = ISI_4K;
+ if (pix->height > ISI_8K)
+ pix->height = ISI_8K;
+
+ pix->num_planes = fmt->memplanes;
+ pix->pixelformat = fmt->fourcc;
+ pix->field = V4L2_FIELD_NONE;
+ pix->colorspace = V4L2_COLORSPACE_SRGB;
+ pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
+ pix->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ memset(pix->reserved, 0x00, sizeof(pix->reserved));
+
+ for (i = 0; i < pix->num_planes; i++) {
+ bpl = pix->plane_fmt[i].bytesperline;
+
+ if ((bpl == 0) || (bpl / (fmt->depth[i] >> 3)) < pix->width)
+ pix->plane_fmt[i].bytesperline =
+ (pix->width * fmt->depth[i]) >> 3;
+
+ if (pix->plane_fmt[i].sizeimage == 0) {
+ if ((i == 1) && (pix->pixelformat == V4L2_PIX_FMT_NV12))
+ pix->plane_fmt[i].sizeimage =
+ (pix->width * (pix->height >> 1) * fmt->depth[i] >> 3);
+ else
+ pix->plane_fmt[i].sizeimage =
+ (pix->width * pix->height * fmt->depth[i] >> 3);
+ }
+ }
+
+ return 0;
+}
+
+/* Update input frame size and formate */
+static int mxc_isi_source_fmt_init(struct mxc_isi_cap_dev *isi_cap)
+{
+ struct mxc_isi_frame *src_f = &isi_cap->src_f;
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ struct v4l2_subdev_format src_fmt;
+ struct media_pad *source_pad;
+ struct v4l2_subdev *src_sd;
+ int ret;
+
+ source_pad = mxc_isi_get_remote_source_pad(&isi_cap->sd);
+ if (!source_pad) {
+ v4l2_err(&isi_cap->sd,
+ "%s, No remote pad found!\n", __func__);
+ return -EINVAL;
+ }
+
+ src_sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!src_sd)
+ return -EINVAL;
+
+ src_fmt.pad = source_pad->index;
+ src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ src_fmt.format.code = MEDIA_BUS_FMT_UYVY8_2X8;
+ src_fmt.format.width = dst_f->width;
+ src_fmt.format.height = dst_f->height;
+ ret = v4l2_subdev_call(src_sd, pad, set_fmt, NULL, &src_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
+ v4l2_err(&isi_cap->sd, "set remote fmt fail!\n");
+ return ret;
+ }
+
+ memset(&src_fmt, 0, sizeof(src_fmt));
+ src_fmt.pad = source_pad->index;
+ src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ ret = v4l2_subdev_call(src_sd, pad, get_fmt, NULL, &src_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
+ v4l2_err(&isi_cap->sd, "get remote fmt fail!\n");
+ return ret;
+ }
+
+ /* Pixel link master will transfer format to RGB32 or YUV32 */
+ src_f->fmt = mxc_isi_get_src_fmt(&src_fmt);
+
+ set_frame_bounds(src_f, src_fmt.format.width, src_fmt.format.height);
+
+ if (dst_f->width > src_f->width || dst_f->height > src_f->height) {
+ dev_err(&isi_cap->pdev->dev,
+ "%s: src:(%d,%d), dst:(%d,%d) Not support upscale\n",
+ __func__,
+ src_f->width, src_f->height,
+ dst_f->width, dst_f->height);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mxc_isi_cap_s_fmt_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ struct mxc_isi_fmt *fmt;
+ int bpl;
+ int i, ret;
+
+ /* Step1: Check format with output support format list.
+ * Step2: Update output frame information.
+ * Step3: Checkout the format whether is supported by remote subdev
+ * Step3.1: If Yes, call remote subdev set_fmt.
+ * Step3.2: If NO, call remote subdev get_fmt.
+ * Step4: Update input frame information.
+ * Step5: Update mxc isi channel configuration.
+ */
+
+ dev_dbg(&isi_cap->pdev->dev, "%s, fmt=0x%X\n", __func__, pix->pixelformat);
+ if (vb2_is_busy(&isi_cap->vb2_q))
+ return -EBUSY;
+
+ /* Check out put format */
+ for (i = 0; i < mxc_isi_out_formats_size; i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (pix && fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= mxc_isi_out_formats_size) {
+ dev_dbg(&isi_cap->pdev->dev,
+ "format(%.4s) is not support!\n", (char *)&pix->pixelformat);
+ return -EINVAL;
+ }
+
+ /* update out put frame size and formate */
+ if (pix->height <= 0 || pix->width <= 0)
+ return -EINVAL;
+
+ ret = mxc_isi_cap_try_fmt_mplane(file, priv, f);
+ if (ret)
+ return ret;
+
+ dst_f->fmt = fmt;
+ dst_f->height = pix->height;
+ dst_f->width = pix->width;
+
+ pix->num_planes = fmt->memplanes;
+
+ for (i = 0; i < pix->num_planes; i++) {
+ bpl = pix->plane_fmt[i].bytesperline;
+
+ if ((bpl == 0) || (bpl / (fmt->depth[i] >> 3)) < pix->width)
+ pix->plane_fmt[i].bytesperline =
+ (pix->width * fmt->depth[i]) >> 3;
+
+ if (pix->plane_fmt[i].sizeimage == 0) {
+ if ((i == 1) && (pix->pixelformat == V4L2_PIX_FMT_NV12))
+ pix->plane_fmt[i].sizeimage =
+ (pix->width * (pix->height >> 1) * fmt->depth[i] >> 3);
+ else
+ pix->plane_fmt[i].sizeimage =
+ (pix->width * pix->height * fmt->depth[i] >> 3);
+ }
+ }
+
+ if (pix->num_planes > 1) {
+ for (i = 0; i < pix->num_planes; i++) {
+ dst_f->bytesperline[i] = pix->plane_fmt[i].bytesperline;
+ dst_f->sizeimage[i] = pix->plane_fmt[i].sizeimage;
+ }
+ } else {
+ dst_f->bytesperline[0] = dst_f->width * dst_f->fmt->depth[0] / 8;
+ dst_f->sizeimage[0] = dst_f->height * dst_f->bytesperline[0];
+ }
+
+ memcpy(&isi_cap->pix, pix, sizeof(*pix));
+ set_frame_bounds(dst_f, pix->width, pix->height);
+
+ return 0;
+}
+
+static int mxc_isi_config_parm(struct mxc_isi_cap_dev *isi_cap)
+{
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ int ret;
+
+ ret = mxc_isi_source_fmt_init(isi_cap);
+ if (ret < 0)
+ return -EINVAL;
+
+ mxc_isi_channel_init(mxc_isi);
+ mxc_isi_channel_config(mxc_isi, &isi_cap->src_f, &isi_cap->dst_f);
+
+ return 0;
+}
+
+static int mxc_isi_cap_g_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *a)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct v4l2_subdev *sd;
+
+ sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!sd)
+ return -ENODEV;
+
+ return v4l2_g_parm_cap(video_devdata(file), sd, a);
+}
+
+static int mxc_isi_cap_s_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *a)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct v4l2_subdev *sd;
+
+ sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!sd)
+ return -ENODEV;
+
+ return v4l2_s_parm_cap(video_devdata(file), sd, a);
+}
+
+
+static int mxc_isi_cap_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ struct device *dev = &isi_cap->pdev->dev;
+ struct v4l2_subdev *src_sd;
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (isi_cap->is_streaming[isi_cap->id]) {
+ dev_err(dev, "ISI channel[%d] is streaming\n", isi_cap->id);
+ return -EBUSY;
+ }
+
+ src_sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ ret = (!src_sd) ? -EINVAL : v4l2_subdev_call(src_sd, core, s_power, 1);
+ if (ret) {
+ v4l2_err(&isi_cap->sd, "Call subdev s_power fail!\n");
+ return ret;
+ }
+
+ ret = mxc_isi_config_parm(isi_cap);
+ if (ret < 0)
+ goto power;
+
+ ret = vb2_ioctl_streamon(file, priv, type);
+ mxc_isi_channel_enable(mxc_isi, mxc_isi->m2m_enabled);
+ ret = mxc_isi_pipeline_enable(isi_cap, 1);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ goto disable;
+
+ isi_cap->is_streaming[isi_cap->id] = 1;
+ mxc_isi->is_streaming = 1;
+
+ return 0;
+
+disable:
+ mxc_isi_channel_disable(mxc_isi);
+ vb2_ioctl_streamoff(file, priv, type);
+power:
+ v4l2_subdev_call(src_sd, core, s_power, 0);
+ return ret;
+}
+
+static int mxc_isi_cap_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ struct device *dev = &isi_cap->pdev->dev;
+ struct v4l2_subdev *src_sd;
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (isi_cap->is_streaming[isi_cap->id] == 0) {
+ dev_err(dev, "ISI channel[%d] has stopped\n", isi_cap->id);
+ return -EBUSY;
+ }
+
+ mxc_isi_pipeline_enable(isi_cap, 0);
+ mxc_isi_channel_disable(mxc_isi);
+ ret = vb2_ioctl_streamoff(file, priv, type);
+
+ isi_cap->is_streaming[isi_cap->id] = 0;
+ mxc_isi->is_streaming = 0;
+
+ src_sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ return v4l2_subdev_call(src_sd, core, s_power, 0);
+}
+
+static int mxc_isi_cap_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_frame *f = &isi_cap->dst_f;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT &&
+ s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ s->r.left = 0;
+ s->r.top = 0;
+ s->r.width = f->o_width;
+ s->r.height = f->o_height;
+ return 0;
+
+ case V4L2_SEL_TGT_COMPOSE:
+ s->r.left = f->h_off;
+ s->r.top = f->v_off;
+ s->r.width = f->c_width;
+ s->r.height = f->c_height;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int enclosed_rectangle(struct v4l2_rect *a, struct v4l2_rect *b)
+{
+ if (a->left < b->left || a->top < b->top)
+ return 0;
+
+ if (a->left + a->width > b->left + b->width)
+ return 0;
+
+ if (a->top + a->height > b->top + b->height)
+ return 0;
+
+ return 1;
+}
+
+static int mxc_isi_cap_s_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_frame *f;
+ struct v4l2_rect rect = s->r;
+ unsigned long flags;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+ if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT &&
+ s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ if (s->target == V4L2_SEL_TGT_COMPOSE)
+ f = &isi_cap->dst_f;
+ else
+ return -EINVAL;
+
+ bounds_adjust(f, &rect);
+
+ if (s->flags & V4L2_SEL_FLAG_LE &&
+ !enclosed_rectangle(&rect, &s->r))
+ return -ERANGE;
+
+ if (s->flags & V4L2_SEL_FLAG_GE &&
+ !enclosed_rectangle(&s->r, &rect))
+ return -ERANGE;
+
+ if ((s->flags & V4L2_SEL_FLAG_LE) &&
+ (s->flags & V4L2_SEL_FLAG_GE) &&
+ (rect.width != s->r.width || rect.height != s->r.height))
+ return -ERANGE;
+
+ s->r = rect;
+ spin_lock_irqsave(&isi_cap->slock, flags);
+ set_frame_crop(f, s->r.left, s->r.top, s->r.width,
+ s->r.height);
+ spin_unlock_irqrestore(&isi_cap->slock, flags);
+
+ return 0;
+}
+
+static int mxc_isi_cap_enum_framesizes(struct file *file, void *priv,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct device_node *parent;
+ struct v4l2_subdev *sd;
+ struct mxc_isi_fmt *fmt;
+ struct v4l2_subdev_frame_size_enum fse = {
+ .index = fsize->index,
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ int ret;
+
+ fmt = mxc_isi_find_format(&fsize->pixel_format, NULL, 0);
+ if (!fmt || fmt->fourcc != fsize->pixel_format)
+ return -EINVAL;
+ fse.code = fmt->mbus_code;
+
+ sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!sd) {
+ v4l2_err(&isi_cap->sd, "Can't find subdev\n");
+ return -ENODEV;
+ }
+
+ ret = v4l2_subdev_call(sd, pad, enum_frame_size, NULL, &fse);
+ if (ret)
+ return ret;
+
+ parent = of_get_parent(isi_cap->pdev->dev.of_node);
+ if ((of_device_is_compatible(parent, "fsl,imx8mp-isi")) &&
+ (fse.max_width > ISI_2K || fse.min_width > ISI_2K) &&
+ (isi_cap->id == 1))
+ return -EINVAL;
+
+ if (fse.min_width == fse.max_width &&
+ fse.min_height == fse.max_height) {
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ fsize->discrete.width = fse.min_width;
+ fsize->discrete.height = fse.min_height;
+ return 0;
+ }
+
+ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+ fsize->stepwise.min_width = fse.min_width;
+ fsize->stepwise.max_width = fse.max_width;
+ fsize->stepwise.min_height = fse.min_height;
+ fsize->stepwise.max_height = fse.max_height;
+ fsize->stepwise.step_width = 1;
+ fsize->stepwise.step_height = 1;
+
+ return 0;
+}
+
+static int mxc_isi_cap_enum_frameintervals(struct file *file, void *fh,
+ struct v4l2_frmivalenum *interval)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct device_node *parent;
+ struct v4l2_subdev *sd;
+ struct mxc_isi_fmt *fmt;
+ struct v4l2_subdev_frame_interval_enum fie = {
+ .index = interval->index,
+ .width = interval->width,
+ .height = interval->height,
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ int ret;
+
+ fmt = mxc_isi_find_format(&interval->pixel_format, NULL, 0);
+ if (!fmt || fmt->fourcc != interval->pixel_format)
+ return -EINVAL;
+ fie.code = fmt->mbus_code;
+
+ sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!sd)
+ return -EINVAL;
+
+ ret = v4l2_subdev_call(sd, pad, enum_frame_interval, NULL, &fie);
+ if (ret)
+ return ret;
+
+ parent = of_get_parent(isi_cap->pdev->dev.of_node);
+ if (of_device_is_compatible(parent, "fsl,imx8mp-isi") &&
+ fie.width > ISI_2K && isi_cap->id == 1)
+ return -EINVAL;
+
+ interval->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+ interval->discrete = fie.interval;
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops mxc_isi_capture_ioctl_ops = {
+ .vidioc_querycap = mxc_isi_cap_querycap,
+
+ .vidioc_enum_fmt_vid_cap = mxc_isi_cap_enum_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = mxc_isi_cap_try_fmt_mplane,
+ .vidioc_s_fmt_vid_cap_mplane = mxc_isi_cap_s_fmt_mplane,
+ .vidioc_g_fmt_vid_cap_mplane = mxc_isi_cap_g_fmt_mplane,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+
+ .vidioc_g_parm = mxc_isi_cap_g_parm,
+ .vidioc_s_parm = mxc_isi_cap_s_parm,
+
+ .vidioc_streamon = mxc_isi_cap_streamon,
+ .vidioc_streamoff = mxc_isi_cap_streamoff,
+
+ .vidioc_g_selection = mxc_isi_cap_g_selection,
+ .vidioc_s_selection = mxc_isi_cap_s_selection,
+
+ .vidioc_enum_framesizes = mxc_isi_cap_enum_framesizes,
+ .vidioc_enum_frameintervals = mxc_isi_cap_enum_frameintervals,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+/* Capture subdev media entity operations */
+static int mxc_isi_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+
+ if (WARN_ON(!isi_cap))
+ return 0;
+
+ if (!(flags & MEDIA_LNK_FL_ENABLED))
+ return 0;
+
+ /* Add ISI source and sink pad link configuration */
+ if (local->flags & MEDIA_PAD_FL_SOURCE) {
+ switch (local->index) {
+ case MXC_ISI_SD_PAD_SOURCE_DC0:
+ case MXC_ISI_SD_PAD_SOURCE_DC1:
+ break;
+ case MXC_ISI_SD_PAD_SOURCE_MEM:
+ break;
+ default:
+ dev_err(&isi_cap->pdev->dev, "invalid source pad\n");
+ return -EINVAL;
+ }
+ } else if (local->flags & MEDIA_PAD_FL_SINK) {
+ switch (local->index) {
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC0:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC1:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC2:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC3:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC0:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC1:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC2:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC3:
+ case MXC_ISI_SD_PAD_SINK_HDMI:
+ case MXC_ISI_SD_PAD_SINK_DC0:
+ case MXC_ISI_SD_PAD_SINK_DC1:
+ case MXC_ISI_SD_PAD_SINK_MEM:
+ case MXC_ISI_SD_PAD_SINK_PARALLEL_CSI:
+ break;
+ default:
+ dev_err(&isi_cap->pdev->dev,
+ "%s invalid sink pad\n", __func__);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static const struct media_entity_operations mxc_isi_sd_media_ops = {
+ .link_setup = mxc_isi_link_setup,
+};
+
+static int mxc_isi_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ return 0;
+}
+
+static int mxc_isi_subdev_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+ struct mxc_isi_frame *f;
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+
+ mutex_lock(&isi_cap->lock);
+
+ switch (fmt->pad) {
+ case MXC_ISI_SD_PAD_SOURCE_MEM:
+ case MXC_ISI_SD_PAD_SOURCE_DC0:
+ case MXC_ISI_SD_PAD_SOURCE_DC1:
+ f = &isi_cap->dst_f;
+ break;
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC0:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC1:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC2:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC3:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC0:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC1:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC2:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC3:
+ case MXC_ISI_SD_PAD_SINK_HDMI:
+ case MXC_ISI_SD_PAD_SINK_DC0:
+ case MXC_ISI_SD_PAD_SINK_DC1:
+ case MXC_ISI_SD_PAD_SINK_MEM:
+ f = &isi_cap->src_f;
+ break;
+ default:
+ mutex_unlock(&isi_cap->lock);
+ v4l2_err(&isi_cap->sd,
+ "%s, Pad is not support now!\n", __func__);
+ return -1;
+ }
+
+ if (!WARN_ON(!f->fmt))
+ mf->code = f->fmt->mbus_code;
+
+ /* Source/Sink pads crop rectangle size */
+ mf->width = f->width;
+ mf->height = f->height;
+ mf->colorspace = V4L2_COLORSPACE_SRGB;
+
+ mutex_unlock(&isi_cap->lock);
+
+ return 0;
+}
+
+static int mxc_isi_subdev_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+ struct device_node *parent;
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ struct mxc_isi_fmt *out_fmt;
+ int i;
+
+ if (fmt->pad < MXC_ISI_SD_PAD_SOURCE_MEM &&
+ vb2_is_busy(&isi_cap->vb2_q))
+ return -EBUSY;
+
+ for (i = 0; i < mxc_isi_out_formats_size; i++) {
+ out_fmt = &mxc_isi_out_formats[i];
+ if (mf->code == out_fmt->mbus_code)
+ break;
+ }
+ if (i >= mxc_isi_out_formats_size) {
+ v4l2_err(&isi_cap->sd,
+ "%s, format is not support!\n", __func__);
+ return -EINVAL;
+ }
+
+ parent = of_get_parent(isi_cap->pdev->dev.of_node);
+ if (of_device_is_compatible(parent, "fsl,imx8mn-isi") &&
+ mf->width > ISI_2K)
+ return -EINVAL;
+
+ mutex_lock(&isi_cap->lock);
+ /* update out put frame size and formate */
+ dst_f->fmt = &mxc_isi_out_formats[i];
+ set_frame_bounds(dst_f, mf->width, mf->height);
+ mutex_unlock(&isi_cap->lock);
+
+ dev_dbg(&isi_cap->pdev->dev, "pad%d: code: 0x%x, %dx%d",
+ fmt->pad, mf->code, mf->width, mf->height);
+
+ return 0;
+}
+
+static int mxc_isi_subdev_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+ struct mxc_isi_frame *f = &isi_cap->src_f;
+ struct v4l2_rect *r = &sel->r;
+ struct v4l2_rect *try_sel;
+
+ mutex_lock(&isi_cap->lock);
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ f = &isi_cap->dst_f;
+ fallthrough;
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ r->width = f->o_width;
+ r->height = f->o_height;
+ r->left = 0;
+ r->top = 0;
+ mutex_unlock(&isi_cap->lock);
+ return 0;
+
+ case V4L2_SEL_TGT_CROP:
+ try_sel = v4l2_subdev_get_try_crop(sd, sd_state, sel->pad);
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ try_sel = v4l2_subdev_get_try_compose(sd, sd_state, sel->pad);
+ f = &isi_cap->dst_f;
+ break;
+ default:
+ mutex_unlock(&isi_cap->lock);
+ return -EINVAL;
+ }
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
+ sel->r = *try_sel;
+ } else {
+ r->left = f->h_off;
+ r->top = f->v_off;
+ r->width = f->width;
+ r->height = f->height;
+ }
+
+ dev_dbg(&isi_cap->pdev->dev,
+ "%s, target %#x: l:%d, t:%d, %dx%d, f_w: %d, f_h: %d",
+ __func__, sel->pad, r->left, r->top, r->width, r->height,
+ f->c_width, f->c_height);
+
+ mutex_unlock(&isi_cap->lock);
+ return 0;
+}
+
+static int mxc_isi_subdev_set_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+ struct mxc_isi_frame *f = &isi_cap->src_f;
+ struct v4l2_rect *r = &sel->r;
+ struct v4l2_rect *try_sel;
+ unsigned long flags;
+
+ mutex_lock(&isi_cap->lock);
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP:
+ try_sel = v4l2_subdev_get_try_crop(sd, sd_state, sel->pad);
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ try_sel = v4l2_subdev_get_try_compose(sd, sd_state, sel->pad);
+ f = &isi_cap->dst_f;
+ break;
+ default:
+ mutex_unlock(&isi_cap->lock);
+ return -EINVAL;
+ }
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
+ *try_sel = sel->r;
+ } else {
+ spin_lock_irqsave(&isi_cap->slock, flags);
+ set_frame_crop(f, r->left, r->top, r->width, r->height);
+ spin_unlock_irqrestore(&isi_cap->slock, flags);
+ }
+
+ dev_dbg(&isi_cap->pdev->dev, "%s, target %#x: (%d,%d)/%dx%d", __func__,
+ sel->target, r->left, r->top, r->width, r->height);
+
+ mutex_unlock(&isi_cap->lock);
+
+ return 0;
+}
+
+static struct v4l2_subdev_pad_ops mxc_isi_subdev_pad_ops = {
+ .enum_mbus_code = mxc_isi_subdev_enum_mbus_code,
+ .get_selection = mxc_isi_subdev_get_selection,
+ .set_selection = mxc_isi_subdev_set_selection,
+ .get_fmt = mxc_isi_subdev_get_fmt,
+ .set_fmt = mxc_isi_subdev_set_fmt,
+};
+
+static struct v4l2_subdev_ops mxc_isi_subdev_ops = {
+ .pad = &mxc_isi_subdev_pad_ops,
+};
+
+static int mxc_isi_register_cap_device(struct mxc_isi_cap_dev *isi_cap,
+ struct v4l2_device *v4l2_dev)
+{
+ struct video_device *vdev = &isi_cap->vdev;
+ struct vb2_queue *q = &isi_cap->vb2_q;
+ int ret = -ENOMEM;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+ memset(vdev, 0, sizeof(*vdev));
+ snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.capture", isi_cap->id);
+
+ vdev->fops = &mxc_isi_capture_fops;
+ vdev->ioctl_ops = &mxc_isi_capture_ioctl_ops;
+ vdev->v4l2_dev = v4l2_dev;
+ vdev->minor = -1;
+ vdev->release = video_device_release_empty;
+ vdev->queue = q;
+ vdev->lock = &isi_cap->lock;
+
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE;
+ video_set_drvdata(vdev, isi_cap);
+
+ INIT_LIST_HEAD(&isi_cap->out_pending);
+ INIT_LIST_HEAD(&isi_cap->out_active);
+ INIT_LIST_HEAD(&isi_cap->out_discard);
+
+ memset(q, 0, sizeof(*q));
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ q->drv_priv = isi_cap;
+ q->ops = &mxc_cap_vb2_qops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct mxc_isi_buffer);
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &isi_cap->lock;
+
+ ret = vb2_queue_init(q);
+ if (ret)
+ goto err_free_ctx;
+
+
+ isi_cap->cap_pad.flags = MEDIA_PAD_FL_SINK;
+ vdev->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
+ ret = media_entity_pads_init(&vdev->entity, 1, &isi_cap->cap_pad);
+ if (ret)
+ goto err_free_ctx;
+
+ ret = mxc_isi_ctrls_create(isi_cap);
+ if (ret)
+ goto err_me_cleanup;
+
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret)
+ goto err_ctrl_free;
+
+ vdev->ctrl_handler = &isi_cap->ctrls.handler;
+ v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n",
+ vdev->name, video_device_node_name(vdev));
+
+ return 0;
+
+err_ctrl_free:
+ mxc_isi_ctrls_delete(isi_cap);
+err_me_cleanup:
+ media_entity_cleanup(&vdev->entity);
+err_free_ctx:
+ return ret;
+}
+
+static int mxc_isi_subdev_registered(struct v4l2_subdev *sd)
+{
+ struct mxc_isi_cap_dev *isi_cap = sd_to_cap_dev(sd);
+ int ret;
+
+ if (!isi_cap)
+ return -ENXIO;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ ret = mxc_isi_register_cap_device(isi_cap, sd->v4l2_dev);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void mxc_isi_subdev_unregistered(struct v4l2_subdev *sd)
+{
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+ struct video_device *vdev;
+
+ if (!isi_cap)
+ return;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ mutex_lock(&isi_cap->lock);
+ vdev = &isi_cap->vdev;
+ if (video_is_registered(vdev)) {
+ video_unregister_device(vdev);
+ mxc_isi_ctrls_delete(isi_cap);
+ media_entity_cleanup(&vdev->entity);
+ }
+ mutex_unlock(&isi_cap->lock);
+}
+
+static const struct v4l2_subdev_internal_ops mxc_isi_capture_sd_internal_ops = {
+ .registered = mxc_isi_subdev_registered,
+ .unregistered = mxc_isi_subdev_unregistered,
+};
+
+static int isi_cap_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mxc_isi_dev *mxc_isi;
+ struct mxc_isi_cap_dev *isi_cap;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ isi_cap = devm_kzalloc(dev, sizeof(*isi_cap), GFP_KERNEL);
+ if (!isi_cap)
+ return -ENOMEM;
+
+ dev->parent = mxc_isi_dev_get_parent(pdev);
+ if (!dev->parent) {
+ dev_info(dev, "deferring %s device registration\n", dev_name(dev));
+ return -EPROBE_DEFER;
+ }
+
+ mxc_isi = mxc_isi_get_hostdata(pdev);
+ if (!mxc_isi) {
+ dev_info(dev, "deferring %s device registration\n", dev_name(dev));
+ return -EPROBE_DEFER;
+ }
+
+ isi_cap->pdev = pdev;
+ isi_cap->id = mxc_isi->id;
+ mxc_isi->isi_cap = isi_cap;
+
+ spin_lock_init(&isi_cap->slock);
+ mutex_init(&isi_cap->lock);
+
+ sd = &isi_cap->sd;
+ v4l2_subdev_init(sd, &mxc_isi_subdev_ops);
+ snprintf(sd->name, sizeof(sd->name), "mxc_isi.%d", isi_cap->id);
+
+ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+
+ /* ISI Sink pads */
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC0].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC1].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC2].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC3].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC0].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC1].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC2].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC3].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_DC0].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_DC1].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_HDMI].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MEM].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_PARALLEL_CSI].flags = MEDIA_PAD_FL_SINK;
+
+ /* ISI source pads */
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SOURCE_MEM].flags = MEDIA_PAD_FL_SOURCE;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SOURCE_DC0].flags = MEDIA_PAD_FL_SOURCE;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SOURCE_DC1].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&sd->entity, MXC_ISI_SD_PADS_NUM, isi_cap->sd_pads);
+ if (ret)
+ return ret;
+
+ sd->entity.ops = &mxc_isi_sd_media_ops;
+ sd->internal_ops = &mxc_isi_capture_sd_internal_ops;
+
+ v4l2_set_subdevdata(sd, isi_cap);
+ platform_set_drvdata(pdev, isi_cap);
+
+ pm_runtime_enable(dev);
+ return 0;
+}
+
+static int isi_cap_remove(struct platform_device *pdev)
+{
+ struct mxc_isi_cap_dev *isi_cap = platform_get_drvdata(pdev);
+ struct v4l2_subdev *sd = &isi_cap->sd;
+
+ v4l2_device_unregister_subdev(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_set_subdevdata(sd, NULL);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id isi_cap_of_match[] = {
+ {.compatible = "imx-isi-capture",},
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, isi_cap_of_match);
+
+static struct platform_driver isi_cap_driver = {
+ .probe = isi_cap_probe,
+ .remove = isi_cap_remove,
+ .driver = {
+ .of_match_table = isi_cap_of_match,
+ .name = "isi-capture",
+ },
+};
+module_platform_driver(isi_cap_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX8 Image Sensor Interface Capture driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ISI Capture");
+MODULE_VERSION("1.0");
diff --git a/drivers/staging/media/imx/imx8-isi-core.c b/drivers/staging/media/imx/imx8-isi-core.c
new file mode 100644
index 000000000000..c8c6c4ed9d33
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-core.c
@@ -0,0 +1,885 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019-2020 NXP
+ *
+ */
+
+#include "imx8-isi-hw.h"
+
+static const struct soc_device_attribute imx8_soc[] = {
+ {
+ .soc_id = "i.MX8QXP",
+ .revision = "1.0",
+ }, {
+ .soc_id = "i.MX8QXP",
+ .revision = "1.1",
+ }, {
+ .soc_id = "i.MX8QXP",
+ .revision = "1.2",
+ }, {
+ .soc_id = "i.MX8QM",
+ .revision = "1.0",
+ }, {
+ .soc_id = "i.MX8QM",
+ .revision = "1.1",
+ }, {
+ .soc_id = "i.MX8MN",
+ .revision = "1.0",
+ }, {
+ .soc_id = "i.MX8MP",
+ }, {
+ .soc_id = "i.MX8ULP",
+ }, {
+ /* sentinel */
+ },
+};
+
+static const struct of_device_id mxc_isi_of_match[];
+
+static irqreturn_t mxc_isi_irq_handler(int irq, void *priv)
+{
+ struct mxc_isi_dev *mxc_isi = priv;
+ struct device *dev = &mxc_isi->pdev->dev;
+ struct mxc_isi_ier_reg *ier_reg = mxc_isi->pdata->ier_reg;
+ unsigned long flags;
+ u32 status;
+
+ spin_lock_irqsave(&mxc_isi->slock, flags);
+
+ status = mxc_isi_get_irq_status(mxc_isi);
+ mxc_isi->status = status;
+ mxc_isi_clean_irq_status(mxc_isi, status);
+
+ if (status & CHNL_STS_FRM_STRD_MASK) {
+ if (mxc_isi->m2m_enabled)
+ mxc_isi_m2m_frame_write_done(mxc_isi);
+ else
+ mxc_isi_cap_frame_write_done(mxc_isi);
+ }
+
+ if (status & (CHNL_STS_AXI_WR_ERR_Y_MASK |
+ CHNL_STS_AXI_WR_ERR_U_MASK |
+ CHNL_STS_AXI_WR_ERR_V_MASK))
+ dev_dbg(dev, "%s, IRQ AXI Error stat=0x%X\n", __func__, status);
+
+ if (status & (ier_reg->panic_y_buf_en.mask |
+ ier_reg->panic_u_buf_en.mask |
+ ier_reg->panic_v_buf_en.mask))
+ dev_dbg(dev, "%s, IRQ Panic OFLW Error stat=0x%X\n", __func__, status);
+
+ if (status & (ier_reg->oflw_y_buf_en.mask |
+ ier_reg->oflw_u_buf_en.mask |
+ ier_reg->oflw_v_buf_en.mask))
+ dev_dbg(dev, "%s, IRQ OFLW Error stat=0x%X\n", __func__, status);
+
+ if (status & (ier_reg->excs_oflw_y_buf_en.mask |
+ ier_reg->excs_oflw_u_buf_en.mask |
+ ier_reg->excs_oflw_v_buf_en.mask))
+ dev_dbg(dev, "%s, IRQ EXCS OFLW Error stat=0x%X\n", __func__, status);
+
+ spin_unlock_irqrestore(&mxc_isi->slock, flags);
+ return IRQ_HANDLED;
+}
+
+static int disp_mix_sft_parse_resets(struct mxc_isi_dev *mxc_isi)
+{
+ struct mxc_isi_plat_data const *pdata = mxc_isi->pdata;
+
+ if (mxc_isi->no_dispmix)
+ return 0;
+
+ if (!pdata->rst_ops || !pdata->rst_ops->parse)
+ return -EINVAL;
+
+ return pdata->rst_ops->parse(mxc_isi);
+}
+
+static int disp_mix_sft_rstn(struct mxc_isi_dev *mxc_isi, bool enable)
+{
+ struct mxc_isi_plat_data const *pdata = mxc_isi->pdata;
+ int ret;
+
+ if (mxc_isi->no_dispmix)
+ return 0;
+
+ if (!pdata->rst_ops ||
+ !pdata->rst_ops->assert ||
+ !pdata->rst_ops->deassert)
+ return -EINVAL;
+
+ ret = enable ? pdata->rst_ops->assert(mxc_isi) :
+ pdata->rst_ops->deassert(mxc_isi);
+ return ret;
+}
+
+static int disp_mix_clks_get(struct mxc_isi_dev *mxc_isi)
+{
+ struct mxc_isi_plat_data const *pdata = mxc_isi->pdata;
+
+ if (mxc_isi->no_dispmix)
+ return 0;
+
+ if (!pdata->gclk_ops || !pdata->gclk_ops->gclk_get)
+ return -EINVAL;
+
+ return pdata->gclk_ops->gclk_get(mxc_isi);
+}
+
+static int disp_mix_clks_enable(struct mxc_isi_dev *mxc_isi, bool enable)
+{
+ struct mxc_isi_plat_data const *pdata = mxc_isi->pdata;
+ int ret;
+
+ if (mxc_isi->no_dispmix)
+ return 0;
+
+ if (!pdata->gclk_ops ||
+ !pdata->gclk_ops->gclk_enable ||
+ !pdata->gclk_ops->gclk_disable)
+ return -EINVAL;
+
+ ret = enable ? pdata->gclk_ops->gclk_enable(mxc_isi) :
+ pdata->gclk_ops->gclk_disable(mxc_isi);
+ return ret;
+}
+
+static int mxc_imx8_clk_get(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+
+ mxc_isi->clk = devm_clk_get(dev, NULL);
+
+ if (IS_ERR(mxc_isi->clk)) {
+ dev_err(dev, "failed to get isi clk\n");
+ return PTR_ERR(mxc_isi->clk);
+ }
+
+ return 0;
+}
+
+static int mxc_imx8_clk_enable(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+ int ret;
+
+ ret = clk_prepare_enable(mxc_isi->clk);
+ if (ret < 0) {
+ dev_err(dev, "%s, enable clk error\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mxc_imx8_clk_disable(struct mxc_isi_dev *mxc_isi)
+{
+ clk_disable_unprepare(mxc_isi->clk);
+}
+
+static struct mxc_isi_dev_ops mxc_imx8_clk_ops = {
+ .clk_get = mxc_imx8_clk_get,
+ .clk_enable = mxc_imx8_clk_enable,
+ .clk_disable = mxc_imx8_clk_disable,
+};
+
+static struct mxc_isi_chan_src mxc_imx8_chan_src = {
+ .src_dc0 = 0,
+ .src_dc1 = 1,
+ .src_mipi0 = 2,
+ .src_mipi1 = 3,
+ .src_hdmi = 4,
+ .src_csi = 4,
+ .src_mem = 5,
+};
+
+/* For i.MX8QM/QXP B0 ISI IER version */
+static struct mxc_isi_ier_reg mxc_imx8_isi_ier_v0 = {
+ .oflw_y_buf_en = { .offset = 16, .mask = 0x10000 },
+ .oflw_u_buf_en = { .offset = 19, .mask = 0x80000 },
+ .oflw_v_buf_en = { .offset = 22, .mask = 0x400000 },
+
+ .excs_oflw_y_buf_en = { .offset = 17, .mask = 0x20000 },
+ .excs_oflw_u_buf_en = { .offset = 20, .mask = 0x100000 },
+ .excs_oflw_v_buf_en = { .offset = 23, .mask = 0x800000 },
+
+ .panic_y_buf_en = {.offset = 18, .mask = 0x40000 },
+ .panic_u_buf_en = {.offset = 21, .mask = 0x200000 },
+ .panic_v_buf_en = {.offset = 24, .mask = 0x1000000 },
+};
+
+/* Panic will assert when the buffers are 50% full */
+static struct mxc_isi_set_thd mxc_imx8_isi_thd_v0 = {
+ .panic_set_thd_y = { .mask = 0x03, .offset = 0, .threshold = 0x2 },
+ .panic_set_thd_u = { .mask = 0x18, .offset = 3, .threshold = 0x2 },
+ .panic_set_thd_v = { .mask = 0xC0, .offset = 6, .threshold = 0x2 },
+};
+
+/* For i.MX8QXP C0 and i.MX8MN ISI IER version */
+static struct mxc_isi_ier_reg mxc_imx8_isi_ier_v1 = {
+ .oflw_y_buf_en = { .offset = 19, .mask = 0x80000 },
+ .oflw_u_buf_en = { .offset = 21, .mask = 0x200000 },
+ .oflw_v_buf_en = { .offset = 23, .mask = 0x800000 },
+
+ .panic_y_buf_en = {.offset = 20, .mask = 0x100000 },
+ .panic_u_buf_en = {.offset = 22, .mask = 0x400000 },
+ .panic_v_buf_en = {.offset = 24, .mask = 0x1000000 },
+};
+
+/* For i.MX8MP ISI IER version */
+static struct mxc_isi_ier_reg mxc_imx8_isi_ier_v2 = {
+ .oflw_y_buf_en = { .offset = 18, .mask = 0x40000 },
+ .oflw_u_buf_en = { .offset = 20, .mask = 0x100000 },
+ .oflw_v_buf_en = { .offset = 22, .mask = 0x400000 },
+
+ .panic_y_buf_en = {.offset = 19, .mask = 0x80000 },
+ .panic_u_buf_en = {.offset = 21, .mask = 0x200000 },
+ .panic_v_buf_en = {.offset = 23, .mask = 0x800000 },
+};
+
+/* Panic will assert when the buffers are 50% full */
+static struct mxc_isi_set_thd mxc_imx8_isi_thd_v1 = {
+ .panic_set_thd_y = { .mask = 0x0000F, .offset = 0, .threshold = 0x7 },
+ .panic_set_thd_u = { .mask = 0x00F00, .offset = 8, .threshold = 0x7 },
+ .panic_set_thd_v = { .mask = 0xF0000, .offset = 16, .threshold = 0x7 },
+};
+
+static struct mxc_isi_plat_data mxc_imx8_data = {
+ .ops = &mxc_imx8_clk_ops,
+ .chan_src = &mxc_imx8_chan_src,
+ .ier_reg = &mxc_imx8_isi_ier_v0,
+ .set_thd = &mxc_imx8_isi_thd_v0,
+};
+
+static int mxc_imx8mn_clk_get(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+
+ mxc_isi->clk_disp_axi = devm_clk_get(dev, "disp_axi");
+ if (IS_ERR(mxc_isi->clk_disp_axi)) {
+ dev_err(dev, "failed to get disp_axi clk\n");
+ return PTR_ERR(mxc_isi->clk_disp_axi);
+ }
+
+ mxc_isi->clk_disp_apb = devm_clk_get(dev, "disp_apb");
+ if (IS_ERR(mxc_isi->clk_disp_apb)) {
+ dev_err(dev, "failed to get disp_apb clk\n");
+ return PTR_ERR(mxc_isi->clk_disp_apb);
+ }
+
+ mxc_isi->clk_root_disp_axi = devm_clk_get(dev, "disp_axi_root");
+ if (IS_ERR(mxc_isi->clk_root_disp_axi)) {
+ dev_err(dev, "failed to get disp axi root clk\n");
+ return PTR_ERR(mxc_isi->clk_root_disp_axi);
+ }
+
+ mxc_isi->clk_root_disp_apb = devm_clk_get(dev, "disp_apb_root");
+ if (IS_ERR(mxc_isi->clk_root_disp_apb)) {
+ dev_err(dev, "failed to get disp apb root clk\n");
+ return PTR_ERR(mxc_isi->clk_root_disp_apb);
+ }
+
+ return 0;
+}
+
+static int mxc_imx8mn_clk_enable(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+ int ret;
+
+ ret = clk_prepare_enable(mxc_isi->clk_disp_axi);
+ if (ret < 0) {
+ dev_err(dev, "prepare and enable axi clk error\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(mxc_isi->clk_disp_apb);
+ if (ret < 0) {
+ dev_err(dev, "prepare and enable abp clk error\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(mxc_isi->clk_root_disp_axi);
+ if (ret < 0) {
+ dev_err(dev, "prepare and enable axi root clk error\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(mxc_isi->clk_root_disp_apb);
+ if (ret < 0) {
+ dev_err(dev, "prepare and enable apb root clk error\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mxc_imx8mn_clk_disable(struct mxc_isi_dev *mxc_isi)
+{
+ clk_disable_unprepare(mxc_isi->clk_root_disp_axi);
+ clk_disable_unprepare(mxc_isi->clk_root_disp_apb);
+ clk_disable_unprepare(mxc_isi->clk_disp_axi);
+ clk_disable_unprepare(mxc_isi->clk_disp_apb);
+}
+
+static struct mxc_isi_chan_src mxc_imx8mn_chan_src = {
+ .src_mipi0 = 0,
+ .src_mipi1 = 1,
+ /* For i.MX8MP */
+ .src_mem = 2,
+};
+
+static struct mxc_isi_dev_ops mxc_imx8mn_clk_ops = {
+ .clk_get = mxc_imx8mn_clk_get,
+ .clk_enable = mxc_imx8mn_clk_enable,
+ .clk_disable = mxc_imx8mn_clk_disable,
+};
+
+static int mxc_isi_imx8mn_parse_resets(struct mxc_isi_dev *mxc_isi)
+{
+ int ret;
+ struct device *dev = &mxc_isi->pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *parent, *child;
+ struct of_phandle_args args;
+ struct reset_control *rstc;
+ const char *compat;
+ u32 len, rstc_num = 0;
+
+ ret = of_parse_phandle_with_args(np, "resets", "#reset-cells",
+ 0, &args);
+ if (ret)
+ return ret;
+
+ parent = args.np;
+ for_each_child_of_node(parent, child) {
+ compat = of_get_property(child, "compatible", NULL);
+ if (!compat)
+ continue;
+
+ rstc = of_reset_control_array_get(child, false, false, true);
+ if (IS_ERR(rstc))
+ continue;
+
+ len = strlen(compat);
+ if (!of_compat_cmp("isi,soft-resetn", compat, len)) {
+ mxc_isi->soft_resetn = rstc;
+ rstc_num++;
+ } else if (!of_compat_cmp("isi,clk-enable", compat, len)) {
+ mxc_isi->clk_enable = rstc;
+ rstc_num++;
+ } else {
+ dev_warn(dev, "invalid isi reset node: %s\n", compat);
+ }
+ }
+
+ if (!rstc_num) {
+ dev_err(dev, "no invalid reset control exists\n");
+ return -EINVAL;
+ }
+
+ of_node_put(parent);
+ return 0;
+}
+
+static int mxc_isi_imx8mn_resets_assert(struct mxc_isi_dev *mxc_isi)
+{
+ if (!mxc_isi->soft_resetn)
+ return -EINVAL;
+
+ return reset_control_assert(mxc_isi->soft_resetn);
+}
+
+static int mxc_isi_imx8mn_resets_deassert(struct mxc_isi_dev *mxc_isi)
+{
+ if (!mxc_isi->soft_resetn)
+ return -EINVAL;
+
+ return reset_control_deassert(mxc_isi->soft_resetn);
+}
+
+static struct mxc_isi_rst_ops mxc_imx8mn_isi_rst_ops = {
+ .parse = mxc_isi_imx8mn_parse_resets,
+ .assert = mxc_isi_imx8mn_resets_assert,
+ .deassert = mxc_isi_imx8mn_resets_deassert,
+};
+
+static int mxc_isi_imx8mn_gclk_get(struct mxc_isi_dev *mxc_isi)
+{
+ if (mxc_isi->clk_enable)
+ return 0;
+
+ return mxc_isi_imx8mn_parse_resets(mxc_isi);
+}
+
+static int mxc_isi_imx8mn_gclk_enable(struct mxc_isi_dev *mxc_isi)
+{
+ if (!mxc_isi->clk_enable)
+ return -EINVAL;
+
+ return reset_control_assert(mxc_isi->clk_enable);
+}
+
+static int mxc_isi_imx8mn_gclk_disable(struct mxc_isi_dev *mxc_isi)
+{
+ if (!mxc_isi->clk_enable)
+ return -EINVAL;
+
+ return reset_control_deassert(mxc_isi->clk_enable);
+}
+
+static struct mxc_isi_gate_clk_ops mxc_imx8mn_isi_gclk_ops = {
+ .gclk_get = mxc_isi_imx8mn_gclk_get,
+ .gclk_enable = mxc_isi_imx8mn_gclk_enable,
+ .gclk_disable = mxc_isi_imx8mn_gclk_disable,
+};
+
+static struct mxc_isi_plat_data mxc_imx8mn_data = {
+ .ops = &mxc_imx8mn_clk_ops,
+ .chan_src = &mxc_imx8mn_chan_src,
+ .ier_reg = &mxc_imx8_isi_ier_v1,
+ .set_thd = &mxc_imx8_isi_thd_v1,
+ .rst_ops = &mxc_imx8mn_isi_rst_ops,
+ .gclk_ops = &mxc_imx8mn_isi_gclk_ops,
+};
+
+static int mxc_isi_imx8mp_parse_resets(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+ struct reset_control *reset;
+
+ reset = devm_reset_control_get_optional_shared(dev, "isi_rst_proc");
+ if (IS_ERR(reset)) {
+ if (PTR_ERR(reset) != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get isi proc reset control\n");
+ return PTR_ERR(reset);
+ }
+ mxc_isi->isi_rst_proc = reset;
+
+ reset = devm_reset_control_get_optional_shared(dev, "isi_rst_apb");
+ if (IS_ERR(reset)) {
+ if (PTR_ERR(reset) != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get isi apb reset control\n");
+ return PTR_ERR(reset);
+ }
+ mxc_isi->isi_rst_apb = reset;
+
+ return 0;
+}
+
+static int mxc_isi_imx8mp_resets_assert(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+ int ret;
+
+ if (!mxc_isi->isi_rst_proc || !mxc_isi->isi_rst_apb)
+ return -EINVAL;
+
+ ret = reset_control_assert(mxc_isi->isi_rst_proc);
+ if (ret) {
+ dev_err(dev, "Failed to assert isi proc reset control\n");
+ return ret;
+ }
+
+ ret = reset_control_assert(mxc_isi->isi_rst_apb);
+ if (ret) {
+ dev_err(dev, "Failed to assert isi apb reset control\n");
+ return ret;
+ }
+
+ ret = reset_control_assert(mxc_isi->isi_rst_bus);
+ if (ret) {
+ dev_err(dev, "Failed to assert isi bus reset control\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static int mxc_isi_imx8mp_resets_deassert(struct mxc_isi_dev *mxc_isi)
+{
+ if (!mxc_isi->isi_rst_proc || !mxc_isi->isi_rst_apb)
+ return -EINVAL;
+
+ reset_control_deassert(mxc_isi->isi_rst_proc);
+ reset_control_deassert(mxc_isi->isi_rst_apb);
+ reset_control_deassert(mxc_isi->isi_rst_bus);
+
+ return 0;
+}
+
+static struct mxc_isi_rst_ops mxc_imx8mp_isi_rst_ops = {
+ .parse = mxc_isi_imx8mp_parse_resets,
+ .assert = mxc_isi_imx8mp_resets_assert,
+ .deassert = mxc_isi_imx8mp_resets_deassert,
+};
+
+static int mxc_isi_imx8mp_gclk_get(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+
+ mxc_isi->isi_proc = devm_clk_get(dev, "media_blk_isi_proc");
+ if (IS_ERR(mxc_isi->isi_proc)) {
+ if (PTR_ERR(mxc_isi->isi_proc) != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get media isi proc clock\n");
+ return -ENODEV;
+ }
+
+ mxc_isi->isi_apb = devm_clk_get(dev, "media_blk_isi_apb");
+ if (IS_ERR(mxc_isi->isi_apb)) {
+ if (PTR_ERR(mxc_isi->isi_apb) != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get media isi apb clock\n");
+ return -ENODEV;
+ }
+
+ mxc_isi->isi_bus = devm_clk_get(dev, "media_blk_bus");
+ if (IS_ERR(mxc_isi->isi_bus)) {
+ if (PTR_ERR(mxc_isi->isi_bus) != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get media bus clock\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int mxc_isi_imx8mp_gclk_enable(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+ int ret;
+
+ ret = clk_prepare_enable(mxc_isi->isi_proc);
+ if (ret) {
+ dev_err(dev, "enable isi proc clock failed!\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(mxc_isi->isi_apb);
+ if (ret) {
+ dev_err(dev, "enable isi apb clock failed!\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(mxc_isi->isi_bus);
+ if (ret) {
+ dev_err(dev, "enable bus clock failed!\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static int mxc_isi_imx8mp_gclk_disable(struct mxc_isi_dev *mxc_isi)
+{
+ clk_disable_unprepare(mxc_isi->isi_proc);
+ clk_disable_unprepare(mxc_isi->isi_apb);
+ clk_disable_unprepare(mxc_isi->isi_bus);
+
+ return 0;
+}
+
+static struct mxc_isi_gate_clk_ops mxc_imx8mp_isi_gclk_ops = {
+ .gclk_get = mxc_isi_imx8mp_gclk_get,
+ .gclk_enable = mxc_isi_imx8mp_gclk_enable,
+ .gclk_disable = mxc_isi_imx8mp_gclk_disable,
+};
+
+static struct mxc_isi_plat_data mxc_imx8mp_data = {
+ .ops = &mxc_imx8mn_clk_ops,
+ .chan_src = &mxc_imx8mn_chan_src,
+ .ier_reg = &mxc_imx8_isi_ier_v1,
+ .set_thd = &mxc_imx8_isi_thd_v1,
+ .rst_ops = &mxc_imx8mp_isi_rst_ops,
+ .gclk_ops = &mxc_imx8mp_isi_gclk_ops,
+};
+
+static struct mxc_isi_plat_data mxc_imx8ulp_data = {
+ .ops = &mxc_imx8_clk_ops,
+ .chan_src = &mxc_imx8mn_chan_src,
+ .ier_reg = &mxc_imx8_isi_ier_v2,
+ .set_thd = &mxc_imx8_isi_thd_v1,
+};
+
+static int mxc_isi_parse_dt(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+ struct device_node *node = dev->of_node;
+ int ret = 0;
+
+ mxc_isi->id = of_alias_get_id(node, "isi");
+
+ ret = of_property_read_u32_array(node, "interface", mxc_isi->interface, 3);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(dev, "%s, isi_%d,interface(%d, %d, %d)\n", __func__,
+ mxc_isi->id,
+ mxc_isi->interface[0],
+ mxc_isi->interface[1],
+ mxc_isi->interface[2]);
+ return 0;
+}
+
+static int mxc_isi_clk_get(struct mxc_isi_dev *mxc_isi)
+{
+ const struct mxc_isi_dev_ops *ops = mxc_isi->pdata->ops;
+
+ if (!ops || !ops->clk_get)
+ return -EINVAL;
+
+ return ops->clk_get(mxc_isi);
+}
+
+static int mxc_isi_clk_enable(struct mxc_isi_dev *mxc_isi)
+{
+ const struct mxc_isi_dev_ops *ops = mxc_isi->pdata->ops;
+
+ if (!ops || !ops->clk_enable)
+ return -EINVAL;
+
+ return ops->clk_enable(mxc_isi);
+}
+
+static void mxc_isi_clk_disable(struct mxc_isi_dev *mxc_isi)
+{
+ const struct mxc_isi_dev_ops *ops = mxc_isi->pdata->ops;
+
+ if (!ops || !ops->clk_disable)
+ return;
+
+ ops->clk_disable(mxc_isi);
+}
+
+static int mxc_isi_soc_match(struct mxc_isi_dev *mxc_isi,
+ const struct soc_device_attribute *data)
+{
+ struct mxc_isi_ier_reg *ier_reg = mxc_isi->pdata->ier_reg;
+ struct mxc_isi_set_thd *set_thd = mxc_isi->pdata->set_thd;
+ const struct soc_device_attribute *match;
+
+ match = soc_device_match(data);
+ if (!match)
+ return -EPROBE_DEFER;
+
+ mxc_isi->buf_active_reverse = false;
+
+ if (!strcmp(match->soc_id, "i.MX8QXP") ||
+ !strcmp(match->soc_id, "i.MX8QM")) {
+ /* Chip C0 */
+ if (strcmp(match->revision, "1.1") > 0) {
+ memcpy(ier_reg, &mxc_imx8_isi_ier_v1, sizeof(*ier_reg));
+ memcpy(set_thd, &mxc_imx8_isi_thd_v1, sizeof(*set_thd));
+ mxc_isi->buf_active_reverse = true;
+ }
+ } else if (!strcmp(match->soc_id, "i.MX8MP") ||
+ !strcmp(match->soc_id, "i.MX8ULP")) {
+ memcpy(ier_reg, &mxc_imx8_isi_ier_v2, sizeof(*ier_reg));
+ mxc_isi->buf_active_reverse = true;
+ }
+
+ return 0;
+}
+
+static int mxc_isi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mxc_isi_dev *mxc_isi;
+ struct resource *res;
+ const struct of_device_id *of_id;
+ int ret = 0;
+
+
+ mxc_isi = devm_kzalloc(dev, sizeof(*mxc_isi), GFP_KERNEL);
+ if (!mxc_isi)
+ return -ENOMEM;
+
+ mxc_isi->pdev = pdev;
+ of_id = of_match_node(mxc_isi_of_match, dev->of_node);
+ if (!of_id)
+ return -EINVAL;
+
+ mxc_isi->pdata = of_id->data;
+ if (!mxc_isi->pdata) {
+ dev_err(dev, "Can't get platform device data\n");
+ return -EINVAL;
+ }
+
+ ret = mxc_isi_soc_match(mxc_isi, imx8_soc);
+ if (ret < 0) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Can't match soc version\n");
+ return ret;
+ }
+
+ ret = mxc_isi_parse_dt(mxc_isi);
+ if (ret < 0)
+ return ret;
+
+ if (mxc_isi->id >= MXC_ISI_MAX_DEVS || mxc_isi->id < 0) {
+ dev_err(dev, "Invalid driver data or device id (%d)\n",
+ mxc_isi->id);
+ return -EINVAL;
+ }
+
+ mxc_isi->chain = syscon_regmap_lookup_by_phandle(dev->of_node, "isi_chain");
+ if (IS_ERR(mxc_isi->chain))
+ mxc_isi->chain = NULL;
+
+ spin_lock_init(&mxc_isi->slock);
+ mutex_init(&mxc_isi->lock);
+ atomic_set(&mxc_isi->usage_count, 0);
+
+ mxc_isi->no_dispmix =
+ of_property_read_bool(dev->of_node, "no-reset-control");
+
+ ret = disp_mix_sft_parse_resets(mxc_isi);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Can not parse reset control for isi\n");
+ return ret;
+ }
+
+ ret = mxc_isi_clk_get(mxc_isi);
+ if (ret < 0) {
+ dev_err(dev, "ISI_%d get clocks fail\n", mxc_isi->id);
+ return ret;
+ }
+
+ ret = disp_mix_clks_get(mxc_isi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to get disp mix clocks");
+ return ret;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mxc_isi->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(mxc_isi->regs)) {
+ dev_err(dev, "Failed to get ISI register map\n");
+ return PTR_ERR(mxc_isi->regs);
+ }
+
+ ret = mxc_isi_clk_enable(mxc_isi);
+ if (ret < 0) {
+ dev_err(dev, "ISI_%d enable clocks fail\n", mxc_isi->id);
+ return ret;
+ }
+ disp_mix_sft_rstn(mxc_isi, false);
+ disp_mix_clks_enable(mxc_isi, true);
+
+ mxc_isi_clean_registers(mxc_isi);
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(dev, "Failed to get IRQ resource\n");
+ goto err;
+ }
+ ret = devm_request_irq(dev, res->start, mxc_isi_irq_handler,
+ 0, dev_name(dev), mxc_isi);
+ if (ret < 0) {
+ dev_err(dev, "failed to install irq (%d)\n", ret);
+ goto err;
+ }
+
+ mxc_isi_channel_set_chain_buf(mxc_isi);
+
+ ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
+ if (ret < 0)
+ dev_warn(dev, "Populate child platform device fail\n");
+
+ mxc_isi_clk_disable(mxc_isi);
+ disp_mix_clks_enable(mxc_isi, false);
+
+ platform_set_drvdata(pdev, mxc_isi);
+ pm_runtime_enable(dev);
+
+ dev_info(dev, "mxc_isi.%d registered successfully\n", mxc_isi->id);
+ return 0;
+
+err:
+ disp_mix_clks_enable(mxc_isi, false);
+ disp_mix_sft_rstn(mxc_isi, true);
+ mxc_isi_clk_disable(mxc_isi);
+ return -ENXIO;
+}
+
+static int mxc_isi_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ of_platform_depopulate(dev);
+ pm_runtime_disable(dev);
+
+ return 0;
+}
+
+static int mxc_isi_pm_suspend(struct device *dev)
+{
+ struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev);
+
+ if (mxc_isi->is_streaming) {
+ dev_warn(dev, "running, prevent entering suspend.\n");
+ return -EAGAIN;
+ }
+
+ return pm_runtime_force_suspend(dev);
+}
+
+static int mxc_isi_pm_resume(struct device *dev)
+{
+ return pm_runtime_force_resume(dev);
+}
+
+static int mxc_isi_runtime_suspend(struct device *dev)
+{
+ struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev);
+
+ disp_mix_clks_enable(mxc_isi, false);
+ mxc_isi_clk_disable(mxc_isi);
+
+ return 0;
+}
+
+static int mxc_isi_runtime_resume(struct device *dev)
+{
+ struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev);
+ int ret;
+
+ ret = mxc_isi_clk_enable(mxc_isi);
+ if (ret) {
+ dev_err(dev, "%s clk enable fail\n", __func__);
+ return ret;
+ }
+ disp_mix_sft_rstn(mxc_isi, false);
+ disp_mix_clks_enable(mxc_isi, true);
+
+ return 0;
+}
+
+static const struct dev_pm_ops mxc_isi_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(mxc_isi_pm_suspend, mxc_isi_pm_resume)
+ SET_RUNTIME_PM_OPS(mxc_isi_runtime_suspend, mxc_isi_runtime_resume, NULL)
+};
+
+static const struct of_device_id mxc_isi_of_match[] = {
+ {.compatible = "fsl,imx8-isi", .data = &mxc_imx8_data },
+ {.compatible = "fsl,imx8mn-isi", .data = &mxc_imx8mn_data },
+ {.compatible = "fsl,imx8mp-isi", .data = &mxc_imx8mp_data },
+ {.compatible = "fsl,imx8ulp-isi", .data = &mxc_imx8ulp_data },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mxc_isi_of_match);
+
+static struct platform_driver mxc_isi_driver = {
+ .probe = mxc_isi_probe,
+ .remove = mxc_isi_remove,
+ .driver = {
+ .of_match_table = mxc_isi_of_match,
+ .name = MXC_ISI_DRIVER_NAME,
+ .pm = &mxc_isi_pm_ops,
+ }
+};
+module_platform_driver(mxc_isi_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX8 Image Subsystem driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ISI");
+MODULE_VERSION("1.0");
diff --git a/drivers/staging/media/imx/imx8-isi-core.h b/drivers/staging/media/imx/imx8-isi-core.h
new file mode 100644
index 000000000000..5dd16c6de297
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-core.h
@@ -0,0 +1,457 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2019-2020 NXP
+ */
+
+#ifndef __MXC_ISI_CORE_H__
+#define __MXC_ISI_CORE_H__
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/list.h>
+#include <linux/mfd/syscon.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <media/media-device.h>
+#include <media/media-entity.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-core.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/sys_soc.h>
+
+#include "imx8-common.h"
+
+#define MXC_ISI_DRIVER_NAME "mxc-isi"
+#define MXC_ISI_CAPTURE "mxc-isi-cap"
+#define MXC_ISI_M2M "mxc-isi-m2m"
+#define MXC_MAX_PLANES 3
+
+struct mxc_isi_dev;
+
+enum mxc_isi_out_fmt {
+ MXC_ISI_OUT_FMT_RGBA32 = 0x0,
+ MXC_ISI_OUT_FMT_ABGR32,
+ MXC_ISI_OUT_FMT_ARGB32,
+ MXC_ISI_OUT_FMT_RGBX32,
+ MXC_ISI_OUT_FMT_XBGR32,
+ MXC_ISI_OUT_FMT_XRGB32,
+ MXC_ISI_OUT_FMT_RGB32P,
+ MXC_ISI_OUT_FMT_BGR32P,
+ MXC_ISI_OUT_FMT_A2BGR10,
+ MXC_ISI_OUT_FMT_A2RGB10,
+ MXC_ISI_OUT_FMT_RGB565,
+ MXC_ISI_OUT_FMT_RAW8,
+ MXC_ISI_OUT_FMT_RAW10,
+ MXC_ISI_OUT_FMT_RAW10P,
+ MXC_ISI_OUT_FMT_RAW12,
+ MXC_ISI_OUT_FMT_RAW16,
+ MXC_ISI_OUT_FMT_YUV444_1P8P,
+ MXC_ISI_OUT_FMT_YUV444_2P8P,
+ MXC_ISI_OUT_FMT_YUV444_3P8P,
+ MXC_ISI_OUT_FMT_YUV444_1P8,
+ MXC_ISI_OUT_FMT_YUV444_1P10,
+ MXC_ISI_OUT_FMT_YUV444_2P10,
+ MXC_ISI_OUT_FMT_YUV444_3P10,
+ MXC_ISI_OUT_FMT_YUV444_1P10P = 0x18,
+ MXC_ISI_OUT_FMT_YUV444_2P10P,
+ MXC_ISI_OUT_FMT_YUV444_3P10P,
+ MXC_ISI_OUT_FMT_YUV444_1P12 = 0x1C,
+ MXC_ISI_OUT_FMT_YUV444_2P12,
+ MXC_ISI_OUT_FMT_YUV444_3P12,
+ MXC_ISI_OUT_FMT_YUV422_1P8P = 0x20,
+ MXC_ISI_OUT_FMT_YUV422_2P8P,
+ MXC_ISI_OUT_FMT_YUV422_3P8P,
+ MXC_ISI_OUT_FMT_YUV422_1P10 = 0x24,
+ MXC_ISI_OUT_FMT_YUV422_2P10,
+ MXC_ISI_OUT_FMT_YUV422_3P10,
+ MXC_ISI_OUT_FMT_YUV422_1P10P = 0x28,
+ MXC_ISI_OUT_FMT_YUV422_2P10P,
+ MXC_ISI_OUT_FMT_YUV422_3P10P,
+ MXC_ISI_OUT_FMT_YUV422_1P12 = 0x2C,
+ MXC_ISI_OUT_FMT_YUV422_2P12,
+ MXC_ISI_OUT_FMT_YUV422_3P12,
+ MXC_ISI_OUT_FMT_YUV420_2P8P = 0x31,
+ MXC_ISI_OUT_FMT_YUV420_3P8P,
+ MXC_ISI_OUT_FMT_YUV420_2P10 = 0x35,
+ MXC_ISI_OUT_FMT_YUV420_3P10,
+ MXC_ISI_OUT_FMT_YUV420_2P10P = 0x39,
+ MXC_ISI_OUT_FMT_YUV420_3P10P,
+ MXC_ISI_OUT_FMT_YUV420_2P12 = 0x3D,
+ MXC_ISI_OUT_FMT_YUV420_3P12,
+};
+
+enum mxc_isi_in_fmt {
+ MXC_ISI_IN_FMT_BGR8P = 0x0,
+};
+
+enum mxc_isi_m2m_in_fmt {
+ MXC_ISI_M2M_IN_FMT_BGR8P = 0x0,
+ MXC_ISI_M2M_IN_FMT_RGB8P,
+ MXC_ISI_M2M_IN_FMT_XRGB8,
+ MXC_ISI_M2M_IN_FMT_RGBX8,
+ MXC_ISI_M2M_IN_FMT_XBGR8,
+ MXC_ISI_M2M_IN_FMT_RGB565,
+ MXC_ISI_M2M_IN_FMT_A2BGR10,
+ MXC_ISI_M2M_IN_FMT_A2RGB10,
+ MXC_ISI_M2M_IN_FMT_YUV444_1P8P,
+ MXC_ISI_M2M_IN_FMT_YUV444_1P10,
+ MXC_ISI_M2M_IN_FMT_YUV444_1P10P,
+ MXC_ISI_M2M_IN_FMT_YUV444_1P12,
+ MXC_ISI_M2M_IN_FMT_YUV444_1P8,
+ MXC_ISI_M2M_IN_FMT_YUV422_1P8P,
+ MXC_ISI_M2M_IN_FMT_YUV422_1P10,
+ MXC_ISI_M2M_IN_FMT_YUV422_1P10P,
+};
+
+struct mxc_isi_fmt {
+ char *name;
+ u32 mbus_code;
+ u32 fourcc;
+ u32 color;
+ u16 memplanes;
+ u16 colplanes;
+ u8 colorspace;
+ u8 depth[MXC_MAX_PLANES];
+ u16 mdataplanes;
+ u16 flags;
+};
+
+struct mxc_isi_ctrls {
+ struct v4l2_ctrl_handler handler;
+ struct v4l2_ctrl *hflip;
+ struct v4l2_ctrl *vflip;
+ struct v4l2_ctrl *alpha;
+ struct v4l2_ctrl *num_cap_buf;
+ struct v4l2_ctrl *num_out_buf;
+ bool ready;
+};
+
+/**
+ * struct addr - physical address set for DMA
+ * @y: luminance plane physical address
+ * @cb: Cb plane physical address
+ * @cr: Cr plane physical address
+ */
+struct frame_addr {
+ u32 y;
+ u32 cb;
+ u32 cr;
+};
+
+/**
+ * struct mxc_isi_frame - source/target frame properties
+ * o_width: original image width from sensor
+ * o_height: original image height from sensor
+ * c_width: crop image width set by g_selection
+ * c_height: crop image height set by g_selection
+ * h_off: crop horizontal pixel offset
+ * v_off: crop vertical pixel offset
+ * width: out image pixel width
+ * height: out image pixel weight
+ * bytesperline: bytesperline value for each plane
+ * paddr: image frame buffer physical addresses
+ * fmt: color format pointer
+ */
+struct mxc_isi_frame {
+ u32 o_width;
+ u32 o_height;
+ u32 c_width;
+ u32 c_height;
+ u32 h_off;
+ u32 v_off;
+ u32 width;
+ u32 height;
+ unsigned int sizeimage[MXC_MAX_PLANES];
+ unsigned int bytesperline[MXC_MAX_PLANES];
+ struct mxc_isi_fmt *fmt;
+};
+
+struct mxc_isi_roi_alpha {
+ u8 alpha;
+ struct v4l2_rect rect;
+};
+
+struct mxc_isi_buffer {
+ struct vb2_v4l2_buffer v4l2_buf;
+ struct list_head list;
+ struct frame_addr paddr;
+ enum mxc_isi_buf_id id;
+ bool discard;
+};
+
+struct mxc_isi_m2m_dev {
+ struct platform_device *pdev;
+
+ struct video_device vdev;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_m2m_dev *m2m_dev;
+ struct v4l2_fh fh;
+ struct v4l2_pix_format_mplane pix;
+
+ struct list_head out_active;
+ struct mxc_isi_ctrls ctrls;
+
+ struct mxc_isi_frame src_f;
+ struct mxc_isi_frame dst_f;
+
+ struct mutex lock;
+ spinlock_t slock;
+
+ unsigned int aborting;
+ unsigned int frame_count;
+
+ enum v4l2_colorspace colorspace;
+ enum v4l2_ycbcr_encoding ycbcr_enc;
+ enum v4l2_quantization quant;
+ enum v4l2_xfer_func xfer_func;
+
+ u32 req_cap_buf_num;
+ u32 req_out_buf_num;
+
+ u8 id;
+ int refcnt;
+};
+
+struct mxc_isi_ctx {
+ struct mxc_isi_m2m_dev *isi_m2m;
+ struct v4l2_fh fh;
+};
+
+struct mxc_isi_chan_src {
+ u32 src_dc0;
+ u32 src_dc1;
+ u32 src_mipi0;
+ u32 src_mipi1;
+ u32 src_hdmi;
+ u32 src_csi;
+ u32 src_mem;
+};
+
+struct mxc_isi_reg {
+ u32 offset;
+ u32 mask;
+};
+
+struct mxc_isi_ier_reg {
+ /* Overflow Y/U/V triggier enable*/
+ struct mxc_isi_reg oflw_y_buf_en;
+ struct mxc_isi_reg oflw_u_buf_en;
+ struct mxc_isi_reg oflw_v_buf_en;
+
+ /* Excess overflow Y/U/V triggier enable*/
+ struct mxc_isi_reg excs_oflw_y_buf_en;
+ struct mxc_isi_reg excs_oflw_u_buf_en;
+ struct mxc_isi_reg excs_oflw_v_buf_en;
+
+ /* Panic Y/U/V triggier enable*/
+ struct mxc_isi_reg panic_y_buf_en;
+ struct mxc_isi_reg panic_v_buf_en;
+ struct mxc_isi_reg panic_u_buf_en;
+};
+
+struct mxc_isi_dev_ops {
+ int (*clk_get)(struct mxc_isi_dev *mxc_isi);
+ int (*clk_enable)(struct mxc_isi_dev *mxc_isi);
+ void (*clk_disable)(struct mxc_isi_dev *mxc_isi);
+};
+
+struct mxc_isi_panic_thd {
+ u32 mask;
+ u32 offset;
+ u32 threshold;
+};
+
+struct mxc_isi_set_thd {
+ struct mxc_isi_panic_thd panic_set_thd_y;
+ struct mxc_isi_panic_thd panic_set_thd_u;
+ struct mxc_isi_panic_thd panic_set_thd_v;
+};
+
+struct mxc_isi_rst_ops {
+ int (*parse)(struct mxc_isi_dev *mxc_isi);
+ int (*assert)(struct mxc_isi_dev *mxc_isi);
+ int (*deassert)(struct mxc_isi_dev *mxc_isi);
+};
+
+struct mxc_isi_gate_clk_ops {
+ int (*gclk_get)(struct mxc_isi_dev *mxc_isi);
+ int (*gclk_enable)(struct mxc_isi_dev *mxc_isi);
+ int (*gclk_disable)(struct mxc_isi_dev *mxc_isi);
+};
+
+struct mxc_isi_plat_data {
+ struct mxc_isi_dev_ops *ops;
+ struct mxc_isi_chan_src *chan_src;
+ struct mxc_isi_ier_reg *ier_reg;
+ struct mxc_isi_set_thd *set_thd;
+ struct mxc_isi_rst_ops *rst_ops;
+ struct mxc_isi_gate_clk_ops *gclk_ops;
+};
+
+struct mxc_isi_cap_dev {
+ struct v4l2_subdev sd;
+ struct video_device vdev;
+ struct v4l2_fh fh;
+ struct vb2_queue vb2_q;
+ struct v4l2_pix_format_mplane pix;
+
+ struct mxc_isi_dev *mxc_isi;
+ struct platform_device *pdev;
+ struct mxc_isi_ctrls ctrls;
+ struct mxc_isi_buffer buf_discard[2];
+
+ struct media_pad cap_pad;
+ struct media_pad sd_pads[MXC_ISI_SD_PADS_NUM];
+
+ struct list_head out_pending;
+ struct list_head out_active;
+ struct list_head out_discard;
+
+ struct mxc_isi_frame src_f;
+ struct mxc_isi_frame dst_f;
+
+ u32 frame_count;
+ u32 id;
+ u32 is_streaming[MXC_ISI_MAX_DEVS];
+ bool is_link_setup;
+
+ struct mutex lock;
+ spinlock_t slock;
+
+ /* dirty buffer */
+ size_t discard_size[MXC_MAX_PLANES];
+ void *discard_buffer[MXC_MAX_PLANES];
+ dma_addr_t discard_buffer_dma[MXC_MAX_PLANES];
+};
+
+struct mxc_isi_dev {
+ /* Pointer to isi capture child device driver data */
+ struct mxc_isi_cap_dev *isi_cap;
+
+ /* Pointer to isi m2m child device driver data */
+ struct mxc_isi_m2m_dev *isi_m2m;
+
+ struct platform_device *pdev;
+
+ /* clk for imx8qxp/qm platform */
+ struct clk *clk;
+
+ /* clks for imx8mn platform */
+ struct clk *clk_disp_axi;
+ struct clk *clk_disp_apb;
+ struct clk *clk_root_disp_axi;
+ struct clk *clk_root_disp_apb;
+ struct clk *isi_proc;
+ struct clk *isi_apb;
+ struct clk *isi_bus;
+
+ const struct mxc_isi_plat_data *pdata;
+
+ struct reset_control *soft_resetn;
+ struct reset_control *clk_enable;
+ struct reset_control *isi_rst_proc;
+ struct reset_control *isi_rst_apb;
+ struct reset_control *isi_rst_bus;
+
+ struct regmap *chain;
+
+ struct mutex lock;
+ spinlock_t slock;
+
+ void __iomem *regs;
+
+ u8 chain_buf;
+ u8 alpha;
+ bool m2m_enabled;
+ bool cap_enabled;
+ bool buf_active_reverse;
+ bool no_dispmix;
+
+ /* manage share ISI channel resource */
+ atomic_t usage_count;
+
+ /* scale factor */
+ u32 xfactor;
+ u32 yfactor;
+ u32 pre_dec_x;
+ u32 pre_dec_y;
+
+ u32 status;
+
+ int interface[MAX_PORTS];
+ int id;
+
+ unsigned int hflip:1;
+ unsigned int vflip:1;
+ unsigned int cscen:1;
+ unsigned int scale:1;
+ unsigned int alphaen:1;
+ unsigned int crop:1;
+ unsigned int deinterlace:3;
+ unsigned int is_streaming:1;
+};
+
+static inline void set_frame_bounds(struct mxc_isi_frame *f,
+ u32 width, u32 height)
+{
+ f->o_width = width;
+ f->o_height = height;
+ f->c_width = width;
+ f->c_height = height;
+ f->width = width;
+ f->height = height;
+}
+
+static inline void set_frame_out(struct mxc_isi_frame *f,
+ u32 width, u32 height)
+{
+ f->c_width = width;
+ f->c_height = height;
+ f->width = width;
+ f->height = height;
+}
+
+static inline void set_frame_crop(struct mxc_isi_frame *f,
+ u32 left, u32 top, u32 width, u32 height)
+{
+ f->h_off = left;
+ f->v_off = top;
+ f->c_width = width;
+ f->c_height = height;
+}
+
+static inline void bounds_adjust(struct mxc_isi_frame *f, struct v4l2_rect *r)
+{
+ if (r->left < 0)
+ r->left = 0;
+ if (r->left >= f->o_width)
+ r->left = f->o_width - 1;
+ if (r->top < 0)
+ r->top = 0;
+ if (r->top >= f->o_height)
+ r->top = f->o_height - 1;
+
+ if (r->left + r->width >= f->o_width)
+ r->width = f->o_width - r->left;
+ if (r->top + r->height >= f->o_height)
+ r->height = f->o_height - r->top;
+}
+#endif /* __MXC_ISI_CORE_H__ */
diff --git a/drivers/staging/media/imx/imx8-isi-fmt.c b/drivers/staging/media/imx/imx8-isi-fmt.c
new file mode 100644
index 000000000000..d9b778680706
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-fmt.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2021 NXP
+ *
+ */
+
+#include "imx8-isi-core.h"
+
+struct mxc_isi_fmt mxc_isi_out_formats[] = {
+ {
+ .name = "RGB565",
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .depth = { 16 },
+ .color = MXC_ISI_OUT_FMT_RGB565,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_RGB565_1X16,
+ }, {
+ .name = "RGB24",
+ .fourcc = V4L2_PIX_FMT_RGB24,
+ .depth = { 24 },
+ .color = MXC_ISI_OUT_FMT_BGR32P,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+ }, {
+ .name = "BGR24",
+ .fourcc = V4L2_PIX_FMT_BGR24,
+ .depth = { 24 },
+ .color = MXC_ISI_OUT_FMT_RGB32P,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_BGR888_1X24,
+ }, {
+ .name = "YUYV-16",
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .depth = { 16 },
+ .color = MXC_ISI_OUT_FMT_YUV422_1P8P,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
+ }, {
+ .name = "YUV32 (X-Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUV32,
+ .depth = { 32 },
+ .color = MXC_ISI_OUT_FMT_YUV444_1P8,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_AYUV8_1X32,
+ }, {
+ .name = "NV12 (YUYV)",
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .depth = { 8, 8 },
+ .color = MXC_ISI_OUT_FMT_YUV420_2P8P,
+ .memplanes = 2,
+ .colplanes = 2,
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
+ }, {
+ .name = "YUV444M (Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUV444M,
+ .depth = { 8, 8, 8 },
+ .color = MXC_ISI_OUT_FMT_YUV444_3P8P,
+ .memplanes = 3,
+ .colplanes = 3,
+ .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
+ }, {
+ .name = "xBGR32",
+ .fourcc = V4L2_PIX_FMT_XBGR32,
+ .depth = { 32 },
+ .color = MXC_ISI_OUT_FMT_XRGB32,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+ }, {
+ .name = "ABGR32",
+ .fourcc = V4L2_PIX_FMT_ABGR32,
+ .depth = { 32 },
+ .color = MXC_ISI_OUT_FMT_ARGB32,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+ }
+};
+
+size_t mxc_isi_out_formats_size = ARRAY_SIZE(mxc_isi_out_formats);
diff --git a/drivers/staging/media/imx/imx8-isi-fmt.h b/drivers/staging/media/imx/imx8-isi-fmt.h
new file mode 100644
index 000000000000..d4243943a7b7
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-fmt.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2021 NXP
+ */
+
+#ifndef __MXC_ISI_FMT_H__
+#define __MXC_ISI_FMT_H__
+
+extern size_t mxc_isi_out_formats_size;
+extern struct mxc_isi_fmt mxc_isi_out_formats[];
+
+#endif /* __MXC_ISI_FMT_H__ */
diff --git a/drivers/staging/media/imx/imx8-isi-hw.c b/drivers/staging/media/imx/imx8-isi-hw.c
new file mode 100644
index 000000000000..08b57fe28f86
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-hw.c
@@ -0,0 +1,829 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019-2020 NXP
+ *
+ */
+#include <dt-bindings/pinctrl/pads-imx8qxp.h>
+
+#include <linux/module.h>
+#include "imx8-isi-hw.h"
+#include "imx8-common.h"
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX8 Image Sensor Interface Hardware driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+
+#define ISI_DOWNSCALE_THRESHOLD 0x4000
+
+#ifdef DEBUG
+void dump_isi_regs(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+ struct {
+ u32 offset;
+ const char *const name;
+ } registers[] = {
+ { 0x00, "CHNL_CTRL" },
+ { 0x04, "CHNL_IMG_CTRL" },
+ { 0x08, "CHNL_OUT_BUF_CTRL" },
+ { 0x0C, "CHNL_IMG_CFG" },
+ { 0x10, "CHNL_IER" },
+ { 0x14, "CHNL_STS" },
+ { 0x18, "CHNL_SCALE_FACTOR" },
+ { 0x1C, "CHNL_SCALE_OFFSET" },
+ { 0x20, "CHNL_CROP_ULC" },
+ { 0x24, "CHNL_CROP_LRC" },
+ { 0x28, "CHNL_CSC_COEFF0" },
+ { 0x2C, "CHNL_CSC_COEFF1" },
+ { 0x30, "CHNL_CSC_COEFF2" },
+ { 0x34, "CHNL_CSC_COEFF3" },
+ { 0x38, "CHNL_CSC_COEFF4" },
+ { 0x3C, "CHNL_CSC_COEFF5" },
+ { 0x40, "CHNL_ROI_0_ALPHA" },
+ { 0x44, "CHNL_ROI_0_ULC" },
+ { 0x48, "CHNL_ROI_0_LRC" },
+ { 0x4C, "CHNL_ROI_1_ALPHA" },
+ { 0x50, "CHNL_ROI_1_ULC" },
+ { 0x54, "CHNL_ROI_1_LRC" },
+ { 0x58, "CHNL_ROI_2_ALPHA" },
+ { 0x5C, "CHNL_ROI_2_ULC" },
+ { 0x60, "CHNL_ROI_2_LRC" },
+ { 0x64, "CHNL_ROI_3_ALPHA" },
+ { 0x68, "CHNL_ROI_3_ULC" },
+ { 0x6C, "CHNL_ROI_3_LRC" },
+ { 0x70, "CHNL_OUT_BUF1_ADDR_Y" },
+ { 0x74, "CHNL_OUT_BUF1_ADDR_U" },
+ { 0x78, "CHNL_OUT_BUF1_ADDR_V" },
+ { 0x7C, "CHNL_OUT_BUF_PITCH" },
+ { 0x80, "CHNL_IN_BUF_ADDR" },
+ { 0x84, "CHNL_IN_BUF_PITCH" },
+ { 0x88, "CHNL_MEM_RD_CTRL" },
+ { 0x8C, "CHNL_OUT_BUF2_ADDR_Y" },
+ { 0x90, "CHNL_OUT_BUF2_ADDR_U" },
+ { 0x94, "CHNL_OUT_BUF2_ADDR_V" },
+ { 0x98, "CHNL_SCL_IMG_CFG" },
+ { 0x9C, "CHNL_FLOW_CTRL" },
+ };
+ u32 i;
+
+ dev_dbg(dev, "ISI CHNLC register dump, isi%d\n", mxc_isi->id);
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ u32 reg = readl(mxc_isi->regs + registers[i].offset);
+ dev_dbg(dev, "%20s[0x%.2x]: %.2x\n",
+ registers[i].name, registers[i].offset, reg);
+ }
+}
+#else
+void dump_isi_regs(struct mxc_isi_dev *mxc_isi)
+{
+}
+#endif
+
+/*
+ * A2,A1, B1, A3, B3, B2,
+ * C2, C1, D1, C3, D3, D2
+ */
+static const u32 coeffs[2][6] = {
+ /* YUV2RGB */
+ { 0x0000012A, 0x012A0198, 0x0730079C,
+ 0x0204012A, 0x01F00000, 0x01800180 },
+
+ /* RGB->YUV */
+ { 0x00810041, 0x07db0019, 0x007007b6,
+ 0x07a20070, 0x001007ee, 0x00800080 },
+};
+
+static void printk_pixelformat(char *prefix, int val)
+{
+ pr_info("%s %c%c%c%c\n", prefix ? prefix : "pixelformat",
+ val & 0xff,
+ (val >> 8) & 0xff,
+ (val >> 16) & 0xff,
+ (val >> 24) & 0xff);
+}
+
+static bool is_rgb(u32 pix_fmt)
+{
+ if ((pix_fmt == V4L2_PIX_FMT_RGB565) ||
+ (pix_fmt == V4L2_PIX_FMT_RGB24) ||
+ (pix_fmt == V4L2_PIX_FMT_RGB32) ||
+ (pix_fmt == V4L2_PIX_FMT_BGR32) ||
+ (pix_fmt == V4L2_PIX_FMT_XRGB32) ||
+ (pix_fmt == V4L2_PIX_FMT_XBGR32) ||
+ (pix_fmt == V4L2_PIX_FMT_BGR24) ||
+ (pix_fmt == V4L2_PIX_FMT_RGBA32) ||
+ (pix_fmt == V4L2_PIX_FMT_ABGR32) ||
+ (pix_fmt == V4L2_PIX_FMT_ARGB32))
+ return true;
+ else
+ return false;
+}
+
+static bool is_yuv(u32 pix_fmt)
+{
+ if ((pix_fmt == V4L2_PIX_FMT_YUYV) ||
+ (pix_fmt == V4L2_PIX_FMT_YUV32) ||
+ (pix_fmt == V4L2_PIX_FMT_YUV444M) ||
+ (pix_fmt == V4L2_PIX_FMT_NV12))
+ return true;
+ else
+ return false;
+}
+
+bool is_buf_active(struct mxc_isi_dev *mxc_isi, int buf_id)
+{
+ u32 status = mxc_isi->status;
+ bool reverse = mxc_isi->buf_active_reverse;
+
+ return (buf_id == 1) ? ((reverse) ? (status & 0x100) : (status & 0x200)) :
+ ((reverse) ? (status & 0x200) : (status & 0x100));
+}
+EXPORT_SYMBOL_GPL(is_buf_active);
+
+static void chain_buf(struct mxc_isi_dev *mxc_isi, struct mxc_isi_frame *frm)
+{
+ u32 val;
+
+ if (frm->o_width > ISI_2K) {
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~CHNL_CTRL_CHAIN_BUF_MASK;
+ val |= (CHNL_CTRL_CHAIN_BUF_2_CHAIN << CHNL_CTRL_CHAIN_BUF_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+ if (mxc_isi->chain)
+ regmap_write(mxc_isi->chain, CHNL_CTRL, CHNL_CTRL_CLK_EN_MASK);
+ mxc_isi->chain_buf = 1;
+ } else {
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~CHNL_CTRL_CHAIN_BUF_MASK;
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+ mxc_isi->chain_buf = 0;
+ }
+}
+
+struct device *mxc_isi_dev_get_parent(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *parent;
+ struct platform_device *parent_pdev;
+
+ if (!pdev)
+ return NULL;
+
+ /* Get parent for isi capture device */
+ parent = of_get_parent(dev->of_node);
+ parent_pdev = of_find_device_by_node(parent);
+ if (!parent_pdev) {
+ of_node_put(parent);
+ return NULL;
+ }
+ of_node_put(parent);
+
+ return &parent_pdev->dev;
+}
+EXPORT_SYMBOL_GPL(mxc_isi_dev_get_parent);
+
+struct mxc_isi_dev *mxc_isi_get_hostdata(struct platform_device *pdev)
+{
+ struct mxc_isi_dev *mxc_isi;
+
+ if (!pdev || !pdev->dev.parent)
+ return NULL;
+
+ mxc_isi = (struct mxc_isi_dev *)dev_get_drvdata(pdev->dev.parent);
+ if (!mxc_isi)
+ return NULL;
+
+ return mxc_isi;
+}
+EXPORT_SYMBOL_GPL(mxc_isi_get_hostdata);
+
+void mxc_isi_channel_set_outbuf(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_buffer *buf)
+{
+ struct vb2_buffer *vb2_buf = &buf->v4l2_buf.vb2_buf;
+ u32 framecount = buf->v4l2_buf.sequence;
+ struct frame_addr *paddr = &buf->paddr;
+ struct mxc_isi_cap_dev *isi_cap;
+ struct v4l2_pix_format_mplane *pix;
+ int val = 0;
+
+ if (buf->discard) {
+ isi_cap = mxc_isi->isi_cap;
+ pix = &isi_cap->pix;
+ paddr->y = isi_cap->discard_buffer_dma[0];
+ if (pix->num_planes == 2)
+ paddr->cb = isi_cap->discard_buffer_dma[1];
+ if (pix->num_planes == 3) {
+ paddr->cb = isi_cap->discard_buffer_dma[1];
+ paddr->cr = isi_cap->discard_buffer_dma[2];
+ }
+ } else {
+ paddr->y = vb2_dma_contig_plane_dma_addr(vb2_buf, 0);
+
+ if (vb2_buf->num_planes == 2)
+ paddr->cb = vb2_dma_contig_plane_dma_addr(vb2_buf, 1);
+ if (vb2_buf->num_planes == 3) {
+ paddr->cb = vb2_dma_contig_plane_dma_addr(vb2_buf, 1);
+ paddr->cr = vb2_dma_contig_plane_dma_addr(vb2_buf, 2);
+ }
+ }
+
+ val = readl(mxc_isi->regs + CHNL_OUT_BUF_CTRL);
+
+ if (framecount == 0 || ((is_buf_active(mxc_isi, 2)) && (framecount != 1))) {
+ writel(paddr->y, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_Y);
+ writel(paddr->cb, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_U);
+ writel(paddr->cr, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_V);
+ val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR_MASK;
+ buf->id = MXC_ISI_BUF1;
+ } else if (framecount == 1 || is_buf_active(mxc_isi, 1)) {
+ writel(paddr->y, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_Y);
+ writel(paddr->cb, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_U);
+ writel(paddr->cr, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_V);
+ val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR_MASK;
+ buf->id = MXC_ISI_BUF2;
+ }
+ writel(val, mxc_isi->regs + CHNL_OUT_BUF_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_set_outbuf);
+
+void mxc_isi_channel_set_m2m_src_addr(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_buffer *buf)
+{
+ struct vb2_buffer *vb2_buf = &buf->v4l2_buf.vb2_buf;
+ struct frame_addr *paddr = &buf->paddr;
+
+ /* Only support one plane */
+ paddr->y = vb2_dma_contig_plane_dma_addr(vb2_buf, 0);
+ writel(paddr->y, mxc_isi->regs + CHNL_IN_BUF_ADDR);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_set_m2m_src_addr);
+
+void mxc_isi_channel_sw_reset(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val |= CHNL_CTRL_SW_RST;
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+ mdelay(5);
+ val &= ~CHNL_CTRL_SW_RST;
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_sw_reset);
+
+void mxc_isi_channel_source_config(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~(CHNL_CTRL_MIPI_VC_ID_MASK |
+ CHNL_CTRL_SRC_INPUT_MASK | CHNL_CTRL_SRC_TYPE_MASK);
+
+ switch (mxc_isi->interface[IN_PORT]) {
+ case ISI_INPUT_INTERFACE_MIPI0_CSI2:
+ val |= mxc_isi->pdata->chan_src->src_mipi0;
+ if (mxc_isi->interface[SUB_IN_PORT] <= CHNL_CTRL_MIPI_VC_ID_VC3 &&
+ mxc_isi->interface[SUB_IN_PORT] >= CHNL_CTRL_MIPI_VC_ID_VC0)
+ val |= (mxc_isi->interface[SUB_IN_PORT] << CHNL_CTRL_MIPI_VC_ID_OFFSET);
+ break;
+ case ISI_INPUT_INTERFACE_MIPI1_CSI2:
+ val |= mxc_isi->pdata->chan_src->src_mipi1;
+ if (mxc_isi->interface[SUB_IN_PORT] <= CHNL_CTRL_MIPI_VC_ID_VC3 &&
+ mxc_isi->interface[SUB_IN_PORT] >= CHNL_CTRL_MIPI_VC_ID_VC0)
+ val |= (mxc_isi->interface[SUB_IN_PORT] << CHNL_CTRL_MIPI_VC_ID_OFFSET);
+ break;
+ case ISI_INPUT_INTERFACE_DC0:
+ val |= mxc_isi->pdata->chan_src->src_dc0;
+ break;
+ case ISI_INPUT_INTERFACE_DC1:
+ val |= mxc_isi->pdata->chan_src->src_dc1;
+ break;
+ case ISI_INPUT_INTERFACE_HDMI:
+ val |= mxc_isi->pdata->chan_src->src_hdmi;
+ break;
+ case ISI_INPUT_INTERFACE_PARALLEL_CSI:
+ val |= mxc_isi->pdata->chan_src->src_csi;
+ break;
+ case ISI_INPUT_INTERFACE_MEM:
+ val |= mxc_isi->pdata->chan_src->src_mem;
+ val |= (CHNL_CTRL_SRC_TYPE_MEMORY << CHNL_CTRL_SRC_TYPE_OFFSET);
+ break;
+ default:
+ dev_err(&mxc_isi->pdev->dev, "invalid interface\n");
+ break;
+ }
+
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_source_config);
+
+void mxc_isi_channel_set_flip(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~(CHNL_IMG_CTRL_VFLIP_EN_MASK | CHNL_IMG_CTRL_HFLIP_EN_MASK);
+
+ if (mxc_isi->vflip)
+ val |= (CHNL_IMG_CTRL_VFLIP_EN_ENABLE << CHNL_IMG_CTRL_VFLIP_EN_OFFSET);
+ if (mxc_isi->hflip)
+ val |= (CHNL_IMG_CTRL_HFLIP_EN_ENABLE << CHNL_IMG_CTRL_HFLIP_EN_OFFSET);
+
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_set_chain_buf);
+
+void mxc_isi_channel_set_csc(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f)
+{
+ struct mxc_isi_fmt *src_fmt = src_f->fmt;
+ struct mxc_isi_fmt *dst_fmt = dst_f->fmt;
+ u32 val, csc = 0;
+
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~(CHNL_IMG_CTRL_FORMAT_MASK |
+ CHNL_IMG_CTRL_YCBCR_MODE_MASK |
+ CHNL_IMG_CTRL_CSC_BYPASS_MASK |
+ CHNL_IMG_CTRL_CSC_MODE_MASK);
+
+ /* set outbuf format */
+ val |= dst_fmt->color << CHNL_IMG_CTRL_FORMAT_OFFSET;
+
+ mxc_isi->cscen = 1;
+
+ if (is_yuv(src_fmt->fourcc) && is_rgb(dst_fmt->fourcc)) {
+ /* YUV2RGB */
+ csc = YUV2RGB;
+ /* YCbCr enable??? */
+ val |= (CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB << CHNL_IMG_CTRL_CSC_MODE_OFFSET);
+ val |= (CHNL_IMG_CTRL_YCBCR_MODE_ENABLE << CHNL_IMG_CTRL_YCBCR_MODE_OFFSET);
+ } else if (is_rgb(src_fmt->fourcc) && is_yuv(dst_fmt->fourcc)) {
+ /* RGB2YUV */
+ csc = RGB2YUV;
+ val |= (CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR << CHNL_IMG_CTRL_CSC_MODE_OFFSET);
+ } else {
+ /* Bypass CSC */
+ pr_info("bypass csc\n");
+ mxc_isi->cscen = 0;
+ val |= CHNL_IMG_CTRL_CSC_BYPASS_ENABLE;
+ }
+
+ printk_pixelformat("input fmt", src_fmt->fourcc);
+ printk_pixelformat("output fmt", dst_fmt->fourcc);
+
+ if (mxc_isi->cscen) {
+ writel(coeffs[csc][0], mxc_isi->regs + CHNL_CSC_COEFF0);
+ writel(coeffs[csc][1], mxc_isi->regs + CHNL_CSC_COEFF1);
+ writel(coeffs[csc][2], mxc_isi->regs + CHNL_CSC_COEFF2);
+ writel(coeffs[csc][3], mxc_isi->regs + CHNL_CSC_COEFF3);
+ writel(coeffs[csc][4], mxc_isi->regs + CHNL_CSC_COEFF4);
+ writel(coeffs[csc][5], mxc_isi->regs + CHNL_CSC_COEFF5);
+ }
+
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+
+void mxc_isi_channel_set_alpha_roi0(struct mxc_isi_dev *mxc_isi,
+ struct v4l2_rect *rect)
+{
+ u32 val0, val1;
+
+ val0 = (rect->left << 16) | rect->top;
+ writel(val0, mxc_isi->regs + CHNL_ROI_0_ULC);
+ val1 = (rect->width << 16) | rect->height;
+ writel(val0 + val1, mxc_isi->regs + CHNL_ROI_0_LRC);
+}
+
+void mxc_isi_channel_set_alpha(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~(CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK | CHNL_IMG_CTRL_GBL_ALPHA_EN_MASK);
+
+ if (mxc_isi->alphaen)
+ val |= ((mxc_isi->alpha << CHNL_IMG_CTRL_GBL_ALPHA_VAL_OFFSET) |
+ (CHNL_IMG_CTRL_GBL_ALPHA_EN_ENABLE << CHNL_IMG_CTRL_GBL_ALPHA_EN_OFFSET));
+
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+
+void mxc_isi_channel_set_panic_threshold(struct mxc_isi_dev *mxc_isi)
+{
+ struct mxc_isi_set_thd *set_thd = mxc_isi->pdata->set_thd;
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_OUT_BUF_CTRL);
+
+ val &= ~(set_thd->panic_set_thd_y.mask);
+ val |= set_thd->panic_set_thd_y.threshold << set_thd->panic_set_thd_y.offset;
+
+ val &= ~(set_thd->panic_set_thd_u.mask);
+ val |= set_thd->panic_set_thd_u.threshold << set_thd->panic_set_thd_u.offset;
+
+ val &= ~(set_thd->panic_set_thd_v.mask);
+ val |= set_thd->panic_set_thd_v.threshold << set_thd->panic_set_thd_v.offset;
+
+ writel(val, mxc_isi->regs + CHNL_OUT_BUF_CTRL);
+}
+
+void mxc_isi_channel_set_chain_buf(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ if (mxc_isi->chain_buf) {
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~CHNL_CTRL_CHAIN_BUF_MASK;
+ val |= (CHNL_CTRL_CHAIN_BUF_2_CHAIN << CHNL_CTRL_CHAIN_BUF_OFFSET);
+
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+ }
+}
+
+void mxc_isi_channel_deinterlace_init(struct mxc_isi_dev *mxc_isi)
+{
+ /* Config for Blending deinterlace */
+}
+
+void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi)
+{
+ /* de-interlacing method
+ * Weaving-------------Yes
+ * Line Doubling-------No
+ * Blending -----------TODO
+ */
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~CHNL_IMG_CTRL_DEINT_MASK;
+ if (mxc_isi->deinterlace)
+ val |= mxc_isi->deinterlace << CHNL_IMG_CTRL_DEINT_OFFSET;
+ if ((mxc_isi->deinterlace == CHNL_IMG_CTRL_DEINT_LDOUBLE_ODD_EVEN) ||
+ (mxc_isi->deinterlace == CHNL_IMG_CTRL_DEINT_LDOUBLE_EVEN_ODD))
+ mxc_isi_channel_deinterlace_init(mxc_isi);
+
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+
+void mxc_isi_channel_set_crop(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *dst_f)
+{
+ struct v4l2_rect crop;
+ u32 val, val0, val1;
+
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~CHNL_IMG_CTRL_CROP_EN_MASK;
+
+ if ((dst_f->o_height == dst_f->c_height) &&
+ (dst_f->o_width == dst_f->c_width)) {
+ mxc_isi->crop = 0;
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+ return;
+ }
+
+ crop.left = dst_f->h_off;
+ crop.top = dst_f->v_off;
+ crop.width = dst_f->c_width - 1;
+ crop.height = dst_f->c_height - 1;
+
+ mxc_isi->crop = 1;
+ val |= (CHNL_IMG_CTRL_CROP_EN_ENABLE << CHNL_IMG_CTRL_CROP_EN_OFFSET);
+ val0 = crop.top | (crop.left << CHNL_CROP_ULC_X_OFFSET);
+ val1 = (crop.top + crop.height) | ((crop.left + crop.width) << CHNL_CROP_LRC_X_OFFSET);
+
+ writel(val0, mxc_isi->regs + CHNL_CROP_ULC);
+ writel(val1, mxc_isi->regs + CHNL_CROP_LRC);
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+
+static void mxc_isi_channel_clear_scaling(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val0;
+
+ writel(0x10001000, mxc_isi->regs + CHNL_SCALE_FACTOR);
+
+ val0 = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val0 &= ~(CHNL_IMG_CTRL_DEC_X_MASK | CHNL_IMG_CTRL_DEC_Y_MASK);
+ writel(val0, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+
+void mxc_isi_channel_set_scaling(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f)
+{
+ u32 decx, decy;
+ u32 xscale, yscale;
+ u32 xdec = 0, ydec = 0;
+ u32 val0, val1;
+
+ if (dst_f->height == src_f->height &&
+ dst_f->width == src_f->width) {
+ mxc_isi->scale = 0;
+ mxc_isi_channel_clear_scaling(mxc_isi);
+ dev_dbg(&mxc_isi->pdev->dev, "%s: no scale\n", __func__);
+ return;
+ }
+
+ dev_info(&mxc_isi->pdev->dev, "input_size(%d,%d), output_size(%d,%d)\n",
+ src_f->width, src_f->height, dst_f->width, dst_f->height);
+
+ mxc_isi->scale = 1;
+
+ decx = src_f->width / dst_f->width;
+ decy = src_f->height / dst_f->height;
+
+ if (decx > 1) {
+ /* Down */
+ if (decx >= 2 && decx < 4) {
+ decx = 2;
+ xdec = 1;
+ } else if (decx >= 4 && decx < 8) {
+ decx = 4;
+ xdec = 2;
+ } else if (decx >= 8) {
+ decx = 8;
+ xdec = 3;
+ }
+ xscale = src_f->width * 0x1000 / (dst_f->width * decx);
+ } else {
+ /* Up */
+ xscale = src_f->width * 0x1000 / dst_f->width;
+ }
+
+ if (decy > 1) {
+ if (decy >= 2 && decy < 4) {
+ decy = 2;
+ ydec = 1;
+ } else if (decy >= 4 && decy < 8) {
+ decy = 4;
+ ydec = 2;
+ } else if (decy >= 8) {
+ decy = 8;
+ ydec = 3;
+ }
+ yscale = src_f->height * 0x1000 / (dst_f->height * decy);
+ } else {
+ yscale = src_f->height * 0x1000 / dst_f->height;
+ }
+
+ val0 = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val0 |= CHNL_IMG_CTRL_YCBCR_MODE_MASK;//YCbCr Sandor???
+ val0 &= ~(CHNL_IMG_CTRL_DEC_X_MASK | CHNL_IMG_CTRL_DEC_Y_MASK);
+ val0 |= (xdec << CHNL_IMG_CTRL_DEC_X_OFFSET) |
+ (ydec << CHNL_IMG_CTRL_DEC_Y_OFFSET);
+ writel(val0, mxc_isi->regs + CHNL_IMG_CTRL);
+
+ if (xscale > ISI_DOWNSCALE_THRESHOLD)
+ xscale = ISI_DOWNSCALE_THRESHOLD;
+ if (yscale > ISI_DOWNSCALE_THRESHOLD)
+ yscale = ISI_DOWNSCALE_THRESHOLD;
+
+ val1 = xscale | (yscale << CHNL_SCALE_FACTOR_Y_SCALE_OFFSET);
+
+ writel(val1, mxc_isi->regs + CHNL_SCALE_FACTOR);
+
+ /* Update scale config if scaling enabled */
+ val1 = dst_f->o_width | (dst_f->o_height << CHNL_SCL_IMG_CFG_HEIGHT_OFFSET);
+ writel(val1, mxc_isi->regs + CHNL_SCL_IMG_CFG);
+
+ writel(0, mxc_isi->regs + CHNL_SCALE_OFFSET);
+
+ return;
+}
+
+void mxc_isi_channel_init(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ /* sw reset */
+ mxc_isi_channel_sw_reset(mxc_isi);
+
+ /* Init channel clk first */
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val |= (CHNL_CTRL_CLK_EN_ENABLE << CHNL_CTRL_CLK_EN_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_init);
+
+void mxc_isi_channel_deinit(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ /* sw reset */
+ mxc_isi_channel_sw_reset(mxc_isi);
+
+ /* deinit channel clk first */
+ val = (CHNL_CTRL_CLK_EN_DISABLE << CHNL_CTRL_CLK_EN_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+
+ if (mxc_isi->chain_buf && mxc_isi->chain)
+ regmap_write(mxc_isi->chain, CHNL_CTRL, 0x0);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_deinit);
+
+void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f)
+{
+ u32 val;
+
+ /* images having higher than 2048 horizontal resolution */
+ chain_buf(mxc_isi, src_f);
+
+ /* config output frame size and format */
+ val = src_f->o_width | (src_f->o_height << CHNL_IMG_CFG_HEIGHT_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_IMG_CFG);
+
+ /* scale size need to equal input size when scaling disabled*/
+ writel(val, mxc_isi->regs + CHNL_SCL_IMG_CFG);
+
+ /* check csc and scaling */
+ mxc_isi_channel_set_csc(mxc_isi, src_f, dst_f);
+
+ mxc_isi_channel_set_scaling(mxc_isi, src_f, dst_f);
+
+ /* set cropping */
+ mxc_isi_channel_set_crop(mxc_isi, dst_f);
+
+ /* select the source input / src type / virtual channel for mipi*/
+ mxc_isi_channel_source_config(mxc_isi);
+
+ /* line pitch */
+ val = dst_f->bytesperline[0];
+ writel(val, mxc_isi->regs + CHNL_OUT_BUF_PITCH);
+
+ /* TODO */
+ mxc_isi_channel_set_flip(mxc_isi);
+
+ mxc_isi_channel_set_alpha(mxc_isi);
+
+ mxc_isi_channel_set_panic_threshold(mxc_isi);
+
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~CHNL_CTRL_CHNL_BYPASS_MASK;
+
+ /* Bypass channel */
+ if (!mxc_isi->cscen && !mxc_isi->scale)
+ val |= (CHNL_CTRL_CHNL_BYPASS_ENABLE << CHNL_CTRL_CHNL_BYPASS_OFFSET);
+
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_config);
+
+void mxc_isi_clean_registers(struct mxc_isi_dev *mxc_isi)
+{
+ u32 status;
+
+ status = mxc_isi_get_irq_status(mxc_isi);
+ mxc_isi_clean_irq_status(mxc_isi, status);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_clean_registers);
+
+void mxc_isi_channel_enable(struct mxc_isi_dev *mxc_isi, bool m2m_enabled)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val |= 0xff << CHNL_CTRL_BLANK_PXL_OFFSET;
+
+ if (m2m_enabled) {
+ val &= ~(CHNL_CTRL_SRC_TYPE_MASK | CHNL_CTRL_SRC_INPUT_MASK);
+ val |= (mxc_isi->pdata->chan_src->src_mem << CHNL_CTRL_SRC_INPUT_OFFSET |
+ CHNL_CTRL_SRC_TYPE_MEMORY << CHNL_CTRL_SRC_TYPE_OFFSET);
+ }
+
+ val &= ~CHNL_CTRL_CHNL_EN_MASK;
+ val |= CHNL_CTRL_CHNL_EN_ENABLE << CHNL_CTRL_CHNL_EN_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+
+ mxc_isi_clean_registers(mxc_isi);
+ mxc_isi_enable_irq(mxc_isi);
+
+ if (m2m_enabled) {
+ mxc_isi_m2m_start_read(mxc_isi);
+ return;
+ }
+
+ dump_isi_regs(mxc_isi);
+ msleep(300);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_enable);
+
+void mxc_isi_channel_disable(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ mxc_isi_disable_irq(mxc_isi);
+
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~(CHNL_CTRL_CHNL_EN_MASK | CHNL_CTRL_CLK_EN_MASK);
+ val |= (CHNL_CTRL_CHNL_EN_DISABLE << CHNL_CTRL_CHNL_EN_OFFSET);
+ val |= (CHNL_CTRL_CLK_EN_DISABLE << CHNL_CTRL_CLK_EN_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_disable);
+
+void mxc_isi_enable_irq(struct mxc_isi_dev *mxc_isi)
+{
+ struct mxc_isi_ier_reg *ier_reg = mxc_isi->pdata->ier_reg;
+ u32 val;
+
+ val = CHNL_IER_FRM_RCVD_EN_MASK |
+ CHNL_IER_AXI_WR_ERR_U_EN_MASK |
+ CHNL_IER_AXI_WR_ERR_V_EN_MASK |
+ CHNL_IER_AXI_WR_ERR_Y_EN_MASK;
+
+ /* Y/U/V overflow enable */
+ val |= ier_reg->oflw_y_buf_en.mask |
+ ier_reg->oflw_u_buf_en.mask |
+ ier_reg->oflw_v_buf_en.mask;
+
+ /* Y/U/V excess overflow enable */
+ val |= ier_reg->excs_oflw_y_buf_en.mask |
+ ier_reg->excs_oflw_u_buf_en.mask |
+ ier_reg->excs_oflw_v_buf_en.mask;
+
+ /* Y/U/V panic enable */
+ val |= ier_reg->panic_y_buf_en.mask |
+ ier_reg->panic_u_buf_en.mask |
+ ier_reg->panic_v_buf_en.mask;
+
+ writel(val, mxc_isi->regs + CHNL_IER);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_enable_irq);
+
+void mxc_isi_disable_irq(struct mxc_isi_dev *mxc_isi)
+{
+ writel(0, mxc_isi->regs + CHNL_IER);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_disable_irq);
+
+u32 mxc_isi_get_irq_status(struct mxc_isi_dev *mxc_isi)
+{
+ return readl(mxc_isi->regs + CHNL_STS);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_get_irq_status);
+
+void mxc_isi_clean_irq_status(struct mxc_isi_dev *mxc_isi, u32 val)
+{
+ writel(val, mxc_isi->regs + CHNL_STS);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_clean_irq_status);
+
+void mxc_isi_m2m_config_src(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f)
+{
+ u32 val;
+
+ /* source format */
+ val = readl(mxc_isi->regs + CHNL_MEM_RD_CTRL);
+ val &= ~CHNL_MEM_RD_CTRL_IMG_TYPE_MASK;
+ val |= src_f->fmt->color << CHNL_MEM_RD_CTRL_IMG_TYPE_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_MEM_RD_CTRL);
+
+ /* source image width and height */
+ val = (src_f->width << CHNL_IMG_CFG_WIDTH_OFFSET |
+ src_f->height << CHNL_IMG_CFG_HEIGHT_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_IMG_CFG);
+
+ /* source pitch */
+ val = src_f->bytesperline[0] << CHNL_IN_BUF_PITCH_LINE_PITCH_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_IN_BUF_PITCH);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_m2m_config_src);
+
+void mxc_isi_m2m_config_dst(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *dst_f)
+{
+ u32 val;
+
+ /* out format */
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~CHNL_IMG_CTRL_FORMAT_MASK;
+ val |= dst_f->fmt->color << CHNL_IMG_CTRL_FORMAT_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+
+ /* out pitch */
+ val = readl(mxc_isi->regs + CHNL_OUT_BUF_PITCH);
+ val &= ~CHNL_IN_BUF_PITCH_LINE_PITCH_MASK;
+ val |= dst_f->bytesperline[0] << CHNL_OUT_BUF_PITCH_LINE_PITCH_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_OUT_BUF_PITCH);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_m2m_config_dst);
+
+void mxc_isi_m2m_start_read(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_MEM_RD_CTRL);
+ val &= ~ CHNL_MEM_RD_CTRL_READ_MEM_MASK;
+ writel(val, mxc_isi->regs + CHNL_MEM_RD_CTRL);
+ udelay(300);
+
+ val |= CHNL_MEM_RD_CTRL_READ_MEM_ENABLE << CHNL_MEM_RD_CTRL_READ_MEM_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_MEM_RD_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_m2m_start_read);
diff --git a/drivers/staging/media/imx/imx8-isi-hw.h b/drivers/staging/media/imx/imx8-isi-hw.h
new file mode 100644
index 000000000000..f9979362ffc8
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-hw.h
@@ -0,0 +1,485 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2019-2020 NXP
+ *
+ */
+
+#ifndef __MXC_ISI_HW_H__
+#define __MXC_ISI_HW_H__
+
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/platform_device.h>
+#include <linux/videodev2.h>
+
+#include "imx8-isi-core.h"
+
+/* ISI Registers Define */
+/* Channel Control Register */
+#define CHNL_CTRL 0x0
+#define CHNL_CTRL_CHNL_EN_OFFSET 31
+#define CHNL_CTRL_CHNL_EN_MASK 0x80000000
+#define CHNL_CTRL_CHNL_EN_DISABLE 0
+#define CHNL_CTRL_CHNL_EN_ENABLE 1
+#define CHNL_CTRL_CLK_EN_OFFSET 30
+#define CHNL_CTRL_CLK_EN_MASK 0x40000000
+#define CHNL_CTRL_CLK_EN_DISABLE 0
+#define CHNL_CTRL_CLK_EN_ENABLE 1
+#define CHNL_CTRL_CHNL_BYPASS_OFFSET 29
+#define CHNL_CTRL_CHNL_BYPASS_MASK 0x20000000
+#define CHNL_CTRL_CHNL_BYPASS_ENABLE 1
+#define CHNL_CTRL_CHAIN_BUF_OFFSET 25
+#define CHNL_CTRL_CHAIN_BUF_MASK 0x6000000
+#define CHNL_CTRL_CHAIN_BUF_NO_CHAIN 0
+#define CHNL_CTRL_CHAIN_BUF_2_CHAIN 1
+#define CHNL_CTRL_SW_RST_OFFSET 24
+#define CHNL_CTRL_SW_RST_MASK 0x1000000
+#define CHNL_CTRL_SW_RST 0x1000000
+#define CHNL_CTRL_BLANK_PXL_OFFSET 16
+#define CHNL_CTRL_MIPI_VC_ID_OFFSET 6
+#define CHNL_CTRL_MIPI_VC_ID_MASK 0xc0
+#define CHNL_CTRL_MIPI_VC_ID_VC0 0
+#define CHNL_CTRL_MIPI_VC_ID_VC1 1
+#define CHNL_CTRL_MIPI_VC_ID_VC2 2
+#define CHNL_CTRL_MIPI_VC_ID_VC3 3
+#define CHNL_CTRL_SRC_TYPE_OFFSET 4
+#define CHNL_CTRL_SRC_TYPE_MASK 0x10
+#define CHNL_CTRL_SRC_TYPE_DEVICE 0
+#define CHNL_CTRL_SRC_TYPE_MEMORY 1
+#define CHNL_CTRL_SRC_INPUT_OFFSET 0
+#define CHNL_CTRL_SRC_INPUT_MASK 0x7
+#define CHNL_CTRL_SRC_INPUT_MEMORY 5
+
+/* Channel Image Control Register */
+#define CHNL_IMG_CTRL 0x4
+#define CHNL_IMG_CTRL_FORMAT_OFFSET 24
+#define CHNL_IMG_CTRL_FORMAT_MASK 0x3F000000
+#define CHNL_IMG_CTRL_GBL_ALPHA_VAL_OFFSET 16
+#define CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK 0xFF0000
+#define CHNL_IMG_CTRL_GBL_ALPHA_EN_OFFSET 15
+#define CHNL_IMG_CTRL_GBL_ALPHA_EN_ENABLE 1
+#define CHNL_IMG_CTRL_GBL_ALPHA_EN_MASK 0x8000
+#define CHNL_IMG_CTRL_DEINT_OFFSET 12
+#define CHNL_IMG_CTRL_DEINT_MASK 0x7000
+#define CHNL_IMG_CTRL_DEINT_WEAVE_ODD_EVEN 2
+#define CHNL_IMG_CTRL_DEINT_WEAVE_EVEN_ODD 3
+#define CHNL_IMG_CTRL_DEINT_BLEND_ODD_EVEN 4
+#define CHNL_IMG_CTRL_DEINT_BLEND_EVEN_ODD 5
+#define CHNL_IMG_CTRL_DEINT_LDOUBLE_ODD_EVEN 6
+#define CHNL_IMG_CTRL_DEINT_LDOUBLE_EVEN_ODD 7
+#define CHNL_IMG_CTRL_DEC_X_OFFSET 10
+#define CHNL_IMG_CTRL_DEC_X_MASK 0xC00
+#define CHNL_IMG_CTRL_DEC_X_0 0
+#define CHNL_IMG_CTRL_DEC_X_2 1
+#define CHNL_IMG_CTRL_DEC_X_4 2
+#define CHNL_IMG_CTRL_DEC_X_8 3
+#define CHNL_IMG_CTRL_DEC_Y_OFFSET 8
+#define CHNL_IMG_CTRL_DEC_Y_MASK 0x300
+#define CHNL_IMG_CTRL_DEC_Y_0 0
+#define CHNL_IMG_CTRL_DEC_Y_2 1
+#define CHNL_IMG_CTRL_DEC_Y_4 2
+#define CHNL_IMG_CTRL_DEC_Y_8 3
+#define CHNL_IMG_CTRL_CROP_EN_OFFSET 7
+#define CHNL_IMG_CTRL_CROP_EN_MASK 0x80
+#define CHNL_IMG_CTRL_CROP_EN_ENABLE 1
+#define CHNL_IMG_CTRL_VFLIP_EN_OFFSET 6
+#define CHNL_IMG_CTRL_VFLIP_EN_MASK 0x40
+#define CHNL_IMG_CTRL_VFLIP_EN_ENABLE 1
+#define CHNL_IMG_CTRL_HFLIP_EN_OFFSET 5
+#define CHNL_IMG_CTRL_HFLIP_EN_MASK 0x20
+#define CHNL_IMG_CTRL_HFLIP_EN_ENABLE 1
+#define CHNL_IMG_CTRL_YCBCR_MODE_OFFSET 3
+#define CHNL_IMG_CTRL_YCBCR_MODE_MASK 0x8
+#define CHNL_IMG_CTRL_YCBCR_MODE_ENABLE 1
+#define CHNL_IMG_CTRL_CSC_MODE_OFFSET 1
+#define CHNL_IMG_CTRL_CSC_MODE_MASK 0x6
+#define CHNL_IMG_CTRL_CSC_MODE_YUV2RGB 0
+#define CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB 1
+#define CHNL_IMG_CTRL_CSC_MODE_RGB2YUV 2
+#define CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR 3
+#define CHNL_IMG_CTRL_CSC_BYPASS_OFFSET 0
+#define CHNL_IMG_CTRL_CSC_BYPASS_MASK 0x1
+#define CHNL_IMG_CTRL_CSC_BYPASS_ENABLE 0x1
+
+/* Channel Output Buffer Control Register */
+#define CHNL_OUT_BUF_CTRL 0x8
+#define CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR_OFFSET 15
+#define CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR_MASK 0x8000
+#define CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR_OFFSET 14
+#define CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR_MASK 0x4000
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_OFFSET 6
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_MASK 0xC0
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_NO_PANIC 0
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_25 1
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_50 2
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_75 3
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_OFFSET 3
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_MASK 0x18
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_NO_PANIC 0
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_25 1
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_50 2
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_75 3
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_OFFSET 0
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_MASK 0x3
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_NO_PANIC 0
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_25 1
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_50 2
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_75 3
+
+/* Channel Image Configuration */
+#define CHNL_IMG_CFG 0xC
+#define CHNL_IMG_CFG_HEIGHT_OFFSET 16
+#define CHNL_IMG_CFG_HEIGHT_MASK 0x1FFF0000
+#define CHNL_IMG_CFG_WIDTH_OFFSET 0
+#define CHNL_IMG_CFG_WIDTH_MASK 0x1FFF
+
+/* Channel Interrupt Enable Register */
+#define CHNL_IER 0x10
+#define CHNL_IER_MEM_RD_DONE_EN_OFFSET 31
+#define CHNL_IER_MEM_RD_DONE_EN_MASK 0x80000000
+#define CHNL_IER_MEM_RD_DONE_EN_ENABLE 1
+#define CHNL_IER_LINE_RCVD_EN_OFFSET 30
+#define CHNL_IER_LINE_RCVD_EN_MASK 0x40000000
+#define CHNL_IER_LINE_RCVD_EN_ENABLE 1
+#define CHNL_IER_FRM_RCVD_EN_OFFSET 29
+#define CHNL_IER_FRM_RCVD_EN_MASK 0x20000000
+#define CHNL_IER_FRM_RCVD_EN_ENABLE 1
+#define CHNL_IER_AXI_WR_ERR_V_EN_OFFSET 28
+#define CHNL_IER_AXI_WR_ERR_V_EN_MASK 0x10000000
+#define CHNL_IER_AXI_WR_ERR_V_EN_ENABLE 1
+#define CHNL_IER_AXI_WR_ERR_U_EN_OFFSET 27
+#define CHNL_IER_AXI_WR_ERR_U_EN_MASK 0x8000000
+#define CHNL_IER_AXI_WR_ERR_U_EN_ENABLE 1
+#define CHNL_IER_AXI_WR_ERR_Y_EN_OFFSET 26
+#define CHNL_IER_AXI_WR_ERR_Y_EN_MASK 0x4000000
+#define CHNL_IER_AXI_WR_ERR_Y_EN_ENABLE 1
+#define CHNL_IER_AXI_RD_ERR_EN_OFFSET 25
+#define CHNL_IER_AXI_RD_ERR_EN_MASK 0x2000000
+#define CHNL_IER_AXI_RD_ERR_EN_ENABLE 1
+
+/* Channel Status Register */
+#define CHNL_STS 0x14
+#define CHNL_STS_MEM_RD_DONE_OFFSET 31
+#define CHNL_STS_MEM_RD_DONE_MASK 0x80000000
+#define CHNL_STS_MEM_RD_DONE_ENABLE 1
+#define CHNL_STS_LINE_STRD_OFFSET 30
+#define CHNL_STS_LINE_STRD_MASK 0x40000000
+#define CHNL_STS_LINE_STRD_ENABLE 1
+#define CHNL_STS_FRM_STRD_OFFSET 29
+#define CHNL_STS_FRM_STRD_MASK 0x20000000
+#define CHNL_STS_FRM_STRD_ENABLE 1
+#define CHNL_STS_AXI_WR_ERR_V_OFFSET 28
+#define CHNL_STS_AXI_WR_ERR_V_MASK 0x10000000
+#define CHNL_STS_AXI_WR_ERR_V_ENABLE 1
+#define CHNL_STS_AXI_WR_ERR_U_OFFSET 27
+#define CHNL_STS_AXI_WR_ERR_U_MASK 0x8000000
+#define CHNL_STS_AXI_WR_ERR_U_ENABLE 1
+#define CHNL_STS_AXI_WR_ERR_Y_OFFSET 26
+#define CHNL_STS_AXI_WR_ERR_Y_MASK 0x4000000
+#define CHNL_STS_AXI_WR_ERR_Y_ENABLE 1
+#define CHNL_STS_AXI_RD_ERR_OFFSET 25
+#define CHNL_STS_AXI_RD_ERR_MASK 0x2000000
+#define CHNL_STS_AXI_RD_ERR_ENABLE 1
+#define CHNL_STS_OFLW_PANIC_V_BUF_OFFSET 24
+#define CHNL_STS_OFLW_PANIC_V_BUF_MASK 0x1000000
+#define CHNL_STS_OFLW_PANIC_V_BUF_ENABLE 1
+#define CHNL_STS_EXCS_OFLW_V_BUF_OFFSET 23
+#define CHNL_STS_EXCS_OFLW_V_BUF_MASK 0x800000
+#define CHNL_STS_EXCS_OFLW_V_BUF_ENABLE 1
+#define CHNL_STS_OFLW_V_BUF_OFFSET 22
+#define CHNL_STS_OFLW_V_BUF_MASK 0x400000
+#define CHNL_STS_OFLW_V_BUF_ENABLE 1
+#define CHNL_STS_OFLW_PANIC_U_BUF_OFFSET 21
+#define CHNL_STS_OFLW_PANIC_U_BUF_MASK 0x200000
+#define CHNL_STS_OFLW_PANIC_U_BUF_ENABLE 1
+#define CHNL_STS_EXCS_OFLW_U_BUF_OFFSET 20
+#define CHNL_STS_EXCS_OFLW_U_BUF_MASK 0x100000
+#define CHNL_STS_EXCS_OFLW_U_BUF_ENABLE 1
+#define CHNL_STS_OFLW_U_BUF_OFFSET 19
+#define CHNL_STS_OFLW_U_BUF_MASK 0x80000
+#define CHNL_STS_OFLW_U_BUF_ENABLE 1
+#define CHNL_STS_OFLW_PANIC_Y_BUF_OFFSET 18
+#define CHNL_STS_OFLW_PANIC_Y_BUF_MASK 0x40000
+#define CHNL_STS_OFLW_PANIC_Y_BUF_ENABLE 1
+#define CHNL_STS_EXCS_OFLW_Y_BUF_OFFSET 17
+#define CHNL_STS_EXCS_OFLW_Y_BUF_MASK 0x20000
+#define CHNL_STS_EXCS_OFLW_Y_BUF_ENABLE 1
+#define CHNL_STS_OFLW_Y_BUF_OFFSET 16
+#define CHNL_STS_OFLW_Y_BUF_MASK 0x10000
+#define CHNL_STS_OFLW_Y_BUF_ENABLE 1
+#define CHNL_STS_OFLW_BYTES_OFFSET 0
+#define CHNL_STS_OFLW_BYTES_MASK 0xFF
+
+/* Channel Scale Factor Register */
+#define CHNL_SCALE_FACTOR 0x18
+#define CHNL_SCALE_FACTOR_Y_SCALE_OFFSET 16
+#define CHNL_SCALE_FACTOR_Y_SCALE_MASK 0x3FFF0000
+#define CHNL_SCALE_FACTOR_X_SCALE_OFFSET 0
+#define CHNL_SCALE_FACTOR_X_SCALE_MASK 0x3FFF
+
+/* Channel Scale Offset Register */
+#define CHNL_SCALE_OFFSET 0x1C
+#define CHNL_SCALE_OFFSET_Y_SCALE_OFFSET 16
+#define CHNL_SCALE_OFFSET_Y_SCALE_MASK 0xFFF0000
+#define CHNL_SCALE_OFFSET_X_SCALE_OFFSET 0
+#define CHNL_SCALE_OFFSET_X_SCALE_MASK 0xFFF
+
+/* Channel Crop Upper Left Corner Coordinate Register */
+#define CHNL_CROP_ULC 0x20
+#define CHNL_CROP_ULC_X_OFFSET 16
+#define CHNL_CROP_ULC_X_MASK 0xFFF0000
+#define CHNL_CROP_ULC_Y_OFFSET 0
+#define CHNL_CROP_ULC_Y_MASK 0xFFF
+
+/* Channel Crop Lower Right Corner Coordinate Register */
+#define CHNL_CROP_LRC 0x24
+#define CHNL_CROP_LRC_X_OFFSET 16
+#define CHNL_CROP_LRC_X_MASK 0xFFF0000
+#define CHNL_CROP_LRC_Y_OFFSET 0
+#define CHNL_CROP_LRC_Y_MASK 0xFFF
+
+/* Channel Color Space Conversion Coefficient Register 0 */
+#define CHNL_CSC_COEFF0 0x28
+#define CHNL_CSC_COEFF0_A2_OFFSET 16
+#define CHNL_CSC_COEFF0_A2_MASK 0x7FF0000
+#define CHNL_CSC_COEFF0_A1_OFFSET 0
+#define CHNL_CSC_COEFF0_A1_MASK 0x7FF
+
+/* Channel Color Space Conversion Coefficient Register 1 */
+#define CHNL_CSC_COEFF1 0x2C
+#define CHNL_CSC_COEFF1_B1_OFFSET 16
+#define CHNL_CSC_COEFF1_B1_MASK 0x7FF0000
+#define CHNL_CSC_COEFF1_A3_OFFSET 0
+#define CHNL_CSC_COEFF1_A3_MASK 0x7FF
+
+/* Channel Color Space Conversion Coefficient Register 2 */
+#define CHNL_CSC_COEFF2 0x30
+#define CHNL_CSC_COEFF2_B3_OFFSET 16
+#define CHNL_CSC_COEFF2_B3_MASK 0x7FF0000
+#define CHNL_CSC_COEFF2_B2_OFFSET 0
+#define CHNL_CSC_COEFF2_B2_MASK 0x7FF
+
+/* Channel Color Space Conversion Coefficient Register 3 */
+#define CHNL_CSC_COEFF3 0x34
+#define CHNL_CSC_COEFF3_C2_OFFSET 16
+#define CHNL_CSC_COEFF3_C2_MASK 0x7FF0000
+#define CHNL_CSC_COEFF3_C1_OFFSET 0
+#define CHNL_CSC_COEFF3_C1_MASK 0x7FF
+
+/* Channel Color Space Conversion Coefficient Register 4 */
+#define CHNL_CSC_COEFF4 0x38
+#define CHNL_CSC_COEFF4_D1_OFFSET 16
+#define CHNL_CSC_COEFF4_D1_MASK 0x1FF0000
+#define CHNL_CSC_COEFF4_C3_OFFSET 0
+#define CHNL_CSC_COEFF4_C3_MASK 0x7FF
+
+/* Channel Color Space Conversion Coefficient Register 5 */
+#define CHNL_CSC_COEFF5 0x3C
+#define CHNL_CSC_COEFF5_D3_OFFSET 16
+#define CHNL_CSC_COEFF5_D3_MASK 0x1FF0000
+#define CHNL_CSC_COEFF5_D2_OFFSET 0
+#define CHNL_CSC_COEFF5_D2_MASK 0x1FF
+
+/* Channel Alpha Value Register for ROI 0 */
+#define CHNL_ROI_0_ALPHA 0x40
+#define CHNL_ROI_0_ALPHA_OFFSET 24
+#define CHNL_ROI_0_ALPHA_MASK 0xFF000000
+#define CHNL_ROI_0_ALPHA_EN_OFFSET 16
+#define CHNL_ROI_0_ALPHA_EN_MASK 0x10000
+
+/* Channel Upper Left Coordinate Register for ROI 0 */
+#define CHNL_ROI_0_ULC 0x44
+#define CHNL_ROI_0_ULC_X_OFFSET 16
+#define CHNL_ROI_0_ULC_X_MASK 0xFFF0000
+#define CHNL_ROI_0_ULC_Y_OFFSET 0
+#define CHNL_ROI_0_ULC_Y_MASK 0xFFF
+
+/* Channel Lower Right Coordinate Register for ROI 0 */
+#define CHNL_ROI_0_LRC 0x48
+#define CHNL_ROI_0_LRC_X_OFFSET 16
+#define CHNL_ROI_0_LRC_X_MASK 0xFFF0000
+#define CHNL_ROI_0_LRC_Y_OFFSET 0
+#define CHNL_ROI_0_LRC_Y_MASK 0xFFF
+
+/* Channel Alpha Value Register for ROI 1 */
+#define CHNL_ROI_1_ALPHA 0x4C
+#define CHNL_ROI_1_ALPHA_OFFSET 24
+#define CHNL_ROI_1_ALPHA_MASK 0xFF000000
+#define CHNL_ROI_1_ALPHA_EN_OFFSET 16
+#define CHNL_ROI_1_ALPHA_EN_MASK 0x10000
+
+/* Channel Upper Left Coordinate Register for ROI 1 */
+#define CHNL_ROI_1_ULC 0x50
+#define CHNL_ROI_1_ULC_X_OFFSET 16
+#define CHNL_ROI_1_ULC_X_MASK 0xFFF0000
+#define CHNL_ROI_1_ULC_Y_OFFSET 0
+#define CHNL_ROI_1_ULC_Y_MASK 0xFFF
+
+/* Channel Lower Right Coordinate Register for ROI 1 */
+#define CHNL_ROI_1_LRC 0x54
+#define CHNL_ROI_1_LRC_X_OFFSET 16
+#define CHNL_ROI_1_LRC_X_MASK 0xFFF0000
+#define CHNL_ROI_1_LRC_Y_OFFSET 0
+#define CHNL_ROI_1_LRC_Y_MASK 0xFFF
+
+/* Channel Alpha Value Register for ROI 2 */
+#define CHNL_ROI_2_ALPHA 0x58
+#define CHNL_ROI_2_ALPHA_OFFSET 24
+#define CHNL_ROI_2_ALPHA_MASK 0xFF000000
+#define CHNL_ROI_2_ALPHA_EN_OFFSET 16
+#define CHNL_ROI_2_ALPHA_EN_MASK 0x10000
+
+/* Channel Upper Left Coordinate Register for ROI 2 */
+#define CHNL_ROI_2_ULC 0x5C
+#define CHNL_ROI_2_ULC_X_OFFSET 16
+#define CHNL_ROI_2_ULC_X_MASK 0xFFF0000
+#define CHNL_ROI_2_ULC_Y_OFFSET 0
+#define CHNL_ROI_2_ULC_Y_MASK 0xFFF
+
+/* Channel Lower Right Coordinate Register for ROI 2 */
+#define CHNL_ROI_2_LRC 0x60
+#define CHNL_ROI_2_LRC_X_OFFSET 16
+#define CHNL_ROI_2_LRC_X_MASK 0xFFF0000
+#define CHNL_ROI_2_LRC_Y_OFFSET 0
+#define CHNL_ROI_2_LRC_Y_MASK 0xFFF
+
+/* Channel Alpha Value Register for ROI 3 */
+#define CHNL_ROI_3_ALPHA 0x64
+#define CHNL_ROI_3_ALPHA_OFFSET 24
+#define CHNL_ROI_3_ALPHA_MASK 0xFF000000
+#define CHNL_ROI_3_ALPHA_EN_OFFSET 16
+#define CHNL_ROI_3_ALPHA_EN_MASK 0x10000
+
+/* Channel Upper Left Coordinate Register for ROI 3 */
+#define CHNL_ROI_3_ULC 0x68
+#define CHNL_ROI_3_ULC_X_OFFSET 16
+#define CHNL_ROI_3_ULC_X_MASK 0xFFF0000
+#define CHNL_ROI_3_ULC_Y_OFFSET 0
+#define CHNL_ROI_3_ULC_Y_MASK 0xFFF
+
+/* Channel Lower Right Coordinate Register for ROI 3 */
+#define CHNL_ROI_3_LRC 0x6C
+#define CHNL_ROI_3_LRC_X_OFFSET 16
+#define CHNL_ROI_3_LRC_X_MASK 0xFFF0000
+#define CHNL_ROI_3_LRC_Y_OFFSET 0
+#define CHNL_ROI_3_LRC_Y_MASK 0xFFF
+
+/* Channel RGB or Luma (Y) Output Buffer 1 Address */
+#define CHNL_OUT_BUF1_ADDR_Y 0x70
+
+/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 1 Address */
+#define CHNL_OUT_BUF1_ADDR_U 0x74
+
+/* Channel Chroma (V/Cr) Output Buffer 1 Address */
+#define CHNL_OUT_BUF1_ADDR_V 0x78
+
+/* Channel Output Buffer Pitch */
+#define CHNL_OUT_BUF_PITCH 0x7C
+#define CHNL_OUT_BUF_PITCH_LINE_PITCH_OFFSET 0
+#define CHNL_OUT_BUF_PITCH_LINE_PITCH_MASK 0xFFFF
+
+/* Channel Input Buffer Address */
+#define CHNL_IN_BUF_ADDR 0x80
+
+/* Channel Input Buffer Pitch */
+#define CHNL_IN_BUF_PITCH 0x84
+#define CHNL_IN_BUF_PITCH_FRM_PITCH_OFFSET 16
+#define CHNL_IN_BUF_PITCH_FRM_PITCH_MASK 0xFFFF0000
+#define CHNL_IN_BUF_PITCH_LINE_PITCH_OFFSET 0
+#define CHNL_IN_BUF_PITCH_LINE_PITCH_MASK 0xFFFF
+
+/* Channel Memory Read Control */
+#define CHNL_MEM_RD_CTRL 0x88
+#define CHNL_MEM_RD_CTRL_IMG_TYPE_OFFSET 28
+#define CHNL_MEM_RD_CTRL_IMG_TYPE_MASK 0xF0000000
+#define CHNL_MEM_RD_CTRL_READ_MEM_OFFSET 0
+#define CHNL_MEM_RD_CTRL_READ_MEM_MASK 1
+#define CHNL_MEM_RD_CTRL_READ_MEM_ENABLE 1
+
+/* Channel RGB or Luma (Y) Output Buffer 2 Address */
+#define CHNL_OUT_BUF2_ADDR_Y 0x8C
+
+/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 2 Address */
+#define CHNL_OUT_BUF2_ADDR_U 0x90
+
+/* Channel Chroma (V/Cr) Output Buffer 2 Address */
+#define CHNL_OUT_BUF2_ADDR_V 0x94
+
+/* Channel scale image config */
+#define CHNL_SCL_IMG_CFG 0x98
+#define CHNL_SCL_IMG_CFG_HEIGHT_OFFSET 16
+#define CHNL_SCL_IMG_CFG_HEIGHT_MASK 0x1FFF0000
+#define CHNL_SCL_IMG_CFG_WIDTH_OFFSET 0
+#define CHNL_SCL_IMG_CFG_WIDTH_MASK 0x1FFF
+
+/* Channel Flow Control Register */
+#define CHNL_FLOW_CTRL 0x9C
+#define CHNL_FLOW_CTRL_FC_DENOM_MASK 0xFF
+#define CHNL_FLOW_CTRL_FC_DENOM_OFFSET 0
+#define CHNL_FLOW_CTRL_FC_NUMER_MASK 0xFF0000
+#define CHNL_FLOW_CTRL_FC_NUMER_OFFSET 0
+
+enum isi_csi_coeff {
+ YUV2RGB = 0,
+ RGB2YUV,
+};
+
+void mxc_isi_channel_init(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_deinit(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_enable(struct mxc_isi_dev *mxc_isi, bool m2m_enabled);
+void mxc_isi_channel_disable(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_sw_reset(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_hw_reset(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_source_config(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_flip(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_alpha(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_chain_buf(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_crop(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *dst_f);
+void mxc_isi_channel_set_memory_image(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_panic_threshold(struct mxc_isi_dev *mxc_isi);
+
+void mxc_isi_channel_set_scaling(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f);
+
+void mxc_isi_channel_set_outbuf(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_buffer *buf);
+
+void mxc_isi_channel_set_csc(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f);
+
+void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f);
+
+void mxc_isi_channel_set_alpha_roi0(struct mxc_isi_dev *mxc_isi,
+ struct v4l2_rect *rect);
+void mxc_isi_channel_set_m2m_src_addr(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_buffer *buf);
+
+void mxc_isi_m2m_config_src(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f);
+void mxc_isi_m2m_config_dst(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *dst_f);
+
+void mxc_isi_m2m_start_read(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_m2m_frame_write_done(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_clean_irq_status(struct mxc_isi_dev *mxc_isi, u32 val);
+void mxc_isi_clean_registers(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_enable_irq(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_disable_irq(struct mxc_isi_dev *mxc_isi);
+void dump_isi_regs(struct mxc_isi_dev *mxc_isi);
+
+u32 mxc_isi_get_irq_status(struct mxc_isi_dev *mxc_isi);
+bool is_buf_active(struct mxc_isi_dev *mxc_isi, int buf_id);
+
+struct device *mxc_isi_dev_get_parent(struct platform_device *pdev);
+struct mxc_isi_dev *mxc_isi_get_hostdata(struct platform_device *pdev);
+#endif /* __MXC_ISI_HW_H__ */
diff --git a/drivers/staging/media/imx/imx8-isi-m2m.c b/drivers/staging/media/imx/imx8-isi-m2m.c
new file mode 100644
index 000000000000..46bffa3707fa
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-m2m.c
@@ -0,0 +1,1375 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ISI V4L2 memory to memory driver for i.MX8QXP/QM platform
+ *
+ * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
+ * used to process image from camera sensor or memory to memory or DC
+ *
+ * Copyright 2019-2021 NXP
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/pm_runtime.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/of_graph.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "imx8-isi-hw.h"
+#include "imx8-common.h"
+#include "imx8-isi-fmt.h"
+
+#define to_isi_buffer(x) \
+ container_of((x), struct mxc_isi_buffer, v4l2_buf)
+
+#define file_to_ctx(file) \
+ container_of(file->private_data, struct mxc_isi_ctx, fh);
+
+struct mxc_isi_fmt mxc_isi_input_formats[] = {
+ /* Pixel link input format */
+ {
+ .name = "XBGR32",
+ .fourcc = V4L2_PIX_FMT_XBGR32,
+ .depth = { 32 },
+ .color = MXC_ISI_M2M_IN_FMT_XRGB8,
+ .memplanes = 1,
+ .colplanes = 1,
+ }, {
+ .name = "RGB565",
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .depth = { 16 },
+ .color = MXC_ISI_M2M_IN_FMT_RGB565,
+ .memplanes = 1,
+ .colplanes = 1,
+ }, {
+ .name = "YUV32 (X-Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUV32,
+ .depth = { 32 },
+ .color = MXC_ISI_M2M_IN_FMT_YUV444_1P8P,
+ .memplanes = 1,
+ .colplanes = 1,
+ }, {
+ .name = "YUV16 (X-Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .depth = { 16 },
+ .color = MXC_ISI_M2M_IN_FMT_YUV422_1P8P,
+ .memplanes = 1,
+ .colplanes = 1,
+ }, {
+ .name = "RGBA (R-G-B-A)",
+ .fourcc = V4L2_PIX_FMT_RGBA32,
+ .depth = { 32 },
+ .color = MXC_ISI_M2M_IN_FMT_XBGR8,
+ .memplanes = 1,
+ .colplanes = 1,
+ }
+};
+
+static struct v4l2_m2m_buffer *to_v4l2_m2m_buffer(struct vb2_v4l2_buffer *vbuf)
+{
+ struct v4l2_m2m_buffer *b;
+
+ b = container_of(vbuf, struct v4l2_m2m_buffer, vb);
+ return b;
+}
+
+void mxc_isi_m2m_frame_write_done(struct mxc_isi_dev *mxc_isi)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_isi->isi_m2m;
+ struct v4l2_fh *fh;
+ struct mxc_isi_ctx *curr_mxc_ctx;
+ struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf;
+ struct mxc_isi_buffer *src_buf, *dst_buf;
+ struct v4l2_m2m_buffer *b;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ curr_mxc_ctx = v4l2_m2m_get_curr_priv(isi_m2m->m2m_dev);
+ if (!curr_mxc_ctx) {
+ dev_err(&isi_m2m->pdev->dev,
+ "Instance released before the end of transaction\n");
+ return;
+ }
+ fh = &curr_mxc_ctx->fh;
+
+ if (isi_m2m->aborting) {
+ mxc_isi_channel_disable(mxc_isi);
+ dev_warn(&isi_m2m->pdev->dev, "Aborting current job\n");
+ goto job_finish;
+ }
+
+ src_vbuf = v4l2_m2m_next_src_buf(fh->m2m_ctx);
+ if (!src_vbuf) {
+ dev_err(&isi_m2m->pdev->dev, "No enought source buffers\n");
+ goto job_finish;
+ }
+ src_buf = to_isi_buffer(src_vbuf);
+ v4l2_m2m_src_buf_remove(fh->m2m_ctx);
+ v4l2_m2m_buf_done(src_vbuf, VB2_BUF_STATE_DONE);
+
+ if (!list_empty(&isi_m2m->out_active)) {
+ dst_buf = list_first_entry(&isi_m2m->out_active,
+ struct mxc_isi_buffer, list);
+ dst_vbuf = &dst_buf->v4l2_buf;
+ list_del_init(&dst_buf->list);
+ dst_buf->v4l2_buf.vb2_buf.timestamp = ktime_get_ns();
+ v4l2_m2m_buf_done(dst_vbuf, VB2_BUF_STATE_DONE);
+
+ }
+ isi_m2m->frame_count++;
+
+ dst_vbuf = v4l2_m2m_next_dst_buf(fh->m2m_ctx);
+ if (dst_vbuf) {
+ dst_vbuf->vb2_buf.state = VB2_BUF_STATE_ACTIVE;
+ dst_buf = to_isi_buffer(dst_vbuf);
+ dst_buf->v4l2_buf.sequence = isi_m2m->frame_count;
+ mxc_isi_channel_set_outbuf(mxc_isi, dst_buf);
+ v4l2_m2m_dst_buf_remove(fh->m2m_ctx);
+ b = to_v4l2_m2m_buffer(dst_vbuf);
+ list_add_tail(&b->list, &isi_m2m->out_active);
+ }
+
+job_finish:
+ v4l2_m2m_job_finish(isi_m2m->m2m_dev, fh->m2m_ctx);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_m2m_frame_write_done);
+
+static void mxc_isi_m2m_device_run(void *priv)
+{
+ struct mxc_isi_ctx *mxc_ctx = priv;
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct v4l2_fh *fh = &mxc_ctx->fh;
+ struct vb2_v4l2_buffer *vbuf;
+ struct mxc_isi_buffer *src_buf;
+ unsigned long flags;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s enter\n", __func__);
+
+ spin_lock_irqsave(&isi_m2m->slock, flags);
+
+ /* SRC */
+ vbuf = v4l2_m2m_next_src_buf(fh->m2m_ctx);
+ if (!vbuf) {
+ dev_err(&isi_m2m->pdev->dev, "Null src buf\n");
+ goto unlock;
+ }
+
+ src_buf = to_isi_buffer(vbuf);
+ mxc_isi_channel_set_m2m_src_addr(mxc_isi, src_buf);
+ mxc_isi_channel_enable(mxc_isi, mxc_isi->m2m_enabled);
+
+unlock:
+ spin_unlock_irqrestore(&isi_m2m->slock, flags);
+}
+
+static int mxc_isi_m2m_job_ready(void *priv)
+{
+ struct mxc_isi_ctx *mxc_ctx = priv;
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct v4l2_fh *fh = &mxc_ctx->fh;
+ unsigned int num_src_bufs_ready;
+ unsigned int num_dst_bufs_ready;
+ unsigned long flags;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ spin_lock_irqsave(&isi_m2m->slock, flags);
+ num_src_bufs_ready = v4l2_m2m_num_src_bufs_ready(fh->m2m_ctx);
+ num_dst_bufs_ready = v4l2_m2m_num_dst_bufs_ready(fh->m2m_ctx);
+ spin_unlock_irqrestore(&isi_m2m->slock, flags);
+
+ if (num_src_bufs_ready >= 1 && num_dst_bufs_ready >= 1)
+ return 1;
+ return 0;
+}
+
+static void mxc_isi_m2m_job_abort(void *priv)
+{
+ struct mxc_isi_ctx *mxc_ctx = priv;
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+
+ isi_m2m->aborting = 1;
+ dev_dbg(&isi_m2m->pdev->dev, "Abort requested\n");
+}
+
+static struct v4l2_m2m_ops mxc_isi_m2m_ops = {
+ .device_run = mxc_isi_m2m_device_run,
+ .job_ready = mxc_isi_m2m_job_ready,
+ .job_abort = mxc_isi_m2m_job_abort,
+};
+
+static int m2m_vb2_queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(q);
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct device *dev = &isi_m2m->pdev->dev;
+ struct mxc_isi_frame *frame;
+ struct mxc_isi_fmt *fmt;
+ unsigned long wh;
+ int i;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ frame = &isi_m2m->dst_f;
+ else
+ frame = &isi_m2m->src_f;
+
+ if (*num_planes) {
+ if (*num_planes != frame->fmt->memplanes)
+ return -EINVAL;
+
+ for (i = 0; i < *num_planes; i++)
+ if (sizes[i] < frame->sizeimage[i])
+ return -EINVAL;
+ }
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ if (*num_buffers < 3) {
+ dev_dbg(dev, "%s at least need 3 buffer\n", __func__);
+ *num_buffers = 3;
+ }
+ isi_m2m->req_cap_buf_num = *num_buffers;
+ } else {
+ if (*num_buffers < 1) {
+ dev_dbg(dev, "%s at least need one buffer\n", __func__);
+ *num_buffers = 1;
+ }
+ isi_m2m->req_out_buf_num = *num_buffers;
+ }
+
+ fmt = frame->fmt;
+ if (fmt == NULL)
+ return -EINVAL;
+
+ for (i = 0; i < fmt->memplanes; i++)
+ alloc_devs[i] = &isi_m2m->pdev->dev;
+
+ *num_planes = fmt->memplanes;
+ wh = frame->width * frame->height;
+
+ for (i = 0; i < fmt->memplanes; i++) {
+ unsigned int size = (wh * fmt->depth[i]) >> 3;
+
+ if (i == 1 && fmt->fourcc == V4L2_PIX_FMT_NV12)
+ size >>= 1;
+ sizes[i] = max_t(u32, size, frame->sizeimage[i]);
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s, buf_n=%d, planes[%d]->size=%d\n",
+ __func__, *num_buffers, i, sizes[i]);
+ }
+
+ return 0;
+}
+
+static int m2m_vb2_buffer_prepare(struct vb2_buffer *vb2)
+{
+ struct vb2_queue *vq = vb2->vb2_queue;
+ struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(vq);
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct mxc_isi_frame *frame;
+ int i;
+
+ if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ frame = &isi_m2m->dst_f;
+ else
+ frame = &isi_m2m->src_f;
+
+ if (frame == NULL)
+ return -EINVAL;
+
+ for (i = 0; i < frame->fmt->memplanes; i++) {
+ unsigned long size = frame->sizeimage[i];
+
+ if (vb2_plane_size(vb2, i) < size) {
+ dev_err(&isi_m2m->pdev->dev,
+ "User buffer too small (%ld < %ld)\n",
+ vb2_plane_size(vb2, i), size);
+ return -EINVAL;
+ }
+ vb2_set_plane_payload(vb2, i, size);
+ }
+
+ return 0;
+}
+
+static void m2m_vb2_buffer_queue(struct vb2_buffer *vb2)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2);
+ struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(vb2->vb2_queue);
+ struct v4l2_fh *fh = &mxc_ctx->fh;
+
+ v4l2_m2m_buf_queue(fh->m2m_ctx, vbuf);
+}
+
+static int m2m_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(q);
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct v4l2_fh *fh = &mxc_ctx->fh;
+ struct vb2_v4l2_buffer *dst_vbuf;
+ struct v4l2_m2m_buffer *b;
+ struct mxc_isi_buffer *dst_buf;
+ unsigned long flags;
+
+ if (V4L2_TYPE_IS_OUTPUT(q->type))
+ return 0;
+
+ if (count < 2) {
+ dev_err(&isi_m2m->pdev->dev, "Need to at leas 2 buffers\n");
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&isi_m2m->slock, flags);
+
+ /* BUF1 */
+ dst_vbuf = v4l2_m2m_next_dst_buf(fh->m2m_ctx);
+ if (!dst_vbuf) {
+ dev_err(&isi_m2m->pdev->dev, "%d: Null dst buf\n", __LINE__);
+ goto unlock;
+ }
+ dst_vbuf->vb2_buf.state = VB2_BUF_STATE_ACTIVE;
+ dst_buf = to_isi_buffer(dst_vbuf);
+ dst_buf->v4l2_buf.sequence = 0;
+ mxc_isi_channel_set_outbuf(mxc_isi, dst_buf);
+ v4l2_m2m_dst_buf_remove(fh->m2m_ctx);
+ b = to_v4l2_m2m_buffer(dst_vbuf);
+ list_add_tail(&b->list, &isi_m2m->out_active);
+
+ /* BUF2 */
+ dst_vbuf = v4l2_m2m_next_dst_buf(fh->m2m_ctx);
+ if (!dst_vbuf) {
+ dev_err(&isi_m2m->pdev->dev, "%d: Null dst buf\n", __LINE__);
+ goto unlock;
+ }
+ dst_vbuf->vb2_buf.state = VB2_BUF_STATE_ACTIVE;
+ dst_buf = to_isi_buffer(dst_vbuf);
+ dst_buf->v4l2_buf.sequence = 1;
+ mxc_isi_channel_set_outbuf(mxc_isi, dst_buf);
+ v4l2_m2m_dst_buf_remove(fh->m2m_ctx);
+ b = to_v4l2_m2m_buffer(dst_vbuf);
+ list_add_tail(&b->list, &isi_m2m->out_active);
+
+ isi_m2m->frame_count = 1;
+ isi_m2m->aborting = 0;
+unlock:
+ spin_unlock_irqrestore(&isi_m2m->slock, flags);
+
+ return 0;
+}
+
+static void m2m_vb2_stop_streaming(struct vb2_queue *q)
+{
+ struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(q);
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct vb2_v4l2_buffer *vb2;
+ struct mxc_isi_buffer *buf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&isi_m2m->slock, flags);
+
+ while ((vb2 = v4l2_m2m_src_buf_remove(mxc_ctx->fh.m2m_ctx)) != NULL)
+ v4l2_m2m_buf_done(vb2, VB2_BUF_STATE_ERROR);
+
+ while ((vb2 = v4l2_m2m_dst_buf_remove(mxc_ctx->fh.m2m_ctx)) != NULL)
+ v4l2_m2m_buf_done(vb2, VB2_BUF_STATE_ERROR);
+
+ while (!list_empty(&isi_m2m->out_active)) {
+ buf = list_entry(isi_m2m->out_active.next, struct mxc_isi_buffer, list);
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ INIT_LIST_HEAD(&isi_m2m->out_active);
+
+ spin_unlock_irqrestore(&isi_m2m->slock, flags);
+}
+
+static struct vb2_ops mxc_m2m_vb2_qops = {
+ .queue_setup = m2m_vb2_queue_setup,
+ .buf_prepare = m2m_vb2_buffer_prepare,
+ .buf_queue = m2m_vb2_buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = m2m_vb2_start_streaming,
+ .stop_streaming = m2m_vb2_stop_streaming,
+};
+
+static int mxc_m2m_queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq)
+{
+ struct mxc_isi_ctx *mxc_ctx = priv;
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ int ret;
+
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ src_vq->drv_priv = mxc_ctx;
+ src_vq->buf_struct_size = sizeof(struct mxc_isi_buffer);
+ src_vq->ops = &mxc_m2m_vb2_qops;
+ src_vq->mem_ops = &vb2_dma_contig_memops;
+ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ src_vq->lock = &isi_m2m->lock;
+ src_vq->dev = &isi_m2m->pdev->dev;
+
+ ret = vb2_queue_init(src_vq);
+ if (ret)
+ return ret;
+
+ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ dst_vq->drv_priv = mxc_ctx;
+ dst_vq->buf_struct_size = sizeof(struct mxc_isi_buffer);
+ dst_vq->ops = &mxc_m2m_vb2_qops;
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
+ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ dst_vq->lock = &isi_m2m->lock;
+ dst_vq->dev = &isi_m2m->pdev->dev;
+
+ ret = vb2_queue_init(dst_vq);
+ return ret;
+}
+
+static void isi_m2m_fmt_init(struct mxc_isi_frame *frm, struct mxc_isi_fmt *fmt)
+{
+ int i;
+
+ frm->fmt = fmt;
+ set_frame_bounds(frm, ISI_4K, ISI_8K);
+
+ for (i = 0; i < frm->fmt->memplanes; i++) {
+ frm->bytesperline[i] = frm->width * frm->fmt->depth[i] >> 3;
+ frm->sizeimage[i] = frm->bytesperline[i] * frm->height;
+ }
+}
+
+static int isi_m2m_try_fmt(struct mxc_isi_frame *frame,
+ struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_fmt *fmt = NULL, *formats;
+ int size;
+ int bpl, i;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ size = mxc_isi_out_formats_size;
+ formats = mxc_isi_out_formats;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ size = ARRAY_SIZE(mxc_isi_input_formats);
+ formats = mxc_isi_input_formats;
+ } else {
+ pr_err("%s, not support buf type=%d\n", __func__, f->type);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < size; i++) {
+ fmt = formats + i;
+ if (fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= size) {
+ pr_err("%s, format is not support!\n", __func__);
+ return -EINVAL;
+ }
+
+
+ pix->num_planes = fmt->memplanes;
+ pix->pixelformat = fmt->fourcc;
+ pix->field = V4L2_FIELD_NONE;
+ pix->width = clamp(pix->width, ISI_MIN, ISI_4K);
+ pix->height = clamp(pix->height, ISI_MIN, ISI_8K);
+ memset(pix->reserved, 0x00, sizeof(pix->reserved));
+
+ for (i = 0; i < pix->num_planes; i++) {
+ bpl = pix->plane_fmt[i].bytesperline;
+
+ if ((bpl == 0) || (bpl / (fmt->depth[i] >> 3)) < pix->width)
+ pix->plane_fmt[i].bytesperline =
+ (pix->width * fmt->depth[i]) >> 3;
+
+ if (pix->plane_fmt[i].sizeimage == 0) {
+ if ((i == 1) && (pix->pixelformat == V4L2_PIX_FMT_NV12))
+ pix->plane_fmt[i].sizeimage =
+ (pix->width * (pix->height >> 1) * fmt->depth[i] >> 3);
+ else
+ pix->plane_fmt[i].sizeimage =
+ (pix->width * pix->height * fmt->depth[i] >> 3);
+ }
+ }
+
+ return 0;
+}
+
+static int mxc_isi_m2m_open(struct file *file)
+{
+ struct video_device *vdev = video_devdata(file);
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct device *dev = &isi_m2m->pdev->dev;
+ struct mxc_isi_ctx *mxc_ctx = NULL;
+ int ret = 0;
+
+ mutex_lock(&isi_m2m->lock);
+ if (mxc_isi->cap_enabled) {
+ dev_err(dev, "ISI channel[%d] is busy\n", isi_m2m->id);
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (isi_m2m->refcnt > 0) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ mxc_ctx = kzalloc(sizeof(*mxc_ctx), GFP_KERNEL);
+ if (!mxc_ctx) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ mxc_ctx->isi_m2m = isi_m2m;
+
+ v4l2_fh_init(&mxc_ctx->fh, vdev);
+ file->private_data = &mxc_ctx->fh;
+
+ mxc_ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(isi_m2m->m2m_dev,
+ mxc_ctx,
+ mxc_m2m_queue_init);
+ if (IS_ERR(mxc_ctx->fh.m2m_ctx)) {
+ dev_err(dev, "v4l2_m2m_ctx_init fail\n");
+ ret = PTR_ERR(mxc_ctx->fh.m2m_ctx);
+ v4l2_fh_exit(&mxc_ctx->fh);
+ kfree(mxc_ctx);
+ goto unlock;
+ }
+ v4l2_fh_add(&mxc_ctx->fh);
+
+ isi_m2m_fmt_init(&isi_m2m->src_f, &mxc_isi_input_formats[0]);
+ isi_m2m_fmt_init(&isi_m2m->dst_f, &mxc_isi_out_formats[0]);
+
+ pm_runtime_get_sync(dev);
+ mxc_isi_channel_init(mxc_isi);
+
+ /* lock host data */
+ mxc_isi->m2m_enabled = true;
+ goto unlock;
+
+unlock:
+ if (ret == 0)
+ isi_m2m->refcnt++;
+ mutex_unlock(&isi_m2m->lock);
+ return ret;
+}
+
+static int mxc_isi_m2m_release(struct file *file)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct device *dev = &isi_m2m->pdev->dev;
+ struct mxc_isi_ctx *mxc_ctx = file_to_ctx(file);
+
+ mutex_lock(&isi_m2m->lock);
+ isi_m2m->refcnt--;
+ if (isi_m2m->refcnt == 0) {
+ v4l2_fh_del(&mxc_ctx->fh);
+ v4l2_fh_exit(&mxc_ctx->fh);
+
+ v4l2_m2m_ctx_release(mxc_ctx->fh.m2m_ctx);
+
+ kfree(mxc_ctx);
+ mxc_isi_channel_deinit(mxc_isi);
+
+ mxc_isi->m2m_enabled = false;
+
+ pm_runtime_put(dev);
+ }
+ mutex_unlock(&isi_m2m->lock);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations mxc_isi_m2m_fops = {
+ .owner = THIS_MODULE,
+ .open = mxc_isi_m2m_open,
+ .release = mxc_isi_m2m_release,
+ .poll = v4l2_m2m_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = v4l2_m2m_fop_mmap,
+};
+
+static int mxc_isi_m2m_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+
+ strlcpy(cap->driver, MXC_ISI_M2M, sizeof(cap->driver));
+ strlcpy(cap->card, MXC_ISI_M2M, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s.%d",
+ dev_name(&isi_m2m->pdev->dev), isi_m2m->id);
+ cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+ return 0;
+}
+
+static int mxc_isi_m2m_enum_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_fmt *fmt;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+ if (f->index >= (int)ARRAY_SIZE(mxc_isi_input_formats))
+ return -EINVAL;
+
+ fmt = &mxc_isi_input_formats[f->index];
+ strncpy(f->description, fmt->name, sizeof(f->description) - 1);
+
+ f->pixelformat = fmt->fourcc;
+
+ return 0;
+}
+
+static int mxc_isi_m2m_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_fmt *fmt;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+ if (f->index >= (int)mxc_isi_out_formats_size)
+ return -EINVAL;
+
+ fmt = &mxc_isi_out_formats[f->index];
+ strncpy(f->description, fmt->name, sizeof(f->description) - 1);
+
+ f->pixelformat = fmt->fourcc;
+
+ return 0;
+}
+
+static int mxc_isi_m2m_try_fmt_vid_out(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ if (!pix->colorspace)
+ pix->colorspace = V4L2_COLORSPACE_SRGB;
+
+ return isi_m2m_try_fmt(&isi_m2m->src_f, f);
+}
+
+static int mxc_isi_m2m_try_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ if (!pix->colorspace)
+ pix->colorspace = isi_m2m->colorspace;
+ if (!pix->ycbcr_enc)
+ pix->ycbcr_enc = isi_m2m->ycbcr_enc;
+ if (!pix->xfer_func)
+ pix->xfer_func = isi_m2m->xfer_func;
+ if (!pix->quantization)
+ pix->quantization = isi_m2m->quant;
+
+ return isi_m2m_try_fmt(&isi_m2m->dst_f, f);
+}
+
+static int mxc_isi_m2m_s_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct v4l2_fh *fh = file->private_data;
+ struct mxc_isi_frame *frame = &isi_m2m->src_f;
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_fmt *fmt;
+ struct vb2_queue *vq;
+ int bpl, i;
+ int ret;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ ret = mxc_isi_m2m_try_fmt_vid_out(file, priv, f);
+ if (ret)
+ return ret;
+
+ vq = v4l2_m2m_get_vq(fh->m2m_ctx, f->type);
+ if (!vq)
+ return -EINVAL;
+
+ if (vb2_is_busy(vq)) {
+ dev_err(&isi_m2m->pdev->dev, "queue busy\n");
+ return -EBUSY;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_input_formats); i++) {
+ fmt = &mxc_isi_input_formats[i];
+ if (pix && fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_input_formats)) {
+ dev_dbg(&isi_m2m->pdev->dev, "%s, format is not support!\n", __func__);
+ return -EINVAL;
+ }
+
+ /* update out put frame size and formate */
+ if (pix->height <= 0 || pix->width <= 0)
+ return -EINVAL;
+
+ frame->fmt = fmt;
+ frame->height = pix->height;
+ frame->width = pix->width;
+
+ pix->field = V4L2_FIELD_NONE;
+ pix->num_planes = fmt->memplanes;
+ for (i = 0; i < pix->num_planes; i++) {
+ bpl = pix->plane_fmt[i].bytesperline;
+
+ if ((bpl == 0) || (bpl / (fmt->depth[i] >> 3)) < pix->width)
+ pix->plane_fmt[i].bytesperline =
+ (pix->width * fmt->depth[i]) >> 3;
+
+ if (pix->plane_fmt[i].sizeimage == 0)
+ pix->plane_fmt[i].sizeimage = (pix->width * pix->height *
+ fmt->depth[i] >> 3);
+ }
+
+ frame->bytesperline[0] = frame->width * frame->fmt->depth[0] / 8;
+ frame->sizeimage[0] = frame->height * frame->bytesperline[0];
+
+ set_frame_bounds(frame, pix->width, pix->height);
+ mxc_isi_m2m_config_src(mxc_isi, frame);
+
+ isi_m2m->colorspace = pix->colorspace;
+ isi_m2m->xfer_func = pix->xfer_func;
+ isi_m2m->ycbcr_enc = pix->ycbcr_enc;
+ isi_m2m->quant = pix->quantization;
+
+ return 0;
+}
+
+static int mxc_isi_m2m_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct v4l2_fh *fh = file->private_data;
+ struct mxc_isi_frame *frame = &isi_m2m->dst_f;
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_fmt *fmt;
+ struct vb2_queue *vq;
+ int bpl, i;
+ int ret;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ ret = mxc_isi_m2m_try_fmt_vid_cap(file, priv, f);
+ if (ret)
+ return ret;
+
+ vq = v4l2_m2m_get_vq(fh->m2m_ctx, f->type);
+ if (!vq)
+ return -EINVAL;
+
+ if (vb2_is_busy(vq)) {
+ dev_err(&isi_m2m->pdev->dev, "queue busy\n");
+ return -EBUSY;
+ }
+
+ for (i = 0; i < mxc_isi_out_formats_size; i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (pix && fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= mxc_isi_out_formats_size) {
+ dev_err(&isi_m2m->pdev->dev, "%s, format is not support!\n", __func__);
+ return -EINVAL;
+ }
+
+ /* update out put frame size and formate */
+ if (pix->height <= 0 || pix->width <= 0) {
+ dev_err(&isi_m2m->pdev->dev,
+ "Invalid width or height(w=%d, h=%d)\n",
+ pix->width, pix->height);
+ return -EINVAL;
+ }
+
+ if ((pix->pixelformat == V4L2_PIX_FMT_NV12) && ((pix->width / 4) % 2)) {
+ dev_err(&isi_m2m->pdev->dev,
+ "Invalid width or height(w=%d, h=%d) for NV12\n",
+ pix->width, pix->height);
+ return -EINVAL;
+ } else if ((pix->pixelformat != V4L2_PIX_FMT_XBGR32) && (pix->width % 2)) {
+ dev_err(&isi_m2m->pdev->dev,
+ "Invalid width or height(w=%d, h=%d) for %.4s\n",
+ pix->width, pix->height, (char *)&pix->pixelformat);
+ return -EINVAL;
+ }
+
+ frame->fmt = fmt;
+ frame->height = pix->height;
+ frame->width = pix->width;
+
+ pix->num_planes = fmt->memplanes;
+ for (i = 0; i < pix->num_planes; i++) {
+ bpl = pix->plane_fmt[i].bytesperline;
+
+ if ((bpl == 0) || (bpl / (fmt->depth[i] >> 3)) < pix->width)
+ pix->plane_fmt[i].bytesperline =
+ (pix->width * fmt->depth[i]) >> 3;
+
+ if (pix->plane_fmt[i].sizeimage == 0) {
+
+ if ((i == 1) && (pix->pixelformat == V4L2_PIX_FMT_NV12))
+ pix->plane_fmt[i].sizeimage =
+ (pix->width * (pix->height >> 1) * fmt->depth[i] >> 3);
+ else
+ pix->plane_fmt[i].sizeimage = (pix->width * pix->height *
+ fmt->depth[i] >> 3);
+ }
+ }
+
+ if (pix->num_planes > 1) {
+ for (i = 0; i < pix->num_planes; i++) {
+ frame->bytesperline[i] = pix->plane_fmt[i].bytesperline;
+ frame->sizeimage[i] = pix->plane_fmt[i].sizeimage;
+ }
+ } else {
+ frame->bytesperline[0] = frame->width * frame->fmt->depth[0] / 8;
+ frame->sizeimage[0] = frame->height * frame->bytesperline[0];
+ }
+
+ memcpy(&isi_m2m->pix, pix, sizeof(*pix));
+
+ set_frame_bounds(frame, pix->width, pix->height);
+ mxc_isi_m2m_config_dst(mxc_isi, frame);
+
+ return 0;
+}
+
+static int mxc_isi_m2m_g_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_frame *frame = &isi_m2m->dst_f;
+ int i;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ pix->width = frame->o_width;
+ pix->height = frame->o_height;
+ pix->field = V4L2_FIELD_NONE;
+ pix->pixelformat = frame->fmt->fourcc;
+ pix->colorspace = isi_m2m->colorspace;
+ pix->xfer_func = isi_m2m->xfer_func;
+ pix->ycbcr_enc = isi_m2m->ycbcr_enc;
+ pix->quantization = isi_m2m->quant;
+ pix->num_planes = frame->fmt->memplanes;
+
+ for (i = 0; i < pix->num_planes; ++i) {
+ pix->plane_fmt[i].bytesperline = frame->bytesperline[i];
+ pix->plane_fmt[i].sizeimage = frame->sizeimage[i];
+ }
+
+ return 0;
+}
+
+static int mxc_isi_m2m_g_fmt_vid_out(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_frame *frame = &isi_m2m->src_f;
+ int i;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ pix->width = frame->o_width;
+ pix->height = frame->o_height;
+ pix->field = V4L2_FIELD_NONE;
+ pix->pixelformat = frame->fmt->fourcc;
+ pix->num_planes = frame->fmt->memplanes;
+ pix->colorspace = isi_m2m->colorspace;
+ pix->xfer_func = isi_m2m->xfer_func;
+ pix->ycbcr_enc = isi_m2m->ycbcr_enc;
+ pix->quantization = isi_m2m->quant;
+
+ for (i = 0; i < pix->num_planes; ++i) {
+ pix->plane_fmt[i].bytesperline = frame->bytesperline[i];
+ pix->plane_fmt[i].sizeimage = frame->sizeimage[i];
+ }
+
+ return 0;
+}
+
+static int mxc_isi_m2m_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct mxc_isi_frame *src_f, *dst_f;
+ int ret;
+
+ src_f = &isi_m2m->src_f;
+ dst_f = &isi_m2m->dst_f;
+
+ if ((dst_f->width > src_f->width) ||
+ (dst_f->height > src_f->height)) {
+ dev_err(&isi_m2m->pdev->dev, "%s Not support upscale\n", __func__);
+ return -EINVAL;
+ }
+
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ isi_m2m->frame_count = 0;
+ mxc_isi_channel_config(mxc_isi, src_f, dst_f);
+ }
+
+ ret = v4l2_m2m_ioctl_streamon(file, priv, type);
+
+ return ret;
+}
+
+static int mxc_isi_m2m_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ int ret;
+
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ mxc_isi_channel_disable(mxc_isi);
+
+ ret = v4l2_m2m_ioctl_streamoff(file, priv, type);
+
+ return ret;
+}
+
+static int mxc_isi_m2m_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_frame *f = &isi_m2m->dst_f;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT &&
+ s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ s->r.left = 0;
+ s->r.top = 0;
+ s->r.width = f->o_width;
+ s->r.height = f->o_height;
+ return 0;
+
+ case V4L2_SEL_TGT_COMPOSE:
+ s->r.left = f->h_off;
+ s->r.top = f->v_off;
+ s->r.width = f->c_width;
+ s->r.height = f->c_height;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int enclosed_rectangle(struct v4l2_rect *a, struct v4l2_rect *b)
+{
+ if (a->left < b->left || a->top < b->top)
+ return 0;
+
+ if (a->left + a->width > b->left + b->width)
+ return 0;
+
+ if (a->top + a->height > b->top + b->height)
+ return 0;
+
+ return 1;
+}
+
+static int mxc_isi_m2m_s_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_frame *f;
+ struct v4l2_rect rect = s->r;
+ unsigned long flags;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+ if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT &&
+ s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ if (s->target == V4L2_SEL_TGT_COMPOSE)
+ f = &isi_m2m->dst_f;
+ else
+ return -EINVAL;
+
+ bounds_adjust(f, &rect);
+
+ if (s->flags & V4L2_SEL_FLAG_LE &&
+ !enclosed_rectangle(&rect, &s->r))
+ return -ERANGE;
+
+ if (s->flags & V4L2_SEL_FLAG_GE &&
+ !enclosed_rectangle(&s->r, &rect))
+ return -ERANGE;
+
+ if ((s->flags & V4L2_SEL_FLAG_LE) &&
+ (s->flags & V4L2_SEL_FLAG_GE) &&
+ (rect.width != s->r.width || rect.height != s->r.height))
+ return -ERANGE;
+
+ s->r = rect;
+ spin_lock_irqsave(&isi_m2m->slock, flags);
+ set_frame_crop(f, s->r.left, s->r.top, s->r.width,
+ s->r.height);
+ spin_unlock_irqrestore(&isi_m2m->slock, flags);
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops mxc_isi_m2m_ioctl_ops = {
+ .vidioc_querycap = mxc_isi_m2m_querycap,
+
+ .vidioc_enum_fmt_vid_cap = mxc_isi_m2m_enum_fmt_vid_cap,
+ .vidioc_enum_fmt_vid_out = mxc_isi_m2m_enum_fmt_vid_out,
+
+ .vidioc_try_fmt_vid_cap_mplane = mxc_isi_m2m_try_fmt_vid_cap,
+ .vidioc_try_fmt_vid_out_mplane = mxc_isi_m2m_try_fmt_vid_out,
+
+ .vidioc_s_fmt_vid_cap_mplane = mxc_isi_m2m_s_fmt_vid_cap,
+ .vidioc_s_fmt_vid_out_mplane = mxc_isi_m2m_s_fmt_vid_out,
+
+ .vidioc_g_fmt_vid_cap_mplane = mxc_isi_m2m_g_fmt_vid_cap,
+ .vidioc_g_fmt_vid_out_mplane = mxc_isi_m2m_g_fmt_vid_out,
+
+ .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+ .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+ .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+
+ .vidioc_streamon = mxc_isi_m2m_streamon,
+ .vidioc_streamoff = mxc_isi_m2m_streamoff,
+
+ .vidioc_g_selection = mxc_isi_m2m_g_selection,
+ .vidioc_s_selection = mxc_isi_m2m_s_selection,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+/*
+ * V4L2 controls handling
+ */
+#define ctrl_to_mxc_isi_m2m(__ctrl) \
+ container_of((__ctrl)->handler, struct mxc_isi_m2m_dev, ctrls.handler)
+
+static int mxc_isi_m2m_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = ctrl_to_mxc_isi_m2m(ctrl);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ unsigned long flags;
+ int ret = 0;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE)
+ return 0;
+
+ spin_lock_irqsave(&mxc_isi->slock, flags);
+
+ switch (ctrl->id) {
+ case V4L2_CID_HFLIP:
+ if (ctrl->val < 0) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+ mxc_isi->hflip = (ctrl->val > 0) ? 1 : 0;
+ break;
+
+ case V4L2_CID_VFLIP:
+ if (ctrl->val < 0) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+ mxc_isi->vflip = (ctrl->val > 0) ? 1 : 0;
+ break;
+
+ case V4L2_CID_ALPHA_COMPONENT:
+ if (ctrl->val < 0 || ctrl->val > 255) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+ mxc_isi->alpha = ctrl->val;
+ mxc_isi->alphaen = 1;
+ break;
+
+ default:
+ dev_err(&isi_m2m->pdev->dev, "%s: Not support %d CID\n", __func__, ctrl->id);
+ ret = -EINVAL;
+ }
+
+unlock:
+ spin_unlock_irqrestore(&mxc_isi->slock, flags);
+ return ret;
+}
+
+static int mxc_isi_m2m_g_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = ctrl_to_mxc_isi_m2m(ctrl);
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&isi_m2m->slock, flags);
+
+ switch (ctrl->id) {
+ case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE:
+ ctrl->val = isi_m2m->req_cap_buf_num;
+ break;
+ case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT:
+ ctrl->val = isi_m2m->req_out_buf_num;
+ break;
+ default:
+ dev_err(&isi_m2m->pdev->dev, "%s: Not support %d CID\n",
+ __func__, ctrl->id);
+ ret = -EINVAL;
+ }
+
+ spin_unlock_irqrestore(&isi_m2m->slock, flags);
+ return ret;
+
+}
+
+static const struct v4l2_ctrl_ops mxc_isi_m2m_ctrl_ops = {
+ .s_ctrl = mxc_isi_m2m_s_ctrl,
+ .g_volatile_ctrl = mxc_isi_m2m_g_ctrl,
+};
+
+static int mxc_isi_m2m_ctrls_create(struct mxc_isi_m2m_dev *isi_m2m)
+{
+ struct mxc_isi_ctrls *ctrls = &isi_m2m->ctrls;
+ struct v4l2_ctrl_handler *handler = &ctrls->handler;
+
+ if (isi_m2m->ctrls.ready)
+ return 0;
+
+ v4l2_ctrl_handler_init(handler, 4);
+
+ ctrls->hflip = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+ ctrls->vflip = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+ ctrls->alpha = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops,
+ V4L2_CID_ALPHA_COMPONENT, 0, 0xff, 1, 0);
+ ctrls->num_cap_buf = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops,
+ V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 3, 16, 1, 3);
+ ctrls->num_out_buf = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops,
+ V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, 1, 16, 1, 1);
+
+ if (!handler->error)
+ ctrls->ready = true;
+
+ return handler->error;
+
+}
+
+void mxc_isi_m2m_ctrls_delete(struct mxc_isi_m2m_dev *isi_m2m)
+{
+ struct mxc_isi_ctrls *ctrls = &isi_m2m->ctrls;
+
+ if (ctrls->ready) {
+ v4l2_ctrl_handler_free(&ctrls->handler);
+ ctrls->ready = false;
+ ctrls->alpha = NULL;
+ }
+}
+
+static int isi_m2m_probe(struct platform_device *pdev)
+{
+ struct mxc_isi_dev *mxc_isi;
+ struct mxc_isi_m2m_dev *isi_m2m;
+ struct v4l2_device *v4l2_dev;
+ struct video_device *vdev;
+ int ret = -ENOMEM;
+
+ isi_m2m = devm_kzalloc(&pdev->dev, sizeof(*isi_m2m), GFP_KERNEL);
+ if (!isi_m2m)
+ return -ENOMEM;
+ isi_m2m->pdev = pdev;
+
+ pdev->dev.parent = mxc_isi_dev_get_parent(pdev);
+ if (!pdev->dev.parent) {
+ dev_info(&pdev->dev, "deferring %s device registration\n",
+ dev_name(&pdev->dev));
+ return -EPROBE_DEFER;
+ }
+
+ mxc_isi = mxc_isi_get_hostdata(pdev);
+ if (!mxc_isi) {
+ dev_info(&pdev->dev, "deferring %s device registration\n",
+ dev_name(&pdev->dev));
+ return -EPROBE_DEFER;
+ }
+ mxc_isi->isi_m2m = isi_m2m;
+ isi_m2m->id = mxc_isi->id;
+ isi_m2m->refcnt = 0;
+ isi_m2m->colorspace = V4L2_COLORSPACE_SRGB;
+ isi_m2m->quant = V4L2_QUANTIZATION_FULL_RANGE;
+ isi_m2m->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(isi_m2m->colorspace);
+ isi_m2m->xfer_func = V4L2_XFER_FUNC_SRGB;
+
+ spin_lock_init(&isi_m2m->slock);
+ mutex_init(&isi_m2m->lock);
+
+ /* m2m */
+ isi_m2m->m2m_dev = v4l2_m2m_init(&mxc_isi_m2m_ops);
+ if (IS_ERR(isi_m2m->m2m_dev)) {
+ dev_err(&pdev->dev, "%s fail to get m2m device\n", __func__);
+ return PTR_ERR(isi_m2m->m2m_dev);
+ }
+
+ /* V4L2 device */
+ v4l2_dev = &isi_m2m->v4l2_dev;
+ strlcpy(v4l2_dev->name, "mx8-isi-m2m", sizeof(v4l2_dev->name));
+
+ ret = v4l2_device_register(&pdev->dev, v4l2_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to register v4l2_device\n");
+ return -EINVAL;
+ }
+
+ INIT_LIST_HEAD(&isi_m2m->out_active);
+
+ /* Video device */
+ vdev = &isi_m2m->vdev;
+ memset(vdev, 0, sizeof(*vdev));
+ snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.m2m", isi_m2m->id);
+
+ vdev->fops = &mxc_isi_m2m_fops;
+ vdev->ioctl_ops = &mxc_isi_m2m_ioctl_ops;
+ vdev->v4l2_dev = v4l2_dev;
+ vdev->minor = -1;
+ vdev->release = video_device_release_empty;
+ vdev->vfl_dir = VFL_DIR_M2M;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
+
+ ret = mxc_isi_m2m_ctrls_create(isi_m2m);
+ if (ret)
+ goto free_m2m;
+
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "%s fail to register video device\n", __func__);
+ goto ctrl_free;
+ }
+
+ vdev->ctrl_handler = &isi_m2m->ctrls.handler;
+ video_set_drvdata(vdev, isi_m2m);
+ platform_set_drvdata(pdev, isi_m2m);
+ pm_runtime_enable(&pdev->dev);
+
+ dev_info(&pdev->dev, "Register m2m success for ISI.%d\n", isi_m2m->id);
+
+ return 0;
+
+ctrl_free:
+ mxc_isi_m2m_ctrls_delete(isi_m2m);
+free_m2m:
+ v4l2_m2m_release(isi_m2m->m2m_dev);
+ return ret;
+
+}
+
+static int isi_m2m_remove(struct platform_device *pdev)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = platform_get_drvdata(pdev);
+ struct video_device *vdev = &isi_m2m->vdev;
+
+ if (video_is_registered(vdev)) {
+ video_unregister_device(vdev);
+ mxc_isi_m2m_ctrls_delete(isi_m2m);
+ media_entity_cleanup(&vdev->entity);
+ }
+ v4l2_m2m_release(isi_m2m->m2m_dev);
+ v4l2_device_unregister(&isi_m2m->v4l2_dev);
+ pm_runtime_disable(&isi_m2m->pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id isi_m2m_of_match[] = {
+ {.compatible = "imx-isi-m2m",},
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, isi_m2m_of_match);
+
+static struct platform_driver isi_m2m_driver = {
+ .probe = isi_m2m_probe,
+ .remove = isi_m2m_remove,
+ .driver = {
+ .of_match_table = isi_m2m_of_match,
+ .name = "isi-m2m",
+ },
+};
+
+static int __init mxc_isi_m2m_init(void)
+{
+ return platform_driver_register(&isi_m2m_driver);
+}
+late_initcall(mxc_isi_m2m_init);
+
+static void __exit mxc_isi_m2m_exit(void)
+{
+ platform_driver_unregister(&isi_m2m_driver);
+}
+module_exit(mxc_isi_m2m_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX8 Image Sensor Interface memory to memory driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ISI M2M");
+MODULE_VERSION("1.0");