summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDong Aisheng <aisheng.dong@nxp.com>2019-12-02 18:00:47 +0800
committerDong Aisheng <aisheng.dong@nxp.com>2019-12-02 18:00:47 +0800
commit028cc80de99b3196ac15580409e84574cfc79861 (patch)
tree3e306e09f44572b5fac558de2dc90f8840fc0b61
parent06e92d2f26e3562206ff9cec4c408d54b8b187f3 (diff)
parent6e2908e6e75e9f7f3a297c70bf006694e95c4838 (diff)
Merge remote-tracking branch 'origin/capture/isi' into capture/next
* origin/capture/isi: (9 commits) staging: media: imx: add g_parm/s_parm for imx8 capture device staging: media: imx: enable ISI for imx8mn platform media: staging: imx: add V4L2 memory to memory driver for ISI of imx8qxp/qm media: staging: imx: add isi capture driver support for imx8qm/qxp media: staging: imx: add isi core driver support for imx8qm/qxp ...
-rw-r--r--Documentation/devicetree/bindings/media/imx8-isi.txt33
-rw-r--r--drivers/staging/media/Makefile2
-rw-r--r--drivers/staging/media/imx/Kconfig17
-rw-r--r--drivers/staging/media/imx/Makefile5
-rw-r--r--drivers/staging/media/imx/imx8-isi-cap.c1766
-rw-r--r--drivers/staging/media/imx/imx8-isi-core.c513
-rw-r--r--drivers/staging/media/imx/imx8-isi-core.h364
-rw-r--r--drivers/staging/media/imx/imx8-isi-hw.c734
-rw-r--r--drivers/staging/media/imx/imx8-isi-hw.h521
-rw-r--r--drivers/staging/media/imx/imx8-isi-m2m.c1202
10 files changed, 5156 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 2f1711a8aeed..7cd981ec74e1 100644
--- a/drivers/staging/media/Makefile
+++ b/drivers/staging/media/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_VIDEO_ALLEGRO_DVT) += allegro-dvt/
-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_SUNXI) += sunxi/
diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig
index e56fd009d899..0bad318383d3 100644
--- a/drivers/staging/media/imx/Kconfig
+++ b/drivers/staging/media/imx/Kconfig
@@ -47,6 +47,23 @@ config VIDEO_IMX_CAPTURE
if VIDEO_IMX_CAPTURE
menu "i.MX8QXP/QM Camera ISI/MIPI Features support"
+config IMX8_ISI_CORE
+ bool "IMX8 Image Sensor Interface Core Driver"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ default y
+
+config IMX8_ISI_CAPTURE
+ bool "IMX8 Image Sensor Interface Capture Device Driver"
+ depends on IMX8_ISI_CORE
+ select VIDEOBUF2_DMA_CONTIG
+ default y
+
+config IMX8_ISI_M2M
+ bool "IMX8 Image Sensor Interface Memory to Memory Device Driver"
+ select V4L2_MEM2MEM_DEV
+ depends on IMX8_ISI_CORE
+ default y
+
config IMX8_MIPI_CSI2
tristate "IMX8 MIPI CSI2 Controller"
depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile
index 64faccf117e4..1805059ba6ea 100644
--- a/drivers/staging/media/imx/Makefile
+++ b/drivers/staging/media/imx/Makefile
@@ -3,6 +3,8 @@ imx6-media-objs := imx-media-dev.o imx-media-internal-sd.o \
imx-ic-common.o imx-ic-prp.o imx-ic-prpencvf.o imx-media-vdic.o \
imx-media-csc-scaler.o
+imx8-capture-objs := imx8-isi-core.o imx8-isi-hw.o
+
imx-media-common-objs := imx-media-capture.o imx-media-dev-common.o \
imx-media-of.o imx-media-utils.o
@@ -16,5 +18,8 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o
obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o
+obj-$(CONFIG_IMX8_ISI_CORE) += imx8-capture.o
+obj-$(CONFIG_IMX8_ISI_CAPTURE) += imx8-isi-cap.o
+obj-$(CONFIG_IMX8_ISI_M2M) += imx8-isi-m2m.o
obj-$(CONFIG_IMX8_MIPI_CSI2) += imx8-mipi-csi2.o
obj-$(CONFIG_IMX8_MIPI_CSI2_SAM) += imx8-mipi-csi2-sam.o
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..f24205cbd3d4
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-cap.c
@@ -0,0 +1,1766 @@
+// 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 (c) 2019 NXP Semiconductor
+ *
+ */
+
+#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/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "imx8-isi-hw.h"
+#include "imx8-common.h"
+
+#define sd_to_cap_dev(ptr) container_of(ptr, struct mxc_isi_cap_dev, sd)
+
+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,
+ }
+};
+
+/*
+ * 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)ARRAY_SIZE(mxc_isi_out_formats))
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); 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);
+ /* fall through */
+ case 2:
+ paddr->cb = vb2_dma_contig_plane_dma_addr(vb2, 1);
+ /* fall through */
+ 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);
+
+ 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);
+}
+
+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;
+
+ 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,
+ 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,
+ (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, *tmp;
+ 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(&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(&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(&buf->list);
+ }
+
+ list_for_each_entry_safe(buf, tmp, &isi_cap->out_active, list) {
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ list_for_each_entry_safe(buf, tmp, &isi_cap->out_pending, list) {
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ 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,
+ 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 mxc_isi_cap_dev *isi_cap)
+{
+ struct v4l2_subdev *subdev = &isi_cap->sd;
+ 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 mxc_isi_cap_dev *isi_cap,
+ 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(isi_cap);
+ if (!source_pad) {
+ v4l2_err(&isi_cap->sd,
+ "%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(&isi_cap->sd, "%s, No remote subdev found!\n", label);
+ return NULL;
+ }
+
+ return sen_sd;
+}
+
+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;
+ struct v4l2_subdev *sd;
+ int ret = -EBUSY;
+
+ if (mxc_isi->m2m_enabled) {
+ dev_err(dev, "ISI channel[%d] is busy\n", isi_cap->id);
+ return ret;
+ }
+
+ sd = mxc_get_remote_subdev(isi_cap, __func__);
+ if (!sd)
+ return -ENODEV;
+
+ 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);
+
+ ret = v4l2_subdev_call(sd, core, s_power, 1);
+ if (ret) {
+ dev_err(dev, "Call subdev s_power fail!\n");
+ pm_runtime_put(dev);
+ return ret;
+ }
+
+ /* increase usage count for ISI channel */
+ mutex_lock(&mxc_isi->lock);
+ atomic_inc(&mxc_isi->usage_count);
+ mxc_isi->m2m_enabled = false;
+ mutex_unlock(&mxc_isi->lock);
+
+ return 0;
+}
+
+static int mxc_isi_capture_release(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;
+ struct v4l2_subdev *sd;
+ int ret = -1;
+
+ sd = mxc_get_remote_subdev(isi_cap, __func__);
+ if (!sd)
+ goto label;
+
+ 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);
+
+ ret = v4l2_subdev_call(sd, core, s_power, 0);
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
+ dev_err(dev, "%s s_power fail\n", __func__);
+ goto label;
+ }
+
+label:
+ 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)ARRAY_SIZE(mxc_isi_out_formats))
+ return -EINVAL;
+
+ fmt = &mxc_isi_out_formats[f->index];
+ if (!fmt)
+ return -EINVAL;
+
+ 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_JPEG;
+ 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 i;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_out_formats)) {
+ 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;
+ }
+
+ 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);
+ 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, __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;
+
+ /* 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 < ARRAY_SIZE(mxc_isi_out_formats); i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (pix && fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_out_formats)) {
+ 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;
+
+ 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, __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, __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);
+ int ret;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ ret = mxc_isi_config_parm(isi_cap);
+ if (ret < 0)
+ return ret;
+
+ 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)
+ return ret;
+
+ mxc_isi->is_streaming = 1;
+
+ return 0;
+}
+
+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);
+ int ret;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ mxc_isi_pipeline_enable(isi_cap, 0);
+ mxc_isi_channel_disable(mxc_isi);
+ ret = vb2_ioctl_streamoff(file, priv, type);
+
+ mxc_isi->is_streaming = 0;
+
+ return ret;
+}
+
+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->src_f;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ f = &isi_cap->dst_f;
+ /* fall through */
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ 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:
+ f = &isi_cap->dst_f;
+ /* fall through */
+ case V4L2_SEL_TGT_CROP:
+ s->r.left = f->h_off;
+ s->r.top = f->v_off;
+ s->r.width = f->width;
+ s->r.height = f->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_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ if (s->target == V4L2_SEL_TGT_COMPOSE)
+ f = &isi_cap->dst_f;
+ else if (s->target == V4L2_SEL_TGT_CROP)
+ f = &isi_cap->src_f;
+ else
+ return -EINVAL;
+
+ 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;
+
+ 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, __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,imx8mn-isi")) &&
+ (fse.max_width > ISI_2K || fse.min_width > ISI_2K))
+ 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, __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,imx8mn-isi") &&
+ fie.width > ISI_2K)
+ 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,
+};
+
+/* 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_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ return 0;
+}
+
+static int mxc_isi_subdev_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ 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;
+
+ 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;
+
+ mutex_unlock(&isi_cap->lock);
+ mf->colorspace = V4L2_COLORSPACE_JPEG;
+
+ return 0;
+}
+
+static int mxc_isi_subdev_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ 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 < ARRAY_SIZE(mxc_isi_out_formats); i++) {
+ out_fmt = &mxc_isi_out_formats[i];
+ if (mf->code == out_fmt->mbus_code)
+ break;
+ }
+ if (i >= ARRAY_SIZE(mxc_isi_out_formats)) {
+ 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_pad_config *cfg,
+ 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;
+ /* fall through */
+ 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, cfg, sel->pad);
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ try_sel = v4l2_subdev_get_try_compose(sd, cfg, 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_pad_config *cfg,
+ 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, cfg, sel->pad);
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ try_sel = v4l2_subdev_get_try_compose(sd, cfg, 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;
+
+ /* Default configuration */
+ isi_cap->dst_f.width = 1280;
+ isi_cap->dst_f.height = 800;
+ isi_cap->dst_f.fmt = &mxc_isi_out_formats[0];
+ isi_cap->src_f.fmt = isi_cap->dst_f.fmt;
+
+ 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_GRABBER, -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);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ 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..8127722c5796
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-core.c
@@ -0,0 +1,513 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 NXP Semiconductor
+ *
+ */
+
+#include "imx8-isi-hw.h"
+
+static const struct of_device_id mxc_isi_of_match[];
+
+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;
+
+ device_lock(pdev->dev.parent);
+ mxc_isi = (struct mxc_isi_dev *)dev_get_drvdata(pdev->dev.parent);
+ if (!mxc_isi) {
+ dev_err(&pdev->dev, "Cann't get host data\n");
+ device_unlock(pdev->dev.parent);
+ return NULL;
+ }
+ device_unlock(pdev->dev.parent);
+
+ return mxc_isi;
+}
+
+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;
+}
+
+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;
+ u32 status;
+
+ spin_lock(&mxc_isi->slock);
+
+ 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 & (CHNL_STS_OFLW_PANIC_Y_BUF_MASK |
+ CHNL_STS_OFLW_PANIC_U_BUF_MASK |
+ CHNL_STS_OFLW_PANIC_V_BUF_MASK))
+ dev_dbg(dev, "%s, IRQ Panic OFLW Error stat=0x%X\n", __func__, status);
+
+ if (status & (CHNL_STS_OFLW_Y_BUF_MASK |
+ CHNL_STS_OFLW_U_BUF_MASK |
+ CHNL_STS_OFLW_V_BUF_MASK))
+ dev_dbg(dev, "%s, IRQ OFLW Error stat=0x%X\n", __func__, status);
+
+ if (status & (CHNL_STS_EXCS_OFLW_Y_BUF_MASK |
+ CHNL_STS_EXCS_OFLW_U_BUF_MASK |
+ CHNL_STS_EXCS_OFLW_V_BUF_MASK))
+ dev_dbg(dev, "%s, IRQ EXCS OFLW Error stat=0x%X\n", __func__, status);
+
+ spin_unlock(&mxc_isi->slock);
+ return IRQ_HANDLED;
+}
+
+static int disp_mix_sft_rstn(struct reset_control *reset, bool enable)
+{
+ int ret;
+
+ if (!reset)
+ return 0;
+
+ ret = enable ? reset_control_assert(reset) :
+ reset_control_deassert(reset);
+ return ret;
+}
+
+static int disp_mix_clks_enable(struct reset_control *reset, bool enable)
+{
+ int ret;
+
+ if (!reset)
+ return 0;
+
+ ret = enable ? reset_control_assert(reset) :
+ reset_control_deassert(reset);
+ 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_data = {
+ .clk_get = mxc_imx8_clk_get,
+ .clk_enable = mxc_imx8_clk_enable,
+ .clk_disable = mxc_imx8_clk_disable,
+};
+
+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_dev_ops mxc_imx8mn_data = {
+ .clk_get = mxc_imx8mn_clk_get,
+ .clk_enable = mxc_imx8mn_clk_enable,
+ .clk_disable = mxc_imx8mn_clk_disable,
+};
+
+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");
+ mxc_isi->chain_buf = of_property_read_bool(node, "fsl,chain_buf");
+
+ 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->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->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->ops;
+
+ if (!ops && !ops->clk_disable)
+ return;
+
+ ops->clk_disable(mxc_isi);
+}
+
+static int mxc_isi_of_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;
+ uint32_t 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_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->ops = of_id->data;
+ if (!mxc_isi->ops) {
+ dev_err(dev, "Can't get platform device data\n");
+ return -EINVAL;
+ }
+
+ 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;
+ }
+
+ spin_lock_init(&mxc_isi->slock);
+ mutex_init(&mxc_isi->lock);
+ atomic_set(&mxc_isi->usage_count, 0);
+
+ if (of_device_is_compatible(dev->of_node, "fsl,imx8mn-isi")) {
+ ret = mxc_isi_of_parse_resets(mxc_isi);
+ if (ret) {
+ dev_warn(dev, "Can not parse reset control\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;
+ }
+
+ 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->soft_resetn, false);
+ disp_mix_clks_enable(mxc_isi->clk_enable, 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);
+
+ 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->clk_enable, false);
+ disp_mix_sft_rstn(mxc_isi->soft_resetn, true);
+ mxc_isi_clk_disable(mxc_isi);
+ return -ENXIO;
+}
+
+static int mxc_isi_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->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->clk_enable, 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->soft_resetn, false);
+ disp_mix_clks_enable(mxc_isi->clk_enable, 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 },
+ { /* 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..44f2a25271ee
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-core.h
@@ -0,0 +1,364 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2019 NXP Semiconductor
+ */
+
+#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 "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;
+
+ u32 req_cap_buf_num;
+ u32 req_out_buf_num;
+
+ u8 id;
+};
+
+struct mxc_isi_ctx {
+ struct mxc_isi_m2m_dev *isi_m2m;
+ struct v4l2_fh fh;
+};
+
+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_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;
+
+ 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;
+
+ const struct mxc_isi_dev_ops *ops;
+
+ struct reset_control *soft_resetn;
+ struct reset_control *clk_enable;
+
+ struct mutex lock;
+ spinlock_t slock;
+
+ void __iomem *regs;
+
+ u8 chain_buf;
+ u8 alpha;
+ bool m2m_enabled;
+
+ /* 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;
+
+ u32 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:1;
+ 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;
+}
+
+#if defined(CONFIG_IMX8_ISI_CORE)
+struct mxc_isi_dev *mxc_isi_get_hostdata(struct platform_device *pdev);
+struct device *mxc_isi_dev_get_parent(struct platform_device *pdev);
+#else
+static inline struct mxc_isi_dev *mxc_isi_get_hostdata(struct platform_device *pdev) {}
+static inline struct struct device *mxc_isi_dev_get_parent(struct platform_device *pdev) {}
+#endif
+
+#endif /* __MXC_ISI_CORE_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..6605341e0b57
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-hw.c
@@ -0,0 +1,734 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 NXP Semiconductor
+ *
+ */
+
+#include <dt-bindings/pinctrl/pads-imx8qxp.h>
+
+#include "imx8-isi-hw.h"
+#include "imx8-common.h"
+
+#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[64];
+ } registers[] = {
+ { 0x00h, "CHNL_CTRL" },
+ { 0x04h, "CHNL_IMG_CTRL" },
+ { 0x08h, "CHNL_OUT_BUF_CTRL" },
+ { 0x0Ch, "CHNL_IMG_CFG" },
+ { 0x10h, "CHNL_IER" },
+ { 0x14h, "CHNL_STS" },
+ { 0x18h, "CHNL_SCALE_FACTOR" },
+ { 0x1Ch, "CHNL_SCALE_OFFSET" },
+ { 0x20h, "CHNL_CROP_ULC" },
+ { 0x24h, "CHNL_CROP_LRC" },
+ { 0x28h, "CHNL_CSC_COEFF0" },
+ { 0x2Ch, "CHNL_CSC_COEFF1" },
+ { 0x30h, "CHNL_CSC_COEFF2" },
+ { 0x34h, "CHNL_CSC_COEFF3" },
+ { 0x38h, "CHNL_CSC_COEFF4" },
+ { 0x3Ch, "CHNL_CSC_COEFF5" },
+ { 0x40h, "CHNL_ROI_0_ALPHA" },
+ { 0x44h, "CHNL_ROI_0_ULC" },
+ { 0x48h, "CHNL_ROI_0_LRC" },
+ { 0x4Ch, "CHNL_ROI_1_ALPHA" },
+ { 0x50h, "CHNL_ROI_1_ULC" },
+ { 0x54h, "CHNL_ROI_1_LRC" },
+ { 0x58h, "CHNL_ROI_2_ALPHA" },
+ { 0x5Ch, "CHNL_ROI_2_ULC" },
+ { 0x60h, "CHNL_ROI_2_LRC" },
+ { 0x64h, "CHNL_ROI_3_ALPHA" },
+ { 0x68h, "CHNL_ROI_3_ULC" },
+ { 0x6Ch, "CHNL_ROI_3_LRC" },
+ { 0x70h, "CHNL_OUT_BUF1_ADDR_Y" },
+ { 0x74h, "CHNL_OUT_BUF1_ADDR_U" },
+ { 0x78h, "CHNL_OUT_BUF1_ADDR_V" },
+ { 0x7Ch, "CHNL_OUT_BUF_PITCH" },
+ { 0x80h, "CHNL_IN_BUF_ADDR" },
+ { 0x84h, "CHNL_IN_BUF_PITCH" },
+ { 0x88h, "CHNL_MEM_RD_CTRL" },
+ { 0x8Ch, "CHNL_OUT_BUF2_ADDR_Y" },
+ { 0x90h, "CHNL_OUT_BUF2_ADDR_U" },
+ { 0x94h, "CHNL_OUT_BUF2_ADDR_V" },
+ { 0x98h, "CHNL_SCL_IMG_CFG" },
+ { 0x9Ch, "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.offset);
+ dev_dbg(dev, "%20s[0x%.2x]: %.2x\n",
+ registers.name, registers.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_RGBA) ||
+ (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_YUV24) ||
+ (pix_fmt == V4L2_PIX_FMT_NV12))
+ return true;
+ else
+ return false;
+}
+
+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);
+ } else if (!mxc_isi->chain_buf) {
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~CHNL_CTRL_CHAIN_BUF_MASK;
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+ }
+}
+
+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 || ((mxc_isi->status & 0x100) && (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 || mxc_isi->status & 0x200) {
+ 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);
+}
+
+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);
+}
+
+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);
+}
+
+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 |= CHNL_CTRL_SRC_INPUT_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 |= CHNL_CTRL_SRC_INPUT_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 |= CHNL_CTRL_SRC_INPUT_DC0;
+ break;
+ case ISI_INPUT_INTERFACE_DC1:
+ val |= CHNL_CTRL_SRC_INPUT_DC1;
+ break;
+ case ISI_INPUT_INTERFACE_HDMI:
+ val |= CHNL_CTRL_SRC_INPUT_HDMI;
+ break;
+ case ISI_INPUT_INTERFACE_PARALLEL_CSI:
+ val |= CHNL_CTRL_SRC_INPUT_CSI;
+ break;
+ case ISI_INPUT_INTERFACE_MEM:
+ val |= CHNL_CTRL_SRC_INPUT_MEMORY;
+ 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);
+}
+
+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);
+}
+
+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_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 *src_f = &mxc_isi->isi_cap->src_f;
+ struct v4l2_rect crop;
+ u32 val, val0, val1, temp;
+
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~CHNL_IMG_CTRL_CROP_EN_MASK;
+
+ if ((src_f->o_height == src_f->height) &&
+ (src_f->o_width == src_f->width)) {
+ mxc_isi->crop = 0;
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+ return;
+ }
+
+ if (mxc_isi->scale) {
+ temp = (src_f->h_off << 12) / mxc_isi->xfactor;
+ crop.left = temp >> mxc_isi->pre_dec_x;
+ temp = (src_f->v_off << 12) / mxc_isi->yfactor;
+ crop.top = temp >> mxc_isi->pre_dec_y;
+ temp = (src_f->width << 12) / mxc_isi->xfactor;
+ crop.width = temp >> mxc_isi->pre_dec_x;
+ temp = (src_f->height << 12) / mxc_isi->yfactor;
+ crop.height = temp >> mxc_isi->pre_dec_y;
+ } else {
+ crop.left = src_f->h_off;
+ crop.top = src_f->v_off;
+ crop.width = src_f->width;
+ crop.height = src_f->height;
+ }
+
+ 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.height | (crop.width << CHNL_CROP_LRC_X_OFFSET);
+
+ writel(val0, mxc_isi->regs + CHNL_CROP_ULC);
+ writel((val1 + val0), 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);
+}
+
+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_ENABLE << CHNL_CTRL_CLK_EN_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+
+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);
+
+ /* 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);
+
+ 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);
+}
+
+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);
+}
+
+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 |= (CHNL_CTRL_SRC_INPUT_MEMORY << 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);
+}
+
+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);
+}
+
+void mxc_isi_enable_irq(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ val = CHNL_IER_FRM_RCVD_EN_MASK |
+ CHNL_IER_OFLW_Y_BUF_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 |
+ CHNL_IER_OFLW_PANIC_V_BUF_EN_MASK |
+ CHNL_IER_EXCS_OFLW_V_BUF_EN_MASK |
+ CHNL_IER_OFLW_V_BUF_EN_MASK |
+ CHNL_IER_OFLW_PANIC_U_BUF_EN_MASK |
+ CHNL_IER_EXCS_OFLW_U_BUF_EN_MASK |
+ CHNL_IER_OFLW_U_BUF_EN_MASK |
+ CHNL_IER_OFLW_PANIC_Y_BUF_EN_MASK |
+ CHNL_IER_EXCS_OFLW_Y_BUF_EN_MASK |
+ CHNL_IER_OFLW_Y_BUF_EN_MASK;
+
+ writel(val, mxc_isi->regs + CHNL_IER);
+}
+
+void mxc_isi_disable_irq(struct mxc_isi_dev *mxc_isi)
+{
+ writel(0, mxc_isi->regs + CHNL_IER);
+}
+
+u32 mxc_isi_get_irq_status(struct mxc_isi_dev *mxc_isi)
+{
+ return readl(mxc_isi->regs + CHNL_STS);
+}
+
+void mxc_isi_clean_irq_status(struct mxc_isi_dev *mxc_isi, u32 val)
+{
+ writel(val, mxc_isi->regs + CHNL_STS);
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
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..268be48d1f7f
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-hw.h
@@ -0,0 +1,521 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2019 NXP Semiconductor
+ *
+ */
+
+#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 0x60000
+#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_DC0 0
+#define CHNL_CTRL_SRC_INPUT_DC1 1
+#define CHNL_CTRL_SRC_INPUT_MIPI0 2
+#define CHNL_CTRL_SRC_INPUT_MIPI1 3
+#define CHNL_CTRL_SRC_INPUT_HDMI 4
+#define CHNL_CTRL_SRC_INPUT_CSI 4
+#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
+#define CHNL_IER_OFLW_PANIC_V_BUF_EN_OFFSET 24
+#define CHNL_IER_OFLW_PANIC_V_BUF_EN_MASK 0x1000000
+#define CHNL_IER_OFLW_PANIC_V_BUF_EN_ENABLE 1
+#define CHNL_IER_EXCS_OFLW_V_BUF_EN_OFFSET 23
+#define CHNL_IER_EXCS_OFLW_V_BUF_EN_MASK 0x800000
+#define CHNL_IER_EXCS_OFLW_V_BUF_EN_ENABLE 1
+#define CHNL_IER_OFLW_V_BUF_EN_OFFSET 22
+#define CHNL_IER_OFLW_V_BUF_EN_MASK 0x400000
+#define CHNL_IER_OFLW_V_BUF_EN_ENABLE 1
+#define CHNL_IER_OFLW_PANIC_U_BUF_EN_OFFSET 21
+#define CHNL_IER_OFLW_PANIC_U_BUF_EN_MASK 0x200000
+#define CHNL_IER_OFLW_PANIC_U_BUF_EN_ENABLE 1
+#define CHNL_IER_EXCS_OFLW_U_BUF_EN_OFFSET 20
+#define CHNL_IER_EXCS_OFLW_U_BUF_EN_MASK 0x100000
+#define CHNL_IER_EXCS_OFLW_U_BUF_EN_ENABLE 1
+#define CHNL_IER_OFLW_U_BUF_EN_OFFSET 19
+#define CHNL_IER_OFLW_U_BUF_EN_MASK 0x80000
+#define CHNL_IER_OFLW_U_BUF_EN_ENABLE 1
+#define CHNL_IER_OFLW_PANIC_Y_BUF_EN_OFFSET 18
+#define CHNL_IER_OFLW_PANIC_Y_BUF_EN_MASK 0x40000
+#define CHNL_IER_OFLW_PANIC_Y_BUF_EN_ENABLE 1
+#define CHNL_IER_EXCS_OFLW_Y_BUF_EN_OFFSET 17
+#define CHNL_IER_EXCS_OFLW_Y_BUF_EN_MASK 0x20000
+#define CHNL_IER_EXCS_OFLW_Y_BUF_EN_ENABLE 1
+#define CHNL_IER_OFLW_Y_BUF_EN_OFFSET 16
+#define CHNL_IER_OFLW_Y_BUF_EN_MASK 0x10000
+#define CHNL_IER_OFLW_Y_BUF_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);
+#if defined(CONFIG_IMX8_ISI_CAPTURE)
+void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi);
+#else
+static inline void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi) {}
+#endif
+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);
+void mxc_isi_channel_set_memory_image(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);
+#if defined(CONFIG_IMX8_ISI_M2M)
+void mxc_isi_m2m_frame_write_done(struct mxc_isi_dev *mxc_isi);
+#else
+static inline void mxc_isi_m2m_frame_write_done(struct mxc_isi_dev *mxc_isi) {}
+#endif
+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);
+
+#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..2d116df4b83e
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-m2m.c
@@ -0,0 +1,1202 @@
+// 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 (c) 2019 NXP Semiconductor
+ *
+ */
+
+#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/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "imx8-isi-hw.h"
+#include "imx8-common.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);
+
+#if defined(CONFIG_IMX8_ISI_CAPTURE)
+extern struct mxc_isi_fmt mxc_isi_out_formats[9];
+#else
+static struct mxc_isi_fmt mxc_isi_out_formats[9] = {};
+#endif
+
+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 = "YUV24 (X-Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUV24,
+ .depth = { 24 },
+ .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_RGBA,
+ .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);
+}
+
+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) {
+ if (*num_buffers < 3) {
+ dev_err(dev, "%s at least need 3 buffer\n", __func__);
+ return -EINVAL;
+ }
+ frame = &isi_m2m->dst_f;
+ isi_m2m->req_cap_buf_num = *num_buffers;
+ } else {
+ if (*num_buffers < 1) {
+ dev_err(dev, "%s at least need one buffer\n", __func__);
+ return -EINVAL;
+ }
+ frame = &isi_m2m->src_f;
+ 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 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;
+
+ if (atomic_read(&mxc_isi->usage_count) > 0) {
+ dev_err(dev, "ISI channel[%d] is busy\n", isi_m2m->id);
+ return -EBUSY;
+ }
+
+ if (mutex_lock_interruptible(&isi_m2m->lock))
+ return -ERESTARTSYS;
+
+ 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);
+
+ pm_runtime_get_sync(dev);
+ if (atomic_inc_return(&mxc_isi->usage_count) == 1)
+ mxc_isi_channel_init(mxc_isi);
+
+ /* lock host data */
+ mutex_lock(&mxc_isi->lock);
+ mxc_isi->m2m_enabled = true;
+ mutex_unlock(&mxc_isi->lock);
+unlock:
+ 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);
+
+ v4l2_fh_del(&mxc_ctx->fh);
+ v4l2_fh_exit(&mxc_ctx->fh);
+
+ mutex_lock(&isi_m2m->lock);
+ v4l2_m2m_ctx_release(mxc_ctx->fh.m2m_ctx);
+ mutex_unlock(&isi_m2m->lock);
+
+ kfree(mxc_ctx);
+ if (atomic_dec_and_test(&mxc_isi->usage_count))
+ mxc_isi_channel_deinit(mxc_isi);
+
+ mutex_lock(&mxc_isi->lock);
+ mxc_isi->m2m_enabled = false;
+ mutex_unlock(&mxc_isi->lock);
+
+ pm_runtime_put(dev);
+ 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 |
+ V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_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)ARRAY_SIZE(mxc_isi_out_formats))
+ 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 device *dev = &isi_m2m->pdev->dev;
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_fmt *fmt = NULL;
+ int i;
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_input_formats); i++) {
+ fmt = &mxc_isi_input_formats[i];
+ if (fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_input_formats)) {
+ dev_err(dev, "%s, format is not support!\n", __func__);
+ return -EINVAL;
+ }
+
+ if (pix->width <= 0 || pix->height <= 0) {
+ dev_err(dev, "%s, width %d, height %d is not valid\n"
+ , __func__, pix->width, pix->height);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+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 device *dev = &isi_m2m->pdev->dev;
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_fmt *fmt = NULL;
+ int i;
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_out_formats)) {
+ dev_err(dev, "%s, format is not support!\n", __func__);
+ return -EINVAL;
+ }
+
+ if (pix->width <= 0 || pix->height <= 0) {
+ dev_err(dev, "%s, width %d, height %d is not valid\n"
+ , __func__, pix->width, pix->height);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+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;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ 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->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);
+
+ 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;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ 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_out_formats); i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (pix && fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_out_formats)) {
+ 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));*/
+ 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 = V4L2_COLORSPACE_JPEG;
+ 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->colorspace = V4L2_COLORSPACE_JPEG;
+ 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_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 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,
+};
+
+/*
+ * 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;
+
+ 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 |
+ V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE;
+
+ ret = mxc_isi_m2m_ctrls_create(isi_m2m);
+ if (ret)
+ goto free_m2m;
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -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");