media: atmel: move microchip_csi2dc to dedicated microchip platform
authorEugen Hristev <eugen.hristev@microchip.com>
Mon, 7 Nov 2022 14:18:13 +0000 (14:18 +0000)
committerMauro Carvalho Chehab <mchehab@kernel.org>
Fri, 25 Nov 2022 07:43:17 +0000 (07:43 +0000)
The Atmel ISC will be moved to staging thus the atmel platform will only
have the ISI driver.
The new media-controller converted ISC driver will be placed inside a
dedicated microchip platform directory.
It is then natural to have the microchip-csi2dc moved to this new platform
directory.
The next step is to add the Microchip ISC driver to the new platform
directory and reside together with the Microchip CSI2DC driver.

Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
MAINTAINERS
drivers/media/platform/Kconfig
drivers/media/platform/Makefile
drivers/media/platform/atmel/Kconfig
drivers/media/platform/atmel/Makefile
drivers/media/platform/atmel/microchip-csi2dc.c [deleted file]
drivers/media/platform/microchip/Kconfig [new file with mode: 0644]
drivers/media/platform/microchip/Makefile [new file with mode: 0644]
drivers/media/platform/microchip/microchip-csi2dc.c [new file with mode: 0644]

index 338b8c3aa30e7db18734bcd39b6ae8b9cfb3f312..6d6d55ea61e41607d216b644b1e11ed780ffdbd8 100644 (file)
@@ -13476,7 +13476,7 @@ M:      Eugen Hristev <eugen.hristev@microchip.com>
 L:     linux-media@vger.kernel.org
 S:     Supported
 F:     Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
-F:     drivers/media/platform/atmel/microchip-csi2dc.c
+F:     drivers/media/platform/microchip/microchip-csi2dc.c
 
 MICROCHIP ECC DRIVER
 M:     Tudor Ambarus <tudor.ambarus@microchip.com>
index a9334263fa9baea627bbea19664886b3ee60e53b..ee579916f8744a5df7c8f805c6605dd3a67b4497 100644 (file)
@@ -72,6 +72,7 @@ source "drivers/media/platform/chips-media/Kconfig"
 source "drivers/media/platform/intel/Kconfig"
 source "drivers/media/platform/marvell/Kconfig"
 source "drivers/media/platform/mediatek/Kconfig"
+source "drivers/media/platform/microchip/Kconfig"
 source "drivers/media/platform/nvidia/Kconfig"
 source "drivers/media/platform/nxp/Kconfig"
 source "drivers/media/platform/qcom/Kconfig"
index a91f42024273d7d6044fd0599c5a5f08c7e5113e..5453bb868e6794d634fa3c32ee1b6a3de35c4880 100644 (file)
@@ -15,6 +15,7 @@ obj-y += chips-media/
 obj-y += intel/
 obj-y += marvell/
 obj-y += mediatek/
+obj-y += microchip/
 obj-y += nvidia/
 obj-y += nxp/
 obj-y += qcom/
index f399dba62e1785904daad1e453e465180e513e18..6d07a31d4c0e7f59f092acd1406080fa1d62f2b4 100644 (file)
@@ -49,18 +49,3 @@ config VIDEO_ATMEL_ISI
          This module makes the ATMEL Image Sensor Interface available
          as a v4l2 device.
 
-config VIDEO_MICROCHIP_CSI2DC
-       tristate "Microchip CSI2 Demux Controller"
-       depends on V4L_PLATFORM_DRIVERS
-       depends on VIDEO_DEV && COMMON_CLK && OF
-       depends on ARCH_AT91 || COMPILE_TEST
-       select MEDIA_CONTROLLER
-       select VIDEO_V4L2_SUBDEV_API
-       select V4L2_FWNODE
-       help
-         CSI2 Demux Controller driver. CSI2DC is a helper chip
-         that converts IDI interface byte stream to a parallel pixel stream.
-         It supports various RAW formats as input.
-
-         To compile this driver as a module, choose M here: the
-         module will be called microchip-csi2dc.
index 794e8f7392878fb7b01c1d4746d3fd254ccc241b..ab3890f957763520eaa96171abd687d8074a7dac 100644 (file)
@@ -7,4 +7,3 @@ obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
 obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o
 obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o
 obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o
-obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
diff --git a/drivers/media/platform/atmel/microchip-csi2dc.c b/drivers/media/platform/atmel/microchip-csi2dc.c
deleted file mode 100644 (file)
index d5b359f..0000000
+++ /dev/null
@@ -1,797 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Microchip CSI2 Demux Controller (CSI2DC) driver
- *
- * Copyright (C) 2018 Microchip Technology, Inc.
- *
- * Author: Eugen Hristev <eugen.hristev@microchip.com>
- *
- */
-
-#include <linux/clk.h>
-#include <linux/mod_devicetable.h>
-#include <linux/module.h>
-#include <linux/of_graph.h>
-#include <linux/platform_device.h>
-#include <linux/pm_runtime.h>
-#include <linux/videodev2.h>
-
-#include <media/v4l2-fwnode.h>
-#include <media/v4l2-subdev.h>
-
-/* Global configuration register */
-#define CSI2DC_GCFG                    0x0
-
-/* MIPI sensor pixel clock is free running */
-#define CSI2DC_GCFG_MIPIFRN            BIT(0)
-/* GPIO parallel interface selection */
-#define CSI2DC_GCFG_GPIOSEL            BIT(1)
-/* Output waveform inter-line minimum delay */
-#define CSI2DC_GCFG_HLC(v)             ((v) << 4)
-#define CSI2DC_GCFG_HLC_MASK           GENMASK(7, 4)
-/* SAMA7G5 requires a HLC delay of 15 */
-#define SAMA7G5_HLC                    (15)
-
-/* Global control register */
-#define CSI2DC_GCTLR                   0x04
-#define CSI2DC_GCTLR_SWRST             BIT(0)
-
-/* Global status register */
-#define CSI2DC_GS                      0x08
-
-/* SSP interrupt status register */
-#define CSI2DC_SSPIS                   0x28
-/* Pipe update register */
-#define CSI2DC_PU                      0xc0
-/* Video pipe attributes update */
-#define CSI2DC_PU_VP                   BIT(0)
-
-/* Pipe update status register */
-#define CSI2DC_PUS                     0xc4
-
-/* Video pipeline Interrupt Status Register */
-#define CSI2DC_VPISR                   0xf4
-
-/* Video pipeline enable register */
-#define CSI2DC_VPE                     0xf8
-#define CSI2DC_VPE_ENABLE              BIT(0)
-
-/* Video pipeline configuration register */
-#define CSI2DC_VPCFG                   0xfc
-/* Data type */
-#define CSI2DC_VPCFG_DT(v)             ((v) << 0)
-#define CSI2DC_VPCFG_DT_MASK           GENMASK(5, 0)
-/* Virtual channel identifier */
-#define CSI2DC_VPCFG_VC(v)             ((v) << 6)
-#define CSI2DC_VPCFG_VC_MASK           GENMASK(7, 6)
-/* Decompression enable */
-#define CSI2DC_VPCFG_DE                        BIT(8)
-/* Decoder mode */
-#define CSI2DC_VPCFG_DM(v)             ((v) << 9)
-#define CSI2DC_VPCFG_DM_DECODER8TO12   0
-/* Decoder predictor 2 selection */
-#define CSI2DC_VPCFG_DP2               BIT(12)
-/* Recommended memory storage */
-#define CSI2DC_VPCFG_RMS               BIT(13)
-/* Post adjustment */
-#define CSI2DC_VPCFG_PA                        BIT(14)
-
-/* Video pipeline column register */
-#define CSI2DC_VPCOL                   0x100
-/* Column number */
-#define CSI2DC_VPCOL_COL(v)            ((v) << 0)
-#define CSI2DC_VPCOL_COL_MASK          GENMASK(15, 0)
-
-/* Video pipeline row register */
-#define CSI2DC_VPROW                   0x104
-/* Row number */
-#define CSI2DC_VPROW_ROW(v)            ((v) << 0)
-#define CSI2DC_VPROW_ROW_MASK          GENMASK(15, 0)
-
-/* Version register */
-#define CSI2DC_VERSION                 0x1fc
-
-/* register read/write helpers */
-#define csi2dc_readl(st, reg)          readl_relaxed((st)->base + (reg))
-#define csi2dc_writel(st, reg, val)    writel_relaxed((val), \
-                                       (st)->base + (reg))
-
-/* supported RAW data types */
-#define CSI2DC_DT_RAW6                 0x28
-#define CSI2DC_DT_RAW7                 0x29
-#define CSI2DC_DT_RAW8                 0x2a
-#define CSI2DC_DT_RAW10                        0x2b
-#define CSI2DC_DT_RAW12                        0x2c
-#define CSI2DC_DT_RAW14                        0x2d
-/* YUV data types */
-#define CSI2DC_DT_YUV422_8B            0x1e
-
-/*
- * struct csi2dc_format - CSI2DC format type struct
- * @mbus_code:         Media bus code for the format
- * @dt:                        Data type constant for this format
- */
-struct csi2dc_format {
-       u32                             mbus_code;
-       u32                             dt;
-};
-
-static const struct csi2dc_format csi2dc_formats[] = {
-       {
-               .mbus_code =            MEDIA_BUS_FMT_SRGGB8_1X8,
-               .dt =                   CSI2DC_DT_RAW8,
-       }, {
-               .mbus_code =            MEDIA_BUS_FMT_SBGGR8_1X8,
-               .dt =                   CSI2DC_DT_RAW8,
-       }, {
-               .mbus_code =            MEDIA_BUS_FMT_SGRBG8_1X8,
-               .dt =                   CSI2DC_DT_RAW8,
-       }, {
-               .mbus_code =            MEDIA_BUS_FMT_SGBRG8_1X8,
-               .dt =                   CSI2DC_DT_RAW8,
-       }, {
-               .mbus_code =            MEDIA_BUS_FMT_SRGGB10_1X10,
-               .dt =                   CSI2DC_DT_RAW10,
-       }, {
-               .mbus_code =            MEDIA_BUS_FMT_SBGGR10_1X10,
-               .dt =                   CSI2DC_DT_RAW10,
-       }, {
-               .mbus_code =            MEDIA_BUS_FMT_SGRBG10_1X10,
-               .dt =                   CSI2DC_DT_RAW10,
-       }, {
-               .mbus_code =            MEDIA_BUS_FMT_SGBRG10_1X10,
-               .dt =                   CSI2DC_DT_RAW10,
-       }, {
-               .mbus_code =            MEDIA_BUS_FMT_YUYV8_2X8,
-               .dt =                   CSI2DC_DT_YUV422_8B,
-       },
-};
-
-enum mipi_csi_pads {
-       CSI2DC_PAD_SINK                 = 0,
-       CSI2DC_PAD_SOURCE               = 1,
-       CSI2DC_PADS_NUM                 = 2,
-};
-
-/*
- * struct csi2dc_device - CSI2DC device driver data/config struct
- * @base:              Register map base address
- * @csi2dc_sd:         v4l2 subdevice for the csi2dc device
- *                     This is the subdevice that the csi2dc device itself
- *                     registers in v4l2 subsystem
- * @dev:               struct device for this csi2dc device
- * @pclk:              Peripheral clock reference
- *                     Input clock that clocks the hardware block internal
- *                     logic
- * @scck:              Sensor Controller clock reference
- *                     Input clock that is used to generate the pixel clock
- * @format:            Current saved format used in g/s fmt
- * @cur_fmt:           Current state format
- * @try_fmt:           Try format that is being tried
- * @pads:              Media entity pads for the csi2dc subdevice
- * @clk_gated:         Whether the clock is gated or free running
- * @video_pipe:                Whether video pipeline is configured
- * @parallel_mode:     The underlying subdevice is connected on a parallel bus
- * @vc:                        Current set virtual channel
- * @notifier:          Async notifier that is used to bound the underlying
- *                     subdevice to the csi2dc subdevice
- * @input_sd:          Reference to the underlying subdevice bound to the
- *                     csi2dc subdevice
- * @remote_pad:                Pad number of the underlying subdevice that is linked
- *                     to the csi2dc subdevice sink pad.
- */
-struct csi2dc_device {
-       void __iomem                    *base;
-       struct v4l2_subdev              csi2dc_sd;
-       struct device                   *dev;
-       struct clk                      *pclk;
-       struct clk                      *scck;
-
-       struct v4l2_mbus_framefmt        format;
-
-       const struct csi2dc_format      *cur_fmt;
-       const struct csi2dc_format      *try_fmt;
-
-       struct media_pad                pads[CSI2DC_PADS_NUM];
-
-       bool                            clk_gated;
-       bool                            video_pipe;
-       bool                            parallel_mode;
-       u32                             vc;
-
-       struct v4l2_async_notifier      notifier;
-
-       struct v4l2_subdev              *input_sd;
-
-       u32                             remote_pad;
-};
-
-static inline struct csi2dc_device *
-csi2dc_sd_to_csi2dc_device(struct v4l2_subdev *csi2dc_sd)
-{
-       return container_of(csi2dc_sd, struct csi2dc_device, csi2dc_sd);
-}
-
-static int csi2dc_enum_mbus_code(struct v4l2_subdev *csi2dc_sd,
-                                struct v4l2_subdev_state *sd_state,
-                                struct v4l2_subdev_mbus_code_enum *code)
-{
-       if (code->index >= ARRAY_SIZE(csi2dc_formats))
-               return -EINVAL;
-
-       code->code = csi2dc_formats[code->index].mbus_code;
-
-       return 0;
-}
-
-static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd,
-                         struct v4l2_subdev_state *sd_state,
-                         struct v4l2_subdev_format *format)
-{
-       struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
-       struct v4l2_mbus_framefmt *v4l2_try_fmt;
-
-       if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
-               v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
-                                                         format->pad);
-               format->format = *v4l2_try_fmt;
-
-               return 0;
-       }
-
-       format->format = csi2dc->format;
-
-       return 0;
-}
-
-static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd,
-                         struct v4l2_subdev_state *sd_state,
-                         struct v4l2_subdev_format *req_fmt)
-{
-       struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
-       const struct csi2dc_format *fmt, *try_fmt = NULL;
-       struct v4l2_mbus_framefmt *v4l2_try_fmt;
-       unsigned int i;
-
-       /*
-        * Setting the source pad is disabled.
-        * The same format is being propagated from the sink to source.
-        */
-       if (req_fmt->pad == CSI2DC_PAD_SOURCE)
-               return -EINVAL;
-
-       for (i = 0; i < ARRAY_SIZE(csi2dc_formats);  i++) {
-               fmt = &csi2dc_formats[i];
-               if (req_fmt->format.code == fmt->mbus_code)
-                       try_fmt = fmt;
-               fmt++;
-       }
-
-       /* in case we could not find the desired format, default to something */
-       if (!try_fmt) {
-               try_fmt = &csi2dc_formats[0];
-
-               dev_dbg(csi2dc->dev,
-                       "CSI2DC unsupported format 0x%x, defaulting to 0x%x\n",
-                       req_fmt->format.code, csi2dc_formats[0].mbus_code);
-       }
-
-       req_fmt->format.code = try_fmt->mbus_code;
-       req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
-       req_fmt->format.field = V4L2_FIELD_NONE;
-
-       if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
-               v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
-                                                         req_fmt->pad);
-               *v4l2_try_fmt = req_fmt->format;
-               /* Trying on the sink pad makes the source pad change too */
-               v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd,
-                                                         sd_state,
-                                                         CSI2DC_PAD_SOURCE);
-               *v4l2_try_fmt = req_fmt->format;
-
-               /* if we are just trying, we are done */
-               return 0;
-       }
-
-       /* save the format for later requests */
-       csi2dc->format = req_fmt->format;
-
-       /* update config */
-       csi2dc->cur_fmt = try_fmt;
-
-       dev_dbg(csi2dc->dev, "new format set: 0x%x @%dx%d\n",
-               csi2dc->format.code, csi2dc->format.width,
-               csi2dc->format.height);
-
-       return 0;
-}
-
-static int csi2dc_power(struct csi2dc_device *csi2dc, int on)
-{
-       int ret = 0;
-
-       if (on) {
-               ret = clk_prepare_enable(csi2dc->pclk);
-               if (ret) {
-                       dev_err(csi2dc->dev, "failed to enable pclk:%d\n", ret);
-                       return ret;
-               }
-
-               ret = clk_prepare_enable(csi2dc->scck);
-               if (ret) {
-                       dev_err(csi2dc->dev, "failed to enable scck:%d\n", ret);
-                       clk_disable_unprepare(csi2dc->pclk);
-                       return ret;
-               }
-
-               /* if powering up, deassert reset line */
-               csi2dc_writel(csi2dc, CSI2DC_GCTLR, CSI2DC_GCTLR_SWRST);
-       } else {
-               /* if powering down, assert reset line */
-               csi2dc_writel(csi2dc, CSI2DC_GCTLR, 0);
-
-               clk_disable_unprepare(csi2dc->scck);
-               clk_disable_unprepare(csi2dc->pclk);
-       }
-
-       return ret;
-}
-
-static int csi2dc_get_mbus_config(struct csi2dc_device *csi2dc)
-{
-       struct v4l2_mbus_config mbus_config = { 0 };
-       int ret;
-
-       ret = v4l2_subdev_call(csi2dc->input_sd, pad, get_mbus_config,
-                              csi2dc->remote_pad, &mbus_config);
-       if (ret == -ENOIOCTLCMD) {
-               dev_dbg(csi2dc->dev,
-                       "no remote mbus configuration available\n");
-               return 0;
-       }
-
-       if (ret) {
-               dev_err(csi2dc->dev,
-                       "failed to get remote mbus configuration\n");
-               return 0;
-       }
-
-       dev_dbg(csi2dc->dev, "subdev sending on channel %d\n", csi2dc->vc);
-
-       csi2dc->clk_gated = mbus_config.bus.parallel.flags &
-                               V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
-
-       dev_dbg(csi2dc->dev, "mbus_config: %s clock\n",
-               csi2dc->clk_gated ? "gated" : "free running");
-
-       return 0;
-}
-
-static void csi2dc_vp_update(struct csi2dc_device *csi2dc)
-{
-       u32 vp, gcfg;
-
-       if (!csi2dc->video_pipe) {
-               dev_err(csi2dc->dev, "video pipeline unavailable\n");
-               return;
-       }
-
-       if (csi2dc->parallel_mode) {
-               /* In parallel mode, GPIO parallel interface must be selected */
-               gcfg = csi2dc_readl(csi2dc, CSI2DC_GCFG);
-               gcfg |= CSI2DC_GCFG_GPIOSEL;
-               csi2dc_writel(csi2dc, CSI2DC_GCFG, gcfg);
-               return;
-       }
-
-       /* serial video pipeline */
-
-       csi2dc_writel(csi2dc, CSI2DC_GCFG,
-                     (SAMA7G5_HLC & CSI2DC_GCFG_HLC_MASK) |
-                     (csi2dc->clk_gated ? 0 : CSI2DC_GCFG_MIPIFRN));
-
-       vp = CSI2DC_VPCFG_DT(csi2dc->cur_fmt->dt) & CSI2DC_VPCFG_DT_MASK;
-       vp |= CSI2DC_VPCFG_VC(csi2dc->vc) & CSI2DC_VPCFG_VC_MASK;
-       vp &= ~CSI2DC_VPCFG_DE;
-       vp |= CSI2DC_VPCFG_DM(CSI2DC_VPCFG_DM_DECODER8TO12);
-       vp &= ~CSI2DC_VPCFG_DP2;
-       vp &= ~CSI2DC_VPCFG_RMS;
-       vp |= CSI2DC_VPCFG_PA;
-
-       csi2dc_writel(csi2dc, CSI2DC_VPCFG, vp);
-       csi2dc_writel(csi2dc, CSI2DC_VPE, CSI2DC_VPE_ENABLE);
-       csi2dc_writel(csi2dc, CSI2DC_PU, CSI2DC_PU_VP);
-}
-
-static int csi2dc_s_stream(struct v4l2_subdev *csi2dc_sd, int enable)
-{
-       struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
-       int ret;
-
-       if (enable) {
-               ret = pm_runtime_resume_and_get(csi2dc->dev);
-               if (ret < 0)
-                       return ret;
-
-               csi2dc_get_mbus_config(csi2dc);
-
-               csi2dc_vp_update(csi2dc);
-
-               return v4l2_subdev_call(csi2dc->input_sd, video, s_stream,
-                                       true);
-       }
-
-       dev_dbg(csi2dc->dev,
-               "Last frame received: VPCOLR = %u, VPROWR= %u, VPISR = %x\n",
-               csi2dc_readl(csi2dc, CSI2DC_VPCOL),
-               csi2dc_readl(csi2dc, CSI2DC_VPROW),
-               csi2dc_readl(csi2dc, CSI2DC_VPISR));
-
-       /* stop streaming scenario */
-       ret = v4l2_subdev_call(csi2dc->input_sd, video, s_stream, false);
-
-       pm_runtime_put_sync(csi2dc->dev);
-
-       return ret;
-}
-
-static int csi2dc_init_cfg(struct v4l2_subdev *csi2dc_sd,
-                          struct v4l2_subdev_state *sd_state)
-{
-       struct v4l2_mbus_framefmt *v4l2_try_fmt =
-               v4l2_subdev_get_try_format(csi2dc_sd, sd_state, 0);
-
-       v4l2_try_fmt->height = 480;
-       v4l2_try_fmt->width = 640;
-       v4l2_try_fmt->code = csi2dc_formats[0].mbus_code;
-       v4l2_try_fmt->colorspace = V4L2_COLORSPACE_SRGB;
-       v4l2_try_fmt->field = V4L2_FIELD_NONE;
-       v4l2_try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
-       v4l2_try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
-       v4l2_try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
-
-       return 0;
-}
-
-static const struct media_entity_operations csi2dc_entity_ops = {
-       .link_validate = v4l2_subdev_link_validate,
-};
-
-static const struct v4l2_subdev_pad_ops csi2dc_pad_ops = {
-       .enum_mbus_code = csi2dc_enum_mbus_code,
-       .set_fmt = csi2dc_set_fmt,
-       .get_fmt = csi2dc_get_fmt,
-       .init_cfg = csi2dc_init_cfg,
-};
-
-static const struct v4l2_subdev_video_ops csi2dc_video_ops = {
-       .s_stream = csi2dc_s_stream,
-};
-
-static const struct v4l2_subdev_ops csi2dc_subdev_ops = {
-       .pad = &csi2dc_pad_ops,
-       .video = &csi2dc_video_ops,
-};
-
-static int csi2dc_async_bound(struct v4l2_async_notifier *notifier,
-                             struct v4l2_subdev *subdev,
-                             struct v4l2_async_subdev *asd)
-{
-       struct csi2dc_device *csi2dc = container_of(notifier,
-                                               struct csi2dc_device, notifier);
-       int pad;
-       int ret;
-
-       csi2dc->input_sd = subdev;
-
-       pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
-                                         MEDIA_PAD_FL_SOURCE);
-       if (pad < 0) {
-               dev_err(csi2dc->dev, "Failed to find pad for %s\n",
-                       subdev->name);
-               return pad;
-       }
-
-       csi2dc->remote_pad = pad;
-
-       ret = media_create_pad_link(&csi2dc->input_sd->entity,
-                                   csi2dc->remote_pad,
-                                   &csi2dc->csi2dc_sd.entity, 0,
-                                   MEDIA_LNK_FL_ENABLED);
-       if (ret) {
-               dev_err(csi2dc->dev,
-                       "Failed to create pad link: %s to %s\n",
-                       csi2dc->input_sd->entity.name,
-                       csi2dc->csi2dc_sd.entity.name);
-               return ret;
-       }
-
-       dev_dbg(csi2dc->dev, "link with %s pad: %d\n",
-               csi2dc->input_sd->name, csi2dc->remote_pad);
-
-       return ret;
-}
-
-static const struct v4l2_async_notifier_operations csi2dc_async_ops = {
-       .bound = csi2dc_async_bound,
-};
-
-static int csi2dc_prepare_notifier(struct csi2dc_device *csi2dc,
-                                  struct fwnode_handle *input_fwnode)
-{
-       struct v4l2_async_subdev *asd;
-       int ret = 0;
-
-       v4l2_async_nf_init(&csi2dc->notifier);
-
-       asd = v4l2_async_nf_add_fwnode_remote(&csi2dc->notifier,
-                                             input_fwnode,
-                                             struct v4l2_async_subdev);
-
-       fwnode_handle_put(input_fwnode);
-
-       if (IS_ERR(asd)) {
-               ret = PTR_ERR(asd);
-               dev_err(csi2dc->dev,
-                       "failed to add async notifier for node %pOF: %d\n",
-                       to_of_node(input_fwnode), ret);
-               v4l2_async_nf_cleanup(&csi2dc->notifier);
-               return ret;
-       }
-
-       csi2dc->notifier.ops = &csi2dc_async_ops;
-
-       ret = v4l2_async_subdev_nf_register(&csi2dc->csi2dc_sd,
-                                           &csi2dc->notifier);
-       if (ret) {
-               dev_err(csi2dc->dev, "fail to register async notifier: %d\n",
-                       ret);
-               v4l2_async_nf_cleanup(&csi2dc->notifier);
-       }
-
-       return ret;
-}
-
-static int csi2dc_of_parse(struct csi2dc_device *csi2dc,
-                          struct device_node *of_node)
-{
-       struct fwnode_handle *input_fwnode, *output_fwnode;
-       struct v4l2_fwnode_endpoint input_endpoint = { 0 },
-                                   output_endpoint = { 0 };
-       int ret;
-
-       input_fwnode = fwnode_graph_get_next_endpoint(of_fwnode_handle(of_node),
-                                                     NULL);
-       if (!input_fwnode) {
-               dev_err(csi2dc->dev,
-                       "missing port node at %pOF, input node is mandatory.\n",
-                       of_node);
-               return -EINVAL;
-       }
-
-       ret = v4l2_fwnode_endpoint_parse(input_fwnode, &input_endpoint);
-       if (ret) {
-               dev_err(csi2dc->dev, "endpoint not defined at %pOF\n", of_node);
-               goto csi2dc_of_parse_err;
-       }
-
-       if (input_endpoint.bus_type == V4L2_MBUS_PARALLEL ||
-           input_endpoint.bus_type == V4L2_MBUS_BT656) {
-               csi2dc->parallel_mode = true;
-               dev_dbg(csi2dc->dev,
-                       "subdevice connected on parallel interface\n");
-       }
-
-       if (input_endpoint.bus_type == V4L2_MBUS_CSI2_DPHY) {
-               csi2dc->clk_gated = input_endpoint.bus.mipi_csi2.flags &
-                                       V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
-               dev_dbg(csi2dc->dev,
-                       "subdevice connected on serial interface\n");
-               dev_dbg(csi2dc->dev, "DT: %s clock\n",
-                       csi2dc->clk_gated ? "gated" : "free running");
-       }
-
-       output_fwnode = fwnode_graph_get_next_endpoint
-                               (of_fwnode_handle(of_node), input_fwnode);
-
-       if (output_fwnode)
-               ret = v4l2_fwnode_endpoint_parse(output_fwnode,
-                                                &output_endpoint);
-
-       fwnode_handle_put(output_fwnode);
-
-       if (!output_fwnode || ret) {
-               dev_info(csi2dc->dev,
-                        "missing output node at %pOF, data pipe available only.\n",
-                        of_node);
-       } else {
-               if (output_endpoint.bus_type != V4L2_MBUS_PARALLEL &&
-                   output_endpoint.bus_type != V4L2_MBUS_BT656) {
-                       dev_err(csi2dc->dev,
-                               "output port must be parallel/bt656.\n");
-                       ret = -EINVAL;
-                       goto csi2dc_of_parse_err;
-               }
-
-               csi2dc->video_pipe = true;
-
-               dev_dbg(csi2dc->dev,
-                       "block %pOF [%d.%d]->[%d.%d] video pipeline\n",
-                       of_node, input_endpoint.base.port,
-                       input_endpoint.base.id, output_endpoint.base.port,
-                       output_endpoint.base.id);
-       }
-
-       /* prepare async notifier for subdevice completion */
-       return csi2dc_prepare_notifier(csi2dc, input_fwnode);
-
-csi2dc_of_parse_err:
-       fwnode_handle_put(input_fwnode);
-       return ret;
-}
-
-static void csi2dc_default_format(struct csi2dc_device *csi2dc)
-{
-       csi2dc->cur_fmt = &csi2dc_formats[0];
-
-       csi2dc->format.height = 480;
-       csi2dc->format.width = 640;
-       csi2dc->format.code = csi2dc_formats[0].mbus_code;
-       csi2dc->format.colorspace = V4L2_COLORSPACE_SRGB;
-       csi2dc->format.field = V4L2_FIELD_NONE;
-       csi2dc->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
-       csi2dc->format.quantization = V4L2_QUANTIZATION_DEFAULT;
-       csi2dc->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
-}
-
-static int csi2dc_probe(struct platform_device *pdev)
-{
-       struct device *dev = &pdev->dev;
-       struct csi2dc_device *csi2dc;
-       int ret = 0;
-       u32 ver;
-
-       csi2dc = devm_kzalloc(dev, sizeof(*csi2dc), GFP_KERNEL);
-       if (!csi2dc)
-               return -ENOMEM;
-
-       csi2dc->dev = dev;
-
-       csi2dc->base = devm_platform_ioremap_resource(pdev, 0);
-       if (IS_ERR(csi2dc->base)) {
-               dev_err(dev, "base address not set\n");
-               return PTR_ERR(csi2dc->base);
-       }
-
-       csi2dc->pclk = devm_clk_get(dev, "pclk");
-       if (IS_ERR(csi2dc->pclk)) {
-               ret = PTR_ERR(csi2dc->pclk);
-               dev_err(dev, "failed to get pclk: %d\n", ret);
-               return ret;
-       }
-
-       csi2dc->scck = devm_clk_get(dev, "scck");
-       if (IS_ERR(csi2dc->scck)) {
-               ret = PTR_ERR(csi2dc->scck);
-               dev_err(dev, "failed to get scck: %d\n", ret);
-               return ret;
-       }
-
-       v4l2_subdev_init(&csi2dc->csi2dc_sd, &csi2dc_subdev_ops);
-
-       csi2dc->csi2dc_sd.owner = THIS_MODULE;
-       csi2dc->csi2dc_sd.dev = dev;
-       snprintf(csi2dc->csi2dc_sd.name, sizeof(csi2dc->csi2dc_sd.name),
-                "csi2dc");
-
-       csi2dc->csi2dc_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
-       csi2dc->csi2dc_sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
-       csi2dc->csi2dc_sd.entity.ops = &csi2dc_entity_ops;
-
-       platform_set_drvdata(pdev, csi2dc);
-
-       ret = csi2dc_of_parse(csi2dc, dev->of_node);
-       if (ret)
-               goto csi2dc_probe_cleanup_entity;
-
-       csi2dc->pads[CSI2DC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
-       if (csi2dc->video_pipe)
-               csi2dc->pads[CSI2DC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
-
-       ret = media_entity_pads_init(&csi2dc->csi2dc_sd.entity,
-                                    csi2dc->video_pipe ? CSI2DC_PADS_NUM : 1,
-                                    csi2dc->pads);
-       if (ret < 0) {
-               dev_err(dev, "media entity init failed\n");
-               goto csi2dc_probe_cleanup_notifier;
-       }
-
-       csi2dc_default_format(csi2dc);
-
-       /* turn power on to validate capabilities */
-       ret = csi2dc_power(csi2dc, true);
-       if (ret < 0)
-               goto csi2dc_probe_cleanup_notifier;
-
-       pm_runtime_set_active(dev);
-       pm_runtime_enable(dev);
-       ver = csi2dc_readl(csi2dc, CSI2DC_VERSION);
-
-       /*
-        * we must register the subdev after PM runtime has been requested,
-        * otherwise we might bound immediately and request pm_runtime_resume
-        * before runtime_enable.
-        */
-       ret = v4l2_async_register_subdev(&csi2dc->csi2dc_sd);
-       if (ret) {
-               dev_err(csi2dc->dev, "failed to register the subdevice\n");
-               goto csi2dc_probe_cleanup_notifier;
-       }
-
-       dev_info(dev, "Microchip CSI2DC version %x\n", ver);
-
-       return 0;
-
-csi2dc_probe_cleanup_notifier:
-       v4l2_async_nf_cleanup(&csi2dc->notifier);
-csi2dc_probe_cleanup_entity:
-       media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
-
-       return ret;
-}
-
-static int csi2dc_remove(struct platform_device *pdev)
-{
-       struct csi2dc_device *csi2dc = platform_get_drvdata(pdev);
-
-       pm_runtime_disable(&pdev->dev);
-
-       v4l2_async_unregister_subdev(&csi2dc->csi2dc_sd);
-       v4l2_async_nf_unregister(&csi2dc->notifier);
-       v4l2_async_nf_cleanup(&csi2dc->notifier);
-       media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
-
-       return 0;
-}
-
-static int __maybe_unused csi2dc_runtime_suspend(struct device *dev)
-{
-       struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
-
-       return csi2dc_power(csi2dc, false);
-}
-
-static int __maybe_unused csi2dc_runtime_resume(struct device *dev)
-{
-       struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
-
-       return csi2dc_power(csi2dc, true);
-}
-
-static const struct dev_pm_ops csi2dc_dev_pm_ops = {
-       SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
-};
-
-static const struct of_device_id csi2dc_of_match[] = {
-       { .compatible = "microchip,sama7g5-csi2dc" },
-       { }
-};
-
-MODULE_DEVICE_TABLE(of, csi2dc_of_match);
-
-static struct platform_driver csi2dc_driver = {
-       .probe  = csi2dc_probe,
-       .remove = csi2dc_remove,
-       .driver = {
-               .name =                 "microchip-csi2dc",
-               .pm =                   &csi2dc_dev_pm_ops,
-               .of_match_table =       of_match_ptr(csi2dc_of_match),
-       },
-};
-
-module_platform_driver(csi2dc_driver);
-
-MODULE_AUTHOR("Eugen Hristev <eugen.hristev@microchip.com>");
-MODULE_DESCRIPTION("Microchip CSI2 Demux Controller driver");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/microchip/Kconfig b/drivers/media/platform/microchip/Kconfig
new file mode 100644 (file)
index 0000000..aa9e902
--- /dev/null
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+comment "Microchip Technology, Inc. media platform drivers"
+
+config VIDEO_MICROCHIP_CSI2DC
+       tristate "Microchip CSI2 Demux Controller"
+       depends on V4L_PLATFORM_DRIVERS
+       depends on VIDEO_DEV && COMMON_CLK && OF
+       depends on ARCH_AT91 || COMPILE_TEST
+       select MEDIA_CONTROLLER
+       select VIDEO_V4L2_SUBDEV_API
+       select V4L2_FWNODE
+       help
+         CSI2 Demux Controller driver. CSI2DC is a helper chip
+         that converts IDI interface byte stream to a parallel pixel stream.
+         It supports various RAW formats as input.
+
+         To compile this driver as a module, choose M here: the
+         module will be called microchip-csi2dc.
diff --git a/drivers/media/platform/microchip/Makefile b/drivers/media/platform/microchip/Makefile
new file mode 100644 (file)
index 0000000..cbcde4a
--- /dev/null
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
diff --git a/drivers/media/platform/microchip/microchip-csi2dc.c b/drivers/media/platform/microchip/microchip-csi2dc.c
new file mode 100644 (file)
index 0000000..d5b359f
--- /dev/null
@@ -0,0 +1,797 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Microchip CSI2 Demux Controller (CSI2DC) driver
+ *
+ * Copyright (C) 2018 Microchip Technology, Inc.
+ *
+ * Author: Eugen Hristev <eugen.hristev@microchip.com>
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+/* Global configuration register */
+#define CSI2DC_GCFG                    0x0
+
+/* MIPI sensor pixel clock is free running */
+#define CSI2DC_GCFG_MIPIFRN            BIT(0)
+/* GPIO parallel interface selection */
+#define CSI2DC_GCFG_GPIOSEL            BIT(1)
+/* Output waveform inter-line minimum delay */
+#define CSI2DC_GCFG_HLC(v)             ((v) << 4)
+#define CSI2DC_GCFG_HLC_MASK           GENMASK(7, 4)
+/* SAMA7G5 requires a HLC delay of 15 */
+#define SAMA7G5_HLC                    (15)
+
+/* Global control register */
+#define CSI2DC_GCTLR                   0x04
+#define CSI2DC_GCTLR_SWRST             BIT(0)
+
+/* Global status register */
+#define CSI2DC_GS                      0x08
+
+/* SSP interrupt status register */
+#define CSI2DC_SSPIS                   0x28
+/* Pipe update register */
+#define CSI2DC_PU                      0xc0
+/* Video pipe attributes update */
+#define CSI2DC_PU_VP                   BIT(0)
+
+/* Pipe update status register */
+#define CSI2DC_PUS                     0xc4
+
+/* Video pipeline Interrupt Status Register */
+#define CSI2DC_VPISR                   0xf4
+
+/* Video pipeline enable register */
+#define CSI2DC_VPE                     0xf8
+#define CSI2DC_VPE_ENABLE              BIT(0)
+
+/* Video pipeline configuration register */
+#define CSI2DC_VPCFG                   0xfc
+/* Data type */
+#define CSI2DC_VPCFG_DT(v)             ((v) << 0)
+#define CSI2DC_VPCFG_DT_MASK           GENMASK(5, 0)
+/* Virtual channel identifier */
+#define CSI2DC_VPCFG_VC(v)             ((v) << 6)
+#define CSI2DC_VPCFG_VC_MASK           GENMASK(7, 6)
+/* Decompression enable */
+#define CSI2DC_VPCFG_DE                        BIT(8)
+/* Decoder mode */
+#define CSI2DC_VPCFG_DM(v)             ((v) << 9)
+#define CSI2DC_VPCFG_DM_DECODER8TO12   0
+/* Decoder predictor 2 selection */
+#define CSI2DC_VPCFG_DP2               BIT(12)
+/* Recommended memory storage */
+#define CSI2DC_VPCFG_RMS               BIT(13)
+/* Post adjustment */
+#define CSI2DC_VPCFG_PA                        BIT(14)
+
+/* Video pipeline column register */
+#define CSI2DC_VPCOL                   0x100
+/* Column number */
+#define CSI2DC_VPCOL_COL(v)            ((v) << 0)
+#define CSI2DC_VPCOL_COL_MASK          GENMASK(15, 0)
+
+/* Video pipeline row register */
+#define CSI2DC_VPROW                   0x104
+/* Row number */
+#define CSI2DC_VPROW_ROW(v)            ((v) << 0)
+#define CSI2DC_VPROW_ROW_MASK          GENMASK(15, 0)
+
+/* Version register */
+#define CSI2DC_VERSION                 0x1fc
+
+/* register read/write helpers */
+#define csi2dc_readl(st, reg)          readl_relaxed((st)->base + (reg))
+#define csi2dc_writel(st, reg, val)    writel_relaxed((val), \
+                                       (st)->base + (reg))
+
+/* supported RAW data types */
+#define CSI2DC_DT_RAW6                 0x28
+#define CSI2DC_DT_RAW7                 0x29
+#define CSI2DC_DT_RAW8                 0x2a
+#define CSI2DC_DT_RAW10                        0x2b
+#define CSI2DC_DT_RAW12                        0x2c
+#define CSI2DC_DT_RAW14                        0x2d
+/* YUV data types */
+#define CSI2DC_DT_YUV422_8B            0x1e
+
+/*
+ * struct csi2dc_format - CSI2DC format type struct
+ * @mbus_code:         Media bus code for the format
+ * @dt:                        Data type constant for this format
+ */
+struct csi2dc_format {
+       u32                             mbus_code;
+       u32                             dt;
+};
+
+static const struct csi2dc_format csi2dc_formats[] = {
+       {
+               .mbus_code =            MEDIA_BUS_FMT_SRGGB8_1X8,
+               .dt =                   CSI2DC_DT_RAW8,
+       }, {
+               .mbus_code =            MEDIA_BUS_FMT_SBGGR8_1X8,
+               .dt =                   CSI2DC_DT_RAW8,
+       }, {
+               .mbus_code =            MEDIA_BUS_FMT_SGRBG8_1X8,
+               .dt =                   CSI2DC_DT_RAW8,
+       }, {
+               .mbus_code =            MEDIA_BUS_FMT_SGBRG8_1X8,
+               .dt =                   CSI2DC_DT_RAW8,
+       }, {
+               .mbus_code =            MEDIA_BUS_FMT_SRGGB10_1X10,
+               .dt =                   CSI2DC_DT_RAW10,
+       }, {
+               .mbus_code =            MEDIA_BUS_FMT_SBGGR10_1X10,
+               .dt =                   CSI2DC_DT_RAW10,
+       }, {
+               .mbus_code =            MEDIA_BUS_FMT_SGRBG10_1X10,
+               .dt =                   CSI2DC_DT_RAW10,
+       }, {
+               .mbus_code =            MEDIA_BUS_FMT_SGBRG10_1X10,
+               .dt =                   CSI2DC_DT_RAW10,
+       }, {
+               .mbus_code =            MEDIA_BUS_FMT_YUYV8_2X8,
+               .dt =                   CSI2DC_DT_YUV422_8B,
+       },
+};
+
+enum mipi_csi_pads {
+       CSI2DC_PAD_SINK                 = 0,
+       CSI2DC_PAD_SOURCE               = 1,
+       CSI2DC_PADS_NUM                 = 2,
+};
+
+/*
+ * struct csi2dc_device - CSI2DC device driver data/config struct
+ * @base:              Register map base address
+ * @csi2dc_sd:         v4l2 subdevice for the csi2dc device
+ *                     This is the subdevice that the csi2dc device itself
+ *                     registers in v4l2 subsystem
+ * @dev:               struct device for this csi2dc device
+ * @pclk:              Peripheral clock reference
+ *                     Input clock that clocks the hardware block internal
+ *                     logic
+ * @scck:              Sensor Controller clock reference
+ *                     Input clock that is used to generate the pixel clock
+ * @format:            Current saved format used in g/s fmt
+ * @cur_fmt:           Current state format
+ * @try_fmt:           Try format that is being tried
+ * @pads:              Media entity pads for the csi2dc subdevice
+ * @clk_gated:         Whether the clock is gated or free running
+ * @video_pipe:                Whether video pipeline is configured
+ * @parallel_mode:     The underlying subdevice is connected on a parallel bus
+ * @vc:                        Current set virtual channel
+ * @notifier:          Async notifier that is used to bound the underlying
+ *                     subdevice to the csi2dc subdevice
+ * @input_sd:          Reference to the underlying subdevice bound to the
+ *                     csi2dc subdevice
+ * @remote_pad:                Pad number of the underlying subdevice that is linked
+ *                     to the csi2dc subdevice sink pad.
+ */
+struct csi2dc_device {
+       void __iomem                    *base;
+       struct v4l2_subdev              csi2dc_sd;
+       struct device                   *dev;
+       struct clk                      *pclk;
+       struct clk                      *scck;
+
+       struct v4l2_mbus_framefmt        format;
+
+       const struct csi2dc_format      *cur_fmt;
+       const struct csi2dc_format      *try_fmt;
+
+       struct media_pad                pads[CSI2DC_PADS_NUM];
+
+       bool                            clk_gated;
+       bool                            video_pipe;
+       bool                            parallel_mode;
+       u32                             vc;
+
+       struct v4l2_async_notifier      notifier;
+
+       struct v4l2_subdev              *input_sd;
+
+       u32                             remote_pad;
+};
+
+static inline struct csi2dc_device *
+csi2dc_sd_to_csi2dc_device(struct v4l2_subdev *csi2dc_sd)
+{
+       return container_of(csi2dc_sd, struct csi2dc_device, csi2dc_sd);
+}
+
+static int csi2dc_enum_mbus_code(struct v4l2_subdev *csi2dc_sd,
+                                struct v4l2_subdev_state *sd_state,
+                                struct v4l2_subdev_mbus_code_enum *code)
+{
+       if (code->index >= ARRAY_SIZE(csi2dc_formats))
+               return -EINVAL;
+
+       code->code = csi2dc_formats[code->index].mbus_code;
+
+       return 0;
+}
+
+static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd,
+                         struct v4l2_subdev_state *sd_state,
+                         struct v4l2_subdev_format *format)
+{
+       struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
+       struct v4l2_mbus_framefmt *v4l2_try_fmt;
+
+       if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+               v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
+                                                         format->pad);
+               format->format = *v4l2_try_fmt;
+
+               return 0;
+       }
+
+       format->format = csi2dc->format;
+
+       return 0;
+}
+
+static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd,
+                         struct v4l2_subdev_state *sd_state,
+                         struct v4l2_subdev_format *req_fmt)
+{
+       struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
+       const struct csi2dc_format *fmt, *try_fmt = NULL;
+       struct v4l2_mbus_framefmt *v4l2_try_fmt;
+       unsigned int i;
+
+       /*
+        * Setting the source pad is disabled.
+        * The same format is being propagated from the sink to source.
+        */
+       if (req_fmt->pad == CSI2DC_PAD_SOURCE)
+               return -EINVAL;
+
+       for (i = 0; i < ARRAY_SIZE(csi2dc_formats);  i++) {
+               fmt = &csi2dc_formats[i];
+               if (req_fmt->format.code == fmt->mbus_code)
+                       try_fmt = fmt;
+               fmt++;
+       }
+
+       /* in case we could not find the desired format, default to something */
+       if (!try_fmt) {
+               try_fmt = &csi2dc_formats[0];
+
+               dev_dbg(csi2dc->dev,
+                       "CSI2DC unsupported format 0x%x, defaulting to 0x%x\n",
+                       req_fmt->format.code, csi2dc_formats[0].mbus_code);
+       }
+
+       req_fmt->format.code = try_fmt->mbus_code;
+       req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
+       req_fmt->format.field = V4L2_FIELD_NONE;
+
+       if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+               v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
+                                                         req_fmt->pad);
+               *v4l2_try_fmt = req_fmt->format;
+               /* Trying on the sink pad makes the source pad change too */
+               v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd,
+                                                         sd_state,
+                                                         CSI2DC_PAD_SOURCE);
+               *v4l2_try_fmt = req_fmt->format;
+
+               /* if we are just trying, we are done */
+               return 0;
+       }
+
+       /* save the format for later requests */
+       csi2dc->format = req_fmt->format;
+
+       /* update config */
+       csi2dc->cur_fmt = try_fmt;
+
+       dev_dbg(csi2dc->dev, "new format set: 0x%x @%dx%d\n",
+               csi2dc->format.code, csi2dc->format.width,
+               csi2dc->format.height);
+
+       return 0;
+}
+
+static int csi2dc_power(struct csi2dc_device *csi2dc, int on)
+{
+       int ret = 0;
+
+       if (on) {
+               ret = clk_prepare_enable(csi2dc->pclk);
+               if (ret) {
+                       dev_err(csi2dc->dev, "failed to enable pclk:%d\n", ret);
+                       return ret;
+               }
+
+               ret = clk_prepare_enable(csi2dc->scck);
+               if (ret) {
+                       dev_err(csi2dc->dev, "failed to enable scck:%d\n", ret);
+                       clk_disable_unprepare(csi2dc->pclk);
+                       return ret;
+               }
+
+               /* if powering up, deassert reset line */
+               csi2dc_writel(csi2dc, CSI2DC_GCTLR, CSI2DC_GCTLR_SWRST);
+       } else {
+               /* if powering down, assert reset line */
+               csi2dc_writel(csi2dc, CSI2DC_GCTLR, 0);
+
+               clk_disable_unprepare(csi2dc->scck);
+               clk_disable_unprepare(csi2dc->pclk);
+       }
+
+       return ret;
+}
+
+static int csi2dc_get_mbus_config(struct csi2dc_device *csi2dc)
+{
+       struct v4l2_mbus_config mbus_config = { 0 };
+       int ret;
+
+       ret = v4l2_subdev_call(csi2dc->input_sd, pad, get_mbus_config,
+                              csi2dc->remote_pad, &mbus_config);
+       if (ret == -ENOIOCTLCMD) {
+               dev_dbg(csi2dc->dev,
+                       "no remote mbus configuration available\n");
+               return 0;
+       }
+
+       if (ret) {
+               dev_err(csi2dc->dev,
+                       "failed to get remote mbus configuration\n");
+               return 0;
+       }
+
+       dev_dbg(csi2dc->dev, "subdev sending on channel %d\n", csi2dc->vc);
+
+       csi2dc->clk_gated = mbus_config.bus.parallel.flags &
+                               V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+
+       dev_dbg(csi2dc->dev, "mbus_config: %s clock\n",
+               csi2dc->clk_gated ? "gated" : "free running");
+
+       return 0;
+}
+
+static void csi2dc_vp_update(struct csi2dc_device *csi2dc)
+{
+       u32 vp, gcfg;
+
+       if (!csi2dc->video_pipe) {
+               dev_err(csi2dc->dev, "video pipeline unavailable\n");
+               return;
+       }
+
+       if (csi2dc->parallel_mode) {
+               /* In parallel mode, GPIO parallel interface must be selected */
+               gcfg = csi2dc_readl(csi2dc, CSI2DC_GCFG);
+               gcfg |= CSI2DC_GCFG_GPIOSEL;
+               csi2dc_writel(csi2dc, CSI2DC_GCFG, gcfg);
+               return;
+       }
+
+       /* serial video pipeline */
+
+       csi2dc_writel(csi2dc, CSI2DC_GCFG,
+                     (SAMA7G5_HLC & CSI2DC_GCFG_HLC_MASK) |
+                     (csi2dc->clk_gated ? 0 : CSI2DC_GCFG_MIPIFRN));
+
+       vp = CSI2DC_VPCFG_DT(csi2dc->cur_fmt->dt) & CSI2DC_VPCFG_DT_MASK;
+       vp |= CSI2DC_VPCFG_VC(csi2dc->vc) & CSI2DC_VPCFG_VC_MASK;
+       vp &= ~CSI2DC_VPCFG_DE;
+       vp |= CSI2DC_VPCFG_DM(CSI2DC_VPCFG_DM_DECODER8TO12);
+       vp &= ~CSI2DC_VPCFG_DP2;
+       vp &= ~CSI2DC_VPCFG_RMS;
+       vp |= CSI2DC_VPCFG_PA;
+
+       csi2dc_writel(csi2dc, CSI2DC_VPCFG, vp);
+       csi2dc_writel(csi2dc, CSI2DC_VPE, CSI2DC_VPE_ENABLE);
+       csi2dc_writel(csi2dc, CSI2DC_PU, CSI2DC_PU_VP);
+}
+
+static int csi2dc_s_stream(struct v4l2_subdev *csi2dc_sd, int enable)
+{
+       struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
+       int ret;
+
+       if (enable) {
+               ret = pm_runtime_resume_and_get(csi2dc->dev);
+               if (ret < 0)
+                       return ret;
+
+               csi2dc_get_mbus_config(csi2dc);
+
+               csi2dc_vp_update(csi2dc);
+
+               return v4l2_subdev_call(csi2dc->input_sd, video, s_stream,
+                                       true);
+       }
+
+       dev_dbg(csi2dc->dev,
+               "Last frame received: VPCOLR = %u, VPROWR= %u, VPISR = %x\n",
+               csi2dc_readl(csi2dc, CSI2DC_VPCOL),
+               csi2dc_readl(csi2dc, CSI2DC_VPROW),
+               csi2dc_readl(csi2dc, CSI2DC_VPISR));
+
+       /* stop streaming scenario */
+       ret = v4l2_subdev_call(csi2dc->input_sd, video, s_stream, false);
+
+       pm_runtime_put_sync(csi2dc->dev);
+
+       return ret;
+}
+
+static int csi2dc_init_cfg(struct v4l2_subdev *csi2dc_sd,
+                          struct v4l2_subdev_state *sd_state)
+{
+       struct v4l2_mbus_framefmt *v4l2_try_fmt =
+               v4l2_subdev_get_try_format(csi2dc_sd, sd_state, 0);
+
+       v4l2_try_fmt->height = 480;
+       v4l2_try_fmt->width = 640;
+       v4l2_try_fmt->code = csi2dc_formats[0].mbus_code;
+       v4l2_try_fmt->colorspace = V4L2_COLORSPACE_SRGB;
+       v4l2_try_fmt->field = V4L2_FIELD_NONE;
+       v4l2_try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+       v4l2_try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
+       v4l2_try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+       return 0;
+}
+
+static const struct media_entity_operations csi2dc_entity_ops = {
+       .link_validate = v4l2_subdev_link_validate,
+};
+
+static const struct v4l2_subdev_pad_ops csi2dc_pad_ops = {
+       .enum_mbus_code = csi2dc_enum_mbus_code,
+       .set_fmt = csi2dc_set_fmt,
+       .get_fmt = csi2dc_get_fmt,
+       .init_cfg = csi2dc_init_cfg,
+};
+
+static const struct v4l2_subdev_video_ops csi2dc_video_ops = {
+       .s_stream = csi2dc_s_stream,
+};
+
+static const struct v4l2_subdev_ops csi2dc_subdev_ops = {
+       .pad = &csi2dc_pad_ops,
+       .video = &csi2dc_video_ops,
+};
+
+static int csi2dc_async_bound(struct v4l2_async_notifier *notifier,
+                             struct v4l2_subdev *subdev,
+                             struct v4l2_async_subdev *asd)
+{
+       struct csi2dc_device *csi2dc = container_of(notifier,
+                                               struct csi2dc_device, notifier);
+       int pad;
+       int ret;
+
+       csi2dc->input_sd = subdev;
+
+       pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
+                                         MEDIA_PAD_FL_SOURCE);
+       if (pad < 0) {
+               dev_err(csi2dc->dev, "Failed to find pad for %s\n",
+                       subdev->name);
+               return pad;
+       }
+
+       csi2dc->remote_pad = pad;
+
+       ret = media_create_pad_link(&csi2dc->input_sd->entity,
+                                   csi2dc->remote_pad,
+                                   &csi2dc->csi2dc_sd.entity, 0,
+                                   MEDIA_LNK_FL_ENABLED);
+       if (ret) {
+               dev_err(csi2dc->dev,
+                       "Failed to create pad link: %s to %s\n",
+                       csi2dc->input_sd->entity.name,
+                       csi2dc->csi2dc_sd.entity.name);
+               return ret;
+       }
+
+       dev_dbg(csi2dc->dev, "link with %s pad: %d\n",
+               csi2dc->input_sd->name, csi2dc->remote_pad);
+
+       return ret;
+}
+
+static const struct v4l2_async_notifier_operations csi2dc_async_ops = {
+       .bound = csi2dc_async_bound,
+};
+
+static int csi2dc_prepare_notifier(struct csi2dc_device *csi2dc,
+                                  struct fwnode_handle *input_fwnode)
+{
+       struct v4l2_async_subdev *asd;
+       int ret = 0;
+
+       v4l2_async_nf_init(&csi2dc->notifier);
+
+       asd = v4l2_async_nf_add_fwnode_remote(&csi2dc->notifier,
+                                             input_fwnode,
+                                             struct v4l2_async_subdev);
+
+       fwnode_handle_put(input_fwnode);
+
+       if (IS_ERR(asd)) {
+               ret = PTR_ERR(asd);
+               dev_err(csi2dc->dev,
+                       "failed to add async notifier for node %pOF: %d\n",
+                       to_of_node(input_fwnode), ret);
+               v4l2_async_nf_cleanup(&csi2dc->notifier);
+               return ret;
+       }
+
+       csi2dc->notifier.ops = &csi2dc_async_ops;
+
+       ret = v4l2_async_subdev_nf_register(&csi2dc->csi2dc_sd,
+                                           &csi2dc->notifier);
+       if (ret) {
+               dev_err(csi2dc->dev, "fail to register async notifier: %d\n",
+                       ret);
+               v4l2_async_nf_cleanup(&csi2dc->notifier);
+       }
+
+       return ret;
+}
+
+static int csi2dc_of_parse(struct csi2dc_device *csi2dc,
+                          struct device_node *of_node)
+{
+       struct fwnode_handle *input_fwnode, *output_fwnode;
+       struct v4l2_fwnode_endpoint input_endpoint = { 0 },
+                                   output_endpoint = { 0 };
+       int ret;
+
+       input_fwnode = fwnode_graph_get_next_endpoint(of_fwnode_handle(of_node),
+                                                     NULL);
+       if (!input_fwnode) {
+               dev_err(csi2dc->dev,
+                       "missing port node at %pOF, input node is mandatory.\n",
+                       of_node);
+               return -EINVAL;
+       }
+
+       ret = v4l2_fwnode_endpoint_parse(input_fwnode, &input_endpoint);
+       if (ret) {
+               dev_err(csi2dc->dev, "endpoint not defined at %pOF\n", of_node);
+               goto csi2dc_of_parse_err;
+       }
+
+       if (input_endpoint.bus_type == V4L2_MBUS_PARALLEL ||
+           input_endpoint.bus_type == V4L2_MBUS_BT656) {
+               csi2dc->parallel_mode = true;
+               dev_dbg(csi2dc->dev,
+                       "subdevice connected on parallel interface\n");
+       }
+
+       if (input_endpoint.bus_type == V4L2_MBUS_CSI2_DPHY) {
+               csi2dc->clk_gated = input_endpoint.bus.mipi_csi2.flags &
+                                       V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+               dev_dbg(csi2dc->dev,
+                       "subdevice connected on serial interface\n");
+               dev_dbg(csi2dc->dev, "DT: %s clock\n",
+                       csi2dc->clk_gated ? "gated" : "free running");
+       }
+
+       output_fwnode = fwnode_graph_get_next_endpoint
+                               (of_fwnode_handle(of_node), input_fwnode);
+
+       if (output_fwnode)
+               ret = v4l2_fwnode_endpoint_parse(output_fwnode,
+                                                &output_endpoint);
+
+       fwnode_handle_put(output_fwnode);
+
+       if (!output_fwnode || ret) {
+               dev_info(csi2dc->dev,
+                        "missing output node at %pOF, data pipe available only.\n",
+                        of_node);
+       } else {
+               if (output_endpoint.bus_type != V4L2_MBUS_PARALLEL &&
+                   output_endpoint.bus_type != V4L2_MBUS_BT656) {
+                       dev_err(csi2dc->dev,
+                               "output port must be parallel/bt656.\n");
+                       ret = -EINVAL;
+                       goto csi2dc_of_parse_err;
+               }
+
+               csi2dc->video_pipe = true;
+
+               dev_dbg(csi2dc->dev,
+                       "block %pOF [%d.%d]->[%d.%d] video pipeline\n",
+                       of_node, input_endpoint.base.port,
+                       input_endpoint.base.id, output_endpoint.base.port,
+                       output_endpoint.base.id);
+       }
+
+       /* prepare async notifier for subdevice completion */
+       return csi2dc_prepare_notifier(csi2dc, input_fwnode);
+
+csi2dc_of_parse_err:
+       fwnode_handle_put(input_fwnode);
+       return ret;
+}
+
+static void csi2dc_default_format(struct csi2dc_device *csi2dc)
+{
+       csi2dc->cur_fmt = &csi2dc_formats[0];
+
+       csi2dc->format.height = 480;
+       csi2dc->format.width = 640;
+       csi2dc->format.code = csi2dc_formats[0].mbus_code;
+       csi2dc->format.colorspace = V4L2_COLORSPACE_SRGB;
+       csi2dc->format.field = V4L2_FIELD_NONE;
+       csi2dc->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+       csi2dc->format.quantization = V4L2_QUANTIZATION_DEFAULT;
+       csi2dc->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+}
+
+static int csi2dc_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct csi2dc_device *csi2dc;
+       int ret = 0;
+       u32 ver;
+
+       csi2dc = devm_kzalloc(dev, sizeof(*csi2dc), GFP_KERNEL);
+       if (!csi2dc)
+               return -ENOMEM;
+
+       csi2dc->dev = dev;
+
+       csi2dc->base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(csi2dc->base)) {
+               dev_err(dev, "base address not set\n");
+               return PTR_ERR(csi2dc->base);
+       }
+
+       csi2dc->pclk = devm_clk_get(dev, "pclk");
+       if (IS_ERR(csi2dc->pclk)) {
+               ret = PTR_ERR(csi2dc->pclk);
+               dev_err(dev, "failed to get pclk: %d\n", ret);
+               return ret;
+       }
+
+       csi2dc->scck = devm_clk_get(dev, "scck");
+       if (IS_ERR(csi2dc->scck)) {
+               ret = PTR_ERR(csi2dc->scck);
+               dev_err(dev, "failed to get scck: %d\n", ret);
+               return ret;
+       }
+
+       v4l2_subdev_init(&csi2dc->csi2dc_sd, &csi2dc_subdev_ops);
+
+       csi2dc->csi2dc_sd.owner = THIS_MODULE;
+       csi2dc->csi2dc_sd.dev = dev;
+       snprintf(csi2dc->csi2dc_sd.name, sizeof(csi2dc->csi2dc_sd.name),
+                "csi2dc");
+
+       csi2dc->csi2dc_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+       csi2dc->csi2dc_sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+       csi2dc->csi2dc_sd.entity.ops = &csi2dc_entity_ops;
+
+       platform_set_drvdata(pdev, csi2dc);
+
+       ret = csi2dc_of_parse(csi2dc, dev->of_node);
+       if (ret)
+               goto csi2dc_probe_cleanup_entity;
+
+       csi2dc->pads[CSI2DC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+       if (csi2dc->video_pipe)
+               csi2dc->pads[CSI2DC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+       ret = media_entity_pads_init(&csi2dc->csi2dc_sd.entity,
+                                    csi2dc->video_pipe ? CSI2DC_PADS_NUM : 1,
+                                    csi2dc->pads);
+       if (ret < 0) {
+               dev_err(dev, "media entity init failed\n");
+               goto csi2dc_probe_cleanup_notifier;
+       }
+
+       csi2dc_default_format(csi2dc);
+
+       /* turn power on to validate capabilities */
+       ret = csi2dc_power(csi2dc, true);
+       if (ret < 0)
+               goto csi2dc_probe_cleanup_notifier;
+
+       pm_runtime_set_active(dev);
+       pm_runtime_enable(dev);
+       ver = csi2dc_readl(csi2dc, CSI2DC_VERSION);
+
+       /*
+        * we must register the subdev after PM runtime has been requested,
+        * otherwise we might bound immediately and request pm_runtime_resume
+        * before runtime_enable.
+        */
+       ret = v4l2_async_register_subdev(&csi2dc->csi2dc_sd);
+       if (ret) {
+               dev_err(csi2dc->dev, "failed to register the subdevice\n");
+               goto csi2dc_probe_cleanup_notifier;
+       }
+
+       dev_info(dev, "Microchip CSI2DC version %x\n", ver);
+
+       return 0;
+
+csi2dc_probe_cleanup_notifier:
+       v4l2_async_nf_cleanup(&csi2dc->notifier);
+csi2dc_probe_cleanup_entity:
+       media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
+
+       return ret;
+}
+
+static int csi2dc_remove(struct platform_device *pdev)
+{
+       struct csi2dc_device *csi2dc = platform_get_drvdata(pdev);
+
+       pm_runtime_disable(&pdev->dev);
+
+       v4l2_async_unregister_subdev(&csi2dc->csi2dc_sd);
+       v4l2_async_nf_unregister(&csi2dc->notifier);
+       v4l2_async_nf_cleanup(&csi2dc->notifier);
+       media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
+
+       return 0;
+}
+
+static int __maybe_unused csi2dc_runtime_suspend(struct device *dev)
+{
+       struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
+
+       return csi2dc_power(csi2dc, false);
+}
+
+static int __maybe_unused csi2dc_runtime_resume(struct device *dev)
+{
+       struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
+
+       return csi2dc_power(csi2dc, true);
+}
+
+static const struct dev_pm_ops csi2dc_dev_pm_ops = {
+       SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
+};
+
+static const struct of_device_id csi2dc_of_match[] = {
+       { .compatible = "microchip,sama7g5-csi2dc" },
+       { }
+};
+
+MODULE_DEVICE_TABLE(of, csi2dc_of_match);
+
+static struct platform_driver csi2dc_driver = {
+       .probe  = csi2dc_probe,
+       .remove = csi2dc_remove,
+       .driver = {
+               .name =                 "microchip-csi2dc",
+               .pm =                   &csi2dc_dev_pm_ops,
+               .of_match_table =       of_match_ptr(csi2dc_of_match),
+       },
+};
+
+module_platform_driver(csi2dc_driver);
+
+MODULE_AUTHOR("Eugen Hristev <eugen.hristev@microchip.com>");
+MODULE_DESCRIPTION("Microchip CSI2 Demux Controller driver");
+MODULE_LICENSE("GPL v2");