Re: [PATCH v2 2/2] media: nxp: add driver for i.MX93 MIPI CSI-2 controller and D-PHY

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

 



Hello,

CC'ing Niklas, for a question down below.

On Wed, Sep 13, 2023 at 08:36:18PM +0000, Sakari Ailus wrote:
> Hi Guonio,
> 
> Thanks for the patch. Looks pretty good overall, but please see comments
> below...
> 
> On Mon, Jul 10, 2023 at 02:03:52PM +0800, guoniu.zhou@xxxxxxxxxxx wrote:
> > From: "Guoniu.zhou" <guoniu.zhou@xxxxxxx>
> > 
> > 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@xxxxxxx>
> > ---
> >  MAINTAINERS                                |   10 +
> >  drivers/media/platform/nxp/Kconfig         |   11 +
> >  drivers/media/platform/nxp/Makefile        |    3 +
> >  drivers/media/platform/nxp/dwc-mipi-csi2.c | 1354 ++++++++++++++++++++
> >  drivers/media/platform/nxp/dwc-mipi-csi2.h |  299 +++++
> >  drivers/media/platform/nxp/dwc-mipi-dphy.c |  295 +++++
> >  6 files changed, 1972 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@xxxxxxxxxxx>
> > +R:	NXP Linux Team <linux-imx@xxxxxxx>
> > +L:	linux-media@xxxxxxxxxxxxxxx
> > +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@xxxxxxxxxxxx>
> >  L:	linux-hwmon@xxxxxxxxxxxxxxx
> > 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.
> 
> You seem to have D-PHY handling embedded in the same driver.
> 
> Would it be possible to use the phy framework instead? I believe there are
> other drivers that already do this.

I think that would be difficult, as the D-PHY isn't memory-mapped to the
CPU.

> > +
> >  # 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

I like that this is a generic driver for the Synopsys CSI-2 host
controller. Should it then be placed in drivers/media/platform/synopsys/
?

> >  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..4a7154b471c5
> > --- /dev/null
> > +++ b/drivers/media/platform/nxp/dwc-mipi-csi2.c
> > @@ -0,0 +1,1354 @@
> > +// 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/v4l2-event.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
> > +
> > +enum imx93_csi_clks {
> > +	PER,
> > +	PIXEL,
> > +	PHY_CFG,
> > +};
> > +
> > +enum model {
> > +	DWC_CSI2RX_IMX93,
> > +};
> > +
> > +enum dwc_csi2rx_intf {
> > +	DWC_CSI2RX_INTF_IDI,
> > +	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[] = {
> 
> const.
> 
> > +	{ 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
> > + */
> > +
> > +/* 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;
> 
> unsigned int?
> 
> > +
> > +	for (i = 0; i < ARRAY_SIZE(dwc_csi_formats); i++)
> > +		if (code == dwc_csi_formats[i].code)
> > +			return &dwc_csi_formats[i];
> > +	return NULL;
> 
> Could you always return a valid format? This makes it easier for the caller
> to handle the return value.
> 
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * 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;
> > +	}

This should never happen, the driver should ensure that a valid format
is configured at all times.

> > +
> > +	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");

Make this a dev_dbg() as it can get triggered by userspace.

> > +		return -EINVAL;

Pipeline validation at stream on time typically returns -EPIPE.

> > +	}
> > +
> > +	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
> > +	 **/

Double **

> > +	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);
> > +}
> > +
> > +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;

The driver supports the IPI interface only, I would drop these checks as
well as the intf field.

> > +
> > +	/* 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 */
> > +	ret = dphy_rx_get_hsfreqrange(csidev);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	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);
> 
> devm_kmalloc_array()?
> 
> > +	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),
> > +	};
> > +
> 
> Extra newline.
> 
> > +	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)
> 
> Please call this "sd" or "subdev" (instead of 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];
> 
> Please use sub-device state. See e.g.

To be precise, this is about storing the subdev active state in a
v4l2_subdev_state structure managed by the subdev core. It should
simplify the driver.

> <URL:https://git.linuxtv.org/sailus/media_tree.git/commit/?h=metadata&id=9e6a17a9750e220a93e0eb5d24c4af4c27d5fe3a>
> for an example. I believe there are other examples in the tree already.
> 
> > +}
> > +
> > +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;
> 
> Do you have more than two pads? The caller will have validated the pad
> field already.
> 
> > +
> > +	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;
> 
> Ditto.
> 
> > +
> > +	/*
> > +	 * 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;
> > +	}
> > +
> > +	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;

Does the CSI-2 receiver support capturing embedded data ?

> > +
> > +	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;
> > +	}

As the driver supports a single connected sensor (it adds a single entry
to the notifier at probe time), you can simplify this by assigning
sensor_sd in the .bound() handler, making the link immutable, and
dropping the check here as well as dropping the .link_setup() handler.

> > +
> > +	mutex_lock(&csidev->lock);
> > +
> > +	if (!enable) {
> > +		dwc_csi_stop_stream(csidev);
> > +		dwc_csi_log_counters(csidev);
> > +		pm_runtime_put(csidev->dev);
> > +		goto sd_stream;
> > +	}

An 

	} else {

here would avoid the goto above.

> > +
> > +	ret = pm_runtime_resume_and_get(csidev->dev);
> > +	if (ret < 0)
> > +		goto unlocked;
> > +
> > +	ret = __v4l2_ctrl_handler_setup(&csidev->ctrl_handler);
> > +	if (ret < 0) {
> > +		pm_runtime_put(csidev->dev);
> > +		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.

That makes the test pattern generator less useful :-( If you need a
sensor anyway, what are the use cases for using the TPG ?

> > +	 */
> > +	v4l2_subdev_call(csidev->sensor_sd, video, s_stream, enable);
> > +	dwc_csi_log_counters(csidev);
> > +unlocked:

s/unlocked/unlock/

> > +	mutex_unlock(&csidev->lock);
> > +	return ret;
> > +}
> > +
> > +static int dwc_csi_subdev_log_status(struct v4l2_subdev *sd)
> > +{
> > +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> > +
> > +	dwc_csi_log_counters(csidev);
> > +	return 0;
> > +}
> > +
> > +static const struct v4l2_subdev_core_ops dwc_csi_subdev_core_ops = {
> > +	.log_status = dwc_csi_subdev_log_status,
> > +	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
> > +	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
> > +};
> > +
> > +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 = {
> > +	.core  = &dwc_csi_subdev_core_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

s/soruce/source/

> > +	 * 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)
> 
> There have been changes since you posted the patch, the equivalent struct
> is now called v4l2_async_connection.
> 
> > +{
> > +	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;
> > +
> > +	dev_dbg(csidev->dev, "data lanes: %d\n", csidev->bus.num_data_lanes);
> > +	dev_dbg(csidev->dev, "flags: 0x%08x\n", csidev->bus.flags);
> > +
> > +	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;
> > +
> > +	return v4l2_async_register_subdev(&csidev->sd);
> > +
> > +err_parse:
> > +	fwnode_handle_put(ep);
> > +
> > +	return ret;
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Pattern Generator Controller operations
> > + */
> > +
> > +static const char * const test_pattern_menu[] = {
> > +	"Disabled",
> > +	"Vertical Color Bars",
> > +	"Horizontal Color Bars",
> > +};
> > +
> > +static inline struct dwc_csi_device *ctrl_to_csidev(struct v4l2_ctrl *ctrl)
> > +{
> > +	return container_of(ctrl->handler, struct dwc_csi_device, ctrl_handler);
> > +}
> > +
> > +static int dwc_csi_s_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +	struct dwc_csi_device *csidev  = ctrl_to_csidev(ctrl);
> > +	int ret = 0;
> > +
> > +	switch (ctrl->id) {
> > +	case V4L2_CID_TEST_PATTERN:
> > +		/* Pattern index start from 0 */
> > +		csidev->pg_pattern = ctrl->val - 1;
> > +		csidev->pg_enable = (ctrl->val) ? true : false;
> > +		break;
> 
> You could already return here. Ret would become redundant. Up to you.
> 
> > +	default:
> > +		ret = -EINVAL;
> > +		break;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct v4l2_ctrl_ops dwc_csi_ctrl_ops = {
> > +	.s_ctrl = dwc_csi_s_ctrl,
> > +};
> > +
> > +static int dwc_csi_controls_init(struct dwc_csi_device *csidev)
> > +{
> > +	struct v4l2_ctrl_handler *handler = &csidev->ctrl_handler;
> > +	int ret;
> > +
> > +	v4l2_ctrl_handler_init(handler, 1);
> > +
> > +	/* Use driver mutex lock for the ctrl lock */
> > +	handler->lock = &csidev->lock;
> > +
> > +	v4l2_ctrl_new_std_menu_items(handler, &dwc_csi_ctrl_ops,
> > +				     V4L2_CID_TEST_PATTERN,
> > +				     ARRAY_SIZE(test_pattern_menu) - 1,
> > +				     0, 0, test_pattern_menu);
> > +
> > +	if (handler->error) {
> > +		ret = handler->error;
> > +		v4l2_ctrl_handler_free(handler);
> > +		return ret;
> > +	}
> > +
> > +	csidev->sd.ctrl_handler = handler;
> > +	return 0;
> > +}
> > +/* -----------------------------------------------------------------------------
> > + * 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;
> > +}

These shouldn't be needed, the device connected to the CSI-2 RX output
should stop the pipeline during system suspend and resume it during
system resume.

> > +
> > +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);

This is the only caller fo the dwc_csi_clk_disable() function, I would
inline it here. Same for dwc_csi_clk_enable() below.

> > +
> > +	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;

	return dwc_csi_clk_enable(csidev);

> > +}
> > +
> > +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;
> 
> unsigned int?
> 
> > +
> > +	/* 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 |
> > +		     V4L2_SUBDEV_FL_HAS_EVENTS;
> > +	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);
> > +	spin_lock_init(&csidev->slock);
> > +
> > +	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;
> 
> Please use div_u64() here. This won't work on some archs otherwise.
> 
> > +
> > +	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;
> > +	}
> > +
> > +	ret = dwc_csi_controls_init(csidev);
> > +	if (ret) {
> > +		dev_err(dev, "Failed to initialize controls\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);
> > +
> > +	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);
> > +
> > +	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..470023787c25
> > --- /dev/null
> > +++ b/drivers/media/platform/nxp/dwc-mipi-csi2.h
> > @@ -0,0 +1,299 @@
> > +/* 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-ctrls.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-mc.h>
> > +#include <media/v4l2-subdev.h>
> 
> I don't think you need many of these headers here. Please move to the .c
> file those you don't need here.
> 
> > +
> > +/* MIPI CSI-2 Host Controller Registers Define */
> > +

Niklas, is it me, or do the registers here look very familiar ? The
R-Car V4H may have the same CSI-2 RX. Should we have a single driver ?

> > +/* 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_pix_format {
> > +	u32 code;
> > +	u32 output;
> > +	u32 data_type;
> > +	u8 width;
> > +};
> > +
> > +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 v4l2_ctrl_handler ctrl_handler;
> > +	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;
> > +	enum {
> > +		PATTERN_DISABLED,
> > +		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);
> > +int dphy_rx_get_hsfreqrange(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..9b9f2f01a31c
> > --- /dev/null
> > +++ b/drivers/media/platform/nxp/dwc-mipi-dphy.c
> > @@ -0,0 +1,295 @@
> > +// 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)
> > +
> > +struct dwc_csi_mbps_reg {
> > +	u16 mbps;
> > +	u16 reg;
> > +};
> > +
> > +/*
> > + * Bit rate to high speed frequency range map table
> > + */
> > +static const struct dwc_csi_mbps_reg dwc_csi_mbps_table[] = {
> > +	{ .mbps = 80,  .reg = 0x00 },
> > +	{ .mbps = 90,  .reg = 0x10 },
> > +	{ .mbps = 100, .reg = 0x20 },
> > +	{ .mbps = 110, .reg = 0x30 },
> > +	{ .mbps = 120, .reg = 0x01 },
> > +	{ .mbps = 130, .reg = 0x11 },
> > +	{ .mbps = 140, .reg = 0x21 },
> > +	{ .mbps = 150, .reg = 0x31 },
> > +	{ .mbps = 160, .reg = 0x02 },
> > +	{ .mbps = 170, .reg = 0x12 },
> > +	{ .mbps = 180, .reg = 0x22 },
> > +	{ .mbps = 190, .reg = 0x32 },
> > +	{ .mbps = 205, .reg = 0x03 },
> > +	{ .mbps = 220, .reg = 0x13 },
> > +	{ .mbps = 235, .reg = 0x23 },
> > +	{ .mbps = 250, .reg = 0x33 },
> > +	{ .mbps = 275, .reg = 0x04 },
> > +	{ .mbps = 300, .reg = 0x14 },
> > +	{ .mbps = 325, .reg = 0x25 },
> > +	{ .mbps = 350, .reg = 0x35 },
> > +	{ .mbps = 400, .reg = 0x05 },
> > +	{ .mbps = 450, .reg = 0x16 },
> > +	{ .mbps = 500, .reg = 0x26 },
> > +	{ .mbps = 550, .reg = 0x37 },
> > +	{ .mbps = 600, .reg = 0x07 },
> > +	{ .mbps = 650, .reg = 0x18 },
> > +	{ .mbps = 700, .reg = 0x28 },
> > +	{ .mbps = 750, .reg = 0x39 },
> > +	{ .mbps = 800, .reg = 0x09 },
> > +	{ .mbps = 850, .reg = 0x19 },
> > +	{ .mbps = 900, .reg = 0x29 },
> > +	{ .mbps = 950, .reg = 0x3a },
> > +	{ .mbps = 1000, .reg = 0x0a },
> > +	{ .mbps = 1050, .reg = 0x1a },
> > +	{ .mbps = 1100, .reg = 0x2a },
> > +	{ .mbps = 1150, .reg = 0x3b },
> > +	{ .mbps = 1200, .reg = 0x0b },
> > +	{ .mbps = 1250, .reg = 0x1b },
> > +	{ .mbps = 1300, .reg = 0x2b },
> > +	{ .mbps = 1350, .reg = 0x3c },
> > +	{ .mbps = 1400, .reg = 0x0c },
> > +	{ .mbps = 1450, .reg = 0x1c },
> > +	{ .mbps = 1500, .reg = 0x2c },
> > +	{ /* sentinel */ },
> > +};
> > +
> > +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));
> > +}
> > +
> > +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));
> > +}
> > +
> > +int dphy_rx_get_hsfreqrange(struct dwc_csi_device *csidev)
> > +{
> > +	struct v4l2_subdev *source = csidev->sensor_sd;
> > +	const struct dwc_csi_mbps_reg *value;
> > +	const struct dwc_csi_mbps_reg *prev_value = NULL;
> > +	s64 link_freq;
> > +	u32 mbsp;
> > +
> > +	link_freq = v4l2_get_link_freq(source->ctrl_handler,
> > +				       csidev->csi_fmt->width,
> > +				       csidev->bus.num_data_lanes * 2);
> > +	if (link_freq < 0) {
> > +		dev_err(csidev->dev, "Unable to obtain link frequency: %d\n",
> > +			(int)link_freq);
> > +		return link_freq;
> > +	}
> > +
> > +	mbsp = link_freq * 2 / 1000000;
> > +
> > +	if (mbsp < 80 || mbsp > 1500) {
> > +		dev_dbg(csidev->dev, "Out-of-bound lane rate %u\n", mbsp);
> > +		return -EINVAL;
> > +	}
> > +
> > +	for (value = dwc_csi_mbps_table; value->mbps; value++) {
> > +		if (value->mbps >= mbsp)
> > +			break;
> > +		prev_value = value;
> > +	}
> > +
> > +	if (prev_value &&
> > +	    ((mbsp - prev_value->mbps) <= (value->mbps - mbsp)))
> > +		value = prev_value;
> > +
> > +	if (!value->mbps) {
> > +		dev_err(csidev->dev, "Unsupported PHY speed (%u Mbps)", mbsp);
> > +		return -ERANGE;
> > +	}
> > +
> > +	csidev->hsfreqrange = value->reg;
> > +
> > +	return 0;
> > +}

-- 
Regards,

Laurent Pinchart




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux