All of lore.kernel.org
 help / color / mirror / Atom feed
From: Alexander Stein <alexander.stein@ew.tq-group.com>
To: linux-media@vger.kernel.org, devicetree@vger.kernel.org,
	linux-imx@nxp.com, guoniu.zhou@oss.nxp.com
Cc: mchehab@kernel.org, laurent.pinchart@ideasonboard.com,
	robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
	conor+dt@kernel.org, jacopo.mondi@ideasonboard.com
Subject: Re: [PATCH 2/2] media: nxp: add driver for i.MX93 MIPI CSI-2 controller and D-PHY
Date: Tue, 04 Jul 2023 11:23:11 +0200	[thread overview]
Message-ID: <10303134.nUPlyArG6x@steina-w> (raw)
In-Reply-To: <20230703113734.762307-3-guoniu.zhou@oss.nxp.com>

Hi Guoniu,

thanks for posting this patch.

Am Montag, 3. Juli 2023, 13:37:34 CEST schrieb guoniu.zhou@oss.nxp.com:
> From: "Guoniu.zhou" <guoniu.zhou@nxp.com>
> 
> The MIPI CSI-2 controller and MIPI Rx D-PHY found on i.MX93 originate
> from Synopsys. MIPI CSI-2 controller implements the CSI-2 protocol on
> host side. MIPI 2-lane Rx D-PHY module implement the physical layer
> for the MIPI D-PHY interface. Lane operation ranging from 80 Mbps to
> 1.5Gbps in forward direction.
> 
> Add V4L2 subdev driver support both for CSI-2 controller and D-PHY
> since the PHY is wrapped by the CSI-2 controller and only expose a
> control interface to the CSI-2 controller.
> 
> Signed-off-by: Guoniu.zhou <guoniu.zhou@nxp.com>
> ---
>  MAINTAINERS                                |   10 +
>  drivers/media/platform/nxp/Kconfig         |   11 +
>  drivers/media/platform/nxp/Makefile        |    3 +
>  drivers/media/platform/nxp/dwc-mipi-csi2.c | 1384 ++++++++++++++++++++
>  drivers/media/platform/nxp/dwc-mipi-csi2.h |  289 ++++
>  drivers/media/platform/nxp/dwc-mipi-dphy.c |  195 +++
>  6 files changed, 1892 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c83475103a25..349d981f9c24 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15189,6 +15189,16 @@ S:	Maintained
>  F:	Documentation/devicetree/bindings/sound/nxp,tfa989x.yaml
>  F:	sound/soc/codecs/tfa989x.c
> 
> +NXP i.MX93 MIPI CSI-2 V4L2 DRIVER
> +M:	G.N. Zhou (OSS) <guoniu.zhou@oss.nxp.com>
> +R:	NXP Linux Team <linux-imx@nxp.com>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/media/nxp,dwc-mipi-csi2.yaml
> +F:	drivers/media/platform/nxp/dwc-mipi-csi2.c
> +F:	drivers/media/platform/nxp/dwc-mipi-csi2.h
> +F:	drivers/media/platform/nxp/dwc-mipi-dphy.c
> +
>  NZXT-KRAKEN2 HARDWARE MONITORING DRIVER
>  M:	Jonas Malaco <jonas@protocubo.io>
>  L:	linux-hwmon@vger.kernel.org
> diff --git a/drivers/media/platform/nxp/Kconfig
> b/drivers/media/platform/nxp/Kconfig index a0ca6b297fb8..4b8b713022d4
> 100644
> --- a/drivers/media/platform/nxp/Kconfig
> +++ b/drivers/media/platform/nxp/Kconfig
> @@ -30,6 +30,17 @@ config VIDEO_IMX_MIPI_CSIS
> 
>  source "drivers/media/platform/nxp/imx8-isi/Kconfig"
> 
> +config VIDEO_DWC_MIPI_CSIS
> +	tristate "DesignWare Cores MIPI CSI-2 receiver found on i.MX93"
> +	depends on ARCH_MXC || COMPILE_TEST
> +	depends on VIDEO_DEV
> +	select MEDIA_CONTROLLER
> +	select V4L2_FWNODE
> +	select VIDEO_V4L2_SUBDEV_API
> +	help
> +	  Video4Linux2 sub-device driver for the DesignWare Cores MIPI
> +	  CSI-2 receiver used on i.MX93.
> +
>  # mem2mem drivers
> 
>  config VIDEO_IMX_PXP
> diff --git a/drivers/media/platform/nxp/Makefile
> b/drivers/media/platform/nxp/Makefile index b8e672b75fed..07f43795dc16
> 100644
> --- a/drivers/media/platform/nxp/Makefile
> +++ b/drivers/media/platform/nxp/Makefile
> @@ -4,6 +4,9 @@ obj-y += dw100/
>  obj-y += imx-jpeg/
>  obj-y += imx8-isi/
> 
> +dwc-mipi-csis-y := dwc-mipi-csi2.o dwc-mipi-dphy.o
> +
> +obj-$(CONFIG_VIDEO_DWC_MIPI_CSIS) += dwc-mipi-csis.o
>  obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
>  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/dwc-mipi-csi2.c
> b/drivers/media/platform/nxp/dwc-mipi-csi2.c new file mode 100644
> index 000000000000..f03a23d9ef71
> --- /dev/null
> +++ b/drivers/media/platform/nxp/dwc-mipi-csi2.c
> @@ -0,0 +1,1384 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2023 NXP
> + *
> + */
> +
> +#include <linux/bits.h>
> +#include <linux/clk.h>
> +#include <linux/errno.h>
> +#include <linux/iopoll.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/mipi-csi2.h>
> +
> +#include "dwc-mipi-csi2.h"
> +
> +#define DWC_MIPI_CSIS_DRIVER_NAME	"dwc-mipi-csi2"
> +
> +#define DWC_CSI2RX_DEF_MBUS_CODE	MEDIA_BUS_FMT_UYVY8_1X16
> +#define DWC_CSI2RX_DEF_PIX_WIDTH	1920U
> +#define DWC_CSI2RX_DEF_PIX_HEIGHT	1080U
> +#define DWC_CSI2RX_MAX_PIX_WIDTH	0xffff
> +#define DWC_CSI2RX_MAX_PIX_HEIGHT	0xffff
> +
> +/* Set default high speed frequency range to 1.5Gbps  */
> +#define DPHY_DEFAULT_FREQRANGE		0x2c
> +
> +enum imx93_csi_clks {
> +	PER,
> +	PIXEL,
> +	PHY_CFG,
> +};
> +
> +enum model {
> +	DWC_CSI2RX_IMX93,
> +};
> +
> +enum dwc_csi2rx_intf {
> +	DWC_CSI2RX_INTF_IDI,

This is unused, what is it intented for?

> +	DWC_CSI2RX_INTF_IPI,
> +};
> +
> +struct dwc_csi_plat_data {
> +	enum model model;
> +	enum dwc_csi2rx_intf intf;
> +
> +	const struct clk_bulk_data *clks;
> +	u32 num_clks;
> +
> +	const struct dwc_csi_event *events;
> +	u32 num_events;
> +	u32 events_mask;
> +};
> +
> +/*
> ---------------------------------------------------------------------------
> -- + * Events
> + */
> +
> +struct dwc_csi_event {
> +	u32 mask;
> +	const char * const name;
> +	unsigned int counter;
> +};
> +
> +static struct dwc_csi_event mxc_imx93_events[] = {
> +	{ CSI2RX_INT_ST_MAIN_FATAL_ERR_IPI, "IPI Interface Fatal Error" },
> +	{ CSI2RX_INT_ST_MAIN_ERR_PHY, "PHY Error" },
> +	{ CSI2RX_INT_ST_MAIN_ERR_ECC, "Header Single Bit Error" },
> +	{ CSI2RX_INT_ST_MAIN_ERR_DID, "Data ID Error" },
> +	{ CSI2RX_INT_ST_MAIN_FATAL_ERR_PLD_CRC, "Payload CRC Fatal Error" },
> +	{ CSI2RX_INT_ST_MAIN_FATAL_ERR_CRC_FRAME, "Frame CRC Fatal Error" },
> +	{ CSI2RX_INT_ST_MAIN_FATAL_ERR_SEQ_FRAME, "Frame Sequence Fatal 
Error" },
> +	{ CSI2RX_INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL, "Frame Boundaries Fatal
> Error" }, +	{ CSI2RX_INT_ST_MAIN_FATAL_ERR_PKT, "Packet Construction 
Fatal
> Error" }, +	{ CSI2RX_INT_ST_MAIN_FATAL_ERR_PHY, "PHY Fatal Error" },
> +};
> +
> +/*
> ---------------------------------------------------------------------------
> -- + * Format helpers
> + */
> +
> +struct dwc_csi_pix_format {
> +	u32 code;
> +	u32 output;
> +	u32 data_type;
> +	u8 width;
> +};
> +
> +/* List of supported pixel formats for the subdev */
> +static const struct dwc_csi_pix_format dwc_csi_formats[] = {
> +	/* YUV formats */
> +	{
> +		.code = MEDIA_BUS_FMT_UYVY8_1X16,
> +		.output = MEDIA_BUS_FMT_UYVY8_1X16,
> +		.data_type = MIPI_CSI2_DT_YUV422_8B,
> +		.width = 16,
> +	},
> +	/* RGB formats */
> +	{
> +		.code = MEDIA_BUS_FMT_RGB565_1X16,
> +		.output = MEDIA_BUS_FMT_RGB565_1X16,
> +		.data_type = MIPI_CSI2_DT_RGB565,
> +		.width = 16,
> +	}, {
> +		.code = MEDIA_BUS_FMT_BGR888_1X24,
> +		.output = MEDIA_BUS_FMT_RGB888_1X24,
> +		.data_type = MIPI_CSI2_DT_RGB888,
> +		.width = 24,
> +	},
> +	/* RAW (Bayer and greyscale) formats. */
> +	{
> +		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
> +		.output = MEDIA_BUS_FMT_SBGGR8_1X8,
> +		.data_type = MIPI_CSI2_DT_RAW8,
> +		.width = 8,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
> +		.output = MEDIA_BUS_FMT_SGBRG8_1X8,
> +		.data_type = MIPI_CSI2_DT_RAW8,
> +		.width = 8,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
> +		.output = MEDIA_BUS_FMT_SGRBG8_1X8,
> +		.data_type = MIPI_CSI2_DT_RAW8,
> +		.width = 8,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
> +		.output = MEDIA_BUS_FMT_SRGGB8_1X8,
> +		.data_type = MIPI_CSI2_DT_RAW8,
> +		.width = 8,
> +	}, {
> +		.code = MEDIA_BUS_FMT_Y8_1X8,
> +		.output = MEDIA_BUS_FMT_Y8_1X8,
> +		.data_type = MIPI_CSI2_DT_RAW8,
> +		.width = 8,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.output = MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.data_type = MIPI_CSI2_DT_RAW10,
> +		.width = 10,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.output = MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.data_type = MIPI_CSI2_DT_RAW10,
> +		.width = 10,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.output = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.data_type = MIPI_CSI2_DT_RAW10,
> +		.width = 10,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.output = MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.data_type = MIPI_CSI2_DT_RAW10,
> +		.width = 10,
> +	}, {
> +		.code = MEDIA_BUS_FMT_Y10_1X10,
> +		.output = MEDIA_BUS_FMT_Y10_1X10,
> +		.data_type = MIPI_CSI2_DT_RAW10,
> +		.width = 10,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.output = MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.data_type = MIPI_CSI2_DT_RAW12,
> +		.width = 12,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.output = MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.data_type = MIPI_CSI2_DT_RAW12,
> +		.width = 12,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.output = MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.data_type = MIPI_CSI2_DT_RAW12,
> +		.width = 12,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.output = MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.data_type = MIPI_CSI2_DT_RAW12,
> +		.width = 12,
> +	}, {
> +		.code = MEDIA_BUS_FMT_Y12_1X12,
> +		.output = MEDIA_BUS_FMT_Y12_1X12,
> +		.data_type = MIPI_CSI2_DT_RAW12,
> +		.width = 12,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SBGGR14_1X14,
> +		.output = MEDIA_BUS_FMT_SBGGR14_1X14,
> +		.data_type = MIPI_CSI2_DT_RAW14,
> +		.width = 14,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGBRG14_1X14,
> +		.output = MEDIA_BUS_FMT_SGBRG14_1X14,
> +		.data_type = MIPI_CSI2_DT_RAW14,
> +		.width = 14,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGRBG14_1X14,
> +		.output = MEDIA_BUS_FMT_SGRBG14_1X14,
> +		.data_type = MIPI_CSI2_DT_RAW14,
> +		.width = 14,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SRGGB14_1X14,
> +		.output = MEDIA_BUS_FMT_SRGGB14_1X14,
> +		.data_type = MIPI_CSI2_DT_RAW14,
> +		.width = 14,
> +	}
> +};
> +
> +static const struct v4l2_mbus_framefmt dwc_csi_default_fmt = {
> +	.code = DWC_CSI2RX_DEF_MBUS_CODE,
> +	.width = DWC_CSI2RX_DEF_PIX_WIDTH,
> +	.height = DWC_CSI2RX_DEF_PIX_HEIGHT,
> +	.field = V4L2_FIELD_NONE,
> +	.colorspace = V4L2_COLORSPACE_SMPTE170M,
> +	.xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SMPTE170M),
> +	.ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SMPTE170M),
> +	.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +};
> +
> +static const struct dwc_csi_pix_format *find_csi_format(u32 code)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(dwc_csi_formats); i++)
> +		if (code == dwc_csi_formats[i].code)
> +			return &dwc_csi_formats[i];
> +	return NULL;
> +}
> +
> +/*
> ---------------------------------------------------------------------------
> -- + * DWC MIPI CSI-2 Host Controller Hardware operation
> + */
> +
> +static int dwc_csi_device_pg_enable(struct dwc_csi_device *csidev)
> +{
> +	const struct dwc_csi_pix_format *csi_fmt = csidev->csi_fmt;
> +	struct v4l2_mbus_framefmt *format;
> +	u32 val;
> +
> +	if (!csidev->pg_enable)
> +		return 0;
> +
> +	if (!csi_fmt) {
> +		dev_err(csidev->dev, "CSI pixel format is NULL\n");
> +		return -EINVAL;
> +	}
> +
> +	format = &csidev->format_mbus[DWC_CSI2RX_PAD_SINK];
> +
> +	if (csi_fmt->data_type != MIPI_CSI2_DT_RGB888) {
> +		dev_err(csidev->dev, "Pattern generator only support 
RGB888\n");
> +		return -EINVAL;
> +	}
> +
> +	val = CSI2RX_PPI_PG_PATTERN_HRES_HRES(format->width);
> +	dwc_csi_write(csidev, CSI2RX_PPI_PG_PATTERN_HRES, val);
> +
> +	val = CSI2RX_PPI_PG_PATTERN_VRES_VRES(format->height);
> +	dwc_csi_write(csidev, CSI2RX_PPI_PG_PATTERN_VRES, val);
> +
> +	val = CSI2RX_PPI_PG_CONFIG_DATA_TYPE(csi_fmt->data_type);
> +	val |= CSI2RX_PPI_PG_CONFIG_VIR_CHAN(0);
> +	val |= CSI2RX_PPI_PG_CONFIG_PG_MODE(csidev->pg_pattern);
> +	dwc_csi_write(csidev, CSI2RX_PPI_PG_CONFIG, val);
> +
> +	/*
> +	 * Select line start packets to construct vertical
> +	 * timing information for IPI interface
> +	 **/
> +	val = CSI2RX_IPI_ADV_FEATURES_SYNC_EVENT_MODE;
> +	val |= CSI2RX_IPI_ADV_FEATURES_SYNC_LS_PKT;
> +	val |= CSI2RX_IPI_ADV_FEATURES_LINE_EVENT_SEL;
> +	dwc_csi_write(csidev, CSI2RX_IPI_ADV_FEATURES, val);
> +
> +	val = CSI2RX_PPI_PG_ENABLE_EN;
> +	dwc_csi_write(csidev, CSI2RX_PPI_PG_ENABLE, val);
> +
> +	return 0;
> +}
> +
> +static void dwc_csi_device_pg_disable(struct dwc_csi_device *csidev)
> +{
> +	dwc_csi_write(csidev, CSI2RX_PPI_PG_ENABLE, 0);
> +	csidev->pg_enable = false;
> +}
> +
> +static void dwc_csi_ipi_enable(struct dwc_csi_device *csidev)
> +{
> +	const struct dwc_csi_plat_data *pdata = csidev->pdata;
> +	u32 val;
> +
> +	if (pdata->intf != DWC_CSI2RX_INTF_IPI)
> +		return;
> +
> +	/* Memory is automatically flushed at each Frame Start */
> +	val = CSI2RX_IPI_MEM_FLUSH_AUTO;
> +	dwc_csi_write(csidev, CSI2RX_IPI_MEM_FLUSH, val);
> +
> +	/* Enable IPI */
> +	val = dwc_csi_read(csidev, CSI2RX_IPI_MODE);
> +	val |= CSI2RX_IPI_MODE_ENABLE;
> +	dwc_csi_write(csidev, CSI2RX_IPI_MODE, val);
> +}
> +
> +static void dwc_csi_ipi_disable(struct dwc_csi_device *csidev)
> +{
> +	const struct dwc_csi_plat_data *pdata = csidev->pdata;
> +
> +	if (pdata->intf != DWC_CSI2RX_INTF_IPI)
> +		return;
> +
> +	dwc_csi_write(csidev, CSI2RX_IPI_MODE, 0);
> +}
> +
> +static void dwc_csi_device_ipi_config(struct dwc_csi_device *csidev)
> +{
> +	const struct dwc_csi_pix_format *csi_fmt = csidev->csi_fmt;
> +	const struct dwc_csi_plat_data *pdata = csidev->pdata;
> +	u32 val;
> +
> +	if (pdata->intf != DWC_CSI2RX_INTF_IPI)
> +		return;
> +
> +	/* Do IPI soft reset */
> +	dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x0);
> +	dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x1);
> +
> +	/* Select virtual channel and data type to be processed by IPI */
> +	val = CSI2RX_IPI_DATA_TYPE_DT(csi_fmt->data_type);
> +	dwc_csi_write(csidev, CSI2RX_IPI_DATA_TYPE, val);
> +
> +	/* Set virtual channel 0 as default */
> +	val  = CSI2RX_IPI_VCID_VC(0);
> +	dwc_csi_write(csidev, CSI2RX_IPI_VCID, val);
> +
> +	/*
> +	 * Select IPI camera timing mode and allow the pixel stream
> +	 * to be non-continuous when pixel interface FIFO is empty
> +	 */
> +	val = dwc_csi_read(csidev, CSI2RX_IPI_MODE);
> +	val &= ~CSI2RX_IPI_MODE_CONTROLLER;
> +	val &= ~CSI2RX_IPI_MODE_COLOR_MODE16;
> +	val |= CSI2RX_IPI_MODE_CUT_THROUGH;
> +	dwc_csi_write(csidev, CSI2RX_IPI_MODE, val);
> +}
> +
> +static void dwc_csi_device_reset(struct dwc_csi_device *csidev)
> +{
> +	/* Reset mipi csi host, active low */
> +	dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0);
> +	dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 1);
> +}
> +
> +static void dwc_csi_device_startup(struct dwc_csi_device *csidev)
> +{
> +	/* Release DWC_mipi_csi2_host from reset */
> +	dwc_csi_device_reset(csidev);
> +
> +	/* Apply PHY Reset */
> +	dphy_rx_reset(csidev);
> +
> +	/* Release PHY test codes from reset */
> +	dphy_rx_test_code_reset(csidev);
> +}
> +
> +static int dwc_csi_device_init(struct dwc_csi_device *csidev)
> +{
> +	struct device *dev = csidev->dev;
> +	u32 val;
> +	int ret;
> +
> +	/* Release Synopsys DPHY test codes from reset */
> +	dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0);
> +	dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0);
> +	dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0);
> +
> +	/* Set testclr=1'b1 */
> +	val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0);
> +	val |= CSI2RX_DPHY_TEST_CTRL0_TEST_CLR;
> +	dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val);
> +
> +	/* Wait for at least 15ns */
> +	ndelay(15);
> +
> +	/* Configure the PHY frequency range */
> +	dphy_rx_test_code_config(csidev);
> +	dphy_rx_test_code_dump(csidev);
> +
> +	/* Config the number of active lanes */
> +	val = CSI2RX_N_LANES_N_LANES(csidev->bus.num_data_lanes - 1);
> +	dwc_csi_write(csidev, CSI2RX_N_LANES, val);
> +
> +	/* Release PHY from reset */
> +	dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x1);
> +	dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x1);
> +	dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0x1);
> +
> +	/* Check if lanes are in stop state */
> +	ret = readl_poll_timeout(csidev->regs + CSI2RX_DPHY_STOPSTATE,
> +				 val, val != 0x10003, 10, 10000);
> +	if (ret) {
> +		dev_err(dev, "Lanes are not in stop state(%#x)\n", val);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void dwc_csi_device_hs_rx_start(struct dwc_csi_device *csidev)
> +{
> +	dwc_csi_ipi_enable(csidev);
> +}
> +
> +static int dwc_csi_device_hs_rx_stop(struct dwc_csi_device *csidev)
> +{
> +	struct device *dev = csidev->dev;
> +	u32 val;
> +
> +	dwc_csi_ipi_disable(csidev);
> +	dphy_rx_power_off(csidev);
> +
> +	/* Check clock lanes are not in High Speed Mode */
> +	val = dwc_csi_read(csidev, CSI2RX_DPHY_RX_STATUS);
> +	if (val & CSI2RX_DPHY_RX_STATUS_CLK_LANE_HS) {
> +		dev_err(dev, "Clock lanes are still in HS mode\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static void dwc_csi_device_enable_interrupts(struct dwc_csi_device *csidev,
> bool on) +{
> +	/* Define errors to be enabled */
> +	dwc_csi_write(csidev, CSI2RX_INT_MSK_DPHY_FATAL, on ? 0x3 : 0);
> +	dwc_csi_write(csidev, CSI2RX_INT_MSK_PKT_FATAL, on ? 0x3 : 0);
> +	dwc_csi_write(csidev, CSI2RX_INT_MSK_DPHY, on ? 0x30003 : 0);
> +	dwc_csi_write(csidev, CSI2RX_INT_MSK_IPI_FATAL, on ? 0x7f : 0);
> +}
> +
> +static int dwc_csi_clk_enable(struct dwc_csi_device *csidev)
> +{
> +	const struct dwc_csi_plat_data *pdata = csidev->pdata;
> +
> +	return clk_bulk_prepare_enable(pdata->num_clks, csidev->clks);
> +}
> +
> +static void dwc_csi_clk_disable(struct dwc_csi_device *csidev)
> +{
> +	const struct dwc_csi_plat_data *pdata = csidev->pdata;
> +
> +	clk_bulk_disable_unprepare(pdata->num_clks, csidev->clks);
> +}
> +
> +static int dwc_csi_clk_get(struct dwc_csi_device *csidev)
> +{
> +	const struct dwc_csi_plat_data *pdata = csidev->pdata;
> +	unsigned int size;
> +	int ret;
> +
> +	size = pdata->num_clks * sizeof(*csidev->clks);
> +
> +	csidev->clks = devm_kmalloc(csidev->dev, size, GFP_KERNEL);
> +	if (!csidev->clks)
> +		return -ENOMEM;
> +
> +	memcpy(csidev->clks, pdata->clks, size);
> +
> +	ret = devm_clk_bulk_get(csidev->dev, pdata->num_clks, csidev->clks);
> +	if (ret < 0) {
> +		dev_err(csidev->dev, "Failed to acquire clocks: %d\n", 
ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> ---------------------------------------------------------------------------
> -- + * Debug
> + */
> +
> +static void dwc_csi_clear_counters(struct dwc_csi_device *csidev)
> +{
> +	unsigned long flags;
> +	unsigned int i;
> +
> +	spin_lock_irqsave(&csidev->slock, flags);
> +
> +	for (i = 0; i < csidev->pdata->num_events; ++i)
> +		csidev->events[i].counter = 0;
> +
> +	spin_unlock_irqrestore(&csidev->slock, flags);
> +}
> +
> +static void dwc_csi_log_counters(struct dwc_csi_device *csidev)
> +{
> +	unsigned int num_events = csidev->pdata->num_events;
> +	unsigned long flags;
> +	unsigned int i;
> +
> +	spin_lock_irqsave(&csidev->slock, flags);
> +
> +	for (i = 0; i < num_events; ++i) {
> +		if (csidev->events[i].counter > 0)
> +			dev_info(csidev->dev, "%s events: %d\n",
> +				 csidev->events[i].name,
> +				 csidev->events[i].counter);
> +	}
> +
> +	spin_unlock_irqrestore(&csidev->slock, flags);
> +}
> +
> +static void dwc_csi_dump_regs(struct dwc_csi_device *csidev)
> +{
> +#define DWC_MIPI_CSIS_DEBUG_REG(name)		{name, #name}
> +	static const struct {
> +		u32 offset;
> +		const char * const name;
> +	} registers[] = {
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_VERSION),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_N_LANES),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_HOST_RESETN),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_MAIN),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_SHUTDOWNZ),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_RSTZ),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_RX_STATUS),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_STOPSTATE),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_TEST_CTRL0),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_TEST_CTRL1),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_PATTERN_VRES),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_PATTERN_HRES),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_CONFIG),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_ENABLE),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_STATUS),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_MODE),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_VCID),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_DATA_TYPE),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_MEM_FLUSH),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_SOFTRSTN),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_ADV_FEATURES),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_DPHY_FATAL),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_PKT_FATAL),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_DPHY_FATAL),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_IPI_FATAL),
> +	};
> +
> +	unsigned int i;
> +	u32 cfg;
> +
> +	dev_dbg(csidev->dev, "--- REGISTERS ---\n");
> +
> +	for (i = 0; i < ARRAY_SIZE(registers); i++) {
> +		cfg = dwc_csi_read(csidev, registers[i].offset);
> +		dev_dbg(csidev->dev, "%14s[0x%02x]: 0x%08x\n",
> +			registers[i].name, registers[i].offset, cfg);
> +	}
> +}
> +
> +/*
> ---------------------------------------------------------------------------
> -- + * V4L2 subdev operations
> + */
> +
> +static inline struct dwc_csi_device *
> +sd_to_dwc_csi_device(struct v4l2_subdev *sdev)
> +{
> +	return container_of(sdev, struct dwc_csi_device, sd);
> +}
> +
> +static struct v4l2_mbus_framefmt *
> +dwc_csi_get_pad_format(struct dwc_csi_device *csidev,
> +			struct v4l2_subdev_state *sd_state,
> +			enum v4l2_subdev_format_whence which,
> +			unsigned int pad)
> +{
> +	if (which == V4L2_SUBDEV_FORMAT_TRY)
> +		return v4l2_subdev_get_try_format(&csidev->sd, sd_state, 
pad);
> +
> +	return &csidev->format_mbus[pad];
> +}
> +
> +static int dwc_csi_subdev_init_cfg(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state 
*sd_state)
> +{
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	struct v4l2_mbus_framefmt *fmt_sink;
> +	struct v4l2_mbus_framefmt *fmt_source;
> +
> +	fmt_sink = dwc_csi_get_pad_format(csidev, sd_state,
> +					   V4L2_SUBDEV_FORMAT_TRY,
> +					   DWC_CSI2RX_PAD_SINK);
> +	*fmt_sink = dwc_csi_default_fmt;
> +
> +	fmt_source = dwc_csi_get_pad_format(csidev, sd_state,
> +					     V4L2_SUBDEV_FORMAT_TRY,
> +					     DWC_CSI2RX_PAD_SOURCE);
> +	*fmt_source = *fmt_sink;
> +
> +	return 0;
> +}
> +
> +static int dwc_csi_subdev_enum_mbus_code(struct v4l2_subdev *sd,
> +					  struct v4l2_subdev_state 
*sd_state,
> +					  struct 
v4l2_subdev_mbus_code_enum *code)
> +{
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +
> +	/*
> +	 * The CSIS can't transcode in any way, the source format is 
identical
> +	 * to the sink format.
> +	 */
> +	if (code->pad == DWC_CSI2RX_PAD_SOURCE) {
> +		struct v4l2_mbus_framefmt *fmt;
> +
> +		if (code->index > 0)
> +			return -EINVAL;
> +
> +		fmt = dwc_csi_get_pad_format(csidev, sd_state, code-
>which,
> +					      code->pad);
> +		code->code = fmt->code;
> +		return 0;
> +	}
> +
> +	if (code->pad != DWC_CSI2RX_PAD_SINK)
> +		return -EINVAL;
> +
> +	if (code->index >= ARRAY_SIZE(dwc_csi_formats))
> +		return -EINVAL;
> +
> +	code->code = dwc_csi_formats[code->index].code;
> +
> +	return 0;
> +}
> +
> +static int dwc_csi_subdev_get_fmt(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *sd_state,
> +				   struct v4l2_subdev_format 
*sdformat)
> +{
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	fmt = dwc_csi_get_pad_format(csidev, sd_state, sdformat->which,
> +				      sdformat->pad);
> +
> +	mutex_lock(&csidev->lock);
> +	sdformat->format = *fmt;
> +	mutex_unlock(&csidev->lock);
> +
> +	return 0;
> +}
> +
> +static int dwc_csi_subdev_set_fmt(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *sd_state,
> +				   struct v4l2_subdev_format 
*sdformat)
> +{
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	struct dwc_csi_pix_format const *csi_fmt;
> +	struct v4l2_mbus_framefmt *fmt;
> +	unsigned int align;
> +
> +	/*
> +	 * The CSIS can't transcode in any way, the source format can't be
> +	 * modified.
> +	 */
> +	if (sdformat->pad == DWC_CSI2RX_PAD_SOURCE)
> +		return dwc_csi_subdev_get_fmt(sd, sd_state, sdformat);
> +
> +	if (sdformat->pad != DWC_CSI2RX_PAD_SINK)
> +		return -EINVAL;
> +
> +	/*
> +	 * Validate the media bus code and clamp and align the size.
> +	 *
> +	 * The total number of bits per line must be a multiple of 8. We 
thus
> +	 * need to align the width for formats that are not multiples of 8
> +	 * bits.
> +	 */
> +	csi_fmt = find_csi_format(sdformat->format.code);
> +	if (!csi_fmt)
> +		csi_fmt = &dwc_csi_formats[0];
> +
> +	switch (csi_fmt->width % 8) {
> +	case 0:
> +		align = 0;
> +		break;
> +	case 4:
> +		align = 1;
> +		break;
> +	case 2:
> +	case 6:
> +		align = 2;
> +		break;
> +	default:
> +		/* 1, 3, 5, 7 */
> +		align = 3;
> +		break;
> +	}

Is this switch-case actually necessary? If the bits per line have to be a 
multiple of 8, IMHO calling v4l_bound_align_image() with walign=3 should be 
enough for all cases.

> +	v4l_bound_align_image(&sdformat->format.width, 1,
> +			      DWC_CSI2RX_MAX_PIX_WIDTH, align,
> +			      &sdformat->format.height, 1,
> +			      DWC_CSI2RX_MAX_PIX_HEIGHT, 0, 0);
> +
> +	fmt = dwc_csi_get_pad_format(csidev, sd_state, sdformat->which,
> +				      sdformat->pad);
> +
> +	mutex_lock(&csidev->lock);
> +
> +	fmt->code = csi_fmt->code;
> +	fmt->width = sdformat->format.width;
> +	fmt->height = sdformat->format.height;
> +	fmt->colorspace = sdformat->format.colorspace;
> +	fmt->quantization = sdformat->format.quantization;
> +	fmt->xfer_func = sdformat->format.xfer_func;
> +	fmt->ycbcr_enc = sdformat->format.ycbcr_enc;
> +
> +	sdformat->format = *fmt;
> +
> +	/* Propagate the format from sink to source. */
> +	fmt = dwc_csi_get_pad_format(csidev, sd_state, sdformat->which,
> +				      DWC_CSI2RX_PAD_SOURCE);
> +	*fmt = sdformat->format;
> +
> +	/* The format on the source pad might change due to unpacking. */
> +	fmt->code = csi_fmt->output;
> +
> +	/* Store the CSIS format descriptor for active formats. */
> +	if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE)
> +		csidev->csi_fmt = csi_fmt;
> +
> +	mutex_unlock(&csidev->lock);
> +
> +	return 0;
> +}
> +
> +static int dwc_csi_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
> +				   struct v4l2_mbus_frame_desc *fd)
> +{
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	struct v4l2_mbus_frame_desc_entry *entry = &fd->entry[0];
> +
> +	if (pad != DWC_CSI2RX_PAD_SOURCE)
> +		return -EINVAL;
> +
> +	fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL;
> +	fd->num_entries = 1;
> +
> +	memset(entry, 0, sizeof(*entry));
> +
> +	mutex_lock(&csidev->lock);
> +
> +	entry->flags = 0;
> +	entry->pixelcode = csidev->csi_fmt->code;
> +	entry->bus.csi2.vc = 0;
> +	entry->bus.csi2.dt = csidev->csi_fmt->data_type;
> +
> +	mutex_unlock(&csidev->lock);
> +
> +	return 0;
> +}
> +
> +static int dwc_csi_start_stream(struct dwc_csi_device *csidev)
> +{
> +	int ret;
> +
> +	dwc_csi_device_startup(csidev);
> +
> +	ret = dwc_csi_device_init(csidev);
> +	if (ret)
> +		return ret;
> +
> +	dwc_csi_device_ipi_config(csidev);
> +
> +	ret = dwc_csi_device_pg_enable(csidev);
> +	if (ret)
> +		return ret;
> +
> +	dwc_csi_device_hs_rx_start(csidev);
> +
> +	dwc_csi_device_enable_interrupts(csidev, true);
> +
> +	return 0;
> +}
> +
> +static void dwc_csi_stop_stream(struct dwc_csi_device *csidev)
> +{
> +	dwc_csi_device_enable_interrupts(csidev, false);
> +	dwc_csi_device_hs_rx_stop(csidev);
> +	dwc_csi_device_pg_disable(csidev);
> +}
> +
> +static int dwc_csi_subdev_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	int ret;
> +
> +	if (!csidev->sensor_sd) {
> +		dev_err(csidev->dev, "Sensor don't link with CSIS pad\n");
> +		return -EPIPE;
> +	}
> +
> +	mutex_lock(&csidev->lock);
> +
> +	if (!enable) {
> +		dwc_csi_stop_stream(csidev);
> +		dwc_csi_log_counters(csidev);
> +		pm_runtime_put(csidev->dev);
> +		goto sd_stream;
> +	}
> +
> +	ret = pm_runtime_resume_and_get(csidev->dev);
> +	if (ret < 0)
> +		goto unlocked;
> +
> +	dwc_csi_clear_counters(csidev);
> +
> +	/* CSIS HW configuration */
> +	ret = dwc_csi_start_stream(csidev);
> +	if (ret) {
> +		pm_runtime_put(csidev->dev);
> +		goto unlocked;
> +	}
> +
> +	dwc_csi_dump_regs(csidev);
> +
> +sd_stream:
> +	/*
> +	 * when enable CSI pattern generator, the clock source of
> +	 * pattern generator will be from external sensor, so it
> +	 * also need to enable external sensor clock.
> +	 */
> +	v4l2_subdev_call(csidev->sensor_sd, video, s_stream, enable);
> +	dwc_csi_log_counters(csidev);
> +unlocked:
> +	mutex_unlock(&csidev->lock);
> +	return ret;
> +}
> +
> +static const struct v4l2_subdev_pad_ops dwc_csi_subdev_pad_ops = {
> +	.init_cfg = dwc_csi_subdev_init_cfg,
> +	.enum_mbus_code	= dwc_csi_subdev_enum_mbus_code,
> +	.get_fmt = dwc_csi_subdev_get_fmt,
> +	.set_fmt = dwc_csi_subdev_set_fmt,
> +	.get_frame_desc = dwc_csi_get_frame_desc,
> +};
> +
> +static const struct v4l2_subdev_video_ops dwc_csi_subdev_video_ops = {
> +	.s_stream = dwc_csi_subdev_s_stream,
> +};
> +
> +static const struct v4l2_subdev_ops dwc_csi_subdev_ops = {
> +	.pad   = &dwc_csi_subdev_pad_ops,
> +	.video = &dwc_csi_subdev_video_ops,
> +};
> +
> +/*
> ---------------------------------------------------------------------------
> -- + * Media entity operations
> + */
> +
> +static int dwc_csi_link_setup(struct media_entity *entity,
> +			       const struct media_pad *local_pad,
> +			       const struct media_pad *remote_pad, u32 
flags)
> +{
> +	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	struct v4l2_subdev *remote_sd;
> +
> +	dev_dbg(csidev->dev, "link setup %s -> %s", remote_pad->entity-
>name,
> +		local_pad->entity->name);
> +
> +	/* We only care about the link to the source. */
> +	if (!(local_pad->flags & MEDIA_PAD_FL_SINK))
> +		return 0;
> +
> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> +
> +	if (flags & MEDIA_LNK_FL_ENABLED) {
> +		if (csidev->sensor_sd)
> +			return -EBUSY;
> +
> +		csidev->sensor_sd = remote_sd;
> +		csidev->remote_pad = remote_pad->index;
> +	} else {
> +		csidev->sensor_sd = NULL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int dwc_csi_link_validate(struct media_link *link)
> +{
> +	struct media_pad *sink_pad = link->sink;
> +	struct v4l2_subdev *sink_sd;
> +	struct dwc_csi_device *csidev;
> +
> +	sink_sd = media_entity_to_v4l2_subdev(sink_pad->entity);
> +	csidev = sd_to_dwc_csi_device(sink_sd);
> +
> +	dev_dbg(csidev->dev, "entity name:%s pad index=%d\n",
> +			   sink_sd->name, sink_pad->index);
> +
> +	/*
> +	 * Skip link validate when pattern enabled since the soruce
> +	 * data will be from CSI pattern generator, not sensor.
> +	 */
> +	if (csidev->pg_enable && sink_pad->index == DWC_CSI2RX_PAD_SINK)
> +		return 0;
> +
> +	return v4l2_subdev_link_validate(link);
> +}
> +
> +static const struct media_entity_operations dwc_csi_entity_ops = {
> +	.link_setup	= dwc_csi_link_setup,
> +	.link_validate	= dwc_csi_link_validate,
> +	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> +};
> +
> +/*
> ---------------------------------------------------------------------------
> -- + * Async subdev notifier
> + */
> +
> +static inline struct dwc_csi_device *
> +notifier_to_dwc_csi_device(struct v4l2_async_notifier *n)
> +{
> +	return container_of(n, struct dwc_csi_device, notifier);
> +}
> +
> +static int dwc_csi_notify_bound(struct v4l2_async_notifier *notifier,
> +				  struct v4l2_subdev *sd,
> +				  struct v4l2_async_subdev *asd)
> +{
> +	struct dwc_csi_device *csidev = 
notifier_to_dwc_csi_device(notifier);
> +	struct media_pad *sink = &csidev-
>sd.entity.pads[DWC_CSI2RX_PAD_SINK];
> +
> +	return v4l2_create_fwnode_links_to_pad(sd, sink, 0);
> +}
> +
> +static const struct v4l2_async_notifier_operations dwc_csi_notify_ops = {
> +	.bound = dwc_csi_notify_bound,
> +};
> +
> +static int dwc_csi_async_register(struct dwc_csi_device *csidev)
> +{
> +	struct v4l2_fwnode_endpoint vep = {
> +		.bus_type = V4L2_MBUS_CSI2_DPHY,
> +	};
> +	struct v4l2_async_subdev *asd;
> +	struct fwnode_handle *ep;
> +	unsigned int i;
> +	int ret;
> +
> +	v4l2_async_nf_init(&csidev->notifier);
> +
> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csidev->dev), 0, 0,
> +					     
FWNODE_GRAPH_ENDPOINT_NEXT);
> +	if (!ep)
> +		return -ENOTCONN;
> +
> +	ret = v4l2_fwnode_endpoint_parse(ep, &vep);
> +	if (ret)
> +		goto err_parse;
> +
> +	for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) {
> +		if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) {
> +			dev_err(csidev->dev,
> +				"data lanes reordering is not 
supported");
> +			ret = -EINVAL;
> +			goto err_parse;
> +		}
> +	}
> +
> +	csidev->bus = vep.bus.mipi_csi2;
> +
> +	if (fwnode_property_read_u32(ep, "fsl,hsfreqrange",
> +				     &csidev->hsfreqrange))
> +		csidev->hsfreqrange = DPHY_DEFAULT_FREQRANGE;
> +
> +	dev_dbg(csidev->dev, "data lanes: %d\n", csidev-
>bus.num_data_lanes);
> +	dev_dbg(csidev->dev, "flags: 0x%08x\n", csidev->bus.flags);
> +	dev_dbg(csidev->dev, "high speed frequency range: 0x%X\n",
> csidev->hsfreqrange); +
> +	asd = v4l2_async_nf_add_fwnode_remote(&csidev->notifier, ep,
> +					      struct 
v4l2_async_subdev);
> +	if (IS_ERR(asd)) {
> +		ret = PTR_ERR(asd);
> +		goto err_parse;
> +	}
> +
> +	fwnode_handle_put(ep);
> +
> +	csidev->notifier.ops = &dwc_csi_notify_ops;
> +
> +	ret = v4l2_async_subdev_nf_register(&csidev->sd, &csidev->notifier);
> +	if (ret)
> +		return ret;

I'm not sure which part causes the following message:
> dwc-mipi-csi2 4ae00000.mipi-csi: Consider updating driver dwc-mipi-csi2 to 
match on endpoints

But as this is a new driver, this should be addressed.

> +
> +	return v4l2_async_register_subdev(&csidev->sd);
> +
> +err_parse:
> +	fwnode_handle_put(ep);
> +
> +	return ret;
> +}
> +
> +/*
> ---------------------------------------------------------------------------
> -- + * Pattern Generator operations
> + */
> +
> +static ssize_t pg_enable_show(struct device *dev,
> +			      struct device_attribute *attr, char *buf)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +
> +	return sprintf(buf, "%d\n", csidev->pg_enable);
> +}
> +
> +static ssize_t pg_enable_store(struct device *dev,
> +			       struct device_attribute *attr,
> +			       const char *buf, size_t len)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	int ret;
> +	u8 val;
> +
> +	ret = kstrtou8(buf, 0, &val);
> +	if (ret)
> +		return ret;
> +
> +	csidev->pg_enable = val;
> +	return len;
> +}
> +static DEVICE_ATTR_RW(pg_enable);
> +
> +static ssize_t pg_active_show(struct device *dev,
> +			      struct device_attribute *attr, char *buf)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	u32 val;
> +
> +	if (!pm_runtime_get_if_in_use(dev)) {
> +		csidev->pg_active = false;
> +		goto out;
> +	}
> +
> +	val = dwc_csi_read(csidev, CSI2RX_PPI_PG_STATUS);
> +	csidev->pg_active = val & BIT(0);
> +
> +out:
> +	return sprintf(buf, "%d\n", csidev->pg_active);
> +}
> +static DEVICE_ATTR_RO(pg_active);
> +
> +static ssize_t pg_pattern_show(struct device *dev,
> +			       struct device_attribute *attr, char *buf)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	char temp[16] = "vertical";
> +
> +	if (csidev->pg_pattern == PATTERN_HORIZONTAL)
> +		strcpy(temp, "horizontal");
> +
> +	return sprintf(buf, "%s\n", temp);
> +}
> +
> +static ssize_t pg_pattern_store(struct device *dev,
> +				struct device_attribute *attr,
> +				const char *buf, size_t len)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	char temp[16];
> +	int ret = -EINVAL;
> +
> +	if (sscanf(buf, "%s", temp) > 0) {
> +		ret = len;
> +		if (!strcmp(temp, "horizontal"))
> +			csidev->pg_pattern = PATTERN_HORIZONTAL;
> +		else if (!strcmp(temp, "vertical"))
> +			csidev->pg_pattern = PATTERN_VERTICAL;
> +		else
> +			ret = -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +static DEVICE_ATTR_RW(pg_pattern);
> +
> +static void dwc_csi_pattern_generator_init(struct dwc_csi_device *csidev)
> +{
> +	csidev->pg_enable = false;
> +	csidev->pg_active = false;
> +	csidev->pg_pattern = PATTERN_VERTICAL;
> +
> +	device_create_file(csidev->dev, &dev_attr_pg_enable);
> +	device_create_file(csidev->dev, &dev_attr_pg_active);
> +	device_create_file(csidev->dev, &dev_attr_pg_pattern);
> +}
> +
> +static void dwc_csi_pattern_generator_deinit(struct dwc_csi_device *csidev)
> +{
> +	device_remove_file(csidev->dev, &dev_attr_pg_pattern);
> +	device_remove_file(csidev->dev, &dev_attr_pg_active);
> +	device_remove_file(csidev->dev, &dev_attr_pg_enable);
> +}

I get the idea, but isn't using a V4L2_CID_TEST_PATTERN control the better 
choice?

> +/*
> ---------------------------------------------------------------------------
> -- + * Suspend/resume
> + */
> +
> +static int dwc_csi_system_suspend(struct device *dev)
> +{
> +	return pm_runtime_force_suspend(dev);
> +}
> +
> +static int dwc_csi_system_resume(struct device *dev)
> +{
> +	int ret;
> +
> +	ret = pm_runtime_force_resume(dev);
> +	if (ret < 0) {
> +		dev_err(dev, "force resume %s failed!\n", dev_name(dev));
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int dwc_csi_runtime_suspend(struct device *dev)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +
> +	dwc_csi_clk_disable(csidev);
> +
> +	return 0;
> +}
> +
> +static int dwc_csi_runtime_resume(struct device *dev)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	int ret;
> +
> +	ret = dwc_csi_clk_enable(csidev);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops dwc_csi_device_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(dwc_csi_system_suspend, 
dwc_csi_system_resume)
> +	SET_RUNTIME_PM_OPS(dwc_csi_runtime_suspend, dwc_csi_runtime_resume, 
NULL)
> +};
> +
> +/*
> ---------------------------------------------------------------------------
> -- + * IRQ handling
> + */
> +
> +static irqreturn_t dwc_csi_irq_handler(int irq, void *priv)
> +{
> +	struct dwc_csi_device *csidev = priv;
> +	unsigned long flags;
> +	u32 status;
> +	int i;
> +
> +	status = dwc_csi_read(csidev, CSI2RX_INT_ST_MAIN);
> +
> +	spin_lock_irqsave(&csidev->slock, flags);
> +
> +	if (status & csidev->pdata->events_mask) {
> +		for (i = 0; i < csidev->pdata->num_events; ++i) {
> +			struct dwc_csi_event *event = &csidev-
>events[i];
> +
> +			if (status & event->mask)
> +				event->counter++;
> +		}
> +	}
> +
> +	spin_unlock_irqrestore(&csidev->slock, flags);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +/*
> ---------------------------------------------------------------------------
> -- + * Probe/remove & platform driver
> + */
> +
> +static int dwc_csi_param_init(struct dwc_csi_device *csidev)
> +{
> +	int i;
> +
> +	/* Initialize the same format for pads of CSIS entity */
> +	for (i = 0; i < DWC_CSI2RX_PADS_NUM; ++i)
> +		csidev->format_mbus[i] = dwc_csi_default_fmt;
> +
> +	csidev->csi_fmt = &dwc_csi_formats[0];
> +
> +	return 0;
> +}
> +
> +static int dwc_csi_event_init(struct dwc_csi_device *csidev)
> +{
> +	unsigned int size = csidev->pdata->num_events
> +			  * sizeof(*csidev->events);
> +
> +	csidev->events = devm_kzalloc(csidev->dev, size, GFP_KERNEL);
> +	if (!csidev->events)
> +		return -ENOMEM;
> +
> +	memcpy(csidev->events, csidev->pdata->events, size);
> +
> +	return 0;
> +}
> +
> +static int dwc_csi_subdev_init(struct dwc_csi_device *csidev)
> +{
> +	struct v4l2_subdev *sd = &csidev->sd;
> +
> +	v4l2_subdev_init(sd, &dwc_csi_subdev_ops);
> +	sd->owner = THIS_MODULE;
> +	snprintf(sd->name, sizeof(sd->name), "csidev-%s", dev_name(csidev-
>dev));
> +
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->entity.ops = &dwc_csi_entity_ops;
> +
> +	sd->dev = csidev->dev;
> +
> +	csidev->pads[DWC_CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	csidev->pads[DWC_CSI2RX_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +
> +	return media_entity_pads_init(&csidev->sd.entity, 
DWC_CSI2RX_PADS_NUM,
> +				      csidev->pads);
> +}
> +
> +static int dwc_csi_device_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct dwc_csi_device *csidev;
> +	unsigned long cfg_rate;
> +	int irq;
> +	int ret;
> +
> +	csidev = devm_kzalloc(dev, sizeof(*csidev), GFP_KERNEL);
> +	if (!csidev)
> +		return -ENOMEM;
> +
> +	mutex_init(&csidev->lock);

I think you are missing the initialization of csidev->slock here.
> +
> +	csidev->dev = dev;
> +	csidev->pdata = of_device_get_match_data(dev);
> +
> +	csidev->regs = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(csidev->regs)) {
> +		dev_err(dev, "Failed to get DWC csi2 register map\n");
> +		return PTR_ERR(csidev->regs);
> +	}
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(dev, "Failed to get IRQ (%d)\n", irq);
> +		return irq;
> +	}
> +
> +	ret = devm_request_irq(dev, irq, dwc_csi_irq_handler, 0,
> +			       dev_name(dev), csidev);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to request IRQ (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	ret = dwc_csi_clk_get(csidev);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to get clocks\n");
> +		return ret;
> +	}
> +
> +	/* cfgclkfreqrange[5:0] = round[(cfg_clk(MHz) - 17) * 4] */
> +	cfg_rate = clk_get_rate(csidev->clks[PHY_CFG].clk);
> +	if (!cfg_rate) {
> +		dev_err(dev, "Failed to get phy_cfg clock rate\n");
> +		return -EINVAL;
> +	}
> +
> +	csidev->cfgclkfreqrange = ((cfg_rate / 1000000) - 17) * 4;
> +
> +	ret = dwc_csi_param_init(csidev);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = dwc_csi_event_init(csidev);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = dwc_csi_subdev_init(csidev);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to initialize subdev\n");
> +		return ret;
> +	}
> +
> +	platform_set_drvdata(pdev, &csidev->sd);
> +
> +	ret = dwc_csi_async_register(csidev);
> +	if (ret < 0) {
> +		dev_err(dev, "Async register failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	pm_runtime_enable(dev);
> +
> +	dwc_csi_pattern_generator_init(csidev);
> +
> +	return 0;
> +}
> +
> +static int dwc_csi_device_remove(struct platform_device *pdev)
> +{
> +	struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +
> +	v4l2_async_nf_unregister(&csidev->notifier);
> +	v4l2_async_nf_cleanup(&csidev->notifier);
> +	v4l2_async_unregister_subdev(&csidev->sd);
> +
> +	/* Remove pattern generator device attribute */
> +	dwc_csi_pattern_generator_deinit(csidev);
> +
> +	pm_runtime_disable(&pdev->dev);
> +
> +	media_entity_cleanup(&csidev->sd.entity);
> +	fwnode_handle_put(csidev->sd.fwnode);
> +	mutex_destroy(&csidev->lock);
> +
> +	pm_runtime_set_suspended(&pdev->dev);
> +	return 0;
> +}
> +
> +static const struct clk_bulk_data mxc_imx93_clks[] = {
> +	{ .id = "per" },
> +	{ .id = "pixel" },
> +	{ .id = "phy_cfg" },
> +};
> +
> +static const struct dwc_csi_plat_data mxc_imx93_data = {
> +	.model		= DWC_CSI2RX_IMX93,
> +	.intf		= DWC_CSI2RX_INTF_IPI,
> +	.clks		= mxc_imx93_clks,
> +	.num_clks	= ARRAY_SIZE(mxc_imx93_clks),
> +	.events		= mxc_imx93_events,
> +	.num_events	= ARRAY_SIZE(mxc_imx93_events),
> +	.events_mask	= 0x500ff,
> +};
> +
> +static const struct of_device_id dwc_csi_device_of_match[] = {
> +	{ .compatible = "fsl,imx93-mipi-csi2", .data = &mxc_imx93_data },
> +	{ /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, dwc_csi_device_of_match);
> +
> +static struct platform_driver dwc_csi_device_driver = {
> +	.driver = {
> +		.owner          = THIS_MODULE,
> +		.name           = DWC_MIPI_CSIS_DRIVER_NAME,
> +		.of_match_table = dwc_csi_device_of_match,
> +		.pm             = &dwc_csi_device_pm_ops,
> +	},
> +	.probe  = dwc_csi_device_probe,
> +	.remove = dwc_csi_device_remove,
> +};
> +
> +module_platform_driver(dwc_csi_device_driver);
> +
> +MODULE_DESCRIPTION("DesignWare Core MIPI CSI2 driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" DWC_MIPI_CSIS_DRIVER_NAME);
> diff --git a/drivers/media/platform/nxp/dwc-mipi-csi2.h
> b/drivers/media/platform/nxp/dwc-mipi-csi2.h new file mode 100644
> index 000000000000..cdb85d867f22
> --- /dev/null
> +++ b/drivers/media/platform/nxp/dwc-mipi-csi2.h
> @@ -0,0 +1,289 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright 2023 NXP
> + */
> +
> +#ifndef __DWC_MIPI_CSI2_H__
> +#define __DWC_MIPI_CSI2_H__
> +
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* MIPI CSI-2 Host Controller Registers Define */
> +
> +/* Core Version */
> +#define CSI2RX_VERSION					0x0
> +
> +/* Number of Lanes */
> +#define CSI2RX_N_LANES					0x4
> +#define CSI2RX_N_LANES_N_LANES(x)			((x) & 0x7)
> +
> +/* Logic Reset */
> +#define CSI2RX_HOST_RESETN				0x8
> +#define CSI2RX_HOST_RESETN_ENABLE			BIT(0)
> +
> +/* Main Interrupt Status */
> +#define CSI2RX_INT_ST_MAIN				0xc
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_IPI		BIT(18)
> +#define CSI2RX_INT_ST_MAIN_ERR_PHY			BIT(16)
> +#define CSI2RX_INT_ST_MAIN_ERR_ECC			BIT(7)
> +#define CSI2RX_INT_ST_MAIN_ERR_DID			BIT(6)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PLD_CRC		BIT(5)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_CRC_FRAME		BIT(4)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_SEQ_FRAME		BIT(3)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL	BIT(2)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PKT		BIT(1)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PHY		BIT(0)
> +
> +/* PHY Shutdown */
> +#define CSI2RX_DPHY_SHUTDOWNZ				0x40
> +#define CSI2RX_DPHY_SHUTDOWNZ_ENABLE			BIT(0)
> +
> +/* DPHY Reset */
> +#define CSI2RX_DPHY_RSTZ				0x44
> +#define CSI2RX_DPHY_RSTZ_ENABLE				BIT(0)
> +
> +/* RX PHY Status */
> +#define CSI2RX_DPHY_RX_STATUS				0x48
> +#define CSI2RX_DPHY_RX_STATUS_CLK_LANE_HS		BIT(17)
> +#define CSI2RX_DPHY_RX_STATUS_CLK_LANE_ULP		BIT(16)
> +#define CSI2RX_DPHY_RX_STATUS_DATA_LANE1_ULP		BIT(1)
> +#define CSI2RX_DPHY_RX_STATUS_DATA_LANE0_ULP		BIT(0)
> +
> +/* STOP STATE PHY Status */
> +#define CSI2RX_DPHY_STOPSTATE				0x4c
> +#define CSI2RX_DPHY_STOPSTATE_CLK_LANE			BIT(16)
> +#define CSI2RX_DPHY_STOPSTATE_DATA_LANE1		BIT(1)
> +#define CSI2RX_DPHY_STOPSTATE_DATA_LANE0		BIT(0)
> +
> +/* DPHY Test and Control Interface 1 */
> +#define CSI2RX_DPHY_TEST_CTRL0				0x50
> +#define CSI2RX_DPHY_TEST_CTRL0_TEST_CLR			BIT(0)
> +#define CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN		BIT(1)
> +
> +/* DPHY Test and Control Interface 2 */
> +#define CSI2RX_DPHY_TEST_CTRL1				0x54
> +#define CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(x)		((x) & 0xff)
> +#define CSI2RX_DPHY_TEST_CTRL1_TEST_DOUT(x)		(((x) & 0xff00) 
>> 8)
> +#define CSI2RX_DPHY_TEST_CTRL1_TEST_EN			BIT(16)
> +
> +/* Pattern Generator vertical Resolution */
> +#define CSI2RX_PPI_PG_PATTERN_VRES			0x60
> +#define CSI2RX_PPI_PG_PATTERN_VRES_VRES(x)		((x) & 0xffff)
> +
> +/* Pattern Generator horizontal Resolution */
> +#define CSI2RX_PPI_PG_PATTERN_HRES			0x64
> +#define CSI2RX_PPI_PG_PATTERN_HRES_HRES(x)		((x) & 0xffff)
> +
> +/* Pattern Generator */
> +#define CSI2RX_PPI_PG_CONFIG				0x68
> +#define CSI2RX_PPI_PG_CONFIG_DATA_TYPE(x)		(((x) & 0x3f) << 
8)
> +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN(x)		(((x) & 0x3) << 
14)
> +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX(x)		(((x) & 0x3) << 
16)
> +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX_2_EN		BIT(18)
> +#define CSI2RX_PPI_PG_CONFIG_PG_MODE(x)			(x)
> +
> +/* Pattern Generator Enable */
> +#define CSI2RX_PPI_PG_ENABLE				0x6c
> +#define CSI2RX_PPI_PG_ENABLE_EN				BIT(0)
> +
> +/* Pattern Generator Status */
> +#define CSI2RX_PPI_PG_STATUS				0x70
> +#define CSI2RX_PPI_PG_STATUS_ACTIVE			BIT(0)
> +
> +/* IPI Mode */
> +#define CSI2RX_IPI_MODE					0x80
> +#define CSI2RX_IPI_MODE_ENABLE				BIT(24)
> +#define CSI2RX_IPI_MODE_CUT_THROUGH			BIT(16)
> +#define CSI2RX_IPI_MODE_COLOR_MODE16			BIT(8)
> +#define CSI2RX_IPI_MODE_CONTROLLER			BIT(1)
> +
> +/* IPI Virtual Channel */
> +#define CSI2RX_IPI_VCID					0x84
> +#define CSI2RX_IPI_VCID_VC(x)				((x)  & 
0x3)
> +#define CSI2RX_IPI_VCID_VC_0_1(x)			(((x) & 0x3) << 
2)
> +#define CSI2RX_IPI_VCID_VC_2				BIT(4)
> +
> +/* IPI Data Type */
> +#define CSI2RX_IPI_DATA_TYPE				0x88
> +#define CSI2RX_IPI_DATA_TYPE_DT(x)			((x) & 0x3f)
> +#define CSI2RX_IPI_DATA_TYPE_EMB_DATA_EN		BIT(8)
> +
> +/* IPI Flush Memory */
> +#define CSI2RX_IPI_MEM_FLUSH				0x8c
> +#define CSI2RX_IPI_MEM_FLUSH_AUTO			BIT(8)
> +
> +/* IPI HSA */
> +#define CSI2RX_IPI_HSA_TIME				0x90
> +#define CSI2RX_IPI_HSA_TIME_VAL(x)			((x) & 0xfff)
> +
> +/* IPI HBP */
> +#define CSI2RX_IPI_HBP_TIME				0x94
> +#define CSI2RX_IPI_HBP_TIME_VAL(x)			((x) & 0xfff)
> +
> +/* IPI HSD */
> +#define CSI2RX_IPI_HSD_TIME				0x98
> +#define CSI2RX_IPI_HSD_TIME_VAL(x)			((x) & 0xfff)
> +
> +/* IPI HLINE */
> +#define CSI2RX_IPI_HLINE_TIME				0x9C
> +#define CSI2RX_IPI_HLINE_TIME_VAL(x)			((x) & 0x3fff)
> +
> +/* IPI Soft Reset */
> +#define CSI2RX_IPI_SOFTRSTN				0xa0
> +
> +/* IPI Advanced Features */
> +#define CSI2RX_IPI_ADV_FEATURES				0xac
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_EVENT_MODE		BIT(24)
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_EMBEDDED_PKT	BIT(21)
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_BLANKING_PKT	BIT(20)
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_NULL_PKT		BIT(19)
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_LS_PKT		BIT(18)
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_VIDEO_PKT		BIT(17)
> +#define CSI2RX_IPI_ADV_FEATURES_LINE_EVENT_SEL		BIT(16)
> +#define CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE(x)	(((x) & 0x3f) << 8)
> +#define CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE_EN	BIT(0)
> +
> +/* IPI VSA */
> +#define CSI2RX_IPI_VSA_LINES				0xb0
> +#define CSI2RX_IPI_VSA_LINES_VAL(x)			((x) & 0x3ff)
> +
> +/* IPI VBP */
> +#define CSI2RX_IPI_VBP_LINES				0xb4
> +#define CSI2RX_IPI_VBP_LINES_VAL(x)			((x) & 0x3ff)
> +
> +/* IPI VFP */
> +#define CSI2RX_IPI_VFP_LINES				0xb8
> +#define CSI2RX_IPI_VFP_LINES_VAL(x)			((x) & 0x3ff)
> +
> +/* IPI VACTIVE */
> +#define CSI2RX_IPI_VACTIVE_LINES			0xbc
> +#define CSI2RX_IPI_VACTIVE_LINES_VAL(x)			((x) & 
0x3fff)
> +
> +/* Fatal Interruption Caused by PHY */
> +#define CSI2RX_INT_ST_DPHY_FATAL			0xe0
> +#define CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE1		BIT(1)
> +#define CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE0		BIT(0)
> +
> +/* Mask for Fatal Interruption Caused by PHY */
> +#define CSI2RX_INT_MSK_DPHY_FATAL			0xe4
> +#define CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE1		BIT(1)
> +#define CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE0		BIT(0)
> +
> +/* Force for Fatal Interruption Caused by PHY */
> +#define CSI2RX_INT_FORCE_DPHY_FATAL			0xe8
> +
> +/* Fatal Interruption Caused During Packet Construction */
> +#define CSI2RX_INT_ST_PKT_FATAL			0xf0
> +#define CSI2RX_INT_ST_PKT_FATAL_ERR_PAYLOAD		BIT(1)
> +#define CSI2RX_INT_ST_PKT_FATAL_ERR_ECC		BIT(0)
> +
> +/* Mask for Fatal Interruption Caused During Packet Construction */
> +#define CSI2RX_INT_MSK_PKT_FATAL			0xf4
> +#define CSI2RX_INT_MSK_PKT_FATAL_ERR_PAYLOAD		BIT(1)
> +#define CSI2RX_INT_MSK_PKT_FATAL_ERR_ECC		BIT(0)
> +
> +/* Force for Fatal Interruption Caused During Packet Construction */
> +#define CSI2RX_INT_FORCE_PKT_FATAL			0xf8
> +
> +/* Interruption Caused by PHY */
> +#define CSI2RX_INT_ST_DPHY				0x110
> +#define CSI2RX_INT_ST_DPHY_ERR_ESC_LANE1		BIT(17)
> +#define CSI2RX_INT_ST_DPHY_ERR_ESC_LANE0		BIT(16)
> +#define CSI2RX_INT_ST_DPHY_ERR_SOT_LANE1		BIT(1)
> +#define CSI2RX_INT_ST_DPHY_ERR_SOT_LANE0		BIT(0)
> +
> +/* Mask for Interruption Caused by PHY */
> +#define CSI2RX_INT_MSK_DPHY				0x114
> +#define CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE1		BIT(17)
> +#define CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE0		BIT(16)
> +#define CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE1		BIT(1)
> +#define CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE0		BIT(0)
> +
> +/* Force for Interruption Caused by PHY */
> +#define CSI2RX_INT_FORCE_DPHY				0x118
> +
> +/* Fatal Interruption Caused by IPI Interface */
> +#define CSI2RX_INT_ST_IPI_FATAL			0x140
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_PD_FIFO_OVERFLOW	BIT(6)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_OVERFLOW	BIT(5)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_HLINE_TIME		BIT(4)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_NOT_EMPTY	BIT(3)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FRAME_SYNC		BIT(2)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_IF_FIFO_OVERFLOW	BIT(1)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_IF_FIFO_UNDERFLOW	BIT(0)
> +
> +/* Mask for Fatal Interruption Caused by IPI Interface */
> +#define CSI2RX_INT_MSK_IPI_FATAL			0x144
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_PD_FIFO_OVERFLOW	BIT(6)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_OVERFLOW	BIT(5)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_HLINE_TIME		BIT(4)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_NOT_EMPTY	BIT(3)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FRAME_SYNC		BIT(2)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_IF_FIFO_OVERFLOW	BIT(1)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_IF_FIFO_UNDERFLOW	BIT(0)
> +
> +/* Force for Fatal Interruption Caused by IPI Interface */
> +#define CSI2RX_INT_FORCE_IPI_FATAL			0x148
> +
> +/* Data De-Scrambling */
> +#define CSI2RX_SCRAMBLING				0x300
> +
> +/* De-scrambler Seed for Lane 1 */
> +#define CSI2RX_SCRAMBLING_SEED1				0x304
> +
> +/* De-scrambler Seed for Lane 2 */
> +#define CSI2RX_SCRAMBLING_SEED2				0x308
> +
> +#define dwc_csi_write(csidev, reg, val)	writel((val), csidev->regs 
+ (reg))
> +#define dwc_csi_read(csidev, reg)	readl(csidev->regs + (reg))
> +
> +#define DWC_CSI2RX_PAD_SINK		0
> +#define DWC_CSI2RX_PAD_SOURCE		1
> +#define DWC_CSI2RX_PADS_NUM		2
> +
> +struct dwc_csi_device {
> +	struct device *dev;
> +	void __iomem *regs;
> +	struct clk_bulk_data *clks;
> +	const struct dwc_csi_plat_data *pdata;
> +
> +	struct v4l2_subdev sd;
> +	struct v4l2_async_notifier notifier;
> +	struct v4l2_subdev *sensor_sd;
> +	struct media_pad pads[DWC_CSI2RX_PADS_NUM];
> +	u16 remote_pad;
> +
> +	struct v4l2_mbus_config_mipi_csi2 bus;
> +	u32 cfgclkfreqrange;
> +	u32 hsfreqrange;
> +
> +	spinlock_t slock;	/* Protect events */
> +	struct mutex lock;
> +
> +	struct dwc_csi_event *events;
> +	const struct dwc_csi_pix_format *csi_fmt;
> +	struct v4l2_mbus_framefmt format_mbus[DWC_CSI2RX_PADS_NUM];
> +
> +	/* Used for pattern generator */
> +	bool pg_enable;
> +	bool pg_active;
> +	enum {
> +		PATTERN_VERTICAL,
> +		PATTERN_HORIZONTAL,
> +	} pg_pattern;
> +};
> +
> +void dphy_rx_reset(struct dwc_csi_device *csidev);
> +void dphy_rx_test_code_reset(struct dwc_csi_device *csidev);
> +void dphy_rx_test_code_config(struct dwc_csi_device *csidev);
> +void dphy_rx_power_off(struct dwc_csi_device *csidev);
> +void dphy_rx_test_code_dump(struct dwc_csi_device *csidev);
> +
> +#endif /* __DWC_MIPI_CSI2_H__ */
> diff --git a/drivers/media/platform/nxp/dwc-mipi-dphy.c
> b/drivers/media/platform/nxp/dwc-mipi-dphy.c new file mode 100644
> index 000000000000..cc443f282bb7
> --- /dev/null
> +++ b/drivers/media/platform/nxp/dwc-mipi-dphy.c
> @@ -0,0 +1,195 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2023 NXP
> + */
> +
> +#include "dwc-mipi-csi2.h"
> +
> +/*
> + * DPHY testcode used to configure Rx DPHY
> + */
> +
> +/* System configuration 0 */
> +#define DPHY_RX_SYS_0					0x01
> +#define HSFREQRANGE_OVR_EN_RW				BIT(5)
> +
> +/* System configuration 1 */
> +#define DPHY_RX_SYS_1					0x02
> +#define HSFREQRANGE_OVR_RW(x)				((x) & 
0x7F)
> +#define TIMEBASE_OVR_EN_RW				BIT(7)
> +
> +/* System configuration 2 */
> +#define DPHY_RX_SYS_2					0x03
> +#define TIMEBASE_OVR_RW(x)				((x) & 0xFF)
> +
> +static inline void dphy_rx_test_ctrl_set(struct dwc_csi_device *csidev,
> +					 u32 offset, u32 mask, u32 
code)
> +{
> +	u32 val;
> +
> +	val = dwc_csi_read(csidev, offset);
> +	val &= ~(mask);
> +	val |= code;
> +	dwc_csi_write(csidev, offset, val);
> +}
> +
> +static inline void dphy_rx_test_ctrl_clr(struct dwc_csi_device *csidev,
> +					 u32 offset, u32 code)
> +{
> +	u32 val;
> +
> +	val = dwc_csi_read(csidev, offset);
> +	val &= ~(code);
> +	dwc_csi_write(csidev, offset, val);
> +}
> +
> +static u8 dphy_rx_test_ctrl_get(struct dwc_csi_device *csidev, u32 offset)
> +{
> +	u32 val;
> +
> +	val = dwc_csi_read(csidev, offset);
> +	val = CSI2RX_DPHY_TEST_CTRL1_TEST_DOUT(val);
> +
> +	return (u8)val;
> +}
> +static void dphy_rx_write(struct dwc_csi_device *csidev, u8 addr, u8 value)
> +{
> +	u32 val;
> +
> +	/*
> +	 * Set PHY_TST_CTRL1, bit[16] and write PHY_TST_CTRL1,
> +	 * bit[7:0] with test code address
> +	 */
> +	val = CSI2RX_DPHY_TEST_CTRL1_TEST_EN;
> +	val |= CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(addr);
> +	dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL1, 0x100ff, val);
> +
> +	/*
> +	 * Set and clear PHY_TST_CTRL0, bit[1]
> +	 */
> +	val = CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN;
> +	dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL0, 0x2, val);
> +	dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL0, val);
> +
> +	/*
> +	 * Write PHY_TST_CTRL1, bit[7:0] with test code content
> +	 */
> +	val = CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(value);
> +	dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL1, 0xff, val);
> +
> +	/*
> +	 * Clear PHY_TST_CTRL1, bit[16]
> +	 */
> +	val = CSI2RX_DPHY_TEST_CTRL1_TEST_EN;
> +	dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL1, val);
> +
> +	/*
> +	 * Set and clear PHY_TST_CTRL0, bit[1]
> +	 */
> +	val = CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN;
> +	dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL0, 0x2, val);
> +	dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL0, val);
> +}
> +
> +static int dphy_rx_read(struct dwc_csi_device *csidev, u8 addr)
> +{
> +	u32 val;
> +
> +	/*
> +	 * Set PHY_TST_CTRL1, bit[16] and write PHY_TST_CTRL1,
> +	 * bit[7:0] with test code address
> +	 */
> +	val = CSI2RX_DPHY_TEST_CTRL1_TEST_EN;
> +	val |= CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(addr);
> +	dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL1, 0x100ff, val);
> +
> +	/* Set and clear PHY_TST_CTRL0, bit[1] */
> +	val = CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN;
> +	dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL0, 0x2, val);
> +	dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL0, val);
> +
> +	/* Read PHY_TST_CTRL1, bit[15:8] with the test code content */
> +	val = dphy_rx_test_ctrl_get(csidev, CSI2RX_DPHY_TEST_CTRL1);
> +
> +	/* Clear PHY_TST_CTRL1, bit[16] */
> +	dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL1,
> +				CSI2RX_DPHY_TEST_CTRL1_TEST_EN);
> +
> +	return val;
> +}
> +
> +void dphy_rx_reset(struct dwc_csi_device *csidev)
> +{
> +	dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0);
> +	dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0);
> +	ndelay(15);
> +
> +	dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x1);
> +	ndelay(5);
> +	dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x1);
> +}
> +
> +void dphy_rx_test_code_reset(struct dwc_csi_device *csidev)
> +{
> +	u32 val;
> +
> +	/* Set PHY_TST_CTRL0, bit[0] */
> +	val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0);
> +	val |= CSI2RX_DPHY_TEST_CTRL0_TEST_CLR;
> +	dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val);
> +
> +	/* Clear PHY_TST_CTRL0, bit[0] */
> +	val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0);
> +	val &= ~CSI2RX_DPHY_TEST_CTRL0_TEST_CLR;
> +	dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val);
> +}
> +
> +void dphy_rx_test_code_config(struct dwc_csi_device *csidev)
> +{
> +	u32 val;
> +	u8 dphy_val;
> +
> +	/* Set testclr=1'b0 */
> +	val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0);
> +	val &= ~CSI2RX_DPHY_TEST_CTRL0_TEST_CLR;
> +	dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val);
> +
> +	/* Enable hsfreqrange_ovr_en and set hsfreqrange */
> +	dphy_rx_write(csidev, DPHY_RX_SYS_0, HSFREQRANGE_OVR_EN_RW);
> +	dphy_rx_write(csidev, DPHY_RX_SYS_1,
> +		      HSFREQRANGE_OVR_RW(csidev->hsfreqrange));
> +
> +	/* Enable timebase_ovr_en */
> +	dphy_val = dphy_rx_read(csidev, DPHY_RX_SYS_1);
> +	dphy_val |= TIMEBASE_OVR_EN_RW;
> +	dphy_rx_write(csidev, DPHY_RX_SYS_1, dphy_val);
> +
> +	/* Set cfgclkfreqrange */
> +	dphy_rx_write(csidev, DPHY_RX_SYS_2,
> +		      TIMEBASE_OVR_RW(csidev->cfgclkfreqrange + 0x44));

RM Rev 2. mentions that depending on cfgclkfreqrange another configuration, 
called counter_for_des_en_config_if, also needs to be set. Is this missing 
here?

> +}
> +
> +void dphy_rx_power_off(struct dwc_csi_device *csidev)
> +{
> +	dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0);
> +	dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0);
> +}
> +
> +void dphy_rx_test_code_dump(struct dwc_csi_device *csidev)
> +{
> +#define DPHY_DEBUG_REG(name)		{name, #name}
> +	static const struct {
> +		u32 offset;
> +		const char * const name;
> +	} test_codes[] = {
> +		DPHY_DEBUG_REG(DPHY_RX_SYS_0),
> +		DPHY_DEBUG_REG(DPHY_RX_SYS_1),
> +		DPHY_DEBUG_REG(DPHY_RX_SYS_2),
> +	};
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(test_codes); i++)
> +		dev_dbg(csidev->dev, "%14s[0x%02x]: 0x%02x\n",
> +			test_codes[i].name, test_codes[i].offset,
> +			dphy_rx_read(csidev, test_codes[i].offset));
> +}

Could you also provide a complete DT configuration? I tried myself, but I just 
ended up in getting errors while trying to use a MIPI-CSI camera
dwc-mipi-csi2 4ae00000.mipi-csi: IPI Interface Fatal Error events: 2800064
dwc-mipi-csi2 4ae00000.mipi-csi: PHY Error events: 2174
dwc-mipi-csi2 4ae00000.mipi-csi: IPI Interface Fatal Error events: 2800064
dwc-mipi-csi2 4ae00000.mipi-csi: PHY Error events: 2174

Best regards,
Alexander
-- 
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
http://www.tq-group.com/



  reply	other threads:[~2023-07-04  9:24 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-07-03 11:37 [PATCH 0/2] media: nxp: add i.MX93 MIPI CSI-2 support guoniu.zhou
2023-07-03 11:37 ` [PATCH 1/2] media: dt-bindings: Add binding doc for i.MX93 MIPI CSI-2 guoniu.zhou
2023-07-04  8:38   ` Alexander Stein
2023-07-05  1:36     ` G.N. Zhou (OSS)
2023-07-05 21:23       ` Laurent Pinchart
2023-07-06 10:08         ` G.N. Zhou (OSS)
2023-07-04 16:53   ` Conor Dooley
2023-07-05  1:30     ` G.N. Zhou (OSS)
2023-07-05 21:08       ` Conor Dooley
2023-07-03 11:37 ` [PATCH 2/2] media: nxp: add driver for i.MX93 MIPI CSI-2 controller and D-PHY guoniu.zhou
2023-07-04  9:23   ` Alexander Stein [this message]
2023-07-05  3:52     ` G.N. Zhou (OSS)
2023-07-05  6:50       ` Alexander Stein

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=10303134.nUPlyArG6x@steina-w \
    --to=alexander.stein@ew.tq-group.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=guoniu.zhou@oss.nxp.com \
    --cc=jacopo.mondi@ideasonboard.com \
    --cc=krzysztof.kozlowski+dt@linaro.org \
    --cc=laurent.pinchart@ideasonboard.com \
    --cc=linux-imx@nxp.com \
    --cc=linux-media@vger.kernel.org \
    --cc=mchehab@kernel.org \
    --cc=robh+dt@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.