Re: [PATCH v2 7/7] media: nxp: Add i.MX8 ISI driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, Jul 11, 2022 at 7:06 PM Laurent Pinchart
<laurent.pinchart@xxxxxxxxxxxxxxxx> wrote:
>
> The Image Sensing Interface (ISI) combines image processing pipelines
> with DMA engines to process and capture frames originating from a
> variety of sources. The inputs to the ISI go through Pixel Link
> interfaces, and their number and nature is SoC-dependent. They cover
> both capture interfaces (MIPI CSI-2 RX, HDMI RX) and memory inputs.
>
I have a patch set pending this which adds the functionality to the
Nano which I have tested using an OV5640 camera.  If/when this gets
accepted, I can submit the corresponding Nano patches.

I haven't tried all possible video formats due to the limitations of
the camera I used, but for those that I tried:

Tested-by: Adam Ford <aford173@xxxxxxxxx> #imx8mn-beacon

> Signed-off-by: Christian Hemp <c.hemp@xxxxxxxxx>
> Signed-off-by: Dong Aisheng <aisheng.dong@xxxxxxx>
> Signed-off-by: Guoniu Zhou <guoniu.zhou@xxxxxxx>
> Signed-off-by: Jacopo Mondi <jacopo@xxxxxxxxxx>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>
> Signed-off-by: Stefan Riedmueller <s.riedmueller@xxxxxxxxx>
> ---
>  MAINTAINERS                                   |    7 +
>  drivers/media/platform/nxp/Kconfig            |    2 +
>  drivers/media/platform/nxp/Makefile           |    1 +
>  drivers/media/platform/nxp/imx8-isi/Kconfig   |   22 +
>  drivers/media/platform/nxp/imx8-isi/Makefile  |    9 +
>  .../platform/nxp/imx8-isi/imx8-isi-core.c     |  646 +++++++
>  .../platform/nxp/imx8-isi/imx8-isi-core.h     |  394 +++++
>  .../platform/nxp/imx8-isi/imx8-isi-crossbar.c |  529 ++++++
>  .../platform/nxp/imx8-isi/imx8-isi-debug.c    |  109 ++
>  .../media/platform/nxp/imx8-isi/imx8-isi-hw.c |  651 +++++++
>  .../platform/nxp/imx8-isi/imx8-isi-m2m.c      |  858 ++++++++++
>  .../platform/nxp/imx8-isi/imx8-isi-pipe.c     |  867 ++++++++++
>  .../platform/nxp/imx8-isi/imx8-isi-regs.h     |  418 +++++
>  .../platform/nxp/imx8-isi/imx8-isi-video.c    | 1513 +++++++++++++++++
>  14 files changed, 6026 insertions(+)
>  create mode 100644 drivers/media/platform/nxp/imx8-isi/Kconfig
>  create mode 100644 drivers/media/platform/nxp/imx8-isi/Makefile
>  create mode 100644 drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
>  create mode 100644 drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h
>  create mode 100644 drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c
>  create mode 100644 drivers/media/platform/nxp/imx8-isi/imx8-isi-debug.c
>  create mode 100644 drivers/media/platform/nxp/imx8-isi/imx8-isi-hw.c
>  create mode 100644 drivers/media/platform/nxp/imx8-isi/imx8-isi-m2m.c
>  create mode 100644 drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c
>  create mode 100644 drivers/media/platform/nxp/imx8-isi/imx8-isi-regs.h
>  create mode 100644 drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1fc9ead83d2a..02327b4d8c9f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14270,6 +14270,13 @@ F:     Documentation/devicetree/bindings/clock/imx*
>  F:     drivers/clk/imx/
>  F:     include/dt-bindings/clock/imx*
>
> +NXP i.MX 8M ISI DRIVER
> +M:     Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>
> +L:     linux-media@xxxxxxxxxxxxxxx
> +S:     Maintained
> +F:     Documentation/devicetree/bindings/media/nxp,imx8-isi.yaml
> +F:     drivers/media/platform/nxp/imx8-isi/
> +
>  NXP i.MX 8MQ DCSS DRIVER
>  M:     Laurentiu Palcu <laurentiu.palcu@xxxxxxxxxxx>
>  R:     Lucas Stach <l.stach@xxxxxxxxxxxxxx>
> diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig
> index 1ac0a6e91111..0f5f8976ae78 100644
> --- a/drivers/media/platform/nxp/Kconfig
> +++ b/drivers/media/platform/nxp/Kconfig
> @@ -27,6 +27,8 @@ config VIDEO_VIU
>           Say Y here if you want to enable VIU device on MPC5121e Rev2+.
>           In doubt, say N.
>
> +source "drivers/media/platform/nxp/imx8-isi/Kconfig"
> +
>  # mem2mem drivers
>
>  config VIDEO_IMX_PXP
> diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile
> index efc38c6578ce..a45a8626d063 100644
> --- a/drivers/media/platform/nxp/Makefile
> +++ b/drivers/media/platform/nxp/Makefile
> @@ -1,6 +1,7 @@
>  # SPDX-License-Identifier: GPL-2.0-only
>
>  obj-y += imx-jpeg/
> +obj-y += imx8-isi/
>
>  obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o
>  obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o
> diff --git a/drivers/media/platform/nxp/imx8-isi/Kconfig b/drivers/media/platform/nxp/imx8-isi/Kconfig
> new file mode 100644
> index 000000000000..fcff33fc2630
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx8-isi/Kconfig
> @@ -0,0 +1,22 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config VIDEO_IMX8_ISI
> +       tristate "i.MX8 Image Sensor Interface (ISI) driver"
> +       depends on ARCH_MXC || COMPILE_TEST
> +       depends on HAS_DMA && PM
> +       depends on VIDEO_DEV
> +       select MEDIA_CONTROLLER
> +       select V4L2_FWNODE
> +       select V4L2_MEM2MEM_DEV if VIDEO_IMX8_ISI_M2M
> +       select VIDEO_V4L2_SUBDEV_API
> +       select VIDEOBUF2_DMA_CONTIG
> +       help
> +         V4L2 driver for the Image Sensor Interface (ISI) found in various
> +         i.MX8 SoCs.
> +
> +config VIDEO_IMX8_ISI_M2M
> +       bool "i.MX8 Image Sensor Interface (ISI) memory-to-memory support"
> +       depends on VIDEO_IMX8_ISI
> +       help
> +         Select 'yes' here to enable support for memory-to-memory processing
> +         in the ISI driver.
> diff --git a/drivers/media/platform/nxp/imx8-isi/Makefile b/drivers/media/platform/nxp/imx8-isi/Makefile
> new file mode 100644
> index 000000000000..6df851a00c2c
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx8-isi/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +imx8-isi-y := imx8-isi-core.o imx8-isi-crossbar.o imx8-isi-hw.o \
> +       imx8-isi-pipe.o imx8-isi-video.o
> +imx8-isi-$(CONFIG_DEBUG_FS) += imx8-isi-debug.o
> +imx8-isi-$(CONFIG_VIDEO_IMX8_ISI_M2M) += imx8-isi-m2m.o
> +
> +obj-$(CONFIG_VIDEO_IMX8_ISI) += imx8-isi.o
> +
> diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
> new file mode 100644
> index 000000000000..1e0f04d1c74f
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
> @@ -0,0 +1,646 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2019-2020 NXP
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/property.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <linux/sys_soc.h>
> +#include <linux/types.h>
> +
> +#include <media/media-device.h>
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-mc.h>
> +
> +#include "imx8-isi-core.h"
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 async subdevs
> + */
> +
> +struct mxc_isi_async_subdev {
> +       struct v4l2_async_subdev asd;
> +       unsigned int port;
> +};
> +
> +static inline struct mxc_isi_async_subdev *
> +asd_to_mxc_isi_async_subdev(struct v4l2_async_subdev *asd)
> +{
> +       return container_of(asd, struct mxc_isi_async_subdev, asd);
> +};
> +
> +static inline struct mxc_isi_dev *
> +notifier_to_mxc_isi_dev(struct v4l2_async_notifier *n)
> +{
> +       return container_of(n, struct mxc_isi_dev, notifier);
> +};
> +
> +static int mxc_isi_async_notifier_bound(struct v4l2_async_notifier *notifier,
> +                                       struct v4l2_subdev *sd,
> +                                       struct v4l2_async_subdev *asd)
> +{
> +       const unsigned int link_flags = MEDIA_LNK_FL_IMMUTABLE
> +                                     | MEDIA_LNK_FL_ENABLED;
> +       struct mxc_isi_dev *isi = notifier_to_mxc_isi_dev(notifier);
> +       struct mxc_isi_async_subdev *masd = asd_to_mxc_isi_async_subdev(asd);
> +       struct media_pad *pad = &isi->crossbar.pads[masd->port];
> +       struct device_link *link;
> +
> +       dev_dbg(isi->dev, "Bound subdev %s to crossbar input %u\n", sd->name,
> +               masd->port);
> +
> +       /*
> +        * Enforce suspend/resume ordering between the source (supplier) and
> +        * the ISI (consumer). The source will be suspended before and resume
> +        * after the ISI.
> +        */
> +       link = device_link_add(isi->dev, sd->dev, DL_FLAG_STATELESS);
> +       if (!link) {
> +               dev_err(isi->dev,
> +                       "Failed to create device link to source %s\n", sd->name);
> +               return -EINVAL;
> +       }
> +
> +       return v4l2_create_fwnode_links_to_pad(sd, pad, link_flags);
> +}
> +
> +static int mxc_isi_async_notifier_complete(struct v4l2_async_notifier *notifier)
> +{
> +       struct mxc_isi_dev *isi = notifier_to_mxc_isi_dev(notifier);
> +       int ret;
> +
> +       dev_dbg(isi->dev, "All subdevs bound\n");
> +
> +       ret = v4l2_device_register_subdev_nodes(&isi->v4l2_dev);
> +       if (ret < 0) {
> +               dev_err(isi->dev,
> +                       "Failed to register subdev nodes: %d\n", ret);
> +               return ret;
> +       }
> +
> +       return media_device_register(&isi->media_dev);
> +}
> +
> +static const struct v4l2_async_notifier_operations mxc_isi_async_notifier_ops = {
> +       .bound = mxc_isi_async_notifier_bound,
> +       .complete = mxc_isi_async_notifier_complete,
> +};
> +
> +static int mxc_isi_pipe_register(struct mxc_isi_pipe *pipe)
> +{
> +       int ret;
> +
> +       ret = v4l2_device_register_subdev(&pipe->isi->v4l2_dev, &pipe->sd);
> +       if (ret < 0)
> +               return ret;
> +
> +       return mxc_isi_video_register(pipe, &pipe->isi->v4l2_dev);
> +}
> +
> +static void mxc_isi_pipe_unregister(struct mxc_isi_pipe *pipe)
> +{
> +       mxc_isi_video_unregister(pipe);
> +}
> +
> +static int mxc_isi_v4l2_init(struct mxc_isi_dev *isi)
> +{
> +       struct fwnode_handle *node = dev_fwnode(isi->dev);
> +       struct media_device *media_dev = &isi->media_dev;
> +       struct v4l2_device *v4l2_dev = &isi->v4l2_dev;
> +       unsigned int i;
> +       int ret;
> +
> +       /* Initialize the media device. */
> +       strscpy(media_dev->model, "FSL Capture Media Device",
> +               sizeof(media_dev->model));
> +       media_dev->dev = isi->dev;
> +
> +       media_device_init(media_dev);
> +
> +       /* Initialize and register the V4L2 device. */
> +       v4l2_dev->mdev = media_dev;
> +       strscpy(v4l2_dev->name, "mx8-img-md", sizeof(v4l2_dev->name));
> +
> +       ret = v4l2_device_register(isi->dev, v4l2_dev);
> +       if (ret < 0) {
> +               dev_err(isi->dev,
> +                       "Failed to register V4L2 device: %d\n", ret);
> +               goto err_media;
> +       }
> +
> +       /* Register the crossbar switch subdev. */
> +       ret = mxc_isi_crossbar_register(&isi->crossbar);
> +       if (ret < 0) {
> +               dev_err(isi->dev, "Failed to register crossbar: %d\n", ret);
> +               goto err_v4l2;
> +       }
> +
> +       /* Register the pipeline subdevs and link them to the crossbar switch. */
> +       for (i = 0; i < isi->pdata->num_channels; ++i) {
> +               struct mxc_isi_pipe *pipe = &isi->pipes[i];
> +
> +               ret = mxc_isi_pipe_register(pipe);
> +               if (ret < 0) {
> +                       dev_err(isi->dev, "Failed to register pipe%u: %d\n", i,
> +                               ret);
> +                       goto err_v4l2;
> +               }
> +
> +               ret = media_create_pad_link(&isi->crossbar.sd.entity,
> +                                           isi->crossbar.num_sinks + i,
> +                                           &pipe->sd.entity,
> +                                           MXC_ISI_PIPE_PAD_SINK,
> +                                           MEDIA_LNK_FL_IMMUTABLE |
> +                                           MEDIA_LNK_FL_ENABLED);
> +               if (ret < 0)
> +                       goto err_v4l2;
> +       }
> +
> +       /* Register the M2M device. */
> +       ret = mxc_isi_m2m_register(isi, v4l2_dev);
> +       if (ret < 0) {
> +               dev_err(isi->dev, "Failed to register M2M device: %d\n", ret);
> +               goto err_v4l2;
> +       }
> +
> +       /* Initialize, fill and register the async notifier. */
> +       v4l2_async_nf_init(&isi->notifier);
> +       isi->notifier.ops = &mxc_isi_async_notifier_ops;
> +
> +       for (i = 0; i < isi->pdata->num_ports; ++i) {
> +               struct mxc_isi_async_subdev *masd;
> +               struct fwnode_handle *ep;
> +
> +               ep = fwnode_graph_get_endpoint_by_id(node, i, 0,
> +                                                    FWNODE_GRAPH_ENDPOINT_NEXT);
> +
> +               if (!ep)
> +                       continue;
> +
> +               masd = v4l2_async_nf_add_fwnode_remote(
> +                               &isi->notifier, ep,
> +                               struct mxc_isi_async_subdev);
> +               fwnode_handle_put(ep);
> +
> +               if (IS_ERR(masd)) {
> +                       ret = PTR_ERR(masd);
> +                       goto err_m2m;
> +               }
> +
> +               masd->port = i;
> +       }
> +
> +       ret = v4l2_async_nf_register(v4l2_dev, &isi->notifier);
> +       if (ret < 0) {
> +               dev_err(isi->dev,
> +                       "Failed to register async notifier: %d\n", ret);
> +               goto err_m2m;
> +       }
> +
> +       return 0;
> +
> +err_m2m:
> +       mxc_isi_m2m_unregister(isi);
> +       v4l2_async_nf_cleanup(&isi->notifier);
> +err_v4l2:
> +       v4l2_device_unregister(v4l2_dev);
> +err_media:
> +       media_device_cleanup(media_dev);
> +       return ret;
> +}
> +
> +static void mxc_isi_v4l2_cleanup(struct mxc_isi_dev *isi)
> +{
> +       unsigned int i;
> +
> +       v4l2_async_nf_unregister(&isi->notifier);
> +       v4l2_async_nf_cleanup(&isi->notifier);
> +
> +       v4l2_device_unregister(&isi->v4l2_dev);
> +       media_device_unregister(&isi->media_dev);
> +
> +       mxc_isi_m2m_unregister(isi);
> +
> +       for (i = 0; i < isi->pdata->num_channels; ++i)
> +               mxc_isi_pipe_unregister(&isi->pipes[i]);
> +
> +       mxc_isi_crossbar_unregister(&isi->crossbar);
> +
> +       media_device_cleanup(&isi->media_dev);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Device information
> + */
> +
> +/* For i.MX8QM/QXP B0 ISI IER version */
> +static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v0 = {
> +       .oflw_y_buf_en = { .offset = 16, .mask = 0x10000  },
> +       .oflw_u_buf_en = { .offset = 19, .mask = 0x80000  },
> +       .oflw_v_buf_en = { .offset = 22, .mask = 0x400000 },
> +
> +       .excs_oflw_y_buf_en = { .offset = 17, .mask = 0x20000  },
> +       .excs_oflw_u_buf_en = { .offset = 20, .mask = 0x100000 },
> +       .excs_oflw_v_buf_en = { .offset = 23, .mask = 0x800000 },
> +
> +       .panic_y_buf_en = {.offset = 18, .mask = 0x40000   },
> +       .panic_u_buf_en = {.offset = 21, .mask = 0x200000  },
> +       .panic_v_buf_en = {.offset = 24, .mask = 0x1000000 },
> +};
> +
> +/* Panic will assert when the buffers are 50% full */
> +static const struct mxc_isi_set_thd mxc_imx8_isi_thd_v0 = {
> +       .panic_set_thd_y = { .mask = 0x03, .offset = 0, .threshold = 0x2 },
> +       .panic_set_thd_u = { .mask = 0x18, .offset = 3, .threshold = 0x2 },
> +       .panic_set_thd_v = { .mask = 0xc0, .offset = 6, .threshold = 0x2 },
> +};
> +
> +/* For i.MX8QXP C0 and i.MX8MN ISI IER version */
> +static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v1 = {
> +       .oflw_y_buf_en = { .offset = 19, .mask = 0x80000  },
> +       .oflw_u_buf_en = { .offset = 21, .mask = 0x200000 },
> +       .oflw_v_buf_en = { .offset = 23, .mask = 0x800000 },
> +
> +       .panic_y_buf_en = {.offset = 20, .mask = 0x100000  },
> +       .panic_u_buf_en = {.offset = 22, .mask = 0x400000  },
> +       .panic_v_buf_en = {.offset = 24, .mask = 0x1000000 },
> +};
> +
> +/* For i.MX8MP ISI IER version */
> +static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v2 = {
> +       .oflw_y_buf_en = { .offset = 18, .mask = 0x40000  },
> +       .oflw_u_buf_en = { .offset = 20, .mask = 0x100000 },
> +       .oflw_v_buf_en = { .offset = 22, .mask = 0x400000 },
> +
> +       .panic_y_buf_en = {.offset = 19, .mask = 0x80000  },
> +       .panic_u_buf_en = {.offset = 21, .mask = 0x200000 },
> +       .panic_v_buf_en = {.offset = 23, .mask = 0x800000 },
> +};
> +
> +/* Panic will assert when the buffers are 50% full */
> +static const struct mxc_isi_set_thd mxc_imx8_isi_thd_v1 = {
> +       .panic_set_thd_y = { .mask = 0x0000f, .offset = 0,  .threshold = 0x7 },
> +       .panic_set_thd_u = { .mask = 0x00f00, .offset = 8,  .threshold = 0x7 },
> +       .panic_set_thd_v = { .mask = 0xf0000, .offset = 16, .threshold = 0x7 },
> +};
> +
> +static const struct clk_bulk_data mxc_imx8_clks[] = {
> +       { .id = NULL },
> +};
> +
> +/* Chip C0 */
> +static const struct mxc_isi_plat_data mxc_imx8_data_v0 = {
> +       .model                  = MXC_ISI_IMX8,
> +       .num_ports              = 5,
> +       .num_channels           = 8,
> +       .reg_offset             = 0x10000,
> +       .ier_reg                = &mxc_imx8_isi_ier_v0,
> +       .set_thd                = &mxc_imx8_isi_thd_v0,
> +       .clks                   = mxc_imx8_clks,
> +       .num_clks               = ARRAY_SIZE(mxc_imx8_clks),
> +       .buf_active_reverse     = false,
> +       .has_gasket             = false,
> +       .has_36bit_dma          = false,
> +};
> +
> +static const struct mxc_isi_plat_data mxc_imx8_data_v1 = {
> +       .model                  = MXC_ISI_IMX8,
> +       .num_ports              = 5,
> +       .num_channels           = 8,
> +       .reg_offset             = 0x10000,
> +       .ier_reg                = &mxc_imx8_isi_ier_v1,
> +       .set_thd                = &mxc_imx8_isi_thd_v1,
> +       .clks                   = mxc_imx8_clks,
> +       .num_clks               = ARRAY_SIZE(mxc_imx8_clks),
> +       .buf_active_reverse     = true,
> +       .has_gasket             = false,
> +       .has_36bit_dma          = false,
> +};
> +
> +static const struct clk_bulk_data mxc_imx8mn_clks[] = {
> +       { .id = "axi" },
> +       { .id = "apb" },
> +};
> +
> +static const struct mxc_isi_plat_data mxc_imx8mn_data = {
> +       .model                  = MXC_ISI_IMX8MN,
> +       .num_ports              = 1,
> +       .num_channels           = 1,
> +       .reg_offset             = 0,
> +       .ier_reg                = &mxc_imx8_isi_ier_v1,
> +       .set_thd                = &mxc_imx8_isi_thd_v1,
> +       .clks                   = mxc_imx8mn_clks,
> +       .num_clks               = ARRAY_SIZE(mxc_imx8mn_clks),
> +       .buf_active_reverse     = false,
> +       .has_gasket             = true,
> +       .has_36bit_dma          = false,
> +};
> +
> +static const struct mxc_isi_plat_data mxc_imx8mp_data = {
> +       .model                  = MXC_ISI_IMX8MP,
> +       .num_ports              = 2,
> +       .num_channels           = 2,
> +       .reg_offset             = 0x2000,
> +       .ier_reg                = &mxc_imx8_isi_ier_v2,
> +       .set_thd                = &mxc_imx8_isi_thd_v1,
> +       .clks                   = mxc_imx8mn_clks,
> +       .num_clks               = ARRAY_SIZE(mxc_imx8mn_clks),
> +       .buf_active_reverse     = true,
> +       .has_gasket             = true,
> +       .has_36bit_dma          = true,
> +};
> +
> +static const struct soc_device_attribute imx8_soc[] = {
> +       {
> +               .soc_id   = "i.MX8QXP",
> +               .revision = "1.0",
> +               .data     = &mxc_imx8_data_v0,
> +       }, {
> +               .soc_id   = "i.MX8QXP",
> +               .revision = "1.1",
> +               .data     = &mxc_imx8_data_v0,
> +       }, {
> +               .soc_id   = "i.MX8QXP",
> +               .revision = "1.2",
> +       }, {
> +               .soc_id   = "i.MX8QM",
> +               .revision = "1.0",
> +               .data     = &mxc_imx8_data_v0,
> +       }, {
> +               .soc_id   = "i.MX8QM",
> +               .revision = "1.1",
> +               .data     = &mxc_imx8_data_v0,
> +       }, {
> +               .soc_id   = "i.MX8MN",
> +               .revision = "1.0",
> +       }, {
> +               .soc_id   = "i.MX8MP",
> +       }, {
> +               /* sentinel */
> +       }
> +};
> +
> +static int mxc_isi_get_platform_data(struct mxc_isi_dev *isi)
> +
> +{
> +       const struct soc_device_attribute *match;
> +
> +       isi->pdata = of_device_get_match_data(isi->dev);
> +
> +       match = soc_device_match(imx8_soc);
> +       if (!match)
> +               return -EINVAL;
> +
> +       if (match->data)
> +               isi->pdata = match->data;
> +
> +       return 0;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Power management
> + */
> +
> +static int mxc_isi_pm_suspend(struct device *dev)
> +{
> +       struct mxc_isi_dev *isi = dev_get_drvdata(dev);
> +       unsigned int i;
> +
> +       for (i = 0; i < isi->pdata->num_channels; ++i) {
> +               struct mxc_isi_pipe *pipe = &isi->pipes[i];
> +
> +               mxc_isi_video_suspend(pipe);
> +       }
> +
> +       return pm_runtime_force_suspend(dev);
> +}
> +
> +static int mxc_isi_pm_resume(struct device *dev)
> +{
> +       struct mxc_isi_dev *isi = dev_get_drvdata(dev);
> +       unsigned int i;
> +       int err = 0;
> +       int ret;
> +
> +       ret = pm_runtime_force_resume(dev);
> +       if (ret < 0)
> +               return ret;
> +
> +       for (i = 0; i < isi->pdata->num_channels; ++i) {
> +               struct mxc_isi_pipe *pipe = &isi->pipes[i];
> +
> +               ret = mxc_isi_video_resume(pipe);
> +               if (ret) {
> +                       dev_err(dev, "Failed to resume pipeline %u (%d)\n", i,
> +                               ret);
> +                       /*
> +                        * Record the last error as it's as meaningful as any,
> +                        * and continue resuming the other pipelines.
> +                        */
> +                       err = ret;
> +               }
> +       }
> +
> +       return err;
> +}
> +
> +static int mxc_isi_runtime_suspend(struct device *dev)
> +{
> +       struct mxc_isi_dev *isi = dev_get_drvdata(dev);
> +
> +       clk_bulk_disable_unprepare(isi->pdata->num_clks, isi->clks);
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_runtime_resume(struct device *dev)
> +{
> +       struct mxc_isi_dev *isi = dev_get_drvdata(dev);
> +       int ret;
> +
> +       ret = clk_bulk_prepare_enable(isi->pdata->num_clks, isi->clks);
> +       if (ret) {
> +               dev_err(dev, "Failed to enable clocks (%d)\n", ret);
> +               return ret;
> +       }
> +
> +       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)
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Probe, remove & driver
> + */
> +
> +static int mxc_isi_clk_get(struct mxc_isi_dev *isi)
> +{
> +       unsigned int size = isi->pdata->num_clks
> +                         * sizeof(*isi->clks);
> +       int ret;
> +
> +       isi->clks = devm_kmalloc(isi->dev, size, GFP_KERNEL);
> +       if (!isi->clks)
> +               return -ENOMEM;
> +
> +       memcpy(isi->clks, isi->pdata->clks, size);
> +
> +       ret = devm_clk_bulk_get(isi->dev, isi->pdata->num_clks,
> +                               isi->clks);
> +       if (ret < 0) {
> +               dev_err(isi->dev, "Failed to acquire clocks: %d\n",
> +                       ret);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct mxc_isi_dev *isi;
> +       unsigned int dma_size;
> +       unsigned int i;
> +       int ret = 0;
> +
> +       isi = devm_kzalloc(dev, sizeof(*isi), GFP_KERNEL);
> +       if (!isi)
> +               return -ENOMEM;
> +
> +       isi->dev = dev;
> +       platform_set_drvdata(pdev, isi);
> +
> +       ret = mxc_isi_get_platform_data(isi);
> +       if (ret < 0) {
> +               dev_err(dev, "Can't get platform device data\n");
> +               return ret;
> +       }
> +
> +       isi->pipes = kcalloc(isi->pdata->num_channels, sizeof(isi->pipes[0]),
> +                            GFP_KERNEL);
> +       if (!isi->pipes)
> +               return -ENOMEM;
> +
> +       ret = mxc_isi_clk_get(isi);
> +       if (ret < 0) {
> +               dev_err(dev, "Failed to get clocks\n");
> +               return ret;
> +       }
> +
> +       isi->regs = devm_platform_ioremap_resource(pdev, 0);
> +       if (IS_ERR(isi->regs)) {
> +               dev_err(dev, "Failed to get ISI register map\n");
> +               return PTR_ERR(isi->regs);
> +       }
> +
> +       if (isi->pdata->has_gasket) {
> +               isi->gasket = syscon_regmap_lookup_by_phandle(dev->of_node,
> +                                                             "fsl,blk-ctrl");
> +               if (IS_ERR(isi->gasket)) {
> +                       ret = PTR_ERR(isi->gasket);
> +                       dev_err(dev, "failed to get gasket: %d\n", ret);
> +                       return ret;
> +               }
> +       }
> +
> +       dma_size = isi->pdata->has_36bit_dma ? 36 : 32;
> +       ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_size));
> +       if (ret) {
> +               dev_err(dev, "failed to set DMA mask\n");
> +               return ret;
> +       }
> +
> +       pm_runtime_enable(dev);
> +
> +       ret = mxc_isi_crossbar_init(isi);
> +       if (ret) {
> +               dev_err(dev, "Failed to initialize crossbar: %d\n", ret);
> +               goto err_pm;
> +       }
> +
> +       for (i = 0; i < isi->pdata->num_channels; ++i) {
> +               ret = mxc_isi_pipe_init(isi, i);
> +               if (ret < 0) {
> +                       dev_err(dev, "Failed to initialize pipe%u: %d\n", i,
> +                               ret);
> +                       goto err_xbar;
> +               }
> +       }
> +
> +       ret = mxc_isi_v4l2_init(isi);
> +       if (ret < 0) {
> +               dev_err(dev, "Failed to initialize V4L2: %d\n", ret);
> +               goto err_xbar;
> +       }
> +
> +       mxc_isi_debug_init(isi);
> +
> +       return 0;
> +
> +err_xbar:
> +       mxc_isi_crossbar_cleanup(&isi->crossbar);
> +err_pm:
> +       pm_runtime_disable(isi->dev);
> +       return ret;
> +}
> +
> +static int mxc_isi_remove(struct platform_device *pdev)
> +{
> +       struct mxc_isi_dev *isi = platform_get_drvdata(pdev);
> +       unsigned int i;
> +
> +       mxc_isi_debug_cleanup(isi);
> +
> +       for (i = 0; i < isi->pdata->num_channels; ++i) {
> +               struct mxc_isi_pipe *pipe = &isi->pipes[i];
> +
> +               mxc_isi_pipe_cleanup(pipe);
> +       }
> +
> +       mxc_isi_crossbar_cleanup(&isi->crossbar);
> +       mxc_isi_v4l2_cleanup(isi);
> +
> +       pm_runtime_disable(isi->dev);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id mxc_isi_of_match[] = {
> +       { .compatible = "fsl,imx8-isi", .data = &mxc_imx8_data_v1 },
> +       { .compatible = "fsl,imx8mn-isi", .data = &mxc_imx8mn_data },
> +       { .compatible = "fsl,imx8mp-isi", .data = &mxc_imx8mp_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_ALIAS("ISI");
> +MODULE_AUTHOR("Freescale Semiconductor, Inc.");
> +MODULE_DESCRIPTION("IMX8 Image Sensing Interface driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h
> new file mode 100644
> index 000000000000..5b703c9c9217
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h
> @@ -0,0 +1,394 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * V4L2 Capture ISI subdev for i.MX8QXP/QM platform
> + *
> + * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
> + * used to process image from camera sensor to memory or DC
> + * Copyright 2019-2020 NXP
> + */
> +
> +#ifndef __MXC_ISI_CORE_H__
> +#define __MXC_ISI_CORE_H__
> +
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/spinlock.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/media-device.h>
> +#include <media/media-entity.h>
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-core.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +struct clk_bulk_data;
> +struct dentry;
> +struct device;
> +struct media_intf_devnode;
> +struct regmap;
> +struct v4l2_m2m_dev;
> +
> +/* Pipeline pads */
> +#define MXC_ISI_PIPE_PAD_SINK          0
> +#define MXC_ISI_PIPE_PAD_SOURCE                1
> +#define MXC_ISI_PIPE_PADS_NUM          2
> +
> +#define MXC_ISI_MIN_WIDTH              1U
> +#define MXC_ISI_MIN_HEIGHT             1U
> +#define MXC_ISI_MAX_WIDTH_UNCHAINED    2048U
> +#define MXC_ISI_MAX_WIDTH_CHAINED      4096U
> +#define MXC_ISI_MAX_HEIGHT             8191U
> +
> +#define MXC_ISI_DEF_WIDTH              1920U
> +#define MXC_ISI_DEF_HEIGHT             1080U
> +#define MXC_ISI_DEF_MBUS_CODE_SINK     MEDIA_BUS_FMT_UYVY8_1X16
> +#define MXC_ISI_DEF_MBUS_CODE_SOURCE   MEDIA_BUS_FMT_YUV8_1X24
> +#define MXC_ISI_DEF_PIXEL_FORMAT       V4L2_PIX_FMT_YUYV
> +#define MXC_ISI_DEF_COLOR_SPACE                V4L2_COLORSPACE_SRGB
> +#define MXC_ISI_DEF_YCBCR_ENC          V4L2_YCBCR_ENC_601
> +#define MXC_ISI_DEF_QUANTIZATION       V4L2_QUANTIZATION_LIM_RANGE
> +#define MXC_ISI_DEF_XFER_FUNC          V4L2_XFER_FUNC_SRGB
> +
> +#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;
> +struct mxc_isi_m2m_ctx;
> +
> +enum mxc_isi_buf_id {
> +       MXC_ISI_BUF1 = 0x0,
> +       MXC_ISI_BUF2,
> +};
> +
> +enum mxc_isi_encoding {
> +       MXC_ISI_ENC_RAW,
> +       MXC_ISI_ENC_RGB,
> +       MXC_ISI_ENC_YUV,
> +};
> +
> +enum mxc_isi_input_id {
> +       /* Inputs from the crossbar switch range from 0 to 15 */
> +       MXC_ISI_INPUT_MEM = 16,
> +};
> +
> +enum mxc_isi_video_type {
> +       MXC_ISI_VIDEO_CAP = BIT(0),
> +       MXC_ISI_VIDEO_M2M_OUT = BIT(1),
> +       MXC_ISI_VIDEO_M2M_CAP = BIT(2),
> +};
> +
> +struct mxc_isi_format_info {
> +       u32     mbus_code;
> +       u32     fourcc;
> +       enum mxc_isi_video_type type;
> +       u32     isi_in_format;
> +       u32     isi_out_format;
> +       u8      mem_planes;
> +       u8      color_planes;
> +       u8      depth[MXC_MAX_PLANES];
> +       u8      hsub;
> +       u8      vsub;
> +       enum mxc_isi_encoding encoding;
> +};
> +
> +struct mxc_isi_bus_format_info {
> +       u32     mbus_code;
> +       u32     output;
> +       u32     pads;
> +       enum mxc_isi_encoding encoding;
> +};
> +
> +struct mxc_isi_buffer {
> +       struct vb2_v4l2_buffer  v4l2_buf;
> +       struct list_head        list;
> +       dma_addr_t              dma_addrs[3];
> +       enum mxc_isi_buf_id     id;
> +       bool discard;
> +};
> +
> +struct mxc_isi_reg {
> +       u32 offset;
> +       u32 mask;
> +};
> +
> +struct mxc_isi_ier_reg {
> +       /* Overflow Y/U/V trigger enable*/
> +       struct mxc_isi_reg oflw_y_buf_en;
> +       struct mxc_isi_reg oflw_u_buf_en;
> +       struct mxc_isi_reg oflw_v_buf_en;
> +
> +       /* Excess overflow Y/U/V trigger enable*/
> +       struct mxc_isi_reg excs_oflw_y_buf_en;
> +       struct mxc_isi_reg excs_oflw_u_buf_en;
> +       struct mxc_isi_reg excs_oflw_v_buf_en;
> +
> +       /* Panic Y/U/V trigger enable*/
> +       struct mxc_isi_reg panic_y_buf_en;
> +       struct mxc_isi_reg panic_v_buf_en;
> +       struct mxc_isi_reg panic_u_buf_en;
> +};
> +
> +struct mxc_isi_panic_thd {
> +       u32 mask;
> +       u32 offset;
> +       u32 threshold;
> +};
> +
> +struct mxc_isi_set_thd {
> +       struct mxc_isi_panic_thd panic_set_thd_y;
> +       struct mxc_isi_panic_thd panic_set_thd_u;
> +       struct mxc_isi_panic_thd panic_set_thd_v;
> +};
> +
> +enum model {
> +       MXC_ISI_IMX8,
> +       MXC_ISI_IMX8MN,
> +       MXC_ISI_IMX8MP,
> +};
> +
> +struct mxc_isi_plat_data {
> +       enum model model;
> +       unsigned int num_ports;
> +       unsigned int num_channels;
> +       unsigned int reg_offset;
> +       const struct mxc_isi_ier_reg  *ier_reg;
> +       const struct mxc_isi_set_thd *set_thd;
> +       const struct clk_bulk_data *clks;
> +       unsigned int num_clks;
> +       bool buf_active_reverse;
> +       bool has_gasket;
> +       bool has_36bit_dma;
> +};
> +
> +struct mxc_isi_dma_buffer {
> +       size_t                          size;
> +       void                            *addr;
> +       dma_addr_t                      dma;
> +};
> +
> +struct mxc_isi_input {
> +       unsigned int                    enable_count;
> +};
> +
> +struct mxc_isi_crossbar {
> +       struct mxc_isi_dev              *isi;
> +
> +       unsigned int                    num_sinks;
> +       unsigned int                    num_sources;
> +       struct mxc_isi_input            *inputs;
> +
> +       struct v4l2_subdev              sd;
> +       struct media_pad                *pads;
> +};
> +
> +struct mxc_isi_video {
> +       struct mxc_isi_pipe             *pipe;
> +
> +       struct video_device             vdev;
> +       struct media_pad                pad;
> +
> +       struct mutex                    lock;
> +       bool                            is_streaming;
> +
> +       struct v4l2_pix_format_mplane   pix;
> +       const struct mxc_isi_format_info *fmtinfo;
> +
> +       struct {
> +               struct v4l2_ctrl_handler handler;
> +               unsigned int            alpha;
> +               bool                    hflip;
> +               bool                    vflip;
> +       } ctrls;
> +
> +       struct vb2_queue                vb2_q;
> +       struct mxc_isi_buffer           buf_discard[3];
> +       struct list_head                out_pending;
> +       struct list_head                out_active;
> +       struct list_head                out_discard;
> +       u32                             frame_count;
> +       /* Protects out_pending, out_active, out_discard and frame_count */
> +       spinlock_t                      buf_lock;
> +
> +       struct mxc_isi_dma_buffer       discard_buffer[MXC_MAX_PLANES];
> +};
> +
> +typedef void(*mxc_isi_pipe_irq_t)(struct mxc_isi_pipe *, u32);
> +
> +struct mxc_isi_pipe {
> +       struct mxc_isi_dev              *isi;
> +       u32                             id;
> +       void __iomem                    *regs;
> +
> +       struct media_pipeline           pipe;
> +
> +       struct v4l2_subdev              sd;
> +       struct media_pad                pads[MXC_ISI_PIPE_PADS_NUM];
> +
> +       struct mxc_isi_video            video;
> +
> +       /*
> +        * Protects use_count, irq_handler, res_available, res_acquired,
> +        * chained_res, and the CHNL_CTRL register.
> +        */
> +       struct mutex                    lock;
> +       unsigned int                    use_count;
> +       mxc_isi_pipe_irq_t              irq_handler;
> +
> +#define MXC_ISI_CHANNEL_RES_LINE_BUF   BIT(0)
> +#define MXC_ISI_CHANNEL_RES_OUTPUT_BUF BIT(1)
> +       u8                              available_res;
> +       u8                              acquired_res;
> +       u8                              chained_res;
> +       bool                            chained;
> +};
> +
> +struct mxc_isi_m2m {
> +       struct mxc_isi_dev              *isi;
> +       struct mxc_isi_pipe             *pipe;
> +
> +       struct media_pad                pad;
> +       struct video_device             vdev;
> +       struct media_intf_devnode       *intf;
> +       struct v4l2_m2m_dev             *m2m_dev;
> +
> +       /* Protects last_ctx, usage_count and chained_count */
> +       struct mutex                    lock;
> +
> +       struct mxc_isi_m2m_ctx          *last_ctx;
> +       int                             usage_count;
> +       int                             chained_count;
> +};
> +
> +struct mxc_isi_dev {
> +       struct device                   *dev;
> +
> +       const struct mxc_isi_plat_data  *pdata;
> +
> +       void __iomem                    *regs;
> +       struct clk_bulk_data            *clks;
> +       struct regmap                   *gasket;
> +
> +       struct mxc_isi_crossbar         crossbar;
> +       struct mxc_isi_pipe             *pipes;
> +       struct mxc_isi_m2m              m2m;
> +
> +       struct media_device             media_dev;
> +       struct v4l2_device              v4l2_dev;
> +       struct v4l2_async_notifier      notifier;
> +
> +       struct dentry                   *debugfs_root;
> +};
> +
> +int mxc_isi_crossbar_init(struct mxc_isi_dev *isi);
> +void mxc_isi_crossbar_cleanup(struct mxc_isi_crossbar *xbar);
> +int mxc_isi_crossbar_register(struct mxc_isi_crossbar *xbar);
> +void mxc_isi_crossbar_unregister(struct mxc_isi_crossbar *xbar);
> +
> +const struct mxc_isi_bus_format_info *
> +mxc_isi_bus_format_by_code(u32 code, unsigned int pad);
> +const struct mxc_isi_bus_format_info *
> +mxc_isi_bus_format_by_index(unsigned int index, unsigned int pad);
> +const struct mxc_isi_format_info *
> +mxc_isi_format_by_fourcc(u32 fourcc, enum mxc_isi_video_type type);
> +const struct mxc_isi_format_info *
> +mxc_isi_format_enum(unsigned int index, enum mxc_isi_video_type type);
> +const struct mxc_isi_format_info *
> +mxc_isi_format_try(struct mxc_isi_pipe *pipe, struct v4l2_pix_format_mplane *pix,
> +                  enum mxc_isi_video_type type);
> +
> +int mxc_isi_pipe_init(struct mxc_isi_dev *isi, unsigned int id);
> +void mxc_isi_pipe_cleanup(struct mxc_isi_pipe *pipe);
> +int mxc_isi_pipe_acquire(struct mxc_isi_pipe *pipe,
> +                        mxc_isi_pipe_irq_t irq_handler);
> +void mxc_isi_pipe_release(struct mxc_isi_pipe *pipe);
> +int mxc_isi_pipe_enable(struct mxc_isi_pipe *pipe);
> +void mxc_isi_pipe_disable(struct mxc_isi_pipe *pipe);
> +
> +int mxc_isi_video_register(struct mxc_isi_pipe *pipe,
> +                          struct v4l2_device *v4l2_dev);
> +void mxc_isi_video_unregister(struct mxc_isi_pipe *pipe);
> +void mxc_isi_video_suspend(struct mxc_isi_pipe *pipe);
> +int mxc_isi_video_resume(struct mxc_isi_pipe *pipe);
> +int mxc_isi_video_queue_setup(const struct v4l2_pix_format_mplane *format,
> +                             const struct mxc_isi_format_info *info,
> +                             unsigned int *num_buffers,
> +                             unsigned int *num_planes, unsigned int sizes[]);
> +void mxc_isi_video_buffer_init(struct vb2_buffer *vb2, dma_addr_t dma_addrs[3],
> +                              const struct mxc_isi_format_info *info,
> +                              const struct v4l2_pix_format_mplane *pix);
> +int mxc_isi_video_buffer_prepare(struct mxc_isi_dev *isi, struct vb2_buffer *vb2,
> +                                const struct mxc_isi_format_info *info,
> +                                const struct v4l2_pix_format_mplane *pix);
> +
> +#ifdef CONFIG_VIDEO_IMX8_ISI_M2M
> +int mxc_isi_m2m_register(struct mxc_isi_dev *isi, struct v4l2_device *v4l2_dev);
> +int mxc_isi_m2m_unregister(struct mxc_isi_dev *isi);
> +#else
> +static inline int mxc_isi_m2m_register(struct mxc_isi_dev *isi,
> +                                      struct v4l2_device *v4l2_dev)
> +{
> +       return 0;
> +}
> +static inline int mxc_isi_m2m_unregister(struct mxc_isi_dev *isi)
> +{
> +       return 0;
> +}
> +#endif
> +
> +int mxc_isi_channel_acquire(struct mxc_isi_pipe *pipe,
> +                           mxc_isi_pipe_irq_t irq_handler, bool bypass);
> +void mxc_isi_channel_release(struct mxc_isi_pipe *pipe);
> +void mxc_isi_channel_get(struct mxc_isi_pipe *pipe);
> +void mxc_isi_channel_put(struct mxc_isi_pipe *pipe);
> +void mxc_isi_channel_enable(struct mxc_isi_pipe *pipe);
> +void mxc_isi_channel_disable(struct mxc_isi_pipe *pipe);
> +int mxc_isi_channel_chain(struct mxc_isi_pipe *pipe, bool bypass);
> +void mxc_isi_channel_unchain(struct mxc_isi_pipe *pipe);
> +
> +void mxc_isi_channel_config(struct mxc_isi_pipe *pipe,
> +                           enum mxc_isi_input_id input,
> +                           const struct v4l2_area *in_size,
> +                           const struct v4l2_area *scale,
> +                           const struct v4l2_rect *crop,
> +                           enum mxc_isi_encoding in_encoding,
> +                           enum mxc_isi_encoding out_encoding);
> +
> +void mxc_isi_channel_set_input_format(struct mxc_isi_pipe *pipe,
> +                                     const struct mxc_isi_format_info *info,
> +                                     const struct v4l2_pix_format_mplane *format);
> +void mxc_isi_channel_set_output_format(struct mxc_isi_pipe *pipe,
> +                                      const struct mxc_isi_format_info *info,
> +                                      struct v4l2_pix_format_mplane *format);
> +void mxc_isi_channel_m2m_start(struct mxc_isi_pipe *pipe);
> +
> +void mxc_isi_channel_set_alpha(struct mxc_isi_pipe *pipe, u8 alpha);
> +void mxc_isi_channel_set_flip(struct mxc_isi_pipe *pipe, bool hflip, bool vflip);
> +
> +void mxc_isi_channel_set_inbuf(struct mxc_isi_pipe *pipe, dma_addr_t dma_addr);
> +void mxc_isi_channel_set_outbuf(struct mxc_isi_pipe *pipe,
> +                               const dma_addr_t dma_addrs[3],
> +                               enum mxc_isi_buf_id buf_id);
> +
> +u32 mxc_isi_channel_irq_status(struct mxc_isi_pipe *pipe, bool clear);
> +void mxc_isi_channel_irq_clear(struct mxc_isi_pipe *pipe);
> +
> +#if IS_ENABLED(CONFIG_DEBUG_FS)
> +void mxc_isi_debug_init(struct mxc_isi_dev *isi);
> +void mxc_isi_debug_cleanup(struct mxc_isi_dev *isi);
> +#else
> +static inline void mxc_isi_debug_init(struct mxc_isi_dev *isi)
> +{
> +}
> +static inline void mxc_isi_debug_cleanup(struct mxc_isi_dev *isi)
> +{
> +}
> +#endif
> +
> +#endif /* __MXC_ISI_CORE_H__ */
> diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c
> new file mode 100644
> index 000000000000..565d1e8eb6ba
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c
> @@ -0,0 +1,529 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * i.MX8 ISI - Input crossbar switch
> + *
> + * Copyright (c) 2022 Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/kernel.h>
> +#include <linux/minmax.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +
> +#include <media/media-entity.h>
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "imx8-isi-core.h"
> +
> +static inline struct mxc_isi_crossbar *to_isi_crossbar(struct v4l2_subdev *sd)
> +{
> +       return container_of(sd, struct mxc_isi_crossbar, sd);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Media block control (i.MX8MN and i.MX8MP only)
> + */
> +#define GASKET_BASE(n)                         (0x0060 + (n) * 0x30)
> +
> +#define GASKET_CTRL                            0x0000
> +#define GASKET_CTRL_DATA_TYPE(dt)              ((dt) << 8)
> +#define GASKET_CTRL_DATA_TYPE_MASK             (0x3f << 8)
> +#define GASKET_CTRL_DUAL_COMP_ENABLE           BIT(1)
> +#define GASKET_CTRL_ENABLE                     BIT(0)
> +
> +#define GASKET_HSIZE                           0x0004
> +#define GASKET_VSIZE                           0x0008
> +
> +static int mxc_isi_crossbar_gasket_enable(struct mxc_isi_crossbar *xbar,
> +                                         struct v4l2_subdev_state *state,
> +                                         struct v4l2_subdev *remote_sd,
> +                                         u32 remote_pad, unsigned int port)
> +{
> +       struct mxc_isi_dev *isi = xbar->isi;
> +       const struct v4l2_mbus_framefmt *fmt;
> +       struct v4l2_mbus_frame_desc fd;
> +       u32 val;
> +       int ret;
> +
> +       if (!isi->pdata->has_gasket)
> +               return 0;
> +
> +       /*
> +        * Configure and enable the gasket with the frame size and CSI-2 data
> +        * type. For YUV422 8-bit, enable dual component mode unconditionally,
> +        * to match the configuration of the CSIS.
> +        */
> +
> +       ret = v4l2_subdev_call(remote_sd, pad, get_frame_desc, remote_pad, &fd);
> +       if (ret) {
> +               dev_err(isi->dev,
> +                       "failed to get frame descriptor from '%s':%u: %d\n",
> +                       remote_sd->name, remote_pad, ret);
> +               return ret;
> +       }
> +
> +       if (fd.num_entries != 1) {
> +               dev_err(isi->dev, "invalid frame descriptor for '%s':%u\n",
> +                       remote_sd->name, remote_pad);
> +               return -EINVAL;
> +       }
> +
> +       fmt = v4l2_subdev_state_get_stream_format(state, port, 0);
> +       if (!fmt)
> +               return -EINVAL;
> +
> +       regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_HSIZE, fmt->width);
> +       regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_VSIZE, fmt->height);
> +
> +       val = GASKET_CTRL_DATA_TYPE(fd.entry[0].bus.csi2.dt)
> +           | GASKET_CTRL_ENABLE;
> +
> +       if (fd.entry[0].bus.csi2.dt == MIPI_CSI2_DT_YUV422_8B)
> +               val |= GASKET_CTRL_DUAL_COMP_ENABLE;
> +
> +       regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_CTRL, val);
> +
> +       return 0;
> +}
> +
> +static void mxc_isi_crossbar_gasket_disable(struct mxc_isi_crossbar *xbar,
> +                                           unsigned int port)
> +{
> +       struct mxc_isi_dev *isi = xbar->isi;
> +
> +       if (!isi->pdata->has_gasket)
> +               return;
> +
> +       regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_CTRL, 0);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 subdev operations
> + */
> +
> +static const struct v4l2_mbus_framefmt mxc_isi_crossbar_default_format = {
> +       .code = MXC_ISI_DEF_MBUS_CODE_SINK,
> +       .width = MXC_ISI_DEF_WIDTH,
> +       .height = MXC_ISI_DEF_HEIGHT,
> +       .field = V4L2_FIELD_NONE,
> +       .colorspace = MXC_ISI_DEF_COLOR_SPACE,
> +       .ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC,
> +       .quantization = MXC_ISI_DEF_QUANTIZATION,
> +       .xfer_func = MXC_ISI_DEF_XFER_FUNC,
> +};
> +
> +static int __mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd,
> +                                         struct v4l2_subdev_state *state,
> +                                         struct v4l2_subdev_krouting *routing)
> +{
> +       struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
> +       struct v4l2_subdev_route *route;
> +       int ret;
> +
> +       ret = v4l2_subdev_routing_validate(sd, routing,
> +                                          V4L2_SUBDEV_ROUTING_NO_N_TO_1);
> +       if (ret)
> +               return ret;
> +
> +       /* The memory input can be routed to the first pipeline only. */
> +       for_each_active_route(&state->routing, route) {
> +               if (route->sink_pad == xbar->num_sinks - 1 &&
> +                   route->source_pad != xbar->num_sinks) {
> +                       dev_dbg(xbar->isi->dev,
> +                               "invalid route from memory input (%u) to pipe %u\n",
> +                               route->sink_pad,
> +                               route->source_pad - xbar->num_sinks);
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       return v4l2_subdev_set_routing_with_fmt(sd, state, routing,
> +                                               &mxc_isi_crossbar_default_format);
> +}
> +
> +static struct v4l2_subdev *
> +mxc_isi_crossbar_xlate_streams(struct mxc_isi_crossbar *xbar,
> +                              struct v4l2_subdev_state *state,
> +                              u32 source_pad, u64 source_streams,
> +                              u32 *__sink_pad, u64 *__sink_streams,
> +                              u32 *remote_pad)
> +{
> +       struct v4l2_subdev_route *route;
> +       struct v4l2_subdev *sd;
> +       struct media_pad *pad;
> +       u64 sink_streams = 0;
> +       int sink_pad = -1;
> +
> +       /*
> +        * Translate the source pad and streams to the sink side. The routing
> +        * validation forbids stream merging, so all matching entries in the
> +        * routing table are guaranteed to have the same sink pad.
> +        *
> +        * TODO: This is likely worth a helper function, it could perhaps be
> +        * supported by v4l2_subdev_state_xlate_streams() with pad1 set to -1.
> +        */
> +       for_each_active_route(&state->routing, route) {
> +               if (route->source_pad != source_pad ||
> +                   !(source_streams & BIT(route->source_stream)))
> +                       continue;
> +
> +               sink_streams |= BIT(route->sink_stream);
> +               sink_pad = route->sink_pad;
> +       }
> +
> +       if (sink_pad < 0) {
> +               dev_dbg(xbar->isi->dev,
> +                       "no stream connected to pipeline %u\n",
> +                       source_pad - xbar->num_sinks);
> +               return ERR_PTR(-EPIPE);
> +       }
> +
> +       pad = media_entity_remote_pad(&xbar->pads[sink_pad]);
> +       sd = media_entity_to_v4l2_subdev(pad->entity);
> +
> +       if (!sd) {
> +               dev_dbg(xbar->isi->dev,
> +                       "no entity connected to crossbar input %u\n",
> +                       sink_pad);
> +               return ERR_PTR(-EPIPE);
> +       }
> +
> +       *__sink_pad = sink_pad;
> +       *__sink_streams = sink_streams;
> +       *remote_pad = pad->index;
> +
> +       return sd;
> +}
> +
> +static int mxc_isi_crossbar_init_cfg(struct v4l2_subdev *sd,
> +                                    struct v4l2_subdev_state *state)
> +{
> +       struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
> +       struct v4l2_subdev_krouting routing = { };
> +       struct v4l2_subdev_route *routes;
> +       unsigned int i;
> +       int ret;
> +
> +       /*
> +        * Create a 1:1 mapping between pixel link inputs and outputs to
> +        * pipelines by default.
> +        */
> +       routes = kcalloc(xbar->num_sources, sizeof(*routes), GFP_KERNEL);
> +       if (!routes)
> +               return -ENOMEM;
> +
> +       for (i = 0; i < xbar->num_sources; ++i) {
> +               struct v4l2_subdev_route *route = &routes[i];
> +
> +               route->sink_pad = i;
> +               route->source_pad = i + xbar->num_sinks;
> +               route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> +       };
> +
> +       routing.num_routes = xbar->num_sources;
> +       routing.routes = routes;
> +
> +       ret = __mxc_isi_crossbar_set_routing(sd, state, &routing);
> +
> +       kfree(routes);
> +
> +       return ret;
> +}
> +
> +static int mxc_isi_crossbar_enum_mbus_code(struct v4l2_subdev *sd,
> +                                      struct v4l2_subdev_state *state,
> +                                      struct v4l2_subdev_mbus_code_enum *code)
> +{
> +       struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
> +       const struct mxc_isi_bus_format_info *info;
> +
> +       if (code->pad >= xbar->num_sinks) {
> +               const struct v4l2_mbus_framefmt *format;
> +
> +               /*
> +                * The media bus code on source pads is identical to the
> +                * connected sink pad.
> +                */
> +               if (code->index > 0)
> +                       return -EINVAL;
> +
> +               format = v4l2_subdev_state_get_opposite_stream_format(state,
> +                                                                     code->pad,
> +                                                                     code->stream);
> +               if (!format)
> +                       return -EINVAL;
> +
> +               code->code = format->code;
> +
> +               return 0;
> +       }
> +
> +       info = mxc_isi_bus_format_by_index(code->index, MXC_ISI_PIPE_PAD_SINK);
> +       if (!info)
> +               return -EINVAL;
> +
> +       code->code = info->mbus_code;
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_crossbar_set_fmt(struct v4l2_subdev *sd,
> +                               struct v4l2_subdev_state *state,
> +                               struct v4l2_subdev_format *fmt)
> +{
> +       struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
> +       struct v4l2_mbus_framefmt *sink_fmt;
> +       struct v4l2_subdev_route *route;
> +
> +       if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
> +           media_pad_is_streaming(&xbar->pads[fmt->pad]))
> +               return -EBUSY;
> +
> +       /*
> +        * The source pad format is always identical to the sink pad format and
> +        * can't be modified.
> +        */
> +       if (fmt->pad >= xbar->num_sinks)
> +               return v4l2_subdev_get_fmt(sd, state, fmt);
> +
> +       /* Validate the requested format. */
> +       if (!mxc_isi_bus_format_by_code(fmt->format.code, MXC_ISI_PIPE_PAD_SINK))
> +               fmt->format.code = MXC_ISI_DEF_MBUS_CODE_SINK;
> +
> +       fmt->format.width = clamp_t(unsigned int, fmt->format.width,
> +                                   MXC_ISI_MIN_WIDTH, MXC_ISI_MAX_WIDTH_CHAINED);
> +       fmt->format.height = clamp_t(unsigned int, fmt->format.height,
> +                                    MXC_ISI_MIN_HEIGHT, MXC_ISI_MAX_HEIGHT);
> +       fmt->format.field = V4L2_FIELD_NONE;
> +
> +       /*
> +        * Set the format on the sink stream and propagate it to the source
> +        * streams.
> +        */
> +       sink_fmt = v4l2_subdev_state_get_stream_format(state, fmt->pad,
> +                                                      fmt->stream);
> +       if (!sink_fmt)
> +               return -EINVAL;
> +
> +       *sink_fmt = fmt->format;
> +
> +       /* TODO: A format propagation helper would be useful. */
> +       for_each_active_route(&state->routing, route) {
> +               struct v4l2_mbus_framefmt *source_fmt;
> +
> +               if (route->sink_pad != fmt->pad ||
> +                   route->sink_stream != fmt->stream)
> +                       continue;
> +
> +               source_fmt = v4l2_subdev_state_get_stream_format(state, route->source_pad,
> +                                                                route->source_stream);
> +               if (!source_fmt)
> +                       return -EINVAL;
> +
> +               *source_fmt = fmt->format;
> +       }
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd,
> +                                       struct v4l2_subdev_state *state,
> +                                       enum v4l2_subdev_format_whence which,
> +                                       struct v4l2_subdev_krouting *routing)
> +{
> +       if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&
> +           media_entity_is_streaming(&sd->entity))
> +               return -EBUSY;
> +
> +       return __mxc_isi_crossbar_set_routing(sd, state, routing);
> +}
> +
> +static int mxc_isi_crossbar_enable_streams(struct v4l2_subdev *sd,
> +                                          struct v4l2_subdev_state *state,
> +                                          u32 pad, u64 streams_mask)
> +{
> +       struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
> +       struct v4l2_subdev *remote_sd;
> +       struct mxc_isi_input *input;
> +       u64 sink_streams;
> +       u32 sink_pad;
> +       u32 remote_pad;
> +       int ret;
> +
> +       remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask,
> +                                                  &sink_pad, &sink_streams,
> +                                                  &remote_pad);
> +       if (IS_ERR(remote_sd))
> +               return PTR_ERR(remote_sd);
> +
> +       input = &xbar->inputs[sink_pad];
> +
> +       /*
> +        * TODO: Track per-stream enable counts to support multiplexed
> +        * streams.
> +        */
> +       if (!input->enable_count) {
> +               ret = mxc_isi_crossbar_gasket_enable(xbar, state, remote_sd,
> +                                                    remote_pad, sink_pad);
> +               if (ret)
> +                       return ret;
> +
> +               ret = v4l2_subdev_enable_streams(remote_sd, remote_pad,
> +                                                sink_streams);
> +               if (ret) {
> +                       dev_err(xbar->isi->dev,
> +                               "failed to %s streams 0x%llx on '%s':%u: %d\n",
> +                               "enable", sink_streams, remote_sd->name,
> +                               remote_pad, ret);
> +                       mxc_isi_crossbar_gasket_disable(xbar, sink_pad);
> +                       return ret;
> +               }
> +       }
> +
> +       input->enable_count++;
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_crossbar_disable_streams(struct v4l2_subdev *sd,
> +                                           struct v4l2_subdev_state *state,
> +                                           u32 pad, u64 streams_mask)
> +{
> +       struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
> +       struct v4l2_subdev *remote_sd;
> +       struct mxc_isi_input *input;
> +       u64 sink_streams;
> +       u32 sink_pad;
> +       u32 remote_pad;
> +       int ret = 0;
> +
> +       remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask,
> +                                                  &sink_pad, &sink_streams,
> +                                                  &remote_pad);
> +       if (IS_ERR(remote_sd))
> +               return PTR_ERR(remote_sd);
> +
> +       input = &xbar->inputs[sink_pad];
> +
> +       input->enable_count--;
> +
> +       if (!input->enable_count) {
> +               ret = v4l2_subdev_disable_streams(remote_sd, remote_pad,
> +                                                 sink_streams);
> +               if (ret)
> +                       dev_err(xbar->isi->dev,
> +                               "failed to %s streams 0x%llx on '%s':%u: %d\n",
> +                               "disable", sink_streams, remote_sd->name,
> +                               remote_pad, ret);
> +
> +               mxc_isi_crossbar_gasket_disable(xbar, sink_pad);
> +       }
> +
> +       return ret;
> +}
> +
> +static const struct v4l2_subdev_pad_ops mxc_isi_crossbar_subdev_pad_ops = {
> +       .init_cfg = mxc_isi_crossbar_init_cfg,
> +       .enum_mbus_code = mxc_isi_crossbar_enum_mbus_code,
> +       .get_fmt = v4l2_subdev_get_fmt,
> +       .set_fmt = mxc_isi_crossbar_set_fmt,
> +       .set_routing = mxc_isi_crossbar_set_routing,
> +       .enable_streams = mxc_isi_crossbar_enable_streams,
> +       .disable_streams = mxc_isi_crossbar_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops mxc_isi_crossbar_subdev_ops = {
> +       .pad = &mxc_isi_crossbar_subdev_pad_ops,
> +};
> +
> +static const struct media_entity_operations mxc_isi_cross_entity_ops = {
> +       .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> +       .link_validate  = v4l2_subdev_link_validate,
> +       .has_route = v4l2_subdev_has_route,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Init & cleanup
> + */
> +
> +int mxc_isi_crossbar_init(struct mxc_isi_dev *isi)
> +{
> +       struct mxc_isi_crossbar *xbar = &isi->crossbar;
> +       struct v4l2_subdev *sd = &xbar->sd;
> +       unsigned int num_pads;
> +       unsigned int i;
> +       int ret;
> +
> +       xbar->isi = isi;
> +
> +       v4l2_subdev_init(sd, &mxc_isi_crossbar_subdev_ops);
> +       sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_MULTIPLEXED;
> +       strscpy(sd->name, "crossbar", sizeof(sd->name));
> +       sd->dev = isi->dev;
> +
> +       sd->entity.function = MEDIA_ENT_F_VID_MUX;
> +       sd->entity.ops = &mxc_isi_cross_entity_ops;
> +
> +       /*
> +        * The subdev has one sink and one source per port, plus one sink for
> +        * the memory input.
> +        */
> +       xbar->num_sinks = isi->pdata->num_ports + 1;
> +       xbar->num_sources = isi->pdata->num_ports;
> +       num_pads = xbar->num_sinks + xbar->num_sources;
> +
> +       xbar->pads = kcalloc(num_pads, sizeof(*xbar->pads), GFP_KERNEL);
> +       if (!xbar->pads)
> +               return -ENOMEM;
> +
> +       xbar->inputs = kcalloc(xbar->num_sinks, sizeof(*xbar->inputs),
> +                              GFP_KERNEL);
> +       if (!xbar->pads) {
> +               ret = -ENOMEM;
> +               goto err_free;
> +       }
> +
> +       for (i = 0; i < xbar->num_sinks; ++i)
> +               xbar->pads[i].flags = MEDIA_PAD_FL_SINK;
> +       for (i = 0; i < xbar->num_sources; ++i)
> +               xbar->pads[i + xbar->num_sinks].flags = MEDIA_PAD_FL_SOURCE;
> +
> +       ret = media_entity_pads_init(&sd->entity, num_pads, xbar->pads);
> +       if (ret)
> +               goto err_free;
> +
> +       ret = v4l2_subdev_init_finalize(sd);
> +       if (ret < 0)
> +               goto err_entity;
> +
> +       return 0;
> +
> +err_entity:
> +       media_entity_cleanup(&sd->entity);
> +err_free:
> +       kfree(xbar->pads);
> +       kfree(xbar->inputs);
> +
> +       return ret;
> +}
> +
> +void mxc_isi_crossbar_cleanup(struct mxc_isi_crossbar *xbar)
> +{
> +       media_entity_cleanup(&xbar->sd.entity);
> +       kfree(xbar->pads);
> +       kfree(xbar->inputs);
> +}
> +
> +int mxc_isi_crossbar_register(struct mxc_isi_crossbar *xbar)
> +{
> +       return v4l2_device_register_subdev(&xbar->isi->v4l2_dev, &xbar->sd);
> +}
> +
> +void mxc_isi_crossbar_unregister(struct mxc_isi_crossbar *xbar)
> +{
> +}
> diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-debug.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-debug.c
> new file mode 100644
> index 000000000000..6709ab7ea1f3
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-debug.c
> @@ -0,0 +1,109 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2019-2020 NXP
> + */
> +
> +#include <linux/debugfs.h>
> +#include <linux/device.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/seq_file.h>
> +#include <linux/types.h>
> +
> +#include "imx8-isi-core.h"
> +#include "imx8-isi-regs.h"
> +
> +static inline u32 mxc_isi_read(struct mxc_isi_pipe *pipe, u32 reg)
> +{
> +       return readl(pipe->regs + reg);
> +}
> +
> +static int mxc_isi_debug_dump_regs_show(struct seq_file *m, void *p)
> +{
> +#define MXC_ISI_DEBUG_REG(name)                { name, #name }
> +       static const struct {
> +               u32 offset;
> +               const char * const name;
> +       } registers[] = {
> +               MXC_ISI_DEBUG_REG(CHNL_CTRL),
> +               MXC_ISI_DEBUG_REG(CHNL_IMG_CTRL),
> +               MXC_ISI_DEBUG_REG(CHNL_OUT_BUF_CTRL),
> +               MXC_ISI_DEBUG_REG(CHNL_IMG_CFG),
> +               MXC_ISI_DEBUG_REG(CHNL_IER),
> +               MXC_ISI_DEBUG_REG(CHNL_STS),
> +               MXC_ISI_DEBUG_REG(CHNL_SCALE_FACTOR),
> +               MXC_ISI_DEBUG_REG(CHNL_SCALE_OFFSET),
> +               MXC_ISI_DEBUG_REG(CHNL_CROP_ULC),
> +               MXC_ISI_DEBUG_REG(CHNL_CROP_LRC),
> +               MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF0),
> +               MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF1),
> +               MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF2),
> +               MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF3),
> +               MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF4),
> +               MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF5),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_0_ALPHA),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_0_ULC),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_0_LRC),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_1_ALPHA),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_1_ULC),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_1_LRC),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_2_ALPHA),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_2_ULC),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_2_LRC),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_3_ALPHA),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_3_ULC),
> +               MXC_ISI_DEBUG_REG(CHNL_ROI_3_LRC),
> +               MXC_ISI_DEBUG_REG(CHNL_OUT_BUF1_ADDR_Y),
> +               MXC_ISI_DEBUG_REG(CHNL_OUT_BUF1_ADDR_U),
> +               MXC_ISI_DEBUG_REG(CHNL_OUT_BUF1_ADDR_V),
> +               MXC_ISI_DEBUG_REG(CHNL_OUT_BUF_PITCH),
> +               MXC_ISI_DEBUG_REG(CHNL_IN_BUF_ADDR),
> +               MXC_ISI_DEBUG_REG(CHNL_IN_BUF_PITCH),
> +               MXC_ISI_DEBUG_REG(CHNL_MEM_RD_CTRL),
> +               MXC_ISI_DEBUG_REG(CHNL_OUT_BUF2_ADDR_Y),
> +               MXC_ISI_DEBUG_REG(CHNL_OUT_BUF2_ADDR_U),
> +               MXC_ISI_DEBUG_REG(CHNL_OUT_BUF2_ADDR_V),
> +               MXC_ISI_DEBUG_REG(CHNL_SCL_IMG_CFG),
> +               MXC_ISI_DEBUG_REG(CHNL_FLOW_CTRL),
> +       };
> +
> +       struct mxc_isi_pipe *pipe = m->private;
> +       unsigned int i;
> +
> +       if (!pm_runtime_get_if_in_use(pipe->isi->dev))
> +               return 0;
> +
> +       seq_printf(m, "--- ISI pipe %u registers ---\n", pipe->id);
> +
> +       for (i = 0; i < ARRAY_SIZE(registers); ++i)
> +               seq_printf(m, "%20s[0x%02x]: 0x%08x\n",
> +                          registers[i].name, registers[i].offset,
> +                          mxc_isi_read(pipe, registers[i].offset));
> +
> +       pm_runtime_put(pipe->isi->dev);
> +
> +       return 0;
> +}
> +DEFINE_SHOW_ATTRIBUTE(mxc_isi_debug_dump_regs);
> +
> +void mxc_isi_debug_init(struct mxc_isi_dev *isi)
> +{
> +       unsigned int i;
> +
> +       isi->debugfs_root = debugfs_create_dir(dev_name(isi->dev), NULL);
> +
> +       for (i = 0; i < isi->pdata->num_channels; ++i) {
> +               struct mxc_isi_pipe *pipe = &isi->pipes[i];
> +               char name[8];
> +
> +               sprintf(name, "pipe%u", pipe->id);
> +               debugfs_create_file(name, 0444, isi->debugfs_root, pipe,
> +                                   &mxc_isi_debug_dump_regs_fops);
> +       }
> +}
> +
> +void mxc_isi_debug_cleanup(struct mxc_isi_dev *isi)
> +{
> +       debugfs_remove_recursive(isi->debugfs_root);
> +}
> diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-hw.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-hw.c
> new file mode 100644
> index 000000000000..eddc7fc36337
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-hw.c
> @@ -0,0 +1,651 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2019-2020 NXP
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/io.h>
> +#include <linux/types.h>
> +
> +#include "imx8-isi-core.h"
> +#include "imx8-isi-regs.h"
> +
> +#define        ISI_DOWNSCALE_THRESHOLD         0x4000
> +
> +static inline u32 mxc_isi_read(struct mxc_isi_pipe *pipe, u32 reg)
> +{
> +       return readl(pipe->regs + reg);
> +}
> +
> +static inline void mxc_isi_write(struct mxc_isi_pipe *pipe, u32 reg, u32 val)
> +{
> +       writel(val, pipe->regs + reg);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Buffers & M2M operation
> + */
> +
> +void mxc_isi_channel_set_inbuf(struct mxc_isi_pipe *pipe, dma_addr_t dma_addr)
> +{
> +       mxc_isi_write(pipe, CHNL_IN_BUF_ADDR, dma_addr);
> +#if CONFIG_ARCH_DMA_ADDR_T_64BIT
> +       if (pipe->isi->pdata->has_36bit_dma)
> +               mxc_isi_write(pipe, CHNL_IN_BUF_XTND_ADDR, dma_addr >> 32);
> +#endif
> +}
> +
> +void mxc_isi_channel_set_outbuf(struct mxc_isi_pipe *pipe,
> +                               const dma_addr_t dma_addrs[3],
> +                               enum mxc_isi_buf_id buf_id)
> +{
> +       int val;
> +
> +       val = mxc_isi_read(pipe, CHNL_OUT_BUF_CTRL);
> +
> +       if (buf_id == MXC_ISI_BUF1) {
> +               mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_Y, dma_addrs[0]);
> +               mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_U, dma_addrs[1]);
> +               mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_V, dma_addrs[2]);
> +#if CONFIG_ARCH_DMA_ADDR_T_64BIT
> +               if (pipe->isi->pdata->has_36bit_dma) {
> +                       mxc_isi_write(pipe, CHNL_Y_BUF1_XTND_ADDR,
> +                                     dma_addrs[0] >> 32);
> +                       mxc_isi_write(pipe, CHNL_U_BUF1_XTND_ADDR,
> +                                     dma_addrs[1] >> 32);
> +                       mxc_isi_write(pipe, CHNL_V_BUF1_XTND_ADDR,
> +                                     dma_addrs[2] >> 32);
> +               }
> +#endif
> +               val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR;
> +       } else  {
> +               mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_Y, dma_addrs[0]);
> +               mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_U, dma_addrs[1]);
> +               mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_V, dma_addrs[2]);
> +#if CONFIG_ARCH_DMA_ADDR_T_64BIT
> +               if (pipe->isi->pdata->has_36bit_dma) {
> +                       mxc_isi_write(pipe, CHNL_Y_BUF2_XTND_ADDR,
> +                                     dma_addrs[0] >> 32);
> +                       mxc_isi_write(pipe, CHNL_U_BUF2_XTND_ADDR,
> +                                     dma_addrs[1] >> 32);
> +                       mxc_isi_write(pipe, CHNL_V_BUF2_XTND_ADDR,
> +                                     dma_addrs[2] >> 32);
> +               }
> +#endif
> +               val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR;
> +       }
> +
> +       mxc_isi_write(pipe, CHNL_OUT_BUF_CTRL, val);
> +}
> +
> +void mxc_isi_channel_m2m_start(struct mxc_isi_pipe *pipe)
> +{
> +       u32 val;
> +
> +       val = mxc_isi_read(pipe, CHNL_MEM_RD_CTRL);
> +       val &= ~CHNL_MEM_RD_CTRL_READ_MEM;
> +       mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, val);
> +       udelay(300);
> +
> +       val |= CHNL_MEM_RD_CTRL_READ_MEM;
> +       mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, val);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Pipeline configuration
> + */
> +
> +static u32 mxc_isi_channel_scaling_ratio(unsigned int from, unsigned int to,
> +                                        u32 *dec)
> +{
> +       unsigned int ratio = from / to;
> +
> +       if (ratio < 2)
> +               *dec = 1;
> +       else if (ratio < 4)
> +               *dec = 2;
> +       else if (ratio < 8)
> +               *dec = 4;
> +       else
> +               *dec = 8;
> +
> +       return min_t(u32, from * 0x1000 / (to * *dec), ISI_DOWNSCALE_THRESHOLD);
> +}
> +
> +static void mxc_isi_channel_set_scaling(struct mxc_isi_pipe *pipe,
> +                                       enum mxc_isi_encoding encoding,
> +                                       const struct v4l2_area *in_size,
> +                                       const struct v4l2_area *out_size,
> +                                       bool *bypass)
> +{
> +       u32 xscale, yscale;
> +       u32 decx, decy;
> +       u32 val;
> +
> +       dev_dbg(pipe->isi->dev, "input %ux%u, output %ux%u\n",
> +               in_size->width, in_size->height,
> +               out_size->width, out_size->height);
> +
> +       xscale = mxc_isi_channel_scaling_ratio(in_size->width, out_size->width,
> +                                              &decx);
> +       yscale = mxc_isi_channel_scaling_ratio(in_size->height, out_size->height,
> +                                              &decy);
> +
> +       val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
> +       val &= ~(CHNL_IMG_CTRL_DEC_X_MASK | CHNL_IMG_CTRL_DEC_Y_MASK |
> +                CHNL_IMG_CTRL_YCBCR_MODE);
> +
> +       val |= CHNL_IMG_CTRL_DEC_X(ilog2(decx))
> +           |  CHNL_IMG_CTRL_DEC_Y(ilog2(decy));
> +
> +       /*
> +        * Contrary to what the documentation states, YCBCR_MODE does not
> +        * control conversion between YCbCr and RGB, but whether the scaler
> +        * operates in YUV mode or in RGB mode. It must be set when the scaler
> +        * input is YUV.
> +        */
> +       if (encoding == MXC_ISI_ENC_YUV)
> +               val |= CHNL_IMG_CTRL_YCBCR_MODE;
> +
> +       mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
> +
> +       mxc_isi_write(pipe, CHNL_SCALE_FACTOR,
> +                     CHNL_SCALE_FACTOR_Y_SCALE(yscale) |
> +                     CHNL_SCALE_FACTOR_X_SCALE(xscale));
> +
> +       mxc_isi_write(pipe, CHNL_SCALE_OFFSET, 0);
> +
> +       mxc_isi_write(pipe, CHNL_SCL_IMG_CFG,
> +                     CHNL_SCL_IMG_CFG_HEIGHT(out_size->height) |
> +                     CHNL_SCL_IMG_CFG_WIDTH(out_size->width));
> +
> +       *bypass = in_size->height == out_size->height &&
> +                 in_size->width == out_size->width;
> +}
> +
> +static void mxc_isi_channel_set_crop(struct mxc_isi_pipe *pipe,
> +                                    const struct v4l2_area *src,
> +                                    const struct v4l2_rect *dst)
> +{
> +       u32 val, val0, val1;
> +
> +       val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
> +       val &= ~CHNL_IMG_CTRL_CROP_EN;
> +
> +       if (src->height == dst->height && src->width == dst->width) {
> +               mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
> +               return;
> +       }
> +
> +       val |= CHNL_IMG_CTRL_CROP_EN;
> +       val0 = CHNL_CROP_ULC_X(dst->left) | CHNL_CROP_ULC_Y(dst->top);
> +       val1 = CHNL_CROP_LRC_X(dst->width) | CHNL_CROP_LRC_Y(dst->height);
> +
> +       mxc_isi_write(pipe, CHNL_CROP_ULC, val0);
> +       mxc_isi_write(pipe, CHNL_CROP_LRC, val1 + val0);
> +       mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
> +}
> +
> +/*
> + * A2,A1,      B1, A3,     B3, B2,
> + * C2, C1,     D1, C3,     D3, D2
> + */
> +static const u32 mxc_isi_yuv2rgb_coeffs[6] = {
> +       /* YUV -> RGB */
> +       0x0000012a, 0x012a0198, 0x0730079c,
> +       0x0204012a, 0x01f00000, 0x01800180
> +};
> +
> +static const u32 mxc_isi_rgb2yuv_coeffs[6] = {
> +       /* RGB->YUV */
> +       0x00810041, 0x07db0019, 0x007007b6,
> +       0x07a20070, 0x001007ee, 0x00800080
> +};
> +
> +static void mxc_isi_channel_set_csc(struct mxc_isi_pipe *pipe,
> +                                   enum mxc_isi_encoding in_encoding,
> +                                   enum mxc_isi_encoding out_encoding,
> +                                   bool *bypass)
> +{
> +       static const char * const encodings[] = {
> +               [MXC_ISI_ENC_RAW] = "RAW",
> +               [MXC_ISI_ENC_RGB] = "RGB",
> +               [MXC_ISI_ENC_YUV] = "YUV",
> +       };
> +       const u32 *coeffs;
> +       bool cscen = true;
> +       u32 val;
> +
> +       val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
> +       val &= ~(CHNL_IMG_CTRL_CSC_BYPASS | CHNL_IMG_CTRL_CSC_MODE_MASK);
> +
> +       if (in_encoding == MXC_ISI_ENC_YUV &&
> +           out_encoding == MXC_ISI_ENC_RGB) {
> +               /* YUV2RGB */
> +               coeffs = mxc_isi_yuv2rgb_coeffs;
> +               /* YCbCr enable???  */
> +               val |= CHNL_IMG_CTRL_CSC_MODE(CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB);
> +       } else if (in_encoding == MXC_ISI_ENC_RGB &&
> +                  out_encoding == MXC_ISI_ENC_YUV) {
> +               /* RGB2YUV */
> +               coeffs = mxc_isi_rgb2yuv_coeffs;
> +               val |= CHNL_IMG_CTRL_CSC_MODE(CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR);
> +       } else {
> +               /* Bypass CSC */
> +               cscen = false;
> +               val |= CHNL_IMG_CTRL_CSC_BYPASS;
> +       }
> +
> +       dev_dbg(pipe->isi->dev, "CSC: %s -> %s\n",
> +               encodings[in_encoding], encodings[out_encoding]);
> +
> +       if (cscen) {
> +               mxc_isi_write(pipe, CHNL_CSC_COEFF0, coeffs[0]);
> +               mxc_isi_write(pipe, CHNL_CSC_COEFF1, coeffs[1]);
> +               mxc_isi_write(pipe, CHNL_CSC_COEFF2, coeffs[2]);
> +               mxc_isi_write(pipe, CHNL_CSC_COEFF3, coeffs[3]);
> +               mxc_isi_write(pipe, CHNL_CSC_COEFF4, coeffs[4]);
> +               mxc_isi_write(pipe, CHNL_CSC_COEFF5, coeffs[5]);
> +       }
> +
> +       mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
> +
> +       *bypass = !cscen;
> +}
> +
> +void mxc_isi_channel_set_alpha(struct mxc_isi_pipe *pipe, u8 alpha)
> +{
> +       u32 val;
> +
> +       val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
> +       val &= ~CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK;
> +       val |= CHNL_IMG_CTRL_GBL_ALPHA_VAL(alpha) |
> +              CHNL_IMG_CTRL_GBL_ALPHA_EN;
> +       mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
> +}
> +
> +void mxc_isi_channel_set_flip(struct mxc_isi_pipe *pipe, bool hflip, bool vflip)
> +{
> +       u32 val;
> +
> +       val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
> +       val &= ~(CHNL_IMG_CTRL_VFLIP_EN | CHNL_IMG_CTRL_HFLIP_EN);
> +
> +       if (vflip)
> +               val |= CHNL_IMG_CTRL_VFLIP_EN;
> +       if (hflip)
> +               val |= CHNL_IMG_CTRL_HFLIP_EN;
> +
> +       mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
> +}
> +
> +static void mxc_isi_channel_set_panic_threshold(struct mxc_isi_pipe *pipe)
> +{
> +       const struct mxc_isi_set_thd *set_thd = pipe->isi->pdata->set_thd;
> +       u32 val;
> +
> +       val = mxc_isi_read(pipe, CHNL_OUT_BUF_CTRL);
> +
> +       val &= ~(set_thd->panic_set_thd_y.mask);
> +       val |= set_thd->panic_set_thd_y.threshold << set_thd->panic_set_thd_y.offset;
> +
> +       val &= ~(set_thd->panic_set_thd_u.mask);
> +       val |= set_thd->panic_set_thd_u.threshold << set_thd->panic_set_thd_u.offset;
> +
> +       val &= ~(set_thd->panic_set_thd_v.mask);
> +       val |= set_thd->panic_set_thd_v.threshold << set_thd->panic_set_thd_v.offset;
> +
> +       mxc_isi_write(pipe, CHNL_OUT_BUF_CTRL, val);
> +}
> +
> +static void mxc_isi_channel_set_control(struct mxc_isi_pipe *pipe,
> +                                       enum mxc_isi_input_id input,
> +                                       bool bypass)
> +{
> +       u32 val;
> +
> +       mutex_lock(&pipe->lock);
> +
> +       val = mxc_isi_read(pipe, CHNL_CTRL);
> +       val &= ~(CHNL_CTRL_CHNL_BYPASS | CHNL_CTRL_CHAIN_BUF_MASK |
> +                CHNL_CTRL_BLANK_PXL_MASK | CHNL_CTRL_SRC_TYPE_MASK |
> +                CHNL_CTRL_MIPI_VC_ID_MASK | CHNL_CTRL_SRC_INPUT_MASK);
> +
> +       /*
> +        * If no scaling or color space conversion is needed, bypass the
> +        * channel.
> +        */
> +       if (bypass)
> +               val |= CHNL_CTRL_CHNL_BYPASS;
> +
> +       /* Chain line buffers if needed. */
> +       if (pipe->chained)
> +               val |= CHNL_CTRL_CHAIN_BUF(CHNL_CTRL_CHAIN_BUF_2_CHAIN);
> +
> +       val |= CHNL_CTRL_BLANK_PXL(0xff);
> +
> +       /* Input source (including VC configuration for CSI-2) */
> +       if (input == MXC_ISI_INPUT_MEM) {
> +               /*
> +                * The memory input is connected to the last port of the
> +                * crossbar switch, after all pixel link inputs. The SRC_INPUT
> +                * field controls the input selection and must be set
> +                * accordingly, despite being documented as ignored when using
> +                * the memory input in the i.MX8MP reference manual, and
> +                * reserved in the i.MX8MN reference manual.
> +                */
> +               val |= CHNL_CTRL_SRC_TYPE(CHNL_CTRL_SRC_TYPE_MEMORY);
> +               val |= CHNL_CTRL_SRC_INPUT(pipe->isi->pdata->num_ports);
> +       } else {
> +               val |= CHNL_CTRL_SRC_TYPE(CHNL_CTRL_SRC_TYPE_DEVICE);
> +               val |= CHNL_CTRL_SRC_INPUT(input);
> +               val |= CHNL_CTRL_MIPI_VC_ID(0); /* FIXME: For CSI-2 only */
> +       }
> +
> +       mxc_isi_write(pipe, CHNL_CTRL, val);
> +
> +       mutex_unlock(&pipe->lock);
> +}
> +
> +void mxc_isi_channel_config(struct mxc_isi_pipe *pipe,
> +                           enum mxc_isi_input_id input,
> +                           const struct v4l2_area *in_size,
> +                           const struct v4l2_area *scale,
> +                           const struct v4l2_rect *crop,
> +                           enum mxc_isi_encoding in_encoding,
> +                           enum mxc_isi_encoding out_encoding)
> +{
> +       bool csc_bypass;
> +       bool scaler_bypass;
> +
> +       /* Input frame size */
> +       mxc_isi_write(pipe, CHNL_IMG_CFG,
> +                     CHNL_IMG_CFG_HEIGHT(in_size->height) |
> +                     CHNL_IMG_CFG_WIDTH(in_size->width));
> +
> +       /* Scaling */
> +       mxc_isi_channel_set_scaling(pipe, in_encoding, in_size, scale,
> +                                   &scaler_bypass);
> +       mxc_isi_channel_set_crop(pipe, scale, crop);
> +
> +       /* CSC */
> +       mxc_isi_channel_set_csc(pipe, in_encoding, out_encoding, &csc_bypass);
> +
> +       /* Output buffer management */
> +       mxc_isi_channel_set_panic_threshold(pipe);
> +
> +       /* Channel control */
> +       mxc_isi_channel_set_control(pipe, input, csc_bypass && scaler_bypass);
> +}
> +
> +void mxc_isi_channel_set_input_format(struct mxc_isi_pipe *pipe,
> +                                     const struct mxc_isi_format_info *info,
> +                                     const struct v4l2_pix_format_mplane *format)
> +{
> +       unsigned int bpl = format->plane_fmt[0].bytesperline;
> +
> +       mxc_isi_write(pipe, CHNL_MEM_RD_CTRL,
> +                     CHNL_MEM_RD_CTRL_IMG_TYPE(info->isi_in_format));
> +       mxc_isi_write(pipe, CHNL_IN_BUF_PITCH,
> +                     CHNL_IN_BUF_PITCH_LINE_PITCH(bpl));
> +}
> +
> +void mxc_isi_channel_set_output_format(struct mxc_isi_pipe *pipe,
> +                                      const struct mxc_isi_format_info *info,
> +                                      struct v4l2_pix_format_mplane *format)
> +{
> +       u32 val;
> +
> +       /* set outbuf format */
> +       dev_dbg(pipe->isi->dev, "output format %p4cc", &format->pixelformat);
> +
> +       val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
> +       val &= ~CHNL_IMG_CTRL_FORMAT_MASK;
> +       val |= CHNL_IMG_CTRL_FORMAT(info->isi_out_format);
> +       mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
> +
> +       /* line pitch */
> +       mxc_isi_write(pipe, CHNL_OUT_BUF_PITCH,
> +                     format->plane_fmt[0].bytesperline);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * IRQ
> + */
> +
> +u32 mxc_isi_channel_irq_status(struct mxc_isi_pipe *pipe, bool clear)
> +{
> +       u32 status;
> +
> +       status = mxc_isi_read(pipe, CHNL_STS);
> +       if (clear)
> +               mxc_isi_write(pipe, CHNL_STS, status);
> +
> +       return status;
> +}
> +
> +void mxc_isi_channel_irq_clear(struct mxc_isi_pipe *pipe)
> +{
> +       mxc_isi_write(pipe, CHNL_STS, 0xffffffff);
> +}
> +
> +static void mxc_isi_channel_irq_enable(struct mxc_isi_pipe *pipe)
> +{
> +       const struct mxc_isi_ier_reg *ier_reg = pipe->isi->pdata->ier_reg;
> +       u32 val;
> +
> +       val = CHNL_IER_FRM_RCVD_EN |
> +               CHNL_IER_AXI_WR_ERR_U_EN |
> +               CHNL_IER_AXI_WR_ERR_V_EN |
> +               CHNL_IER_AXI_WR_ERR_Y_EN;
> +
> +       /* Y/U/V overflow enable */
> +       val |= ier_reg->oflw_y_buf_en.mask |
> +              ier_reg->oflw_u_buf_en.mask |
> +              ier_reg->oflw_v_buf_en.mask;
> +
> +       /* Y/U/V excess overflow enable */
> +       val |= ier_reg->excs_oflw_y_buf_en.mask |
> +              ier_reg->excs_oflw_u_buf_en.mask |
> +              ier_reg->excs_oflw_v_buf_en.mask;
> +
> +       /* Y/U/V panic enable */
> +       val |= ier_reg->panic_y_buf_en.mask |
> +              ier_reg->panic_u_buf_en.mask |
> +              ier_reg->panic_v_buf_en.mask;
> +
> +       mxc_isi_channel_irq_clear(pipe);
> +       mxc_isi_write(pipe, CHNL_IER, val);
> +}
> +
> +static void mxc_isi_channel_irq_disable(struct mxc_isi_pipe *pipe)
> +{
> +       mxc_isi_write(pipe, CHNL_IER, 0);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Init, deinit, enable, disable
> + */
> +
> +static void mxc_isi_channel_sw_reset(struct mxc_isi_pipe *pipe, bool enable_clk)
> +{
> +       mxc_isi_write(pipe, CHNL_CTRL, CHNL_CTRL_SW_RST);
> +       mdelay(5);
> +       mxc_isi_write(pipe, CHNL_CTRL, enable_clk ? CHNL_CTRL_CLK_EN : 0);
> +}
> +
> +static void __mxc_isi_channel_get(struct mxc_isi_pipe *pipe)
> +{
> +       if (!pipe->use_count++)
> +               mxc_isi_channel_sw_reset(pipe, true);
> +}
> +
> +void mxc_isi_channel_get(struct mxc_isi_pipe *pipe)
> +{
> +       mutex_lock(&pipe->lock);
> +       __mxc_isi_channel_get(pipe);
> +       mutex_unlock(&pipe->lock);
> +}
> +
> +static void __mxc_isi_channel_put(struct mxc_isi_pipe *pipe)
> +{
> +       if (!--pipe->use_count)
> +               mxc_isi_channel_sw_reset(pipe, false);
> +}
> +
> +void mxc_isi_channel_put(struct mxc_isi_pipe *pipe)
> +{
> +       mutex_lock(&pipe->lock);
> +       __mxc_isi_channel_put(pipe);
> +       mutex_unlock(&pipe->lock);
> +}
> +
> +void mxc_isi_channel_enable(struct mxc_isi_pipe *pipe)
> +{
> +       u32 val;
> +
> +       mxc_isi_channel_irq_enable(pipe);
> +
> +       mutex_lock(&pipe->lock);
> +
> +       val = mxc_isi_read(pipe, CHNL_CTRL);
> +       val |= CHNL_CTRL_CHNL_EN;
> +       mxc_isi_write(pipe, CHNL_CTRL, val);
> +
> +       mutex_unlock(&pipe->lock);
> +
> +       msleep(300);
> +}
> +
> +void mxc_isi_channel_disable(struct mxc_isi_pipe *pipe)
> +{
> +       u32 val;
> +
> +       mxc_isi_channel_irq_disable(pipe);
> +
> +       mutex_lock(&pipe->lock);
> +
> +       val = mxc_isi_read(pipe, CHNL_CTRL);
> +       val &= ~CHNL_CTRL_CHNL_EN;
> +       mxc_isi_write(pipe, CHNL_CTRL, val);
> +
> +       mutex_unlock(&pipe->lock);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Resource management & chaining
> + */
> +int mxc_isi_channel_acquire(struct mxc_isi_pipe *pipe,
> +                           mxc_isi_pipe_irq_t irq_handler, bool bypass)
> +{
> +       u8 resources;
> +       int ret = 0;
> +
> +       mutex_lock(&pipe->lock);
> +
> +       if (pipe->irq_handler) {
> +               ret = -EBUSY;
> +               goto unlock;
> +       }
> +
> +       /*
> +        * Make sure the resources we need are available. The output buffer is
> +        * always needed to operate the channel, the line buffer is needed only
> +        * when the channel isn't in bypass mode.
> +        */
> +       resources = MXC_ISI_CHANNEL_RES_OUTPUT_BUF
> +                 | (!bypass ? MXC_ISI_CHANNEL_RES_LINE_BUF : 0);
> +       if ((pipe->available_res & resources) != resources) {
> +               ret = -EBUSY;
> +               goto unlock;
> +       }
> +
> +       /* Acquire the channel resources. */
> +       pipe->acquired_res = resources;
> +       pipe->available_res &= ~resources;
> +       pipe->irq_handler = irq_handler;
> +
> +unlock:
> +       mutex_unlock(&pipe->lock);
> +
> +       return ret;
> +}
> +
> +void mxc_isi_channel_release(struct mxc_isi_pipe *pipe)
> +{
> +       mutex_lock(&pipe->lock);
> +
> +       pipe->irq_handler = NULL;
> +       pipe->available_res |= pipe->acquired_res;
> +       pipe->acquired_res = 0;
> +
> +       mutex_unlock(&pipe->lock);
> +}
> +
> +/*
> + * We currently support line buffer chaining only, for handling images with a
> + * width larger than 2048 pixels.
> + *
> + * TODO: Support secondary line buffer for downscaling YUV420 images.
> + */
> +int mxc_isi_channel_chain(struct mxc_isi_pipe *pipe, bool bypass)
> +{
> +       /* Channel chaining requires both line and output buffer. */
> +       const u8 resources = MXC_ISI_CHANNEL_RES_OUTPUT_BUF
> +                          | MXC_ISI_CHANNEL_RES_LINE_BUF;
> +       struct mxc_isi_pipe *chained_pipe = pipe + 1;
> +       int ret = 0;
> +
> +       /*
> +        * If buffer chaining is required, make sure this channel is not the
> +        * last one, otherwise there's no 'next' channel to chain with. This
> +        * should be prevented by checks in the set format handlers, but let's
> +        * be defensive.
> +        */
> +       if (WARN_ON(pipe->id == pipe->isi->pdata->num_channels - 1))
> +               return -EINVAL;
> +
> +       mutex_lock(&chained_pipe->lock);
> +
> +       /* Safety checks. */
> +       if (WARN_ON(pipe->chained || chained_pipe->chained_res)) {
> +               ret = -EINVAL;
> +               goto unlock;
> +       }
> +
> +       if ((chained_pipe->available_res & resources) != resources) {
> +               ret = -EBUSY;
> +               goto unlock;
> +       }
> +
> +       pipe->chained = true;
> +       chained_pipe->chained_res |= resources;
> +       chained_pipe->available_res &= ~resources;
> +
> +       __mxc_isi_channel_get(chained_pipe);
> +
> +unlock:
> +       mutex_unlock(&chained_pipe->lock);
> +
> +       return ret;
> +}
> +
> +void mxc_isi_channel_unchain(struct mxc_isi_pipe *pipe)
> +{
> +       struct mxc_isi_pipe *chained_pipe = pipe + 1;
> +
> +       if (!pipe->chained)
> +               return;
> +
> +       pipe->chained = false;
> +
> +       mutex_lock(&chained_pipe->lock);
> +
> +       chained_pipe->available_res |= chained_pipe->chained_res;
> +       chained_pipe->chained_res = 0;
> +
> +       __mxc_isi_channel_put(chained_pipe);
> +
> +       mutex_unlock(&chained_pipe->lock);
> +}
> diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-m2m.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-m2m.c
> new file mode 100644
> index 000000000000..9745d6219a16
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-m2m.c
> @@ -0,0 +1,858 @@
> +// 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/container_of.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/kernel.h>
> +#include <linux/limits.h>
> +#include <linux/minmax.h>
> +#include <linux/mutex.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/media-entity.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fh.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-core.h"
> +
> +struct mxc_isi_m2m_buffer {
> +       struct v4l2_m2m_buffer buf;
> +       dma_addr_t dma_addrs[3];
> +};
> +
> +struct mxc_isi_m2m_ctx_queue_data {
> +       struct v4l2_pix_format_mplane format;
> +       const struct mxc_isi_format_info *info;
> +       u32 sequence;
> +};
> +
> +struct mxc_isi_m2m_ctx {
> +       struct v4l2_fh fh;
> +       struct mxc_isi_m2m *m2m;
> +
> +       /* Protects the m2m vb2 queues */
> +       struct mutex vb2_lock;
> +
> +       struct {
> +               struct mxc_isi_m2m_ctx_queue_data out;
> +               struct mxc_isi_m2m_ctx_queue_data cap;
> +       } queues;
> +
> +       struct {
> +               struct v4l2_ctrl_handler handler;
> +               unsigned int alpha;
> +               bool hflip;
> +               bool vflip;
> +       } ctrls;
> +
> +       bool chained;
> +};
> +
> +static inline struct mxc_isi_m2m_buffer *
> +to_isi_m2m_buffer(struct vb2_v4l2_buffer *buf)
> +{
> +       return container_of(buf, struct mxc_isi_m2m_buffer, buf.vb);
> +}
> +
> +static inline struct mxc_isi_m2m_ctx *to_isi_m2m_ctx(struct v4l2_fh *fh)
> +{
> +       return container_of(fh, struct mxc_isi_m2m_ctx, fh);
> +}
> +
> +static inline struct mxc_isi_m2m_ctx_queue_data *
> +mxc_isi_m2m_ctx_qdata(struct mxc_isi_m2m_ctx *ctx, enum v4l2_buf_type type)
> +{
> +       if (V4L2_TYPE_IS_OUTPUT(type))
> +               return &ctx->queues.out;
> +       else
> +               return &ctx->queues.cap;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 M2M device operations
> + */
> +
> +static void mxc_isi_m2m_frame_write_done(struct mxc_isi_pipe *pipe, u32 status)
> +{
> +       struct mxc_isi_m2m *m2m = &pipe->isi->m2m;
> +       struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf;
> +       struct mxc_isi_m2m_ctx *ctx;
> +
> +       ctx = v4l2_m2m_get_curr_priv(m2m->m2m_dev);
> +       if (!ctx) {
> +               dev_err(m2m->isi->dev,
> +                       "Instance released before the end of transaction\n");
> +               return;
> +       }
> +
> +       src_vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
> +       dst_vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
> +
> +       v4l2_m2m_buf_copy_metadata(src_vbuf, dst_vbuf, false);
> +
> +       src_vbuf->sequence = ctx->queues.out.sequence++;
> +       dst_vbuf->sequence = ctx->queues.cap.sequence++;
> +
> +       v4l2_m2m_buf_done(src_vbuf, VB2_BUF_STATE_DONE);
> +       v4l2_m2m_buf_done(dst_vbuf, VB2_BUF_STATE_DONE);
> +
> +       v4l2_m2m_job_finish(m2m->m2m_dev, ctx->fh.m2m_ctx);
> +}
> +
> +static void mxc_isi_m2m_device_run(void *priv)
> +{
> +       struct mxc_isi_m2m_ctx *ctx = priv;
> +       struct mxc_isi_m2m *m2m = ctx->m2m;
> +       struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf;
> +       struct mxc_isi_m2m_buffer *src_buf, *dst_buf;
> +
> +       mxc_isi_channel_disable(m2m->pipe);
> +
> +       mutex_lock(&m2m->lock);
> +
> +       /* If the context has changed, reconfigure the channel. */
> +       if (m2m->last_ctx != ctx) {
> +               const struct v4l2_area in_size = {
> +                       .width = ctx->queues.out.format.width,
> +                       .height = ctx->queues.out.format.height,
> +               };
> +               const struct v4l2_area scale = {
> +                       .width = ctx->queues.cap.format.width,
> +                       .height = ctx->queues.cap.format.height,
> +               };
> +               const struct v4l2_rect crop = {
> +                       .width = ctx->queues.cap.format.width,
> +                       .height = ctx->queues.cap.format.height,
> +               };
> +
> +               mxc_isi_channel_config(m2m->pipe, MXC_ISI_INPUT_MEM,
> +                                      &in_size, &scale, &crop,
> +                                      ctx->queues.out.info->encoding,
> +                                      ctx->queues.cap.info->encoding);
> +               mxc_isi_channel_set_input_format(m2m->pipe,
> +                                                ctx->queues.out.info,
> +                                                &ctx->queues.out.format);
> +               mxc_isi_channel_set_output_format(m2m->pipe,
> +                                                 ctx->queues.cap.info,
> +                                                 &ctx->queues.cap.format);
> +
> +               m2m->last_ctx = ctx;
> +       }
> +
> +       mutex_unlock(&m2m->lock);
> +
> +       mutex_lock(ctx->ctrls.handler.lock);
> +       mxc_isi_channel_set_alpha(m2m->pipe, ctx->ctrls.alpha);
> +       mxc_isi_channel_set_flip(m2m->pipe, ctx->ctrls.hflip, ctx->ctrls.vflip);
> +       mutex_unlock(ctx->ctrls.handler.lock);
> +
> +       src_vbuf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
> +       dst_vbuf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
> +
> +       src_buf = to_isi_m2m_buffer(src_vbuf);
> +       dst_buf = to_isi_m2m_buffer(dst_vbuf);
> +
> +       mxc_isi_channel_set_inbuf(m2m->pipe, src_buf->dma_addrs[0]);
> +       mxc_isi_channel_set_outbuf(m2m->pipe, dst_buf->dma_addrs, MXC_ISI_BUF1);
> +       mxc_isi_channel_set_outbuf(m2m->pipe, dst_buf->dma_addrs, MXC_ISI_BUF2);
> +
> +       mxc_isi_channel_enable(m2m->pipe);
> +
> +       mxc_isi_channel_m2m_start(m2m->pipe);
> +}
> +
> +static const struct v4l2_m2m_ops mxc_isi_m2m_ops = {
> +       .device_run = mxc_isi_m2m_device_run,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * videobuf2 queue operations
> + */
> +
> +static int mxc_isi_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_m2m_ctx *ctx = vb2_get_drv_priv(q);
> +       const struct mxc_isi_m2m_ctx_queue_data *qdata =
> +               mxc_isi_m2m_ctx_qdata(ctx, q->type);
> +
> +       return mxc_isi_video_queue_setup(&qdata->format, qdata->info,
> +                                        num_buffers, num_planes, sizes);
> +}
> +
> +static int mxc_isi_m2m_vb2_buffer_init(struct vb2_buffer *vb2)
> +{
> +       struct vb2_queue *vq = vb2->vb2_queue;
> +       struct mxc_isi_m2m_buffer *buf = to_isi_m2m_buffer(to_vb2_v4l2_buffer(vb2));
> +       struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vb2->vb2_queue);
> +       const struct mxc_isi_m2m_ctx_queue_data *qdata =
> +               mxc_isi_m2m_ctx_qdata(ctx, vq->type);
> +
> +       mxc_isi_video_buffer_init(vb2, buf->dma_addrs, qdata->info,
> +                                 &qdata->format);
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_m2m_vb2_buffer_prepare(struct vb2_buffer *vb2)
> +{
> +       struct vb2_queue *vq = vb2->vb2_queue;
> +       struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vq);
> +       const struct mxc_isi_m2m_ctx_queue_data *qdata =
> +               mxc_isi_m2m_ctx_qdata(ctx, vq->type);
> +
> +       return mxc_isi_video_buffer_prepare(ctx->m2m->isi, vb2, qdata->info,
> +                                           &qdata->format);
> +}
> +
> +static void mxc_isi_m2m_vb2_buffer_queue(struct vb2_buffer *vb2)
> +{
> +       struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2);
> +       struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vb2->vb2_queue);
> +
> +       v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
> +}
> +
> +static int mxc_isi_m2m_vb2_start_streaming(struct vb2_queue *q,
> +                                          unsigned int count)
> +{
> +       struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q);
> +       struct mxc_isi_m2m_ctx_queue_data *qdata =
> +               mxc_isi_m2m_ctx_qdata(ctx, q->type);
> +
> +       qdata->sequence = 0;
> +
> +       return 0;
> +}
> +
> +static void mxc_isi_m2m_vb2_stop_streaming(struct vb2_queue *q)
> +{
> +       struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q);
> +       struct vb2_v4l2_buffer *vbuf;
> +
> +       for (;;) {
> +               if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> +                       vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
> +               else
> +                       vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
> +               if (!vbuf)
> +                       break;
> +
> +               v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
> +       }
> +}
> +
> +static const struct vb2_ops mxc_isi_m2m_vb2_qops = {
> +       .queue_setup            = mxc_isi_m2m_vb2_queue_setup,
> +       .buf_init               = mxc_isi_m2m_vb2_buffer_init,
> +       .buf_prepare            = mxc_isi_m2m_vb2_buffer_prepare,
> +       .buf_queue              = mxc_isi_m2m_vb2_buffer_queue,
> +       .wait_prepare           = vb2_ops_wait_prepare,
> +       .wait_finish            = vb2_ops_wait_finish,
> +       .start_streaming        = mxc_isi_m2m_vb2_start_streaming,
> +       .stop_streaming         = mxc_isi_m2m_vb2_stop_streaming,
> +};
> +
> +static int mxc_isi_m2m_queue_init(void *priv, struct vb2_queue *src_vq,
> +                                 struct vb2_queue *dst_vq)
> +{
> +       struct mxc_isi_m2m_ctx *ctx = priv;
> +       struct mxc_isi_m2m *m2m = ctx->m2m;
> +       int ret;
> +
> +       src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> +       src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
> +       src_vq->drv_priv = ctx;
> +       src_vq->buf_struct_size = sizeof(struct mxc_isi_m2m_buffer);
> +       src_vq->ops = &mxc_isi_m2m_vb2_qops;
> +       src_vq->mem_ops = &vb2_dma_contig_memops;
> +       src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +       src_vq->lock = &ctx->vb2_lock;
> +       src_vq->dev = m2m->isi->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_DMABUF;
> +       dst_vq->drv_priv = ctx;
> +       dst_vq->buf_struct_size = sizeof(struct mxc_isi_m2m_buffer);
> +       dst_vq->ops = &mxc_isi_m2m_vb2_qops;
> +       dst_vq->mem_ops = &vb2_dma_contig_memops;
> +       dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +       dst_vq->lock = &ctx->vb2_lock;
> +       dst_vq->dev = m2m->isi->dev;
> +
> +       return vb2_queue_init(dst_vq);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 controls
> + */
> +
> +static inline struct mxc_isi_m2m_ctx *
> +ctrl_to_mxc_isi_m2m_ctx(struct v4l2_ctrl *ctrl)
> +{
> +       return container_of(ctrl->handler, struct mxc_isi_m2m_ctx, ctrls.handler);
> +}
> +
> +static int mxc_isi_m2m_ctx_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +       struct mxc_isi_m2m_ctx *ctx = ctrl_to_mxc_isi_m2m_ctx(ctrl);
> +
> +       switch (ctrl->id) {
> +       case V4L2_CID_HFLIP:
> +               ctx->ctrls.hflip = ctrl->val;
> +               break;
> +
> +       case V4L2_CID_VFLIP:
> +               ctx->ctrls.vflip = ctrl->val;
> +               break;
> +
> +       case V4L2_CID_ALPHA_COMPONENT:
> +               ctx->ctrls.alpha = ctrl->val;
> +               break;
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct v4l2_ctrl_ops mxc_isi_m2m_ctx_ctrl_ops = {
> +       .s_ctrl = mxc_isi_m2m_ctx_s_ctrl,
> +};
> +
> +static int mxc_isi_m2m_ctx_ctrls_create(struct mxc_isi_m2m_ctx *ctx)
> +{
> +       struct v4l2_ctrl_handler *handler = &ctx->ctrls.handler;
> +       int ret;
> +
> +       v4l2_ctrl_handler_init(handler, 3);
> +
> +       v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops,
> +                         V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0);
> +       v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops,
> +                         V4L2_CID_HFLIP, 0, 1, 1, 0);
> +       v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops,
> +                         V4L2_CID_VFLIP, 0, 1, 1, 0);
> +
> +       if (handler->error) {
> +               ret = handler->error;
> +               v4l2_ctrl_handler_free(handler);
> +               return ret;
> +       }
> +
> +       ctx->fh.ctrl_handler = handler;
> +
> +       return 0;
> +}
> +
> +static void mxc_isi_m2m_ctx_ctrls_delete(struct mxc_isi_m2m_ctx *ctx)
> +{
> +       v4l2_ctrl_handler_free(&ctx->ctrls.handler);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 ioctls
> + */
> +
> +static int mxc_isi_m2m_querycap(struct file *file, void *fh,
> +                               struct v4l2_capability *cap)
> +{
> +       strscpy(cap->driver, MXC_ISI_DRIVER_NAME, sizeof(cap->driver));
> +       strscpy(cap->card, MXC_ISI_M2M, sizeof(cap->card));
> +       cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
> +       cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_m2m_enum_fmt_vid(struct file *file, void *fh,
> +                                   struct v4l2_fmtdesc *f)
> +{
> +       const enum mxc_isi_video_type type =
> +               f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ?
> +               MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP;
> +       const struct mxc_isi_format_info *info;
> +
> +       info = mxc_isi_format_enum(f->index, type);
> +       if (!info)
> +               return -EINVAL;
> +
> +       f->pixelformat = info->fourcc;
> +       f->flags |= V4L2_FMT_FLAG_CSC_COLORSPACE | V4L2_FMT_FLAG_CSC_YCBCR_ENC
> +                |  V4L2_FMT_FLAG_CSC_QUANTIZATION | V4L2_FMT_FLAG_CSC_XFER_FUNC;
> +
> +       return 0;
> +}
> +
> +static const struct mxc_isi_format_info *
> +__mxc_isi_m2m_try_fmt_vid(struct mxc_isi_m2m_ctx *ctx,
> +                         struct v4l2_pix_format_mplane *pix,
> +                         const enum mxc_isi_video_type type)
> +{
> +       if (type == MXC_ISI_VIDEO_M2M_CAP) {
> +               /* Downscaling only  */
> +               pix->width = min(pix->width, ctx->queues.out.format.width);
> +               pix->height = min(pix->height, ctx->queues.out.format.height);
> +       }
> +
> +       return mxc_isi_format_try(ctx->m2m->pipe, pix, type);
> +}
> +
> +static int mxc_isi_m2m_try_fmt_vid(struct file *file, void *fh,
> +                                  struct v4l2_format *f)
> +{
> +       const enum mxc_isi_video_type type =
> +               f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ?
> +               MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP;
> +       struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
> +
> +       __mxc_isi_m2m_try_fmt_vid(ctx, &f->fmt.pix_mp, type);
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_m2m_g_fmt_vid(struct file *file, void *fh,
> +                                struct v4l2_format *f)
> +{
> +       struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
> +       const struct mxc_isi_m2m_ctx_queue_data *qdata =
> +               mxc_isi_m2m_ctx_qdata(ctx, f->type);
> +
> +       f->fmt.pix_mp = qdata->format;
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_m2m_s_fmt_vid(struct file *file, void *fh,
> +                                struct v4l2_format *f)
> +{
> +       const enum mxc_isi_video_type type =
> +               f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ?
> +               MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP;
> +       struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
> +       struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
> +       const struct mxc_isi_format_info *info;
> +       struct vb2_queue *vq;
> +
> +       vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
> +       if (!vq)
> +               return -EINVAL;
> +
> +       if (vb2_is_busy(vq))
> +               return -EBUSY;
> +
> +       info = __mxc_isi_m2m_try_fmt_vid(ctx, pix, type);
> +
> +       if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> +               ctx->queues.out.format = *pix;
> +               ctx->queues.out.info = info;
> +       }
> +
> +       /*
> +        * Always set the format on the capture side, due to either format
> +        * propagation or direct setting.
> +        */
> +       ctx->queues.cap.format = *pix;
> +       ctx->queues.cap.info = info;
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_m2m_streamon(struct file *file, void *fh,
> +                               enum v4l2_buf_type type)
> +{
> +       struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
> +       const struct v4l2_pix_format_mplane *out_pix = &ctx->queues.out.format;
> +       const struct v4l2_pix_format_mplane *cap_pix = &ctx->queues.cap.format;
> +       const struct mxc_isi_format_info *cap_info = ctx->queues.cap.info;
> +       const struct mxc_isi_format_info *out_info = ctx->queues.out.info;
> +       struct mxc_isi_m2m *m2m = ctx->m2m;
> +       bool bypass;
> +
> +       int ret;
> +
> +       mutex_lock(&m2m->lock);
> +
> +       if (m2m->usage_count == INT_MAX) {
> +               ret = -EOVERFLOW;
> +               goto unlock;
> +       }
> +
> +       bypass = cap_pix->width == out_pix->width &&
> +                cap_pix->height == out_pix->height &&
> +                cap_info->encoding == out_info->encoding;
> +
> +       /*
> +        * Acquire the pipe and initialize the channel with the first user of
> +        * the M2M device.
> +        */
> +       if (m2m->usage_count == 0) {
> +               ret = mxc_isi_channel_acquire(m2m->pipe,
> +                                             &mxc_isi_m2m_frame_write_done,
> +                                             bypass);
> +               if (ret)
> +                       goto unlock;
> +
> +               mxc_isi_channel_get(m2m->pipe);
> +       }
> +
> +       m2m->usage_count++;
> +
> +       /*
> +        * Allocate resources for the channel, counting how many users require
> +        * buffer chaining.
> +        */
> +       if (!ctx->chained && out_pix->width > MXC_ISI_MAX_WIDTH_UNCHAINED) {
> +               ret = mxc_isi_channel_chain(m2m->pipe, bypass);
> +               if (ret)
> +                       goto deinit;
> +
> +               m2m->chained_count++;
> +               ctx->chained = true;
> +       }
> +
> +       /*
> +        * Drop the lock to start the stream, as the .device_run() operation
> +        * needs to acquire it.
> +        */
> +       mutex_unlock(&m2m->lock);
> +       ret = v4l2_m2m_ioctl_streamon(file, fh, type);
> +       if (ret) {
> +               /* Reacquire the lock for the cleanup path. */
> +               mutex_lock(&m2m->lock);
> +               goto unchain;
> +       }
> +
> +       return 0;
> +
> +unchain:
> +       if (ctx->chained && --m2m->chained_count == 0)
> +               mxc_isi_channel_unchain(m2m->pipe);
> +       ctx->chained = false;
> +
> +deinit:
> +       if (--m2m->usage_count == 0) {
> +               mxc_isi_channel_put(m2m->pipe);
> +               mxc_isi_channel_release(m2m->pipe);
> +       }
> +
> +unlock:
> +       mutex_unlock(&m2m->lock);
> +       return ret;
> +}
> +
> +static int mxc_isi_m2m_streamoff(struct file *file, void *fh,
> +                                enum v4l2_buf_type type)
> +{
> +       struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
> +       struct mxc_isi_m2m *m2m = ctx->m2m;
> +
> +       v4l2_m2m_ioctl_streamoff(file, fh, type);
> +
> +       mutex_lock(&m2m->lock);
> +
> +       /*
> +        * If the last context is this one, reset it to make sure the device
> +        * will be reconfigured when streaming is restarted.
> +        */
> +       if (m2m->last_ctx == ctx)
> +               m2m->last_ctx = NULL;
> +
> +       /* Free the channel resources if this is the last chained context. */
> +       if (ctx->chained && --m2m->chained_count == 0)
> +               mxc_isi_channel_unchain(m2m->pipe);
> +       ctx->chained = false;
> +
> +       /* Turn off the light with the last user. */
> +       if (--m2m->usage_count == 0) {
> +               mxc_isi_channel_disable(m2m->pipe);
> +               mxc_isi_channel_put(m2m->pipe);
> +               mxc_isi_channel_release(m2m->pipe);
> +       }
> +
> +       WARN_ON(m2m->usage_count < 0);
> +
> +       mutex_unlock(&m2m->lock);
> +
> +       return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops mxc_isi_m2m_ioctl_ops = {
> +       .vidioc_querycap                = mxc_isi_m2m_querycap,
> +
> +       .vidioc_enum_fmt_vid_cap        = mxc_isi_m2m_enum_fmt_vid,
> +       .vidioc_enum_fmt_vid_out        = mxc_isi_m2m_enum_fmt_vid,
> +       .vidioc_g_fmt_vid_cap_mplane    = mxc_isi_m2m_g_fmt_vid,
> +       .vidioc_g_fmt_vid_out_mplane    = mxc_isi_m2m_g_fmt_vid,
> +       .vidioc_s_fmt_vid_cap_mplane    = mxc_isi_m2m_s_fmt_vid,
> +       .vidioc_s_fmt_vid_out_mplane    = mxc_isi_m2m_s_fmt_vid,
> +       .vidioc_try_fmt_vid_cap_mplane  = mxc_isi_m2m_try_fmt_vid,
> +       .vidioc_try_fmt_vid_out_mplane  = mxc_isi_m2m_try_fmt_vid,
> +
> +       .vidioc_reqbufs                 = v4l2_m2m_ioctl_reqbufs,
> +       .vidioc_querybuf                = v4l2_m2m_ioctl_querybuf,
> +       .vidioc_qbuf                    = v4l2_m2m_ioctl_qbuf,
> +       .vidioc_dqbuf                   = v4l2_m2m_ioctl_dqbuf,
> +       .vidioc_expbuf                  = v4l2_m2m_ioctl_expbuf,
> +       .vidioc_prepare_buf             = v4l2_m2m_ioctl_prepare_buf,
> +       .vidioc_create_bufs             = v4l2_m2m_ioctl_create_bufs,
> +
> +       .vidioc_streamon                = mxc_isi_m2m_streamon,
> +       .vidioc_streamoff               = mxc_isi_m2m_streamoff,
> +
> +       .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> +       .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Video device file operations
> + */
> +
> +static void mxc_isi_m2m_init_format(struct mxc_isi_m2m_ctx *ctx,
> +                                   struct mxc_isi_m2m_ctx_queue_data *qdata,
> +                                   enum mxc_isi_video_type type)
> +{
> +       qdata->format.width = MXC_ISI_DEF_WIDTH;
> +       qdata->format.height = MXC_ISI_DEF_HEIGHT;
> +       qdata->format.pixelformat = MXC_ISI_DEF_PIXEL_FORMAT;
> +
> +       qdata->info = mxc_isi_format_try(ctx->m2m->pipe, &qdata->format, type);
> +}
> +
> +static int mxc_isi_m2m_open(struct file *file)
> +{
> +       struct video_device *vdev = video_devdata(file);
> +       struct mxc_isi_m2m *m2m = video_drvdata(file);
> +       struct mxc_isi_m2m_ctx *ctx;
> +       int ret;
> +
> +       ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +       if (!ctx)
> +               return -ENOMEM;
> +
> +       ctx->m2m = m2m;
> +       mutex_init(&ctx->vb2_lock);
> +
> +       v4l2_fh_init(&ctx->fh, vdev);
> +       file->private_data = &ctx->fh;
> +
> +       ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(m2m->m2m_dev, ctx,
> +                                           &mxc_isi_m2m_queue_init);
> +       if (IS_ERR(ctx->fh.m2m_ctx)) {
> +               ret = PTR_ERR(ctx->fh.m2m_ctx);
> +               ctx->fh.m2m_ctx = NULL;
> +               goto err_fh;
> +       }
> +
> +       mxc_isi_m2m_init_format(ctx, &ctx->queues.out, MXC_ISI_VIDEO_M2M_OUT);
> +       mxc_isi_m2m_init_format(ctx, &ctx->queues.cap, MXC_ISI_VIDEO_M2M_CAP);
> +
> +       ret = mxc_isi_m2m_ctx_ctrls_create(ctx);
> +       if (ret)
> +               goto err_ctx;
> +
> +       ret = pm_runtime_resume_and_get(m2m->isi->dev);
> +       if (ret)
> +               goto err_ctrls;
> +
> +       v4l2_fh_add(&ctx->fh);
> +
> +       return 0;
> +
> +err_ctrls:
> +       mxc_isi_m2m_ctx_ctrls_delete(ctx);
> +err_ctx:
> +       v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
> +err_fh:
> +       v4l2_fh_exit(&ctx->fh);
> +       mutex_destroy(&ctx->vb2_lock);
> +       kfree(ctx);
> +       return ret;
> +}
> +
> +static int mxc_isi_m2m_release(struct file *file)
> +{
> +       struct mxc_isi_m2m *m2m = video_drvdata(file);
> +       struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(file->private_data);
> +
> +       v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
> +       mxc_isi_m2m_ctx_ctrls_delete(ctx);
> +
> +       v4l2_fh_del(&ctx->fh);
> +       v4l2_fh_exit(&ctx->fh);
> +
> +       mutex_destroy(&ctx->vb2_lock);
> +       kfree(ctx);
> +
> +       pm_runtime_put(m2m->isi->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,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Registration
> + */
> +
> +int mxc_isi_m2m_register(struct mxc_isi_dev *isi, struct v4l2_device *v4l2_dev)
> +{
> +       struct mxc_isi_m2m *m2m = &isi->m2m;
> +       struct video_device *vdev = &m2m->vdev;
> +       struct media_link *link;
> +       int ret;
> +
> +       m2m->isi = isi;
> +       m2m->pipe = &isi->pipes[0];
> +
> +       mutex_init(&m2m->lock);
> +
> +       /* Initialize the video device and create controls. */
> +       snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.m2m");
> +
> +       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;
> +       video_set_drvdata(vdev, m2m);
> +
> +       /* Create the M2M device. */
> +       m2m->m2m_dev = v4l2_m2m_init(&mxc_isi_m2m_ops);
> +       if (IS_ERR(m2m->m2m_dev)) {
> +               dev_err(isi->dev, "failed to initialize m2m device\n");
> +               ret = PTR_ERR(m2m->m2m_dev);
> +               goto err_mutex;
> +       }
> +
> +       /* Register the video device. */
> +       ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +       if (ret < 0) {
> +               dev_err(isi->dev, "failed to register m2m device\n");
> +               goto err_m2m;
> +       }
> +
> +       /*
> +        * Populate the media graph. We can't use the mem2mem helper
> +        * v4l2_m2m_register_media_controller() as the M2M interface needs to
> +        * be connected to the existing entities in the graph, so we have to
> +        * wire things up manually:
> +        *
> +        * - The entity in the video_device, which isn't touched by the V4L2
> +        *   core for M2M devices, is used as the source I/O entity in the
> +        *   graph, connected to the crossbar switch.
> +        *
> +        * - The video device at the end of the pipeline provides the sink
> +        *   entity, and is already wired up in the graph.
> +        *
> +        * - A new interface is created, pointing at both entities. The sink
> +        *   entity will thus have two interfaces pointing to it.
> +        */
> +       m2m->pad.flags = MEDIA_PAD_FL_SOURCE;
> +       vdev->entity.name = "mxc_isi.output";
> +       vdev->entity.function = MEDIA_ENT_F_IO_V4L;
> +       ret = media_entity_pads_init(&vdev->entity, 1, &m2m->pad);
> +       if (ret)
> +               goto err_video;
> +
> +       ret = media_device_register_entity(v4l2_dev->mdev, &vdev->entity);
> +       if (ret)
> +               goto err_entity_cleanup;
> +
> +       ret = media_create_pad_link(&vdev->entity, 0,
> +                                   &m2m->isi->crossbar.sd.entity,
> +                                   m2m->isi->crossbar.num_sinks - 1,
> +                                   MEDIA_LNK_FL_IMMUTABLE |
> +                                   MEDIA_LNK_FL_ENABLED);
> +       if (ret)
> +               goto err_entity_unreg;
> +
> +       m2m->intf = media_devnode_create(v4l2_dev->mdev, MEDIA_INTF_T_V4L_VIDEO,
> +                                        0, VIDEO_MAJOR, vdev->minor);
> +       if (!m2m->intf) {
> +               ret = -ENOMEM;
> +               goto err_entity_unreg;
> +       }
> +
> +       link = media_create_intf_link(&vdev->entity, &m2m->intf->intf,
> +                                     MEDIA_LNK_FL_IMMUTABLE |
> +                                     MEDIA_LNK_FL_ENABLED);
> +       if (!link) {
> +               ret = -ENOMEM;
> +               goto err_devnode;
> +       }
> +
> +       link = media_create_intf_link(&m2m->pipe->video.vdev.entity,
> +                                     &m2m->intf->intf,
> +                                     MEDIA_LNK_FL_IMMUTABLE |
> +                                     MEDIA_LNK_FL_ENABLED);
> +       if (!link) {
> +               ret = -ENOMEM;
> +               goto err_devnode;
> +       }
> +
> +       return 0;
> +
> +err_devnode:
> +       media_devnode_remove(m2m->intf);
> +err_entity_unreg:
> +       media_device_unregister_entity(&vdev->entity);
> +err_entity_cleanup:
> +       media_entity_cleanup(&vdev->entity);
> +err_video:
> +       video_unregister_device(vdev);
> +err_m2m:
> +       v4l2_m2m_release(m2m->m2m_dev);
> +err_mutex:
> +       mutex_destroy(&m2m->lock);
> +       return ret;
> +}
> +
> +int mxc_isi_m2m_unregister(struct mxc_isi_dev *isi)
> +{
> +       struct mxc_isi_m2m *m2m = &isi->m2m;
> +       struct video_device *vdev = &m2m->vdev;
> +
> +       video_unregister_device(vdev);
> +
> +       v4l2_m2m_release(m2m->m2m_dev);
> +       media_devnode_remove(m2m->intf);
> +       media_entity_cleanup(&vdev->entity);
> +       mutex_destroy(&m2m->lock);
> +
> +       return 0;
> +}
> diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c
> new file mode 100644
> index 000000000000..c4454aa1cb34
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c
> @@ -0,0 +1,867 @@
> +// 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/device.h>
> +#include <linux/errno.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/minmax.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/media-entity.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#include "imx8-isi-core.h"
> +#include "imx8-isi-regs.h"
> +
> +/*
> + * While the ISI receives data from the gasket on a 3x12-bit bus, the pipeline
> + * subdev conceptually includes the gasket in order to avoid exposing an extra
> + * subdev between the CSIS and the ISI. We thus need to expose media bus codes
> + * corresponding to the CSIS output, which is narrower.
> + */
> +static const struct mxc_isi_bus_format_info mxc_isi_bus_formats[] = {
> +       /* YUV formats */
> +       {
> +               .mbus_code      = MEDIA_BUS_FMT_UYVY8_1X16,
> +               .output         = MEDIA_BUS_FMT_YUV8_1X24,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK),
> +               .encoding       = MXC_ISI_ENC_YUV,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_YUV8_1X24,
> +               .output         = MEDIA_BUS_FMT_YUV8_1X24,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_YUV,
> +       },
> +       /* RGB formats */
> +       {
> +               .mbus_code      = MEDIA_BUS_FMT_RGB565_1X16,
> +               .output         = MEDIA_BUS_FMT_RGB888_1X24,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK),
> +               .encoding       = MXC_ISI_ENC_RGB,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_RGB888_1X24,
> +               .output         = MEDIA_BUS_FMT_RGB888_1X24,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RGB,
> +       },
> +       /* RAW formats */
> +       {
> +               .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
> +               .output         = MEDIA_BUS_FMT_Y8_1X8,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_Y10_1X10,
> +               .output         = MEDIA_BUS_FMT_Y10_1X10,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_Y12_1X12,
> +               .output         = MEDIA_BUS_FMT_Y12_1X12,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_Y14_1X14,
> +               .output         = MEDIA_BUS_FMT_Y14_1X14,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SBGGR8_1X8,
> +               .output         = MEDIA_BUS_FMT_SBGGR8_1X8,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGBRG8_1X8,
> +               .output         = MEDIA_BUS_FMT_SGBRG8_1X8,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGRBG8_1X8,
> +               .output         = MEDIA_BUS_FMT_SGRBG8_1X8,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SRGGB8_1X8,
> +               .output         = MEDIA_BUS_FMT_SRGGB8_1X8,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SBGGR10_1X10,
> +               .output         = MEDIA_BUS_FMT_SBGGR10_1X10,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGBRG10_1X10,
> +               .output         = MEDIA_BUS_FMT_SGBRG10_1X10,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGRBG10_1X10,
> +               .output         = MEDIA_BUS_FMT_SGRBG10_1X10,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SRGGB10_1X10,
> +               .output         = MEDIA_BUS_FMT_SRGGB10_1X10,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SBGGR12_1X12,
> +               .output         = MEDIA_BUS_FMT_SBGGR12_1X12,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGBRG12_1X12,
> +               .output         = MEDIA_BUS_FMT_SGBRG12_1X12,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGRBG12_1X12,
> +               .output         = MEDIA_BUS_FMT_SGRBG12_1X12,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SRGGB12_1X12,
> +               .output         = MEDIA_BUS_FMT_SRGGB12_1X12,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SBGGR14_1X14,
> +               .output         = MEDIA_BUS_FMT_SBGGR14_1X14,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGBRG14_1X14,
> +               .output         = MEDIA_BUS_FMT_SGBRG14_1X14,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGRBG14_1X14,
> +               .output         = MEDIA_BUS_FMT_SGRBG14_1X14,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SRGGB14_1X14,
> +               .output         = MEDIA_BUS_FMT_SRGGB14_1X14,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       },
> +       /* JPEG */
> +       {
> +               .mbus_code      = MEDIA_BUS_FMT_JPEG_1X8,
> +               .output         = MEDIA_BUS_FMT_JPEG_1X8,
> +               .pads           = BIT(MXC_ISI_PIPE_PAD_SINK)
> +                               | BIT(MXC_ISI_PIPE_PAD_SOURCE),
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }
> +};
> +
> +const struct mxc_isi_bus_format_info *
> +mxc_isi_bus_format_by_code(u32 code, unsigned int pad)
> +{
> +       unsigned int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); i++) {
> +               const struct mxc_isi_bus_format_info *info =
> +                       &mxc_isi_bus_formats[i];
> +
> +               if (info->mbus_code == code && info->pads & BIT(pad))
> +                       return info;
> +       }
> +
> +       return NULL;
> +}
> +
> +const struct mxc_isi_bus_format_info *
> +mxc_isi_bus_format_by_index(unsigned int index, unsigned int pad)
> +{
> +       unsigned int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); i++) {
> +               const struct mxc_isi_bus_format_info *info =
> +                       &mxc_isi_bus_formats[i];
> +
> +               if (!(info->pads & BIT(pad)))
> +                       continue;
> +
> +               if (!index)
> +                       return info;
> +
> +               index--;
> +       }
> +
> +       return NULL;
> +}
> +
> +static inline struct mxc_isi_pipe *to_isi_pipe(struct v4l2_subdev *sd)
> +{
> +       return container_of(sd, struct mxc_isi_pipe, sd);
> +}
> +
> +int mxc_isi_pipe_enable(struct mxc_isi_pipe *pipe)
> +{
> +       struct mxc_isi_crossbar *xbar = &pipe->isi->crossbar;
> +       const struct mxc_isi_bus_format_info *sink_info;
> +       const struct mxc_isi_bus_format_info *src_info;
> +       const struct v4l2_mbus_framefmt *sink_fmt;
> +       const struct v4l2_mbus_framefmt *src_fmt;
> +       const struct v4l2_rect *compose;
> +       struct v4l2_subdev_state *state;
> +       struct v4l2_subdev *sd = &pipe->sd;
> +       struct v4l2_area in_size, scale;
> +       struct v4l2_rect crop;
> +       u32 input;
> +       int ret;
> +
> +       /*
> +        * Find the connected input by inspecting the crossbar switch routing
> +        * table.
> +        */
> +       state = v4l2_subdev_lock_and_get_active_state(&xbar->sd);
> +       ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
> +                                                   xbar->num_sinks + pipe->id,
> +                                                   0, &input, NULL);
> +       v4l2_subdev_unlock_state(state);
> +
> +       if (ret)
> +               return -EPIPE;
> +
> +       /* Configure the pipeline. */
> +       state = v4l2_subdev_lock_and_get_active_state(sd);
> +
> +       sink_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SINK);
> +       src_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SOURCE);
> +       compose = v4l2_subdev_get_try_compose(sd, state, MXC_ISI_PIPE_PAD_SINK);
> +       crop = *v4l2_subdev_get_try_crop(sd, state, MXC_ISI_PIPE_PAD_SOURCE);
> +
> +       sink_info = mxc_isi_bus_format_by_code(sink_fmt->code,
> +                                              MXC_ISI_PIPE_PAD_SINK);
> +       src_info = mxc_isi_bus_format_by_code(src_fmt->code,
> +                                             MXC_ISI_PIPE_PAD_SOURCE);
> +
> +       in_size.width = sink_fmt->width;
> +       in_size.height = sink_fmt->height;
> +       scale.width = compose->width;
> +       scale.height = compose->height;
> +
> +       v4l2_subdev_unlock_state(state);
> +
> +       /* Configure the ISI channel. */
> +       mxc_isi_channel_config(pipe, input, &in_size, &scale, &crop,
> +                              sink_info->encoding, src_info->encoding);
> +
> +       mxc_isi_channel_enable(pipe);
> +
> +       /* Enable streams on the crossbar switch. */
> +       ret = v4l2_subdev_enable_streams(&xbar->sd, xbar->num_sinks + pipe->id,
> +                                        BIT(0));
> +       if (ret) {
> +               mxc_isi_channel_disable(pipe);
> +               dev_err(pipe->isi->dev, "Failed to enable pipe %u\n",
> +                       pipe->id);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +void mxc_isi_pipe_disable(struct mxc_isi_pipe *pipe)
> +{
> +       struct mxc_isi_crossbar *xbar = &pipe->isi->crossbar;
> +       int ret;
> +
> +       ret = v4l2_subdev_disable_streams(&xbar->sd, xbar->num_sinks + pipe->id,
> +                                         BIT(0));
> +       if (ret)
> +               dev_err(pipe->isi->dev, "Failed to disable pipe %u\n",
> +                       pipe->id);
> +
> +       mxc_isi_channel_disable(pipe);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 subdev operations
> + */
> +
> +static struct v4l2_mbus_framefmt *
> +mxc_isi_pipe_get_pad_format(struct mxc_isi_pipe *pipe,
> +                           struct v4l2_subdev_state *state,
> +                           unsigned int pad)
> +{
> +       return v4l2_subdev_get_try_format(&pipe->sd, state, pad);
> +}
> +
> +static struct v4l2_rect *
> +mxc_isi_pipe_get_pad_crop(struct mxc_isi_pipe *pipe,
> +                         struct v4l2_subdev_state *state,
> +                         unsigned int pad)
> +{
> +       return v4l2_subdev_get_try_crop(&pipe->sd, state, pad);
> +}
> +
> +static struct v4l2_rect *
> +mxc_isi_pipe_get_pad_compose(struct mxc_isi_pipe *pipe,
> +                            struct v4l2_subdev_state *state,
> +                            unsigned int pad)
> +{
> +       return v4l2_subdev_get_try_compose(&pipe->sd, state, pad);
> +}
> +
> +static int mxc_isi_pipe_init_cfg(struct v4l2_subdev *sd,
> +                                struct v4l2_subdev_state *state)
> +{
> +       struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
> +       struct v4l2_mbus_framefmt *fmt_source;
> +       struct v4l2_mbus_framefmt *fmt_sink;
> +       struct v4l2_rect *compose;
> +       struct v4l2_rect *crop;
> +
> +       fmt_sink = mxc_isi_pipe_get_pad_format(pipe, state,
> +                                              MXC_ISI_PIPE_PAD_SINK);
> +       fmt_source = mxc_isi_pipe_get_pad_format(pipe, state,
> +                                                MXC_ISI_PIPE_PAD_SOURCE);
> +
> +       fmt_sink->width = MXC_ISI_DEF_WIDTH;
> +       fmt_sink->height = MXC_ISI_DEF_HEIGHT;
> +       fmt_sink->code = MXC_ISI_DEF_MBUS_CODE_SINK;
> +       fmt_sink->field = V4L2_FIELD_NONE;
> +       fmt_sink->colorspace = V4L2_COLORSPACE_JPEG;
> +       fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace);
> +       fmt_sink->quantization =
> +               V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace,
> +                                             fmt_sink->ycbcr_enc);
> +       fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace);
> +
> +       *fmt_source = *fmt_sink;
> +       fmt_source->code = MXC_ISI_DEF_MBUS_CODE_SOURCE;
> +
> +       compose = mxc_isi_pipe_get_pad_compose(pipe, state,
> +                                              MXC_ISI_PIPE_PAD_SINK);
> +       crop = mxc_isi_pipe_get_pad_crop(pipe, state, MXC_ISI_PIPE_PAD_SOURCE);
> +
> +       compose->left = 0;
> +       compose->top = 0;
> +       compose->width = MXC_ISI_DEF_WIDTH;
> +       compose->height = MXC_ISI_DEF_HEIGHT;
> +
> +       *crop = *compose;
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_pipe_enum_mbus_code(struct v4l2_subdev *sd,
> +                                      struct v4l2_subdev_state *state,
> +                                      struct v4l2_subdev_mbus_code_enum *code)
> +{
> +       static const u32 output_codes[] = {
> +               MEDIA_BUS_FMT_YUV8_1X24,
> +               MEDIA_BUS_FMT_RGB888_1X24,
> +       };
> +       struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
> +       const struct mxc_isi_bus_format_info *info;
> +       unsigned int index;
> +       unsigned int i;
> +
> +       if (code->pad == MXC_ISI_PIPE_PAD_SOURCE) {
> +               const struct v4l2_mbus_framefmt *format;
> +
> +               format = mxc_isi_pipe_get_pad_format(pipe, state,
> +                                                    MXC_ISI_PIPE_PAD_SINK);
> +               info = mxc_isi_bus_format_by_code(format->code,
> +                                                 MXC_ISI_PIPE_PAD_SINK);
> +
> +               if (info->encoding == MXC_ISI_ENC_RAW) {
> +                       /*
> +                        * For RAW formats, the sink and source media bus codes
> +                        * must match.
> +                        */
> +                       if (code->index)
> +                               return -EINVAL;
> +
> +                       code->code = info->output;
> +               } else {
> +                       /*
> +                        * For RGB or YUV formats, the ISI supports format
> +                        * conversion. Either of the two output formats can be
> +                        * used regardless of the input.
> +                        */
> +                       if (code->index > 1)
> +                               return -EINVAL;
> +
> +                       code->code = output_codes[code->index];
> +               }
> +
> +               return 0;
> +       }
> +
> +       index = code->index;
> +
> +       for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); ++i) {
> +               info = &mxc_isi_bus_formats[i];
> +
> +               if (!(info->pads & BIT(MXC_ISI_PIPE_PAD_SINK)))
> +                       continue;
> +
> +               if (index == 0) {
> +                       code->code = info->mbus_code;
> +                       return 0;
> +               }
> +
> +               index--;
> +       }
> +
> +       return -EINVAL;
> +}
> +
> +static int mxc_isi_pipe_set_fmt(struct v4l2_subdev *sd,
> +                               struct v4l2_subdev_state *state,
> +                               struct v4l2_subdev_format *fmt)
> +{
> +       struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
> +       struct v4l2_mbus_framefmt *mf = &fmt->format;
> +       const struct mxc_isi_bus_format_info *info;
> +       struct v4l2_mbus_framefmt *format;
> +       struct v4l2_rect *rect;
> +
> +       if (vb2_is_busy(&pipe->video.vb2_q))
> +               return -EBUSY;
> +
> +       if (fmt->pad == MXC_ISI_PIPE_PAD_SINK) {
> +               unsigned int max_width;
> +
> +               info = mxc_isi_bus_format_by_code(mf->code,
> +                                                 MXC_ISI_PIPE_PAD_SINK);
> +               if (!info)
> +                       info = mxc_isi_bus_format_by_code(MXC_ISI_DEF_MBUS_CODE_SINK,
> +                                                         MXC_ISI_PIPE_PAD_SINK);
> +
> +               /*
> +                * Limit the max line length if there's no adjacent pipe to
> +                * chain with.
> +                */
> +               max_width = pipe->id == pipe->isi->pdata->num_channels - 1
> +                         ? MXC_ISI_MAX_WIDTH_UNCHAINED
> +                         : MXC_ISI_MAX_WIDTH_CHAINED;
> +
> +               mf->code = info->mbus_code;
> +               mf->width = clamp(mf->width, MXC_ISI_MIN_WIDTH, max_width);
> +               mf->height = clamp(mf->height, MXC_ISI_MIN_HEIGHT,
> +                                  MXC_ISI_MAX_HEIGHT);
> +
> +               /* Propagate the format to the source pad. */
> +               rect = mxc_isi_pipe_get_pad_compose(pipe, state,
> +                                                   MXC_ISI_PIPE_PAD_SINK);
> +               rect->width = mf->width;
> +               rect->height = mf->height;
> +
> +               rect = mxc_isi_pipe_get_pad_crop(pipe, state,
> +                                                MXC_ISI_PIPE_PAD_SOURCE);
> +               rect->left = 0;
> +               rect->top = 0;
> +               rect->width = mf->width;
> +               rect->height = mf->height;
> +
> +               format = mxc_isi_pipe_get_pad_format(pipe, state,
> +                                                    MXC_ISI_PIPE_PAD_SOURCE);
> +               format->code = info->output;
> +               format->width = mf->width;
> +               format->height = mf->height;
> +       } else {
> +               /*
> +                * For RGB or YUV formats, the ISI supports RGB <-> YUV format
> +                * conversion. For RAW formats, the sink and source media bus
> +                * codes must match.
> +                */
> +               format = mxc_isi_pipe_get_pad_format(pipe, state,
> +                                                    MXC_ISI_PIPE_PAD_SINK);
> +               info = mxc_isi_bus_format_by_code(format->code,
> +                                                 MXC_ISI_PIPE_PAD_SINK);
> +
> +               if (info->encoding != MXC_ISI_ENC_RAW) {
> +                       if (mf->code != MEDIA_BUS_FMT_YUV8_1X24 &&
> +                           mf->code != MEDIA_BUS_FMT_RGB888_1X24)
> +                               mf->code = info->output;
> +
> +                       info = mxc_isi_bus_format_by_code(mf->code,
> +                                                         MXC_ISI_PIPE_PAD_SOURCE);
> +               }
> +
> +               mf->code = info->output;
> +
> +               /*
> +                * The width and height on the source can't be changed, they
> +                * must match the crop rectangle size.
> +                */
> +               rect = mxc_isi_pipe_get_pad_crop(pipe, state,
> +                                                MXC_ISI_PIPE_PAD_SOURCE);
> +
> +               mf->width = rect->width;
> +               mf->height = rect->height;
> +       }
> +
> +       format = mxc_isi_pipe_get_pad_format(pipe, state, fmt->pad);
> +       *format = *mf;
> +
> +       dev_dbg(pipe->isi->dev, "pad%u: code: 0x%04x, %ux%u",
> +               fmt->pad, mf->code, mf->width, mf->height);
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_pipe_get_selection(struct v4l2_subdev *sd,
> +                                     struct v4l2_subdev_state *state,
> +                                     struct v4l2_subdev_selection *sel)
> +{
> +       struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
> +       const struct v4l2_mbus_framefmt *format;
> +       const struct v4l2_rect *rect;
> +
> +       switch (sel->target) {
> +       case V4L2_SEL_TGT_COMPOSE_BOUNDS:
> +               if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
> +                       /* No compose rectangle on source pad. */
> +                       return -EINVAL;
> +
> +               /* The sink compose is bound by the sink format. */
> +               format = mxc_isi_pipe_get_pad_format(pipe, state,
> +                                                    MXC_ISI_PIPE_PAD_SINK);
> +               sel->r.left = 0;
> +               sel->r.top = 0;
> +               sel->r.width = format->width;
> +               sel->r.height = format->height;
> +               break;
> +
> +       case V4L2_SEL_TGT_CROP_BOUNDS:
> +               if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
> +                       /* No crop rectangle on sink pad. */
> +                       return -EINVAL;
> +
> +               /* The source crop is bound by the sink compose. */
> +               rect = mxc_isi_pipe_get_pad_compose(pipe, state,
> +                                                   MXC_ISI_PIPE_PAD_SINK);
> +               sel->r = *rect;
> +               break;
> +
> +       case V4L2_SEL_TGT_CROP:
> +               if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
> +                       /* No crop rectangle on sink pad. */
> +                       return -EINVAL;
> +
> +               rect = mxc_isi_pipe_get_pad_crop(pipe, state, sel->pad);
> +               sel->r = *rect;
> +               break;
> +
> +       case V4L2_SEL_TGT_COMPOSE:
> +               if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
> +                       /* No compose rectangle on source pad. */
> +                       return -EINVAL;
> +
> +               rect = mxc_isi_pipe_get_pad_compose(pipe, state, sel->pad);
> +               sel->r = *rect;
> +               break;
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_pipe_set_selection(struct v4l2_subdev *sd,
> +                                     struct v4l2_subdev_state *state,
> +                                     struct v4l2_subdev_selection *sel)
> +{
> +       struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
> +       struct v4l2_mbus_framefmt *format;
> +       struct v4l2_rect *rect;
> +
> +       switch (sel->target) {
> +       case V4L2_SEL_TGT_CROP:
> +               if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
> +                       /* The pipeline support cropping on the source only. */
> +                       return -EINVAL;
> +
> +               /* The source crop is bound by the sink compose. */
> +               rect = mxc_isi_pipe_get_pad_compose(pipe, state,
> +                                                   MXC_ISI_PIPE_PAD_SINK);
> +               sel->r.left = clamp_t(s32, sel->r.left, 0, rect->width - 1);
> +               sel->r.top = clamp_t(s32, sel->r.top, 0, rect->height - 1);
> +               sel->r.width = clamp(sel->r.width, MXC_ISI_MIN_WIDTH,
> +                                    rect->width - sel->r.left);
> +               sel->r.height = clamp(sel->r.height, MXC_ISI_MIN_HEIGHT,
> +                                     rect->height - sel->r.top);
> +
> +               rect = mxc_isi_pipe_get_pad_crop(pipe, state,
> +                                                MXC_ISI_PIPE_PAD_SOURCE);
> +               *rect = sel->r;
> +
> +               /* Propagate the crop rectangle to the source pad. */
> +               format = mxc_isi_pipe_get_pad_format(pipe, state,
> +                                                    MXC_ISI_PIPE_PAD_SOURCE);
> +               format->width = sel->r.width;
> +               format->height = sel->r.height;
> +               break;
> +
> +       case V4L2_SEL_TGT_COMPOSE:
> +               if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
> +                       /* Composing is supported on the sink only. */
> +                       return -EINVAL;
> +
> +               /* The sink crop is bound by the sink format downscaling only). */
> +               format = mxc_isi_pipe_get_pad_format(pipe, state,
> +                                                    MXC_ISI_PIPE_PAD_SINK);
> +
> +               sel->r.left = 0;
> +               sel->r.top = 0;
> +               sel->r.width = clamp(sel->r.width, MXC_ISI_MIN_WIDTH,
> +                                    format->width);
> +               sel->r.height = clamp(sel->r.height, MXC_ISI_MIN_HEIGHT,
> +                                     format->height);
> +
> +               rect = mxc_isi_pipe_get_pad_compose(pipe, state,
> +                                                   MXC_ISI_PIPE_PAD_SINK);
> +               *rect = sel->r;
> +
> +               /* Propagate the compose rectangle to the source pad. */
> +               rect = mxc_isi_pipe_get_pad_crop(pipe, state,
> +                                                MXC_ISI_PIPE_PAD_SOURCE);
> +               rect->left = 0;
> +               rect->top = 0;
> +               rect->width = sel->r.width;
> +               rect->height = sel->r.height;
> +
> +               format = mxc_isi_pipe_get_pad_format(pipe, state,
> +                                                    MXC_ISI_PIPE_PAD_SOURCE);
> +               format->width = sel->r.width;
> +               format->height = sel->r.height;
> +               break;
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       dev_dbg(pipe->isi->dev, "%s, target %#x: (%d,%d)/%dx%d", __func__,
> +               sel->target, sel->r.left, sel->r.top, sel->r.width,
> +               sel->r.height);
> +
> +       return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops mxc_isi_pipe_subdev_pad_ops = {
> +       .init_cfg = mxc_isi_pipe_init_cfg,
> +       .enum_mbus_code = mxc_isi_pipe_enum_mbus_code,
> +       .get_fmt = v4l2_subdev_get_fmt,
> +       .set_fmt = mxc_isi_pipe_set_fmt,
> +       .get_selection = mxc_isi_pipe_get_selection,
> +       .set_selection = mxc_isi_pipe_set_selection,
> +};
> +
> +static const struct v4l2_subdev_ops mxc_isi_pipe_subdev_ops = {
> +       .pad = &mxc_isi_pipe_subdev_pad_ops,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * IRQ handling
> + */
> +
> +static irqreturn_t mxc_isi_pipe_irq_handler(int irq, void *priv)
> +{
> +       struct mxc_isi_pipe *pipe = priv;
> +       const struct mxc_isi_ier_reg *ier_reg = pipe->isi->pdata->ier_reg;
> +       u32 status;
> +
> +       status = mxc_isi_channel_irq_status(pipe, true);
> +
> +       if (status & CHNL_STS_FRM_STRD) {
> +               if (!WARN_ON(!pipe->irq_handler))
> +                       pipe->irq_handler(pipe, status);
> +       }
> +
> +       if (status & (CHNL_STS_AXI_WR_ERR_Y |
> +                     CHNL_STS_AXI_WR_ERR_U |
> +                     CHNL_STS_AXI_WR_ERR_V))
> +               dev_dbg(pipe->isi->dev, "%s: IRQ AXI Error stat=0x%X\n",
> +                       __func__, status);
> +
> +       if (status & (ier_reg->panic_y_buf_en.mask |
> +                     ier_reg->panic_u_buf_en.mask |
> +                     ier_reg->panic_v_buf_en.mask))
> +               dev_dbg(pipe->isi->dev, "%s: IRQ Panic OFLW Error stat=0x%X\n",
> +                       __func__, status);
> +
> +       if (status & (ier_reg->oflw_y_buf_en.mask |
> +                     ier_reg->oflw_u_buf_en.mask |
> +                     ier_reg->oflw_v_buf_en.mask))
> +               dev_dbg(pipe->isi->dev, "%s: IRQ OFLW Error stat=0x%X\n",
> +                       __func__, status);
> +
> +       if (status & (ier_reg->excs_oflw_y_buf_en.mask |
> +                     ier_reg->excs_oflw_u_buf_en.mask |
> +                     ier_reg->excs_oflw_v_buf_en.mask))
> +               dev_dbg(pipe->isi->dev, "%s: IRQ EXCS OFLW Error stat=0x%X\n",
> +                       __func__, status);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Init & cleanup
> + */
> +
> +static const struct media_entity_operations mxc_isi_pipe_entity_ops = {
> +       .link_validate  = v4l2_subdev_link_validate,
> +};
> +
> +int mxc_isi_pipe_init(struct mxc_isi_dev *isi, unsigned int id)
> +{
> +       struct mxc_isi_pipe *pipe = &isi->pipes[id];
> +       struct v4l2_subdev *sd;
> +       int irq;
> +       int ret;
> +
> +       pipe->id = id;
> +       pipe->isi = isi;
> +       pipe->regs = isi->regs + id * isi->pdata->reg_offset;
> +
> +       mutex_init(&pipe->lock);
> +
> +       pipe->available_res = MXC_ISI_CHANNEL_RES_LINE_BUF
> +                           | MXC_ISI_CHANNEL_RES_OUTPUT_BUF;
> +       pipe->acquired_res = 0;
> +       pipe->chained_res = 0;
> +       pipe->chained = false;
> +
> +       sd = &pipe->sd;
> +       v4l2_subdev_init(sd, &mxc_isi_pipe_subdev_ops);
> +       sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +       snprintf(sd->name, sizeof(sd->name), "mxc_isi.%d", pipe->id);
> +       sd->dev = isi->dev;
> +
> +       sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> +       sd->entity.ops = &mxc_isi_pipe_entity_ops;
> +
> +       pipe->pads[MXC_ISI_PIPE_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +       pipe->pads[MXC_ISI_PIPE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +
> +       ret = media_entity_pads_init(&sd->entity, MXC_ISI_PIPE_PADS_NUM,
> +                                    pipe->pads);
> +       if (ret)
> +               goto error;
> +
> +       ret = v4l2_subdev_init_finalize(sd);
> +       if (ret < 0)
> +               goto error;
> +
> +       /* Register IRQ handler. */
> +       mxc_isi_channel_irq_clear(pipe);
> +
> +       irq = platform_get_irq(to_platform_device(isi->dev), id);
> +       if (irq < 0) {
> +               dev_err(pipe->isi->dev, "Failed to get IRQ (%d)\n", irq);
> +               ret = irq;
> +               goto error;
> +       }
> +
> +       ret = devm_request_irq(isi->dev, irq, mxc_isi_pipe_irq_handler,
> +                              0, dev_name(isi->dev), pipe);
> +       if (ret < 0) {
> +               dev_err(isi->dev, "failed to request IRQ (%d)\n", ret);
> +               goto error;
> +       }
> +
> +       return 0;
> +
> +error:
> +       media_entity_cleanup(&sd->entity);
> +       mutex_destroy(&pipe->lock);
> +
> +       return ret;
> +}
> +
> +void mxc_isi_pipe_cleanup(struct mxc_isi_pipe *pipe)
> +{
> +       struct v4l2_subdev *sd = &pipe->sd;
> +
> +       media_entity_cleanup(&sd->entity);
> +       mutex_destroy(&pipe->lock);
> +}
> +
> +int mxc_isi_pipe_acquire(struct mxc_isi_pipe *pipe,
> +                        mxc_isi_pipe_irq_t irq_handler)
> +{
> +       const struct mxc_isi_bus_format_info *sink_info;
> +       const struct mxc_isi_bus_format_info *src_info;
> +       struct v4l2_mbus_framefmt *sink_fmt;
> +       const struct v4l2_mbus_framefmt *src_fmt;
> +       struct v4l2_subdev *sd = &pipe->sd;
> +       struct v4l2_subdev_state *state;
> +       bool bypass;
> +       int ret;
> +
> +       state = v4l2_subdev_lock_and_get_active_state(sd);
> +       sink_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SINK);
> +       src_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SOURCE);
> +       v4l2_subdev_unlock_state(state);
> +
> +       sink_info = mxc_isi_bus_format_by_code(sink_fmt->code,
> +                                              MXC_ISI_PIPE_PAD_SINK);
> +       src_info = mxc_isi_bus_format_by_code(src_fmt->code,
> +                                             MXC_ISI_PIPE_PAD_SOURCE);
> +
> +       bypass = sink_fmt->width == src_fmt->width &&
> +                sink_fmt->height == src_fmt->height &&
> +                sink_info->encoding == src_info->encoding;
> +
> +       ret = mxc_isi_channel_acquire(pipe, irq_handler, bypass);
> +       if (ret)
> +               return ret;
> +
> +       /* Chain the channel if needed for wide resolutions. */
> +       if (sink_fmt->width > MXC_ISI_MAX_WIDTH_UNCHAINED) {
> +               ret = mxc_isi_channel_chain(pipe, bypass);
> +               if (ret)
> +                       mxc_isi_channel_release(pipe);
> +       }
> +
> +       return ret;
> +}
> +
> +void mxc_isi_pipe_release(struct mxc_isi_pipe *pipe)
> +{
> +       mxc_isi_channel_release(pipe);
> +       mxc_isi_channel_unchain(pipe);
> +}
> diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-regs.h b/drivers/media/platform/nxp/imx8-isi/imx8-isi-regs.h
> new file mode 100644
> index 000000000000..1b65eccdf0da
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-regs.h
> @@ -0,0 +1,418 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright 2019-2020 NXP
> + */
> +
> +#ifndef __IMX8_ISI_REGS_H__
> +#define __IMX8_ISI_REGS_H__
> +
> +#include <linux/bits.h>
> +
> +/* ISI Registers Define  */
> +/* Channel Control Register */
> +#define CHNL_CTRL                                              0x0000
> +#define CHNL_CTRL_CHNL_EN                                      BIT(31)
> +#define CHNL_CTRL_CLK_EN                                       BIT(30)
> +#define CHNL_CTRL_CHNL_BYPASS                                  BIT(29)
> +#define CHNL_CTRL_CHAIN_BUF(n)                                 ((n) << 25)
> +#define CHNL_CTRL_CHAIN_BUF_MASK                               GENMASK(26, 25)
> +#define CHNL_CTRL_CHAIN_BUF_NO_CHAIN                           0
> +#define CHNL_CTRL_CHAIN_BUF_2_CHAIN                            1
> +#define CHNL_CTRL_SW_RST                                       BIT(24)
> +#define CHNL_CTRL_BLANK_PXL(n)                                 ((n) << 16)
> +#define CHNL_CTRL_BLANK_PXL_MASK                               GENMASK(23, 16)
> +#define CHNL_CTRL_MIPI_VC_ID(n)                                        ((n) << 6)
> +#define CHNL_CTRL_MIPI_VC_ID_MASK                              GENMASK(7, 6)
> +#define CHNL_CTRL_SRC_TYPE(n)                                  ((n) << 4)
> +#define CHNL_CTRL_SRC_TYPE_MASK                                        BIT(4)
> +#define CHNL_CTRL_SRC_TYPE_DEVICE                              0
> +#define CHNL_CTRL_SRC_TYPE_MEMORY                              1
> +#define CHNL_CTRL_SRC_INPUT(n)                                 ((n) << 0)
> +#define CHNL_CTRL_SRC_INPUT_MASK                               GENMASK(2, 0)
> +
> +/* Channel Image Control Register */
> +#define CHNL_IMG_CTRL                                          0x0004
> +#define CHNL_IMG_CTRL_FORMAT(n)                                        ((n) << 24)
> +#define CHNL_IMG_CTRL_FORMAT_MASK                              GENMASK(29, 24)
> +#define CHNL_IMG_CTRL_FORMAT_RGBA8888                          0x00
> +#define CHNL_IMG_CTRL_FORMAT_ABGR8888                          0x01
> +#define CHNL_IMG_CTRL_FORMAT_ARGB8888                          0x02
> +#define CHNL_IMG_CTRL_FORMAT_RGBX888                           0x03
> +#define CHNL_IMG_CTRL_FORMAT_XBGR888                           0x04
> +#define CHNL_IMG_CTRL_FORMAT_XRGB888                           0x05
> +#define CHNL_IMG_CTRL_FORMAT_RGB888P                           0x06
> +#define CHNL_IMG_CTRL_FORMAT_BGR888P                           0x07
> +#define CHNL_IMG_CTRL_FORMAT_A2BGR10                           0x08
> +#define CHNL_IMG_CTRL_FORMAT_A2RGB10                           0x09
> +#define CHNL_IMG_CTRL_FORMAT_RGB565                            0x0a
> +#define CHNL_IMG_CTRL_FORMAT_RAW8                              0x0b
> +#define CHNL_IMG_CTRL_FORMAT_RAW10                             0x0c
> +#define CHNL_IMG_CTRL_FORMAT_RAW10P                            0x0d
> +#define CHNL_IMG_CTRL_FORMAT_RAW12                             0x0e
> +#define CHNL_IMG_CTRL_FORMAT_RAW16                             0x0f
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_1P8P                       0x10
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_2P8P                       0x11
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_3P8P                       0x12
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_1P8                                0x13
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_1P10                       0x14
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_2P10                       0x15
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_3P10                       0x16
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_1P10P                      0x18
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_2P10P                      0x19
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_3P10P                      0x1a
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_1P12                       0x1c
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_2P12                       0x1d
> +#define CHNL_IMG_CTRL_FORMAT_YUV444_3P12                       0x1e
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_1P8P                       0x20
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_2P8P                       0x21
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_3P8P                       0x22
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_1P10                       0x24
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_2P10                       0x25
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_3P10                       0x26
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_1P10P                      0x28
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_2P10P                      0x29
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_3P10P                      0x2a
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_1P12                       0x2c
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_2P12                       0x2d
> +#define CHNL_IMG_CTRL_FORMAT_YUV422_3P12                       0x2e
> +#define CHNL_IMG_CTRL_FORMAT_YUV420_2P8P                       0x31
> +#define CHNL_IMG_CTRL_FORMAT_YUV420_3P8P                       0x32
> +#define CHNL_IMG_CTRL_FORMAT_YUV420_2P10                       0x35
> +#define CHNL_IMG_CTRL_FORMAT_YUV420_3P10                       0x36
> +#define CHNL_IMG_CTRL_FORMAT_YUV420_2P10P                      0x39
> +#define CHNL_IMG_CTRL_FORMAT_YUV420_3P10P                      0x3a
> +#define CHNL_IMG_CTRL_FORMAT_YUV420_2P12                       0x3d
> +#define CHNL_IMG_CTRL_FORMAT_YUV420_3P12                       0x3e
> +#define CHNL_IMG_CTRL_GBL_ALPHA_VAL(n)                         ((n) << 16)
> +#define CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK                       GENMASK(23, 16)
> +#define CHNL_IMG_CTRL_GBL_ALPHA_EN                             BIT(15)
> +#define CHNL_IMG_CTRL_DEINT(n)                                 ((n) << 12)
> +#define CHNL_IMG_CTRL_DEINT_MASK                               GENMASK(14, 12)
> +#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(n)                                 ((n) << 10)
> +#define CHNL_IMG_CTRL_DEC_X_MASK                               GENMASK(11, 10)
> +#define CHNL_IMG_CTRL_DEC_Y(n)                                 ((n) << 8)
> +#define CHNL_IMG_CTRL_DEC_Y_MASK                               GENMASK(9, 8)
> +#define CHNL_IMG_CTRL_CROP_EN                                  BIT(7)
> +#define CHNL_IMG_CTRL_VFLIP_EN                                 BIT(6)
> +#define CHNL_IMG_CTRL_HFLIP_EN                                 BIT(5)
> +#define CHNL_IMG_CTRL_YCBCR_MODE                               BIT(3)
> +#define CHNL_IMG_CTRL_CSC_MODE(n)                              ((n) << 1)
> +#define CHNL_IMG_CTRL_CSC_MODE_MASK                            GENMASK(2, 1)
> +#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                               BIT(0)
> +
> +/* Channel Output Buffer Control Register */
> +#define CHNL_OUT_BUF_CTRL                                      0x0008
> +#define CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR                       BIT(15)
> +#define CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR                       BIT(14)
> +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V(n)              ((n) << 6)
> +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_MASK            GENMASK(7, 6)
> +#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(n)              ((n) << 3)
> +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_MASK            GENMASK(4, 3)
> +#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(n)              ((n) << 0)
> +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_MASK            GENMASK(1, 0)
> +#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                                           0x000c
> +#define CHNL_IMG_CFG_HEIGHT(n)                                 ((n) << 16)
> +#define CHNL_IMG_CFG_HEIGHT_MASK                               GENMASK(28, 16)
> +#define CHNL_IMG_CFG_WIDTH(n)                                  ((n) << 0)
> +#define CHNL_IMG_CFG_WIDTH_MASK                                        GENMASK(12, 0)
> +
> +/* Channel Interrupt Enable Register */
> +#define CHNL_IER                                               0x0010
> +#define CHNL_IER_MEM_RD_DONE_EN                                        BIT(31)
> +#define CHNL_IER_LINE_RCVD_EN                                  BIT(30)
> +#define CHNL_IER_FRM_RCVD_EN                                   BIT(29)
> +#define CHNL_IER_AXI_WR_ERR_V_EN                               BIT(28)
> +#define CHNL_IER_AXI_WR_ERR_U_EN                               BIT(27)
> +#define CHNL_IER_AXI_WR_ERR_Y_EN                               BIT(26)
> +#define CHNL_IER_AXI_RD_ERR_EN                                 BIT(25)
> +
> +/* Channel Status Register */
> +#define CHNL_STS                                               0x0014
> +#define CHNL_STS_MEM_RD_DONE                                   BIT(31)
> +#define CHNL_STS_LINE_STRD                                     BIT(30)
> +#define CHNL_STS_FRM_STRD                                      BIT(29)
> +#define CHNL_STS_AXI_WR_ERR_V                                  BIT(28)
> +#define CHNL_STS_AXI_WR_ERR_U                                  BIT(27)
> +#define CHNL_STS_AXI_WR_ERR_Y                                  BIT(26)
> +#define CHNL_STS_AXI_RD_ERR                                    BIT(25)
> +#define CHNL_STS_OFLW_PANIC_V_BUF                              BIT(24)
> +#define CHNL_STS_EXCS_OFLW_V_BUF                               BIT(23)
> +#define CHNL_STS_OFLW_V_BUF                                    BIT(22)
> +#define CHNL_STS_OFLW_PANIC_U_BUF                              BIT(21)
> +#define CHNL_STS_EXCS_OFLW_U_BUF                               BIT(20)
> +#define CHNL_STS_OFLW_U_BUF                                    BIT(19)
> +#define CHNL_STS_OFLW_PANIC_Y_BUF                              BIT(18)
> +#define CHNL_STS_EXCS_OFLW_Y_BUF                               BIT(17)
> +#define CHNL_STS_OFLW_Y_BUF                                    BIT(16)
> +#define CHNL_STS_EARLY_VSYNC_ERR                               BIT(15)
> +#define CHNL_STS_LATE_VSYNC_ERR                                        BIT(14)
> +#define CHNL_STS_MEM_RD_OFLOW                                  BIT(10)
> +#define CHNL_STS_BUF2_ACTIVE                                   BIT(9)
> +#define CHNL_STS_BUF1_ACTIVE                                   BIT(8)
> +#define CHNL_STS_OFLW_BYTES(n)                                 ((n) << 0)
> +#define CHNL_STS_OFLW_BYTES_MASK                               GENMASK(7, 0)
> +
> +/* Channel Scale Factor Register */
> +#define CHNL_SCALE_FACTOR                                      0x0018
> +#define CHNL_SCALE_FACTOR_Y_SCALE(n)                           ((n) << 16)
> +#define CHNL_SCALE_FACTOR_Y_SCALE_MASK                         GENMASK(29, 16)
> +#define CHNL_SCALE_FACTOR_X_SCALE(n)                           ((n) << 0)
> +#define CHNL_SCALE_FACTOR_X_SCALE_MASK                         GENMASK(13, 0)
> +
> +/* Channel Scale Offset Register */
> +#define CHNL_SCALE_OFFSET                                      0x001c
> +#define CHNL_SCALE_OFFSET_Y_SCALE(n)                           ((n) << 16)
> +#define CHNL_SCALE_OFFSET_Y_SCALE_MASK                         GENMASK(27, 16)
> +#define CHNL_SCALE_OFFSET_X_SCALE(n)                           ((n) << 0)
> +#define CHNL_SCALE_OFFSET_X_SCALE_MASK                         GENMASK(11, 0)
> +
> +/* Channel Crop Upper Left Corner Coordinate Register */
> +#define CHNL_CROP_ULC                                          0x0020
> +#define CHNL_CROP_ULC_X(n)                                     ((n) << 16)
> +#define CHNL_CROP_ULC_X_MASK                                   GENMASK(27, 16)
> +#define CHNL_CROP_ULC_Y(n)                                     ((n) << 0)
> +#define CHNL_CROP_ULC_Y_MASK                                   GENMASK(11, 0)
> +
> +/* Channel Crop Lower Right Corner Coordinate Register */
> +#define CHNL_CROP_LRC                                          0x0024
> +#define CHNL_CROP_LRC_X(n)                                     ((n) << 16)
> +#define CHNL_CROP_LRC_X_MASK                                   GENMASK(27, 16)
> +#define CHNL_CROP_LRC_Y(n)                                     ((n) << 0)
> +#define CHNL_CROP_LRC_Y_MASK                                   GENMASK(11, 0)
> +
> +/* Channel Color Space Conversion Coefficient Register 0 */
> +#define CHNL_CSC_COEFF0                                                0x0028
> +#define CHNL_CSC_COEFF0_A2(n)                                  ((n) << 16)
> +#define CHNL_CSC_COEFF0_A2_MASK                                        GENMASK(26, 16)
> +#define CHNL_CSC_COEFF0_A1(n)                                  ((n) << 0)
> +#define CHNL_CSC_COEFF0_A1_MASK                                        GENMASK(10, 0)
> +
> +/* Channel Color Space Conversion Coefficient Register 1 */
> +#define CHNL_CSC_COEFF1                                                0x002c
> +#define CHNL_CSC_COEFF1_B1(n)                                  ((n) << 16)
> +#define CHNL_CSC_COEFF1_B1_MASK                                        GENMASK(26, 16)
> +#define CHNL_CSC_COEFF1_A3(n)                                  ((n) << 0)
> +#define CHNL_CSC_COEFF1_A3_MASK                                        GENMASK(10, 0)
> +
> +/* Channel Color Space Conversion Coefficient Register 2 */
> +#define CHNL_CSC_COEFF2                                                0x0030
> +#define CHNL_CSC_COEFF2_B3(n)                                  ((n) << 16)
> +#define CHNL_CSC_COEFF2_B3_MASK                                        GENMASK(26, 16)
> +#define CHNL_CSC_COEFF2_B2(n)                                  ((n) << 0)
> +#define CHNL_CSC_COEFF2_B2_MASK                                        GENMASK(10, 0)
> +
> +/* Channel Color Space Conversion Coefficient Register 3 */
> +#define CHNL_CSC_COEFF3                                                0x0034
> +#define CHNL_CSC_COEFF3_C2(n)                                  ((n) << 16)
> +#define CHNL_CSC_COEFF3_C2_MASK                                        GENMASK(26, 16)
> +#define CHNL_CSC_COEFF3_C1(n)                                  ((n) << 0)
> +#define CHNL_CSC_COEFF3_C1_MASK                                        GENMASK(10, 0)
> +
> +/* Channel Color Space Conversion Coefficient Register 4 */
> +#define CHNL_CSC_COEFF4                                                0x0038
> +#define CHNL_CSC_COEFF4_D1(n)                                  ((n) << 16)
> +#define CHNL_CSC_COEFF4_D1_MASK                                        GENMASK(24, 16)
> +#define CHNL_CSC_COEFF4_C3(n)                                  ((n) << 0)
> +#define CHNL_CSC_COEFF4_C3_MASK                                        GENMASK(10, 0)
> +
> +/* Channel Color Space Conversion Coefficient Register 5 */
> +#define CHNL_CSC_COEFF5                                                0x003c
> +#define CHNL_CSC_COEFF5_D3(n)                                  ((n) << 16)
> +#define CHNL_CSC_COEFF5_D3_MASK                                        GENMASK(24, 16)
> +#define CHNL_CSC_COEFF5_D2(n)                                  ((n) << 0)
> +#define CHNL_CSC_COEFF5_D2_MASK                                        GENMASK(8, 0)
> +
> +/* Channel Alpha Value Register for ROI 0 */
> +#define CHNL_ROI_0_ALPHA                                       0x0040
> +#define CHNL_ROI_0_ALPHA_VAL(n)                                        ((n) << 24)
> +#define CHNL_ROI_0_ALPHA_MASK                                  GENMASK(31, 24)
> +#define CHNL_ROI_0_ALPHA_EN                                    BIT(16)
> +
> +/* Channel Upper Left Coordinate Register for ROI 0 */
> +#define CHNL_ROI_0_ULC                                         0x0044
> +#define CHNL_ROI_0_ULC_X(n)                                    ((n) << 16)
> +#define CHNL_ROI_0_ULC_X_MASK                                  GENMASK(27, 16)
> +#define CHNL_ROI_0_ULC_Y(n)                                    ((n) << 0)
> +#define CHNL_ROI_0_ULC_Y_MASK                                  GENMASK(11, 0)
> +
> +/* Channel Lower Right Coordinate Register for ROI 0 */
> +#define CHNL_ROI_0_LRC                                         0x0048
> +#define CHNL_ROI_0_LRC_X(n)                                    ((n) << 16)
> +#define CHNL_ROI_0_LRC_X_MASK                                  GENMASK(27, 16)
> +#define CHNL_ROI_0_LRC_Y(n)                                    ((n) << 0)
> +#define CHNL_ROI_0_LRC_Y_MASK                                  GENMASK(11, 0)
> +
> +/* Channel Alpha Value Register for ROI 1 */
> +#define CHNL_ROI_1_ALPHA                                       0x004c
> +#define CHNL_ROI_1_ALPHA_VAL(n)                                        ((n) << 24)
> +#define CHNL_ROI_1_ALPHA_MASK                                  GENMASK(31, 24)
> +#define CHNL_ROI_1_ALPHA_EN                                    BIT(16)
> +
> +/* Channel Upper Left Coordinate Register for ROI 1 */
> +#define CHNL_ROI_1_ULC                                         0x0050
> +#define CHNL_ROI_1_ULC_X(n)                                    ((n) << 16)
> +#define CHNL_ROI_1_ULC_X_MASK                                  GENMASK(27, 16)
> +#define CHNL_ROI_1_ULC_Y(n)                                    ((n) << 0)
> +#define CHNL_ROI_1_ULC_Y_MASK                                  GENMASK(11, 0)
> +
> +/* Channel Lower Right Coordinate Register for ROI 1 */
> +#define CHNL_ROI_1_LRC                                         0x0054
> +#define CHNL_ROI_1_LRC_X(n)                                    ((n) << 16)
> +#define CHNL_ROI_1_LRC_X_MASK                                  GENMASK(27, 16)
> +#define CHNL_ROI_1_LRC_Y(n)                                    ((n) << 0)
> +#define CHNL_ROI_1_LRC_Y_MASK                                  GENMASK(11, 0)
> +
> +/* Channel Alpha Value Register for ROI 2 */
> +#define CHNL_ROI_2_ALPHA                                       0x0058
> +#define CHNL_ROI_2_ALPHA_VAL(n)                                        ((n) << 24)
> +#define CHNL_ROI_2_ALPHA_MASK                                  GENMASK(31, 24)
> +#define CHNL_ROI_2_ALPHA_EN                                    BIT(16)
> +
> +/* Channel Upper Left Coordinate Register for ROI 2 */
> +#define CHNL_ROI_2_ULC                                         0x005c
> +#define CHNL_ROI_2_ULC_X(n)                                    ((n) << 16)
> +#define CHNL_ROI_2_ULC_X_MASK                                  GENMASK(27, 16)
> +#define CHNL_ROI_2_ULC_Y(n)                                    ((n) << 0)
> +#define CHNL_ROI_2_ULC_Y_MASK                                  GENMASK(11, 0)
> +
> +/* Channel Lower Right Coordinate Register for ROI 2 */
> +#define CHNL_ROI_2_LRC                                         0x0060
> +#define CHNL_ROI_2_LRC_X(n)                                    ((n) << 16)
> +#define CHNL_ROI_2_LRC_X_MASK                                  GENMASK(27, 16)
> +#define CHNL_ROI_2_LRC_Y(n)                                    ((n) << 0)
> +#define CHNL_ROI_2_LRC_Y_MASK                                  GENMASK(11, 0)
> +
> +/* Channel Alpha Value Register for ROI 3 */
> +#define CHNL_ROI_3_ALPHA                                       0x0064
> +#define CHNL_ROI_3_ALPHA_VAL(n)                                        ((n) << 24)
> +#define CHNL_ROI_3_ALPHA_MASK                                  GENMASK(31, 24)
> +#define CHNL_ROI_3_ALPHA_EN                                    BIT(16)
> +
> +/* Channel Upper Left Coordinate Register for ROI 3 */
> +#define CHNL_ROI_3_ULC                                         0x0068
> +#define CHNL_ROI_3_ULC_X(n)                                    ((n) << 16)
> +#define CHNL_ROI_3_ULC_X_MASK                                  GENMASK(27, 16)
> +#define CHNL_ROI_3_ULC_Y(n)                                    ((n) << 0)
> +#define CHNL_ROI_3_ULC_Y_MASK                                  GENMASK(11, 0)
> +
> +/* Channel Lower Right Coordinate Register for ROI 3 */
> +#define CHNL_ROI_3_LRC                                         0x006c
> +#define CHNL_ROI_3_LRC_X(n)                                    ((n) << 16)
> +#define CHNL_ROI_3_LRC_X_MASK                                  GENMASK(27, 16)
> +#define CHNL_ROI_3_LRC_Y(n)                                    ((n) << 0)
> +#define CHNL_ROI_3_LRC_Y_MASK                                  GENMASK(11, 0)
> +/* Channel RGB or Luma (Y) Output Buffer 1 Address */
> +#define CHNL_OUT_BUF1_ADDR_Y                                   0x0070
> +
> +/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 1 Address */
> +#define CHNL_OUT_BUF1_ADDR_U                                   0x0074
> +
> +/* Channel Chroma (V/Cr) Output Buffer 1 Address */
> +#define CHNL_OUT_BUF1_ADDR_V                                   0x0078
> +
> +/* Channel Output Buffer Pitch */
> +#define CHNL_OUT_BUF_PITCH                                     0x007c
> +#define CHNL_OUT_BUF_PITCH_LINE_PITCH(n)                       ((n) << 0)
> +#define CHNL_OUT_BUF_PITCH_LINE_PITCH_MASK                     GENMASK(15, 0)
> +
> +/* Channel Input Buffer Address */
> +#define CHNL_IN_BUF_ADDR                                       0x0080
> +
> +/* Channel Input Buffer Pitch */
> +#define CHNL_IN_BUF_PITCH                                      0x0084
> +#define CHNL_IN_BUF_PITCH_FRM_PITCH(n)                         ((n) << 16)
> +#define CHNL_IN_BUF_PITCH_FRM_PITCH_MASK                       GENMASK(31, 16)
> +#define CHNL_IN_BUF_PITCH_LINE_PITCH(n)                                ((n) << 0)
> +#define CHNL_IN_BUF_PITCH_LINE_PITCH_MASK                      GENMASK(15, 0)
> +
> +/* Channel Memory Read Control */
> +#define CHNL_MEM_RD_CTRL                                       0x0088
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE(n)                           ((n) << 28)
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_MASK                         GENMASK(31, 28)
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_BGR8P                                0x00
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_RGB8P                                0x01
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_XRGB8                                0x02
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_RGBX8                                0x03
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_XBGR8                                0x04
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_RGB565                       0x05
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_A2BGR10                      0x06
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_A2RGB10                      0x07
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P8P                  0x08
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P10                  0x09
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P10P                 0x0a
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P12                  0x0b
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P8                   0x0c
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P8P                  0x0d
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P10                  0x0e
> +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P12                  0x0f
> +#define CHNL_MEM_RD_CTRL_READ_MEM                              BIT(0)
> +
> +/* Channel RGB or Luma (Y) Output Buffer 2 Address */
> +#define CHNL_OUT_BUF2_ADDR_Y                                   0x008c
> +
> +/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 2 Address  */
> +#define CHNL_OUT_BUF2_ADDR_U                                   0x0090
> +
> +/* Channel Chroma (V/Cr) Output Buffer 2 Address   */
> +#define CHNL_OUT_BUF2_ADDR_V                                   0x0094
> +
> +/* Channel scale image config */
> +#define CHNL_SCL_IMG_CFG                                       0x0098
> +#define CHNL_SCL_IMG_CFG_HEIGHT(n)                             ((n) << 16)
> +#define CHNL_SCL_IMG_CFG_HEIGHT_MASK                           GENMASK(28, 16)
> +#define CHNL_SCL_IMG_CFG_WIDTH(n)                              ((n) << 0)
> +#define CHNL_SCL_IMG_CFG_WIDTH_MASK                            GENMASK(12, 0)
> +
> +/* Channel Flow Control Register */
> +#define CHNL_FLOW_CTRL                                         0x009c
> +#define CHNL_FLOW_CTRL_FC_DENOM_MASK                           GENMASK(7, 0)
> +#define CHNL_FLOW_CTRL_FC_DENOM(n)                             ((n) << 0)
> +#define CHNL_FLOW_CTRL_FC_NUMER_MASK                           GENMASK(23, 16)
> +#define CHNL_FLOW_CTRL_FC_NUMER(n)                             ((n) << 0)
> +
> +/* Channel Output Y-Buffer 1 Extended Address Bits */
> +#define CHNL_Y_BUF1_XTND_ADDR                                  0x00a0
> +
> +/* Channel Output U-Buffer 1 Extended Address Bits */
> +#define CHNL_U_BUF1_XTND_ADDR                                  0x00a4
> +
> +/* Channel Output V-Buffer 1 Extended Address Bits */
> +#define CHNL_V_BUF1_XTND_ADDR                                  0x00a8
> +
> +/* Channel Output Y-Buffer 2 Extended Address Bits */
> +#define CHNL_Y_BUF2_XTND_ADDR                                  0x00ac
> +
> +/* Channel Output U-Buffer 2 Extended Address Bits */
> +#define CHNL_U_BUF2_XTND_ADDR                                  0x00b0
> +
> +/* Channel Output V-Buffer 2 Extended Address Bits */
> +#define CHNL_V_BUF2_XTND_ADDR                                  0x00b4
> +
> +/* Channel Input Buffer Extended Address Bits */
> +#define CHNL_IN_BUF_XTND_ADDR                                  0x00b8
> +
> +#endif /* __IMX8_ISI_REGS_H__ */
> diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c
> new file mode 100644
> index 000000000000..13d38f2746bf
> --- /dev/null
> +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c
> @@ -0,0 +1,1513 @@
> +// 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/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/errno.h>
> +#include <linux/kernel.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/minmax.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/media-entity.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fh.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-core.h>
> +#include <media/videobuf2-dma-contig.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#include "imx8-isi-core.h"
> +#include "imx8-isi-regs.h"
> +
> +/* Keep the first entry matching MXC_ISI_DEF_PIXEL_FORMAT */
> +static const struct mxc_isi_format_info mxc_isi_formats[] = {
> +       /* YUV formats */
> +       {
> +               .mbus_code      = MEDIA_BUS_FMT_YUV8_1X24,
> +               .fourcc         = V4L2_PIX_FMT_YUYV,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT
> +                               | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_in_format  = CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P8P,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_1P8P,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_YUV,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_YUV8_1X24,
> +               .fourcc         = V4L2_PIX_FMT_YUVA32,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV444_1P8,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 32 },
> +               .encoding       = MXC_ISI_ENC_YUV,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_YUV8_1X24,
> +               .fourcc         = V4L2_PIX_FMT_NV12,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV420_2P8P,
> +               .color_planes   = 2,
> +               .mem_planes     = 1,
> +               .depth          = { 8, 16 },
> +               .hsub           = 2,
> +               .vsub           = 2,
> +               .encoding       = MXC_ISI_ENC_YUV,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_YUV8_1X24,
> +               .fourcc         = V4L2_PIX_FMT_NV12M,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV420_2P8P,
> +               .mem_planes     = 2,
> +               .color_planes   = 2,
> +               .depth          = { 8, 16 },
> +               .hsub           = 2,
> +               .vsub           = 2,
> +               .encoding       = MXC_ISI_ENC_YUV,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_YUV8_1X24,
> +               .fourcc         = V4L2_PIX_FMT_NV16,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_2P8P,
> +               .color_planes   = 2,
> +               .mem_planes     = 1,
> +               .depth          = { 8, 16 },
> +               .hsub           = 2,
> +               .vsub           = 1,
> +               .encoding       = MXC_ISI_ENC_YUV,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_YUV8_1X24,
> +               .fourcc         = V4L2_PIX_FMT_NV16M,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_2P8P,
> +               .mem_planes     = 2,
> +               .color_planes   = 2,
> +               .depth          = { 8, 16 },
> +               .hsub           = 2,
> +               .vsub           = 1,
> +               .encoding       = MXC_ISI_ENC_YUV,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_YUV8_1X24,
> +               .fourcc         = V4L2_PIX_FMT_YUV444M,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV444_3P8P,
> +               .mem_planes     = 3,
> +               .color_planes   = 3,
> +               .depth          = { 8, 8, 8 },
> +               .hsub           = 1,
> +               .vsub           = 1,
> +               .encoding       = MXC_ISI_ENC_YUV,
> +       },
> +       /* RGB formats */
> +       {
> +               .mbus_code      = MEDIA_BUS_FMT_RGB888_1X24,
> +               .fourcc         = V4L2_PIX_FMT_RGB565,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT
> +                               | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_in_format  = CHNL_MEM_RD_CTRL_IMG_TYPE_RGB565,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RGB565,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RGB,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_RGB888_1X24,
> +               .fourcc         = V4L2_PIX_FMT_RGB24,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT
> +                               | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_in_format  = CHNL_MEM_RD_CTRL_IMG_TYPE_BGR8P,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_BGR888P,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 24 },
> +               .encoding       = MXC_ISI_ENC_RGB,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_RGB888_1X24,
> +               .fourcc         = V4L2_PIX_FMT_BGR24,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT
> +                               | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_in_format  = CHNL_MEM_RD_CTRL_IMG_TYPE_RGB8P,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RGB888P,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 24 },
> +               .encoding       = MXC_ISI_ENC_RGB,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_RGB888_1X24,
> +               .fourcc         = V4L2_PIX_FMT_XBGR32,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT
> +                               | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_in_format  = CHNL_MEM_RD_CTRL_IMG_TYPE_XBGR8,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_XRGB888,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 32 },
> +               .encoding       = MXC_ISI_ENC_RGB,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_RGB888_1X24,
> +               .fourcc         = V4L2_PIX_FMT_ABGR32,
> +               .type           = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_ARGB8888,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 32 },
> +               .encoding       = MXC_ISI_ENC_RGB,
> +       },
> +       /*
> +        * RAW formats
> +        *
> +        * The ISI shifts the 10-bit and 12-bit formats left by 6 and 4 bits
> +        * when using CHNL_IMG_CTRL_FORMAT_RAW10 or MXC_ISI_OUT_FMT_RAW12
> +        * respectively, to align the bits to the left and pad with zeros in
> +        * the LSBs. The corresponding V4L2 formats are however right-aligned,
> +        * we have to use CHNL_IMG_CTRL_FORMAT_RAW16 to avoid the left shift.
> +        */
> +       {
> +               .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
> +               .fourcc         = V4L2_PIX_FMT_GREY,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 8 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_Y10_1X10,
> +               .fourcc         = V4L2_PIX_FMT_Y10,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_Y12_1X12,
> +               .fourcc         = V4L2_PIX_FMT_Y12,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_Y14_1X14,
> +               .fourcc         = V4L2_PIX_FMT_Y14,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SBGGR8_1X8,
> +               .fourcc         = V4L2_PIX_FMT_SBGGR8,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 8 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGBRG8_1X8,
> +               .fourcc         = V4L2_PIX_FMT_SGBRG8,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 8 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGRBG8_1X8,
> +               .fourcc         = V4L2_PIX_FMT_SGRBG8,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 8 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SRGGB8_1X8,
> +               .fourcc         = V4L2_PIX_FMT_SRGGB8,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 8 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SBGGR10_1X10,
> +               .fourcc         = V4L2_PIX_FMT_SBGGR10,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGBRG10_1X10,
> +               .fourcc         = V4L2_PIX_FMT_SGBRG10,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGRBG10_1X10,
> +               .fourcc         = V4L2_PIX_FMT_SGRBG10,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SRGGB10_1X10,
> +               .fourcc         = V4L2_PIX_FMT_SRGGB10,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SBGGR12_1X12,
> +               .fourcc         = V4L2_PIX_FMT_SBGGR12,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGBRG12_1X12,
> +               .fourcc         = V4L2_PIX_FMT_SGBRG12,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGRBG12_1X12,
> +               .fourcc         = V4L2_PIX_FMT_SGRBG12,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SRGGB12_1X12,
> +               .fourcc         = V4L2_PIX_FMT_SRGGB12,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SBGGR14_1X14,
> +               .fourcc         = V4L2_PIX_FMT_SBGGR14,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGBRG14_1X14,
> +               .fourcc         = V4L2_PIX_FMT_SGBRG14,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SGRBG14_1X14,
> +               .fourcc         = V4L2_PIX_FMT_SGRBG14,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }, {
> +               .mbus_code      = MEDIA_BUS_FMT_SRGGB14_1X14,
> +               .fourcc         = V4L2_PIX_FMT_SRGGB14,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 16 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       },
> +       /* JPEG */
> +       {
> +               .mbus_code      = MEDIA_BUS_FMT_JPEG_1X8,
> +               .fourcc         = V4L2_PIX_FMT_MJPEG,
> +               .type           = MXC_ISI_VIDEO_CAP,
> +               .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
> +               .mem_planes     = 1,
> +               .color_planes   = 1,
> +               .depth          = { 8 },
> +               .encoding       = MXC_ISI_ENC_RAW,
> +       }
> +};
> +
> +const struct mxc_isi_format_info *
> +mxc_isi_format_by_fourcc(u32 fourcc, enum mxc_isi_video_type type)
> +{
> +       unsigned int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) {
> +               const struct mxc_isi_format_info *fmt = &mxc_isi_formats[i];
> +
> +               if (fmt->fourcc == fourcc && fmt->type & type)
> +                       return fmt;
> +       }
> +
> +       return NULL;
> +}
> +
> +const struct mxc_isi_format_info *
> +mxc_isi_format_enum(unsigned int index, enum mxc_isi_video_type type)
> +{
> +       unsigned int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) {
> +               const struct mxc_isi_format_info *fmt = &mxc_isi_formats[i];
> +
> +               if (!(fmt->type & type))
> +                       continue;
> +
> +               if (!index)
> +                       return fmt;
> +
> +               index--;
> +       }
> +
> +       return NULL;
> +}
> +
> +const struct mxc_isi_format_info *
> +mxc_isi_format_try(struct mxc_isi_pipe *pipe, struct v4l2_pix_format_mplane *pix,
> +                  enum mxc_isi_video_type type)
> +{
> +       const struct mxc_isi_format_info *fmt;
> +       unsigned int max_width;
> +       unsigned int i;
> +
> +       max_width = pipe->id == pipe->isi->pdata->num_channels - 1
> +                 ? MXC_ISI_MAX_WIDTH_UNCHAINED
> +                 : MXC_ISI_MAX_WIDTH_CHAINED;
> +
> +       fmt = mxc_isi_format_by_fourcc(pix->pixelformat, type);
> +       if (!fmt)
> +               fmt = &mxc_isi_formats[0];
> +
> +       pix->width = clamp(pix->width, MXC_ISI_MIN_WIDTH, max_width);
> +       pix->height = clamp(pix->height, MXC_ISI_MIN_HEIGHT, MXC_ISI_MAX_HEIGHT);
> +       pix->pixelformat = fmt->fourcc;
> +       pix->field = V4L2_FIELD_NONE;
> +
> +       if (pix->colorspace == V4L2_COLORSPACE_DEFAULT) {
> +               pix->colorspace = MXC_ISI_DEF_COLOR_SPACE;
> +               pix->ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC;
> +               pix->quantization = MXC_ISI_DEF_QUANTIZATION;
> +               pix->xfer_func = MXC_ISI_DEF_XFER_FUNC;
> +       }
> +
> +       if (pix->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT)
> +               pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
> +       if (pix->quantization == V4L2_QUANTIZATION_DEFAULT) {
> +               bool is_rgb = fmt->encoding == MXC_ISI_ENC_RGB;
> +
> +               pix->quantization =
> +                       V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb, pix->colorspace,
> +                                                     pix->ycbcr_enc);
> +       }
> +       if (pix->xfer_func == V4L2_XFER_FUNC_DEFAULT)
> +               pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace);
> +
> +       pix->num_planes = fmt->mem_planes;
> +
> +       for (i = 0; i < fmt->color_planes; ++i) {
> +               struct v4l2_plane_pix_format *plane = &pix->plane_fmt[i];
> +               unsigned int bpl;
> +
> +               /* The pitch must be identical for all planes. */
> +               if (i == 0)
> +                       bpl = clamp(plane->bytesperline,
> +                                   pix->width * fmt->depth[0] / 8,
> +                                   65535U);
> +               else
> +                       bpl = pix->plane_fmt[0].bytesperline;
> +
> +               plane->bytesperline = bpl;
> +
> +               plane->sizeimage = plane->bytesperline * pix->height;
> +               if (i >= 1)
> +                       plane->sizeimage /= fmt->vsub;
> +       }
> +
> +       /*
> +        * For single-planar pixel formats with multiple color planes,
> +        * concatenate the size of all planes and clear all planes but the
> +        * first one.
> +        */
> +       if (fmt->color_planes != fmt->mem_planes) {
> +               for (i = 1; i < fmt->color_planes; ++i) {
> +                       struct v4l2_plane_pix_format *plane = &pix->plane_fmt[i];
> +
> +                       pix->plane_fmt[0].sizeimage += plane->sizeimage;
> +                       plane->bytesperline = 0;
> +                       plane->sizeimage = 0;
> +               }
> +       }
> +
> +       return fmt;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * videobuf2 queue operations
> + */
> +
> +static void mxc_isi_video_frame_write_done(struct mxc_isi_pipe *pipe,
> +                                          u32 status)
> +{
> +       struct mxc_isi_video *video = &pipe->video;
> +       struct device *dev = pipe->isi->dev;
> +       struct mxc_isi_buffer *next_buf;
> +       struct mxc_isi_buffer *buf;
> +       enum mxc_isi_buf_id buf_id;
> +
> +       spin_lock(&video->buf_lock);
> +
> +       /*
> +        * The ISI hardware handles buffers using a ping-pong mechanism with
> +        * two sets of destination addresses (with shadow registers to allow
> +        * programming addresses for all planes atomically) named BUF1 and
> +        * BUF2. Addresses can be loaded and copied to shadow registers at any
> +        * at any time.
> +        *
> +        * The hardware keeps track of which buffer is being written to and
> +        * automatically switches to the other buffer at frame end, copying the
> +        * corresponding address to another set of shadow registers that track
> +        * the address being written to. The active buffer tracking bits are
> +        * accessible through the CHNL_STS register.
> +        *
> +        *  BUF1        BUF2  |   Event   | Action
> +        *                    |           |
> +        *                    |           | Program initial buffers
> +        *                    |           | B0 in BUF1, B1 in BUF2
> +        *                    | Start ISI |
> +        * +----+             |           |
> +        * | B0 |             |           |
> +        * +----+             |           |
> +        *             +----+ | FRM IRQ 0 | B0 complete, BUF2 now active
> +        *             | B1 | |           | Program B2 in BUF1
> +        *             +----+ |           |
> +        * +----+             | FRM IRQ 1 | B1 complete, BUF1 now active
> +        * | B2 |             |           | Program B3 in BUF2
> +        * +----+             |           |
> +        *             +----+ | FRM IRQ 2 | B2 complete, BUF2 now active
> +        *             | B3 | |           | Program B4 in BUF1
> +        *             +----+ |           |
> +        * +----+             | FRM IRQ 3 | B3 complete, BUF1 now active
> +        * | B4 |             |           | Program B5 in BUF2
> +        * +----+             |           |
> +        *        ...         |           |
> +        *
> +        * Races between address programming and buffer switching can be
> +        * detected by checking if a frame end interrupt occurred after
> +        * programming the addresses.
> +        *
> +        * As none of the shadow registers are accessible, races can occur
> +        * between address programming and buffer switching. It is possible to
> +        * detect the race condition by checking if a frame end interrupt
> +        * occurred after programming the addresses, but impossible to
> +        * determine if the race has been won or lost.
> +        *
> +        * In addition to this, we need to use discard buffers if no pending
> +        * buffers are available. To simplify handling of discard buffer, we
> +        * need to allocate three of them, as two can be active concurrently
> +        * and we need to still be able to get hold of a next buffer. The logic
> +        * could be improved to use two buffers only, but as all discard
> +        * buffers share the same memory, an additional buffer is cheap.
> +        */
> +
> +       /* Check which buffer has just completed. */
> +       buf_id = pipe->isi->pdata->buf_active_reverse
> +              ? (status & CHNL_STS_BUF1_ACTIVE ? MXC_ISI_BUF2 : MXC_ISI_BUF1)
> +              : (status & CHNL_STS_BUF1_ACTIVE ? MXC_ISI_BUF1 : MXC_ISI_BUF2);
> +
> +       buf = list_first_entry_or_null(&video->out_active,
> +                                      struct mxc_isi_buffer, list);
> +
> +       /* Safety check, this should really never happen. */
> +       if (!buf) {
> +               dev_warn(dev, "trying to access empty active list\n");
> +               goto done;
> +       }
> +
> +       /*
> +        * If the buffer that has completed doesn't match the buffer on the
> +        * front of the active list, it means we have lost one frame end
> +        * interrupt (or possibly a large odd number of interrupts, although
> +        * quite unlikely).
> +        *
> +        * For instance, if IRQ1 is lost and we handle IRQ2, both B1 and B2
> +        * have been completed, but B3 hasn't been programmed, BUF2 still
> +        * addresses B1 and the ISI is now writing in B1 instead of B3. We
> +        * can't complete B2 as that would result in out-of-order completion.
> +        *
> +        * The only option is to ignore this interrupt and try again. When IRQ3
> +        * will be handled, we will complete B1 and be in sync again.
> +        */
> +       if (buf->id != buf_id) {
> +               dev_dbg(dev, "buffer ID mismatch (expected %u, got %u), skipping\n",
> +                       buf->id, buf_id);
> +
> +               /*
> +                * Increment the frame count by two to account for the missed
> +                * and the ignored interrupts.
> +                */
> +               video->frame_count += 2;
> +               goto done;
> +       }
> +
> +       /* Pick the next buffer and queue it to the hardware. */
> +       next_buf = list_first_entry_or_null(&video->out_pending,
> +                                           struct mxc_isi_buffer, list);
> +       if (!next_buf) {
> +               next_buf = list_first_entry_or_null(&video->out_discard,
> +                                                   struct mxc_isi_buffer, list);
> +
> +               /* Safety check, this should never happen. */
> +               if (!next_buf) {
> +                       dev_warn(dev, "trying to access empty discard list\n");
> +                       goto done;
> +               }
> +       }
> +
> +       mxc_isi_channel_set_outbuf(pipe, next_buf->dma_addrs, buf_id);
> +       next_buf->id = buf_id;
> +
> +       /*
> +        * Check if we have raced with the end of frame interrupt. If so, we
> +        * can't tell if the ISI has recorded the new address, or is still
> +        * using the previous buffer. We must assume the latter as that is the
> +        * worst case.
> +        *
> +        * For instance, if we are handling IRQ1 and now detect the FRM
> +        * interrupt, assume B2 has completed and the ISI has switched to BUF2
> +        * using B1 just before we programmed B3. Unlike in the previous race
> +        * condition, B3 has been programmed and will be written to the next
> +        * time the ISI switches to BUF2. We can however handle this exactly as
> +        * the first race condition, as we'll program B3 (still at the head of
> +        * the pending list) when handling IRQ3.
> +        */
> +       status = mxc_isi_channel_irq_status(pipe, false);
> +       if (status & CHNL_STS_FRM_STRD) {
> +               dev_dbg(dev, "raced with frame end interrupt\n");
> +               video->frame_count += 2;
> +               goto done;
> +       }
> +
> +       /*
> +        * The next buffer has been queued successfully, move it to the active
> +        * list, and complete the current buffer.
> +        */
> +       list_move_tail(&next_buf->list, &video->out_active);
> +
> +       if (!buf->discard) {
> +               list_del_init(&buf->list);
> +               buf->v4l2_buf.sequence = video->frame_count;
> +               buf->v4l2_buf.vb2_buf.timestamp = ktime_get_ns();
> +               vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_DONE);
> +       } else {
> +               list_move_tail(&buf->list, &video->out_discard);
> +       }
> +
> +       video->frame_count++;
> +
> +done:
> +       spin_unlock(&video->buf_lock);
> +}
> +
> +static void mxc_isi_video_free_discard_buffers(struct mxc_isi_video *video)
> +{
> +       unsigned int i;
> +
> +       for (i = 0; i < video->pix.num_planes; i++) {
> +               struct mxc_isi_dma_buffer *buf = &video->discard_buffer[i];
> +
> +               if (!buf->addr)
> +                       continue;
> +
> +               dma_free_coherent(video->pipe->isi->dev, buf->size, buf->addr,
> +                                 buf->dma);
> +               buf->addr = NULL;
> +       }
> +}
> +
> +static int mxc_isi_video_alloc_discard_buffers(struct mxc_isi_video *video)
> +{
> +       unsigned int i, j;
> +
> +       /* Allocate memory for each plane. */
> +       for (i = 0; i < video->pix.num_planes; i++) {
> +               struct mxc_isi_dma_buffer *buf = &video->discard_buffer[i];
> +
> +               buf->size = PAGE_ALIGN(video->pix.plane_fmt[i].sizeimage);
> +               buf->addr = dma_alloc_coherent(video->pipe->isi->dev, buf->size,
> +                                              &buf->dma, GFP_DMA | GFP_KERNEL);
> +               if (!buf->addr) {
> +                       mxc_isi_video_free_discard_buffers(video);
> +                       return -ENOMEM;
> +               }
> +
> +               dev_dbg(video->pipe->isi->dev,
> +                       "discard buffer plane %u: %zu bytes @%pad (CPU address %p)\n",
> +                       i, buf->size, &buf->dma, buf->addr);
> +       }
> +
> +       /* Fill the DMA addresses in the discard buffers. */
> +       for (i = 0; i < ARRAY_SIZE(video->buf_discard); ++i) {
> +               struct mxc_isi_buffer *buf = &video->buf_discard[i];
> +
> +               buf->discard = true;
> +
> +               for (j = 0; j < video->pix.num_planes; ++j)
> +                       buf->dma_addrs[j] = video->discard_buffer[j].dma;
> +       }
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_video_validate_format(struct mxc_isi_video *video)
> +{
> +       const struct v4l2_mbus_framefmt *format;
> +       const struct mxc_isi_format_info *info;
> +       struct v4l2_subdev_state *state;
> +       struct v4l2_subdev *sd = &video->pipe->sd;
> +       int ret = 0;
> +
> +       state = v4l2_subdev_lock_and_get_active_state(sd);
> +
> +       info = mxc_isi_format_by_fourcc(video->pix.pixelformat,
> +                                       MXC_ISI_VIDEO_CAP);
> +       format = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SOURCE);
> +
> +       if (format->code != info->mbus_code ||
> +           format->width != video->pix.width ||
> +           format->height != video->pix.height) {
> +               dev_dbg(video->pipe->isi->dev,
> +                       "%s: configuration mismatch, 0x%04x/%ux%u != 0x%04x/%ux%u\n",
> +                       __func__, format->code, format->width, format->height,
> +                       info->mbus_code, video->pix.width, video->pix.height);
> +               ret = -EINVAL;
> +       }
> +
> +       v4l2_subdev_unlock_state(state);
> +
> +       return ret;
> +}
> +
> +static void mxc_isi_video_return_buffers(struct mxc_isi_video *video,
> +                                        enum vb2_buffer_state state)
> +{
> +       struct mxc_isi_buffer *buf;
> +
> +       spin_lock_irq(&video->buf_lock);
> +
> +       while (!list_empty(&video->out_active)) {
> +               buf = list_first_entry(&video->out_active,
> +                                      struct mxc_isi_buffer, list);
> +               list_del_init(&buf->list);
> +               if (buf->discard)
> +                       continue;
> +
> +               vb2_buffer_done(&buf->v4l2_buf.vb2_buf, state);
> +       }
> +
> +       while (!list_empty(&video->out_pending)) {
> +               buf = list_first_entry(&video->out_pending,
> +                                      struct mxc_isi_buffer, list);
> +               list_del_init(&buf->list);
> +               vb2_buffer_done(&buf->v4l2_buf.vb2_buf, state);
> +       }
> +
> +       while (!list_empty(&video->out_discard)) {
> +               buf = list_first_entry(&video->out_discard,
> +                                      struct mxc_isi_buffer, list);
> +               list_del_init(&buf->list);
> +       }
> +
> +       INIT_LIST_HEAD(&video->out_active);
> +       INIT_LIST_HEAD(&video->out_pending);
> +       INIT_LIST_HEAD(&video->out_discard);
> +
> +       spin_unlock_irq(&video->buf_lock);
> +}
> +
> +static void mxc_isi_video_queue_first_buffers(struct mxc_isi_video *video)
> +{
> +       unsigned int discard;
> +       unsigned int i;
> +
> +       lockdep_assert_held(&video->buf_lock);
> +
> +       /*
> +        * Queue two ISI channel output buffers. We are not guaranteed to have
> +        * any buffer in the pending list when this function is called from the
> +        * system resume handler. Use pending buffers as much as possible, and
> +        * use discard buffers to fill the remaining slots.
> +        */
> +
> +       /* How many discard buffers do we need to queue first ? */
> +       discard = list_empty(&video->out_pending) ? 2
> +               : list_is_singular(&video->out_pending) ? 1
> +               : 0;
> +
> +       for (i = 0; i < 2; ++i) {
> +               enum mxc_isi_buf_id buf_id = i == 0 ? MXC_ISI_BUF1
> +                                          : MXC_ISI_BUF2;
> +               struct mxc_isi_buffer *buf;
> +               struct list_head *list;
> +
> +               list = i < discard ? &video->out_discard : &video->out_pending;
> +               buf = list_first_entry(list, struct mxc_isi_buffer, list);
> +
> +               mxc_isi_channel_set_outbuf(video->pipe, buf->dma_addrs, buf_id);
> +               buf->id = buf_id;
> +               list_move_tail(&buf->list, &video->out_active);
> +       }
> +}
> +
> +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);
> +}
> +
> +int mxc_isi_video_queue_setup(const struct v4l2_pix_format_mplane *format,
> +                             const struct mxc_isi_format_info *info,
> +                             unsigned int *num_buffers,
> +                             unsigned int *num_planes, unsigned int sizes[])
> +{
> +       unsigned int i;
> +
> +       if (*num_planes) {
> +               if (*num_planes != info->mem_planes)
> +                       return -EINVAL;
> +
> +               for (i = 0; i < info->mem_planes; ++i) {
> +                       if (sizes[i] < format->plane_fmt[i].sizeimage)
> +                               return -EINVAL;
> +               }
> +
> +               return 0;
> +       }
> +
> +       *num_planes = info->mem_planes;
> +
> +       for (i = 0; i < info->mem_planes; ++i)
> +               sizes[i] = format->plane_fmt[i].sizeimage;
> +
> +       return 0;
> +}
> +
> +void mxc_isi_video_buffer_init(struct vb2_buffer *vb2, dma_addr_t dma_addrs[3],
> +                              const struct mxc_isi_format_info *info,
> +                              const struct v4l2_pix_format_mplane *pix)
> +{
> +       unsigned int i;
> +
> +       for (i = 0; i < info->mem_planes; ++i)
> +               dma_addrs[i] = vb2_dma_contig_plane_dma_addr(vb2, i);
> +
> +       /*
> +        * For single-planar pixel formats with multiple color planes, split
> +        * the buffer into color planes.
> +        */
> +       if (info->color_planes != info->mem_planes) {
> +               unsigned int size = pix->plane_fmt[0].bytesperline * pix->height;
> +
> +               for (i = 1; i < info->color_planes; ++i) {
> +                       unsigned int vsub = i > 1 ? info->vsub : 1;
> +
> +                       dma_addrs[i] = dma_addrs[i-1] + size / vsub;
> +               }
> +       }
> +}
> +
> +int mxc_isi_video_buffer_prepare(struct mxc_isi_dev *isi, struct vb2_buffer *vb2,
> +                                const struct mxc_isi_format_info *info,
> +                                const struct v4l2_pix_format_mplane *pix)
> +{
> +       unsigned int i;
> +
> +       for (i = 0; i < info->mem_planes; i++) {
> +               unsigned long size = pix->plane_fmt[i].sizeimage;
> +
> +               if (vb2_plane_size(vb2, i) < size) {
> +                       dev_err(isi->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 int mxc_isi_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_video *video = vb2_get_drv_priv(q);
> +
> +       return mxc_isi_video_queue_setup(&video->pix, video->fmtinfo,
> +                                        num_buffers, num_planes, sizes);
> +}
> +
> +static int mxc_isi_vb2_buffer_init(struct vb2_buffer *vb2)
> +{
> +       struct mxc_isi_buffer *buf = to_isi_buffer(to_vb2_v4l2_buffer(vb2));
> +       struct mxc_isi_video *video = vb2_get_drv_priv(vb2->vb2_queue);
> +
> +       mxc_isi_video_buffer_init(vb2, buf->dma_addrs, video->fmtinfo,
> +                                 &video->pix);
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_vb2_buffer_prepare(struct vb2_buffer *vb2)
> +{
> +       struct mxc_isi_video *video = vb2_get_drv_priv(vb2->vb2_queue);
> +
> +       return mxc_isi_video_buffer_prepare(video->pipe->isi, vb2,
> +                                           video->fmtinfo, &video->pix);
> +}
> +
> +static void mxc_isi_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_video *video = vb2_get_drv_priv(vb2->vb2_queue);
> +
> +       spin_lock_irq(&video->buf_lock);
> +       list_add_tail(&buf->list, &video->out_pending);
> +       spin_unlock_irq(&video->buf_lock);
> +}
> +
> +static void mxc_isi_video_init_channel(struct mxc_isi_video *video)
> +{
> +       struct mxc_isi_pipe *pipe = video->pipe;
> +
> +       mxc_isi_channel_get(pipe);
> +
> +       mutex_lock(video->ctrls.handler.lock);
> +       mxc_isi_channel_set_alpha(pipe, video->ctrls.alpha);
> +       mxc_isi_channel_set_flip(pipe, video->ctrls.hflip, video->ctrls.vflip);
> +       mutex_unlock(video->ctrls.handler.lock);
> +
> +       mxc_isi_channel_set_output_format(pipe, video->fmtinfo, &video->pix);
> +}
> +
> +static int mxc_isi_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
> +{
> +       struct mxc_isi_video *video = vb2_get_drv_priv(q);
> +       unsigned int i;
> +       int ret;
> +
> +       /* Initialize the ISI channel. */
> +       mxc_isi_video_init_channel(video);
> +
> +       spin_lock_irq(&video->buf_lock);
> +
> +       /* Add the discard buffers to the out_discard list. */
> +       for (i = 0; i < ARRAY_SIZE(video->buf_discard); ++i) {
> +               struct mxc_isi_buffer *buf = &video->buf_discard[i];
> +
> +               list_add_tail(&buf->list, &video->out_discard);
> +       }
> +
> +       /* Queue the first buffers. */
> +       mxc_isi_video_queue_first_buffers(video);
> +
> +       /* Clear frame count */
> +       video->frame_count = 0;
> +
> +       spin_unlock_irq(&video->buf_lock);
> +
> +       ret = mxc_isi_pipe_enable(video->pipe);
> +       if (ret)
> +               goto error;
> +
> +       return 0;
> +
> +error:
> +       mxc_isi_channel_put(video->pipe);
> +       mxc_isi_video_return_buffers(video, VB2_BUF_STATE_QUEUED);
> +       return ret;
> +}
> +
> +static void mxc_isi_vb2_stop_streaming(struct vb2_queue *q)
> +{
> +       struct mxc_isi_video *video = vb2_get_drv_priv(q);
> +
> +       mxc_isi_pipe_disable(video->pipe);
> +       mxc_isi_channel_put(video->pipe);
> +
> +       mxc_isi_video_return_buffers(video, VB2_BUF_STATE_ERROR);
> +}
> +
> +static const struct vb2_ops mxc_isi_vb2_qops = {
> +       .queue_setup            = mxc_isi_vb2_queue_setup,
> +       .buf_init               = mxc_isi_vb2_buffer_init,
> +       .buf_prepare            = mxc_isi_vb2_buffer_prepare,
> +       .buf_queue              = mxc_isi_vb2_buffer_queue,
> +       .wait_prepare           = vb2_ops_wait_prepare,
> +       .wait_finish            = vb2_ops_wait_finish,
> +       .start_streaming        = mxc_isi_vb2_start_streaming,
> +       .stop_streaming         = mxc_isi_vb2_stop_streaming,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 controls
> + */
> +
> +static inline struct mxc_isi_video *ctrl_to_isi_video(struct v4l2_ctrl *ctrl)
> +{
> +       return container_of(ctrl->handler, struct mxc_isi_video, ctrls.handler);
> +}
> +
> +static int mxc_isi_video_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +       struct mxc_isi_video *video = ctrl_to_isi_video(ctrl);
> +
> +       switch (ctrl->id) {
> +       case V4L2_CID_ALPHA_COMPONENT:
> +               video->ctrls.alpha = ctrl->val;
> +               break;
> +       case V4L2_CID_VFLIP:
> +               video->ctrls.vflip = ctrl->val;
> +               break;
> +       case V4L2_CID_HFLIP:
> +               video->ctrls.hflip = ctrl->val;
> +               break;
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct v4l2_ctrl_ops mxc_isi_video_ctrl_ops = {
> +       .s_ctrl = mxc_isi_video_s_ctrl,
> +};
> +
> +static int mxc_isi_video_ctrls_create(struct mxc_isi_video *video)
> +{
> +       struct v4l2_ctrl_handler *handler = &video->ctrls.handler;
> +       int ret;
> +
> +       v4l2_ctrl_handler_init(handler, 3);
> +
> +       v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops,
> +                         V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0);
> +
> +       v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops,
> +                         V4L2_CID_VFLIP, 0, 1, 1, 0);
> +
> +       v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops,
> +                         V4L2_CID_HFLIP, 0, 1, 1, 0);
> +
> +       if (handler->error) {
> +               ret = handler->error;
> +               v4l2_ctrl_handler_free(handler);
> +               return ret;
> +       }
> +
> +       video->vdev.ctrl_handler = handler;
> +
> +       return 0;
> +}
> +
> +static void mxc_isi_video_ctrls_delete(struct mxc_isi_video *video)
> +{
> +       v4l2_ctrl_handler_free(&video->ctrls.handler);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 ioctls
> + */
> +
> +static int mxc_isi_video_querycap(struct file *file, void *priv,
> +                                 struct v4l2_capability *cap)
> +{
> +       strscpy(cap->driver, MXC_ISI_DRIVER_NAME, sizeof(cap->driver));
> +       strscpy(cap->card, MXC_ISI_CAPTURE, sizeof(cap->card));
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_video_enum_fmt(struct file *file, void *priv,
> +                                 struct v4l2_fmtdesc *f)
> +{
> +       const struct mxc_isi_format_info *fmt;
> +       unsigned int index = f->index;
> +       unsigned int i;
> +
> +       if (f->mbus_code) {
> +               /*
> +                * If a media bus code is specified, only enumerate formats
> +                * compatible with it.
> +                */
> +               for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) {
> +                       fmt = &mxc_isi_formats[i];
> +                       if (fmt->mbus_code != f->mbus_code)
> +                               continue;
> +
> +                       if (index == 0)
> +                               break;
> +
> +                       index--;
> +               }
> +
> +               if (i == ARRAY_SIZE(mxc_isi_formats))
> +                       return -EINVAL;
> +       } else {
> +               /* Otherwise, enumerate all formatS. */
> +               if (f->index >= ARRAY_SIZE(mxc_isi_formats))
> +                       return -EINVAL;
> +
> +               fmt = &mxc_isi_formats[f->index];
> +       }
> +
> +       f->pixelformat = fmt->fourcc;
> +       f->flags |= V4L2_FMT_FLAG_CSC_COLORSPACE | V4L2_FMT_FLAG_CSC_YCBCR_ENC
> +                |  V4L2_FMT_FLAG_CSC_QUANTIZATION | V4L2_FMT_FLAG_CSC_XFER_FUNC;
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_video_g_fmt(struct file *file, void *fh,
> +                              struct v4l2_format *f)
> +{
> +       struct mxc_isi_video *video = video_drvdata(file);
> +
> +       f->fmt.pix_mp = video->pix;
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_video_try_fmt(struct file *file, void *fh,
> +                                struct v4l2_format *f)
> +{
> +       struct mxc_isi_video *video = video_drvdata(file);
> +
> +       mxc_isi_format_try(video->pipe, &f->fmt.pix_mp, MXC_ISI_VIDEO_CAP);
> +       return 0;
> +}
> +
> +static int mxc_isi_video_s_fmt(struct file *file, void *priv,
> +                              struct v4l2_format *f)
> +{
> +       struct mxc_isi_video *video = video_drvdata(file);
> +       struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
> +
> +       if (vb2_is_busy(&video->vb2_q))
> +               return -EBUSY;
> +
> +       video->fmtinfo = mxc_isi_format_try(video->pipe, pix, MXC_ISI_VIDEO_CAP);
> +       video->pix = *pix;
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_video_streamon(struct file *file, void *priv,
> +                                 enum v4l2_buf_type type)
> +{
> +       struct mxc_isi_video *video = video_drvdata(file);
> +       struct media_device *mdev = &video->pipe->isi->media_dev;
> +       struct media_pipeline *pipe;
> +       int ret;
> +
> +       if (vb2_queue_is_busy(&video->vb2_q, file))
> +               return -EBUSY;
> +
> +       /*
> +        * Get a pipeline for the video node and start it. This must be done
> +        * here and not in the queue .start_streaming() handler, so that
> +        * pipeline start errors can be reported from VIDIOC_STREAMON and not
> +        * delayed until subsequent VIDIOC_QBUF calls.
> +        */
> +       mutex_lock(&mdev->graph_mutex);
> +
> +       ret = mxc_isi_pipe_acquire(video->pipe, &mxc_isi_video_frame_write_done);
> +       if (ret) {
> +               mutex_unlock(&mdev->graph_mutex);
> +               return ret;
> +       }
> +
> +       pipe = media_entity_pipeline(&video->vdev.entity) ? : &video->pipe->pipe;
> +
> +       ret = __media_pipeline_start(video->vdev.entity.pads, pipe);
> +       if (ret) {
> +               mutex_unlock(&mdev->graph_mutex);
> +               goto err_release;
> +       }
> +
> +       mutex_unlock(&mdev->graph_mutex);
> +
> +       /* Verify that the video format matches the output of the subdev. */
> +       ret = mxc_isi_video_validate_format(video);
> +       if (ret)
> +               goto err_stop;
> +
> +       /* Allocate buffers for discard operation. */
> +       ret = mxc_isi_video_alloc_discard_buffers(video);
> +       if (ret)
> +               goto err_stop;
> +
> +       ret = vb2_streamon(&video->vb2_q, type);
> +       if (ret)
> +               goto err_free;
> +
> +       video->is_streaming = true;
> +
> +       return 0;
> +
> +err_free:
> +       mxc_isi_video_free_discard_buffers(video);
> +err_stop:
> +       media_pipeline_stop(video->vdev.entity.pads);
> +err_release:
> +       mxc_isi_pipe_release(video->pipe);
> +       return ret;
> +}
> +
> +
> +static void mxc_isi_video_cleanup_streaming(struct mxc_isi_video *video)
> +{
> +       lockdep_assert_held(&video->lock);
> +
> +       if (!video->is_streaming)
> +               return;
> +
> +       mxc_isi_video_free_discard_buffers(video);
> +       media_pipeline_stop(video->vdev.entity.pads);
> +       mxc_isi_pipe_release(video->pipe);
> +
> +       video->is_streaming = false;
> +}
> +
> +static int mxc_isi_video_streamoff(struct file *file, void *priv,
> +                                  enum v4l2_buf_type type)
> +{
> +       struct mxc_isi_video *video = video_drvdata(file);
> +       int ret;
> +
> +       ret = vb2_ioctl_streamoff(file, priv, type);
> +       if (ret)
> +               return ret;
> +
> +       mxc_isi_video_cleanup_streaming(video);
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_video_enum_framesizes(struct file *file, void *priv,
> +                                        struct v4l2_frmsizeenum *fsize)
> +{
> +       struct mxc_isi_video *video = video_drvdata(file);
> +       const struct mxc_isi_format_info *info;
> +       unsigned int max_width;
> +       unsigned int h_align;
> +       unsigned int v_align;
> +
> +       if (fsize->index)
> +               return -EINVAL;
> +
> +       info = mxc_isi_format_by_fourcc(fsize->pixel_format, MXC_ISI_VIDEO_CAP);
> +       if (!info)
> +               return -EINVAL;
> +
> +       h_align = max_t(unsigned int, info->hsub, 1);
> +       v_align = max_t(unsigned int, info->vsub, 1);
> +
> +       max_width = video->pipe->id == video->pipe->isi->pdata->num_channels - 1
> +                 ? MXC_ISI_MAX_WIDTH_UNCHAINED
> +                 : MXC_ISI_MAX_WIDTH_CHAINED;
> +
> +       fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +       fsize->stepwise.min_width = ALIGN(MXC_ISI_MIN_WIDTH, h_align);
> +       fsize->stepwise.min_height = ALIGN(MXC_ISI_MIN_HEIGHT, v_align);
> +       fsize->stepwise.max_width = ALIGN_DOWN(max_width, h_align);
> +       fsize->stepwise.max_height = ALIGN_DOWN(MXC_ISI_MAX_HEIGHT, v_align);
> +       fsize->stepwise.step_width = h_align;
> +       fsize->stepwise.step_height = v_align;
> +
> +       /*
> +        * The width can be further restricted due to line buffer sharing
> +        * between pipelines when scaling, but we have no way to know here if
> +        * the scaler will be used.
> +        */
> +
> +       return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops mxc_isi_video_ioctl_ops = {
> +       .vidioc_querycap                = mxc_isi_video_querycap,
> +
> +       .vidioc_enum_fmt_vid_cap        = mxc_isi_video_enum_fmt,
> +       .vidioc_try_fmt_vid_cap_mplane  = mxc_isi_video_try_fmt,
> +       .vidioc_s_fmt_vid_cap_mplane    = mxc_isi_video_s_fmt,
> +       .vidioc_g_fmt_vid_cap_mplane    = mxc_isi_video_g_fmt,
> +
> +       .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_streamon                = mxc_isi_video_streamon,
> +       .vidioc_streamoff               = mxc_isi_video_streamoff,
> +
> +       .vidioc_enum_framesizes         = mxc_isi_video_enum_framesizes,
> +
> +       .vidioc_subscribe_event         = v4l2_ctrl_subscribe_event,
> +       .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Video device file operations
> + */
> +
> +static int mxc_isi_video_open(struct file *file)
> +{
> +       struct mxc_isi_video *video = video_drvdata(file);
> +       int ret;
> +
> +       ret = v4l2_fh_open(file);
> +       if (ret)
> +               return ret;
> +
> +       ret = pm_runtime_resume_and_get(video->pipe->isi->dev);
> +       if (ret) {
> +               v4l2_fh_release(file);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int mxc_isi_video_release(struct file *file)
> +{
> +       struct mxc_isi_video *video = video_drvdata(file);
> +       int ret;
> +
> +       ret = vb2_fop_release(file);
> +       if (ret)
> +               dev_err(video->pipe->isi->dev, "%s fail\n", __func__);
> +
> +       mutex_lock(&video->lock);
> +       mxc_isi_video_cleanup_streaming(video);
> +       mutex_unlock(&video->lock);
> +
> +       pm_runtime_put(video->pipe->isi->dev);
> +       return ret;
> +}
> +
> +static const struct v4l2_file_operations mxc_isi_video_fops = {
> +       .owner          = THIS_MODULE,
> +       .open           = mxc_isi_video_open,
> +       .release        = mxc_isi_video_release,
> +       .poll           = vb2_fop_poll,
> +       .unlocked_ioctl = video_ioctl2,
> +       .mmap           = vb2_fop_mmap,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Suspend & resume
> + */
> +
> +void mxc_isi_video_suspend(struct mxc_isi_pipe *pipe)
> +{
> +       struct mxc_isi_video *video = &pipe->video;
> +
> +       if (!video->is_streaming)
> +               return;
> +
> +       mxc_isi_pipe_disable(pipe);
> +       mxc_isi_channel_put(pipe);
> +
> +       spin_lock_irq(&video->buf_lock);
> +
> +       /*
> +        * Move the active buffers back to the pending or discard list. We must
> +        * iterate the active list backward and move the buffers to the head of
> +        * the pending list to preserve the buffer queueing order.
> +        */
> +       while (!list_empty(&video->out_active)) {
> +               struct mxc_isi_buffer *buf =
> +                       list_last_entry(&video->out_active,
> +                                       struct mxc_isi_buffer, list);
> +
> +               if (buf->discard)
> +                       list_move(&buf->list, &video->out_discard);
> +               else
> +                       list_move(&buf->list, &video->out_pending);
> +       }
> +
> +       spin_unlock_irq(&video->buf_lock);
> +}
> +
> +int mxc_isi_video_resume(struct mxc_isi_pipe *pipe)
> +{
> +       struct mxc_isi_video *video = &pipe->video;
> +
> +       if (!video->is_streaming)
> +               return 0;
> +
> +       mxc_isi_video_init_channel(video);
> +
> +       spin_lock_irq(&video->buf_lock);
> +       mxc_isi_video_queue_first_buffers(video);
> +       spin_unlock_irq(&video->buf_lock);
> +
> +       return mxc_isi_pipe_enable(pipe);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Registration
> + */
> +
> +int mxc_isi_video_register(struct mxc_isi_pipe *pipe,
> +                          struct v4l2_device *v4l2_dev)
> +{
> +       struct mxc_isi_video *video = &pipe->video;
> +       struct v4l2_pix_format_mplane *pix = &video->pix;
> +       struct video_device *vdev = &video->vdev;
> +       struct vb2_queue *q = &video->vb2_q;
> +       int ret = -ENOMEM;
> +
> +       video->pipe = pipe;
> +
> +       mutex_init(&video->lock);
> +       spin_lock_init(&video->buf_lock);
> +
> +       pix->width = MXC_ISI_DEF_WIDTH;
> +       pix->height = MXC_ISI_DEF_HEIGHT;
> +       pix->pixelformat = MXC_ISI_DEF_PIXEL_FORMAT;
> +       pix->colorspace = MXC_ISI_DEF_COLOR_SPACE;
> +       pix->ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC;
> +       pix->quantization = MXC_ISI_DEF_QUANTIZATION;
> +       pix->xfer_func = MXC_ISI_DEF_XFER_FUNC;
> +       video->fmtinfo = mxc_isi_format_try(video->pipe, pix, MXC_ISI_VIDEO_CAP);
> +
> +       memset(vdev, 0, sizeof(*vdev));
> +       snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.capture", pipe->id);
> +
> +       vdev->fops      = &mxc_isi_video_fops;
> +       vdev->ioctl_ops = &mxc_isi_video_ioctl_ops;
> +       vdev->v4l2_dev  = v4l2_dev;
> +       vdev->minor     = -1;
> +       vdev->release   = video_device_release_empty;
> +       vdev->queue     = q;
> +       vdev->lock      = &video->lock;
> +
> +       vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE
> +                         | V4L2_CAP_IO_MC;
> +       video_set_drvdata(vdev, video);
> +
> +       INIT_LIST_HEAD(&video->out_pending);
> +       INIT_LIST_HEAD(&video->out_active);
> +       INIT_LIST_HEAD(&video->out_discard);
> +
> +       memset(q, 0, sizeof(*q));
> +       q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +       q->io_modes = VB2_MMAP | VB2_DMABUF;
> +       q->drv_priv = video;
> +       q->ops = &mxc_isi_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->min_buffers_needed = 2;
> +       q->lock = &video->lock;
> +       q->dev = pipe->isi->dev;
> +
> +       ret = vb2_queue_init(q);
> +       if (ret)
> +               goto err_free_ctx;
> +
> +       video->pad.flags = MEDIA_PAD_FL_SINK;
> +       vdev->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
> +       ret = media_entity_pads_init(&vdev->entity, 1, &video->pad);
> +       if (ret)
> +               goto err_free_ctx;
> +
> +       ret = mxc_isi_video_ctrls_create(video);
> +       if (ret)
> +               goto err_me_cleanup;
> +
> +       ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +       if (ret)
> +               goto err_ctrl_free;
> +
> +       ret = media_create_pad_link(&pipe->sd.entity,
> +                                   MXC_ISI_PIPE_PAD_SOURCE,
> +                                   &vdev->entity, 0,
> +                                   MEDIA_LNK_FL_IMMUTABLE |
> +                                   MEDIA_LNK_FL_ENABLED);
> +       if (ret)
> +               goto err_video_unreg;
> +
> +       return 0;
> +
> +err_video_unreg:
> +       video_unregister_device(vdev);
> +err_ctrl_free:
> +       mxc_isi_video_ctrls_delete(video);
> +err_me_cleanup:
> +       media_entity_cleanup(&vdev->entity);
> +err_free_ctx:
> +       return ret;
> +}
> +
> +void mxc_isi_video_unregister(struct mxc_isi_pipe *pipe)
> +{
> +       struct mxc_isi_video *video = &pipe->video;
> +       struct video_device *vdev = &video->vdev;
> +
> +       mutex_lock(&video->lock);
> +
> +       if (video_is_registered(vdev)) {
> +               video_unregister_device(vdev);
> +               mxc_isi_video_ctrls_delete(video);
> +               media_entity_cleanup(&vdev->entity);
> +       }
> +
> +       mutex_unlock(&video->lock);
> +}
> --
> Regards,
>
> Laurent Pinchart
>



[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux