lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <1933947.CQOukoFCf9@steina-w>
Date: Wed, 02 Jul 2025 08:04:36 +0200
From: Alexander Stein <alexander.stein@...tq-group.com>
To: Rui Miguel Silva <rmfrfs@...il.com>,
 Laurent Pinchart <laurent.pinchart@...asonboard.com>,
 Martin Kepplinger <martink@...teo.de>, Purism Kernel Team <kernel@...i.sm>,
 Mauro Carvalho Chehab <mchehab@...nel.org>, Rob Herring <robh@...nel.org>,
 Krzysztof Kozlowski <krzk+dt@...nel.org>, Conor Dooley <conor+dt@...nel.org>,
 Eugen Hristev <eugen.hristev@...aro.org>, Shawn Guo <shawnguo@...nel.org>,
 Sascha Hauer <s.hauer@...gutronix.de>,
 Pengutronix Kernel Team <kernel@...gutronix.de>,
 Fabio Estevam <festevam@...il.com>, Peng Fan <peng.fan@....com>,
 Alice Yuan <alice.yuan@....com>, Vinod Koul <vkoul@...nel.org>,
 Kishon Vijay Abraham I <kishon@...nel.org>,
 Philipp Zabel <p.zabel@...gutronix.de>, Frank Li <Frank.Li@....com>
Cc: linux-media@...r.kernel.org, devicetree@...r.kernel.org,
 linux-kernel@...r.kernel.org, imx@...ts.linux.dev,
 linux-arm-kernel@...ts.infradead.org, linux-phy@...ts.infradead.org,
 Frank Li <Frank.Li@....com>, "Guoniu.zhou" <guoniu.zhou@....com>
Subject:
 Re: [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver

Hi,

thanks for the patch.

Am Mittwoch, 2. Juli 2025, 00:06:10 CEST schrieb Frank Li:
> From: "Guoniu.zhou" <guoniu.zhou@....com>
> 
> Add V4L2 subdev driver for DesignWare MIPI CSI2 controller.
> 
> Signed-off-by: Guoniu.zhou <guoniu.zhou@....com>
> Signed-off-by: Frank Li <Frank.Li@....com>
> ---
>  MAINTAINERS                                |    1 +
>  drivers/media/platform/nxp/Kconfig         |   11 +
>  drivers/media/platform/nxp/Makefile        |    1 +
>  drivers/media/platform/nxp/dwc-mipi-csi2.c | 1675 ++++++++++++++++++++++++++++
>  4 files changed, 1688 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e208c91f1405f..f67db23cb9d6f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15022,6 +15022,7 @@ F:	Documentation/devicetree/bindings/media/nxp,imx-mipi-csi2.yaml
>  F:	Documentation/devicetree/bindings/media/nxp,imx7-csi.yaml
>  F:	Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml
>  F:	Documentation/devicetree/bindings/media/snps,dw-mipi-csi2-v150.yaml
> +F:	drivers/media/platform/nxp/dwc-mipi-csi2.c
>  F:	drivers/media/platform/nxp/imx-mipi-csis.c
>  F:	drivers/media/platform/nxp/imx-parallel-csi.c
>  F:	drivers/media/platform/nxp/imx7-media-csi.c
> diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig
> index 5df6f97d16f29..b7a4aed706443 100644
> --- a/drivers/media/platform/nxp/Kconfig
> +++ b/drivers/media/platform/nxp/Kconfig
> @@ -4,6 +4,17 @@
>  
>  comment "NXP media platform drivers"
>  
> +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.
> +
>  config VIDEO_IMX7_CSI
>  	tristate "NXP CSI Bridge driver"
>  	depends on ARCH_MXC || COMPILE_TEST
> diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile
> index 9a9b2a1d6886c..a0a025c169aca 100644
> --- a/drivers/media/platform/nxp/Makefile
> +++ b/drivers/media/platform/nxp/Makefile
> @@ -4,6 +4,7 @@ obj-y += dw100/
>  obj-y += imx-jpeg/
>  obj-y += imx8-isi/
>  
> +obj-$(CONFIG_VIDEO_DWC_MIPI_CSIS) += dwc-mipi-csi2.o
>  obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
>  obj-$(CONFIG_VIDEO_IMX8MQ_MIPI_CSI2) += imx8mq-mipi-csi2.o
>  obj-$(CONFIG_VIDEO_IMX_PARALLEL_CSI) += imx-parallel-csi.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 0000000000000..cd57c06b95848
> --- /dev/null
> +++ b/drivers/media/platform/nxp/dwc-mipi-csi2.c
> @@ -0,0 +1,1675 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2025 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/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.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)			FIELD_PREP(GENMASK(2, 0), (x) - 1)
> +
> +/* 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_PHY		BIT(0)
> +#define   CSI2RX_INT_ST_MAIN_FATAL_ERR_PKT		BIT(1)
> +#define   CSI2RX_INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL	BIT(2)
> +#define   CSI2RX_INT_ST_MAIN_FATAL_ERR_SEQ_FRAME	BIT(3)
> +#define   CSI2RX_INT_ST_MAIN_FATAL_ERR_CRC_FRAME	BIT(4)
> +#define   CSI2RX_INT_ST_MAIN_FATAL_ERR_PLD_CRC		BIT(5)
> +#define   CSI2RX_INT_ST_MAIN_ERR_DID			BIT(6)
> +#define   CSI2RX_INT_ST_MAIN_ERR_ECC			BIT(7)
> +#define   CSI2RX_INT_ST_MAIN_ERR_PHY			BIT(16)
> +#define   CSI2RX_INT_ST_MAIN_FATAL_ERR_IPI		BIT(18)
> +
> +/* Data monitor */
> +#define CSI2RX_DATA_IDS_1_DT				0x10
> +#define   CSI2RX_DATA_IDS_1_DT_DATA_ID0(x)		FIELD_PREP(GENMASK(5, 0), (x))
> +#define   CSI2RX_DATA_IDS_1_DT_DATA_ID1(x)		FIELD_PREP(GENMASK(13, 8), (x))
> +#define   CSI2RX_DATA_IDS_1_DT_DATA_ID2(x)		FIELD_PREP(GENMASK(21, 16), (x))
> +#define   CSI2RX_DATA_IDS_1_DT_DATA_ID3(x)		FIELD_PREP(GENMASK(29, 24), (x))
> +
> +#define CSI2RX_DATA_IDS_2_DT				0x14
> +#define   CSI2RX_DATA_IDS_2_DT_DATA_ID4(x)		FIELD_PREP(GENMASK(5, 0), (x))
> +#define   CSI2RX_DATA_IDS_2_DT_DATA_ID5(x)		FIELD_PREP(GENMASK(13, 8), (x))
> +#define   CSI2RX_DATA_IDS_2_DT_DATA_ID6(x)		FIELD_PREP(GENMASK(21, 16), (x))
> +#define   CSI2RX_DATA_IDS_2_DT_DATA_ID7(x)		FIELD_PREP(GENMASK(29, 24), (x))
> +
> +#define CSI2RX_DATA_IDS_1_VC				0x30
> +#define   CSI2RX_DATA_IDS_1_VC_DATA_ID0(x)		FIELD_PREP(GENMASK(3, 0), (x))
> +#define   CSI2RX_DATA_IDS_1_VC_DATA_ID1(x)		FIELD_PREP(GENMASK(11, 8), (x))
> +#define   CSI2RX_DATA_IDS_1_VC_DATA_ID2(x)		FIELD_PREP(GENMASK(19, 16), (x))
> +#define   CSI2RX_DATA_IDS_1_VC_DATA_ID3(x)		FIELD_PREP(GENMASK(27, 24), (x))
> +
> +#define CSI2RX_DATA_IDS_2_VC				0x34
> +#define   CSI2RX_DATA_IDS_2_VC_DATA_ID4(x)		FIELD_PREP(GENMASK(3, 0), (x))
> +#define   CSI2RX_DATA_IDS_2_VC_DATA_ID5(x)		FIELD_PREP(GENMASK(11, 8), (x))
> +#define   CSI2RX_DATA_IDS_2_VC_DATA_ID6(x)		FIELD_PREP(GENMASK(19, 16), (x))
> +#define   CSI2RX_DATA_IDS_2_VC_DATA_ID7(x)		FIELD_PREP(GENMASK(27, 24), (x))
> +
> +/* 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_DATA_LANE0_ULP		BIT(0)
> +#define   CSI2RX_DPHY_RX_STATUS_DATA_LANE1_ULP		BIT(1)
> +#define   CSI2RX_DPHY_RX_STATUS_CLK_LANE_ULP		BIT(16)
> +#define   CSI2RX_DPHY_RX_STATUS_CLK_LANE_HS		BIT(17)
> +
> +/* STOP STATE PHY Status */
> +#define CSI2RX_DPHY_STOPSTATE				0x4c
> +#define   CSI2RX_DPHY_STOPSTATE_DATA_LANE0		BIT(0)
> +#define   CSI2RX_DPHY_STOPSTATE_DATA_LANE1		BIT(1)
> +#define   CSI2RX_DPHY_STOPSTATE_DATA_LANE2		BIT(2)
> +#define   CSI2RX_DPHY_STOPSTATE_DATA_LANE3		BIT(3)
> +#define   CSI2RX_DPHY_STOPSTATE_CLK_LANE		BIT(16)
> +
> +/* 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)		FIELD_PREP(GENMASK(7, 0), (x))
> +#define   CSI2RX_DPHY_TEST_CTRL1_TEST_DOUT(x)		FIELD_GET(GENMASK(15, 8), (x))
> +#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)		FIELD_PREP(GENMASK(15, 0), (x))
> +
> +/* Pattern Generator horizontal Resolution */
> +#define CSI2RX_PPI_PG_PATTERN_HRES			0x64
> +#define   CSI2RX_PPI_PG_PATTERN_HRES_HRES(x)		FIELD_PREP(GENMASK(15, 0), (x))
> +
> +/* Pattern Generator */
> +#define CSI2RX_PPI_PG_CONFIG				0x68
> +#define   CSI2RX_PPI_PG_CONFIG_PG_MODE(x)		FIELD_PREP(1, (x))
> +#define   CSI2RX_PPI_PG_CONFIG_DATA_TYPE(x)		FIELD_PREP(GENMASK(13, 8), (x))
> +#define   CSI2RX_PPI_PG_CONFIG_VIR_CHAN(x)		FIELD_PREP(GENMASK(15, 14), (x))
> +#define   CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX(x)		FIELD_PREP(GENMASK(17, 16), (x))
> +#define   CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX_2_EN		BIT(18)
> +
> +/* 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_CONTROLLER			BIT(1)
> +#define   CSI2RX_IPI_MODE_COLOR_MODE16			BIT(8)
> +#define   CSI2RX_IPI_MODE_CUT_THROUGH			BIT(16)
> +#define   CSI2RX_IPI_MODE_ENABLE			BIT(24)
> +
> +/* IPI Virtual Channel */
> +#define CSI2RX_IPI_VCID					0x84
> +#define   CSI2RX_IPI_VCID_VC(x)				FIELD_PREP(GENMASK(1, 0), (x))
> +#define   CSI2RX_IPI_VCID_VC_0_1(x)			FIELD_PREP(GENMASK(3, 2), (x))
> +#define   CSI2RX_IPI_VCID_VC_2				BIT(4)
> +
> +/* IPI Data Type */
> +#define CSI2RX_IPI_DATA_TYPE				0x88
> +#define   CSI2RX_IPI_DATA_TYPE_DT(x)			FIELD_PREP(GENMASK(5, 0), (x))
> +#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)			FIELD_PREP(GENMASK(11, 0), (x))
> +
> +/* IPI HBP */
> +#define CSI2RX_IPI_HBP_TIME				0x94
> +#define   CSI2RX_IPI_HBP_TIME_VAL(x)			FIELD_PREP(GENMASK(11, 0), (x))
> +
> +/* IPI HSD */
> +#define CSI2RX_IPI_HSD_TIME				0x98
> +#define   CSI2RX_IPI_HSD_TIME_VAL(x)			FIELD_PREP(GENMASK(11, 0), (x))
> +
> +/* IPI HLINE */
> +#define CSI2RX_IPI_HLINE_TIME				0x9C
> +#define   CSI2RX_IPI_HLINE_TIME_VAL(x)			FIELD_PREP(GENMASK(14, 0), (x))
> +
> +/* IPI Soft Reset */
> +#define CSI2RX_IPI_SOFTRSTN				0xa0
> +
> +/* IPI Advanced Features */
> +#define CSI2RX_IPI_ADV_FEATURES				0xac
> +#define   CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE_EN	BIT(0)
> +#define   CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE(x)	FIELD_PREP(GENMASK(13, 8), (x))
> +#define   CSI2RX_IPI_ADV_FEATURES_LINE_EVENT_SEL	BIT(16)
> +#define   CSI2RX_IPI_ADV_FEATURES_SYNC_VIDEO_PKT	BIT(17)
> +#define   CSI2RX_IPI_ADV_FEATURES_SYNC_LS_PKT		BIT(18)
> +#define   CSI2RX_IPI_ADV_FEATURES_SYNC_NULL_PKT		BIT(19)
> +#define   CSI2RX_IPI_ADV_FEATURES_SYNC_BLANKING_PKT	BIT(20)
> +#define   CSI2RX_IPI_ADV_FEATURES_SYNC_EMBEDDED_PKT	BIT(21)
> +#define   CSI2RX_IPI_ADV_FEATURES_SYNC_EVENT_MODE	BIT(24)
> +
> +/* IPI VSA */
> +#define CSI2RX_IPI_VSA_LINES				0xb0
> +#define   CSI2RX_IPI_VSA_LINES_VAL(x)			FIELD_PREP(GENMASK(9, 0), (x))
> +
> +/* IPI VBP */
> +#define CSI2RX_IPI_VBP_LINES				0xb4
> +#define   CSI2RX_IPI_VBP_LINES_VAL(x)			FIELD_PREP(GENMASK(9, 0), (x))
> +
> +/* IPI VFP */
> +#define CSI2RX_IPI_VFP_LINES				0xb8
> +#define   CSI2RX_IPI_VFP_LINES_VAL(x)			FIELD_PREP(GENMASK(9, 0), (x))
> +
> +/* IPI VACTIVE */
> +#define CSI2RX_IPI_VACTIVE_LINES			0xbc
> +#define   CSI2RX_IPI_VACTIVE_LINES_VAL(x)		FIELD_PREP(GENMASK(13, 0), (x))
> +
> +/* Fatal Interruption Caused by PHY */
> +#define CSI2RX_INT_ST_DPHY_FATAL			0xe0
> +#define   CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE0	BIT(0)
> +#define   CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE1	BIT(1)
> +
> +/* Mask for Fatal Interruption Caused by PHY */
> +#define CSI2RX_INT_MSK_DPHY_FATAL			0xe4
> +#define   CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE0	BIT(0)
> +#define   CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE1	BIT(1)
> +
> +/* 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_ECC		BIT(0)
> +#define   CSI2RX_INT_ST_PKT_FATAL_ERR_PAYLOAD		BIT(1)
> +
> +/* Mask for Fatal Interruption Caused During Packet Construction */
> +#define CSI2RX_INT_MSK_PKT_FATAL			0xf4
> +#define   CSI2RX_INT_MSK_PKT_FATAL_ERR_ECC		BIT(0)
> +#define   CSI2RX_INT_MSK_PKT_FATAL_ERR_PAYLOAD		BIT(1)
> +
> +/* 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_SOT_LANE0		BIT(0)
> +#define   CSI2RX_INT_ST_DPHY_ERR_SOT_LANE1		BIT(1)
> +#define   CSI2RX_INT_ST_DPHY_ERR_ESC_LANE0		BIT(16)
> +#define   CSI2RX_INT_ST_DPHY_ERR_ESC_LANE1		BIT(17)
> +
> +/* Mask for Interruption Caused by PHY */
> +#define CSI2RX_INT_MSK_DPHY				0x114
> +#define   CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE0		BIT(0)
> +#define   CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE1		BIT(1)
> +#define   CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE0		BIT(16)
> +#define   CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE1		BIT(17)
> +
> +/* 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_IFFIFO_UNDERFLOW	BIT(0)
> +#define   CSI2RX_INT_ST_IPI_FATAL_ERR_IFFIFO_OVERFLOW	BIT(1)
> +#define   CSI2RX_INT_ST_IPI_FATAL_ERR_FRAME_SYNC	BIT(2)
> +#define   CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_NOT_EMPTY	BIT(3)
> +#define   CSI2RX_INT_ST_IPI_FATAL_ERR_HLINE_TIME	BIT(4)
> +#define   CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_OVERFLOW	BIT(5)
> +#define   CSI2RX_INT_ST_IPI_FATAL_ERR_PD_FIFO_OVERFLOW	BIT(6)
> +
> +/* Mask for Fatal Interruption Caused by IPI Interface */
> +#define CSI2RX_INT_MSK_IPI_FATAL			0x144
> +#define   CSI2RX_INT_MSK_IPI_FATAL_ERR_IFFIFO_UNDERFLOW	BIT(0)
> +#define   CSI2RX_INT_MSK_IPI_FATAL_ERR_IFFIFO_OVERFLOW	BIT(1)
> +#define   CSI2RX_INT_MSK_IPI_FATAL_ERR_FRAME_SYNC	BIT(2)
> +#define   CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_NOT_EMPTY	BIT(3)
> +#define   CSI2RX_INT_MSK_IPI_FATAL_ERR_HLINE_TIME	BIT(4)
> +#define   CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_OVERFLOW	BIT(5)
> +#define   CSI2RX_INT_MSK_IPI_FATAL_ERR_PD_FIFO_OVERFLOW	BIT(6)
> +
> +/* 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_CSI2RX_PAD_SINK				0
> +#define DWC_CSI2RX_PAD_SOURCE				1
> +#define DWC_CSI2RX_PADS_NUM				2
> +
> +#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
> +
> +struct dwc_csi_event {
> +	u32 mask;
> +	const char * const name;
> +	unsigned int counter;
> +};
> +
> +static const struct dwc_csi_event dwc_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" },
> +};
> +
> +#define DWC_NUM_EVENTS		ARRAY_SIZE(dwc_events)
> +#define DWC_EVENT_MASK		0x500ff
> +
> +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 phy *phy;
> +	struct clk_bulk_data *clks;
> +	int num_clks;
> +	struct v4l2_subdev sd;
> +	struct v4l2_async_notifier notifier;
> +	struct v4l2_subdev *source_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;
> +	u64 enabled_streams;
> +
> +	/* Use driver mutex lock for the ctrl lock */
> +	struct mutex lock;
> +
> +	struct dwc_csi_event events[DWC_NUM_EVENTS];
> +	const struct dwc_csi_pix_format *csi_fmt;
> +
> +	/* Used for pattern generator */
> +	bool pg_enable;
> +	enum {
> +		PATTERN_DISABLED,
> +		PATTERN_VERTICAL,
> +		PATTERN_HORIZONTAL,
> +	} pg_pattern;
> +};
> +
> +/* 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,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SBGGR16_1X16,
> +		.output = MEDIA_BUS_FMT_SBGGR16_1X16,
> +		.data_type = MIPI_CSI2_DT_RAW16,
> +		.width = 16,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGBRG16_1X16,
> +		.output = MEDIA_BUS_FMT_SGBRG16_1X16,
> +		.data_type = MIPI_CSI2_DT_RAW16,
> +		.width = 16,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGRBG16_1X16,
> +		.output = MEDIA_BUS_FMT_SGRBG16_1X16,
> +		.data_type = MIPI_CSI2_DT_RAW16,
> +		.width = 16,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SRGGB16_1X16,
> +		.output = MEDIA_BUS_FMT_SRGGB16_1X16,
> +		.data_type = MIPI_CSI2_DT_RAW16,
> +		.width = 16,
> +	}
> +};
> +
> +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 &dwc_csi_formats[0];
> +}
> +
> +static inline void dwc_csi_write(struct dwc_csi_device *csidev, unsigned int offset, u32 val)
> +{
> +	writel(val, csidev->regs + offset);
> +}
> +
> +static inline u32 dwc_csi_read(struct dwc_csi_device *csidev, unsigned int offset)
> +{
> +	return readl(csidev->regs + offset);
> +}

Please don't add simple wrappers around writel/readl. Instead consider
using regmap-mmio. This also adds the possibility to have register access
tracing.

> +
> +/*
> + * 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_subdev *sd = &csidev->sd;
> +	struct v4l2_subdev_state *state;
> +	struct v4l2_mbus_framefmt *fmt;
> +	u32 val;
> +
> +	if (!csidev->pg_enable)
> +		return 0;
> +
> +	if (!csi_fmt) {
> +		dev_err(csidev->dev, "CSI pixel format is NULL\n");
> +		return -EINVAL;
> +	}
> +
> +	if (csi_fmt->data_type != MIPI_CSI2_DT_RGB888) {
> +		dev_err(csidev->dev, "Pattern generator only support RGB888\n");

s/support/supports/

> +		return -EINVAL;
> +	}
> +
> +	state = v4l2_subdev_lock_and_get_active_state(sd);
> +	/* Pattern generator create data stream only according to stream 0 */

s/create/creates/

> +	fmt = v4l2_subdev_state_get_format(state, DWC_CSI2RX_PAD_SINK, 0);
> +
> +	val = CSI2RX_PPI_PG_PATTERN_HRES_HRES(fmt->width);
> +	dwc_csi_write(csidev, CSI2RX_PPI_PG_PATTERN_HRES, val);
> +
> +	val = CSI2RX_PPI_PG_PATTERN_VRES_VRES(fmt->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);
> +
> +	v4l2_subdev_unlock_state(state);
> +
> +	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)
> +{
> +	u32 val;
> +
> +	/* 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)
> +{
> +	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;
> +	u32 val;
> +
> +	/* Do IPI soft reset */
> +	dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x0);
> +	dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x1);

Use dwc_csi_device_reset()

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

So this enables 48 Bit interface, instead of 16 Bit. What does that
mean given that IPI is an internal IP block?

> +	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);
> +
> +	phy_init(csidev->phy);
> +
> +	phy_reset(csidev->phy);
> +}
> +
> +static int dwc_csi_get_dphy_configuration(struct dwc_csi_device *csidev,
> +					  union phy_configure_opts *opts)
> +{
> +	struct phy_configure_opts_mipi_dphy *cfg = &opts->mipi_dphy;
> +	struct v4l2_subdev *source = csidev->source_sd;
> +	s64 link_freq;
> +
> +	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;
> +	}
> +
> +	memset(cfg, 0x0, sizeof(*cfg));
> +	cfg->hs_clk_rate = link_freq * 2;
> +	cfg->lanes = csidev->bus.num_data_lanes;
> +
> +	return 0;
> +}
> +
> +static void dwc_csi_dphy_prep(struct dwc_csi_device *csidev)
> +{
> +	u32 val;
> +
> +	/* Release synopsis DPHY test codes from reset */
> +	dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0);
> +	dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0);
> +
> +	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);
> +
> +	/*
> +	 * ndelay is not necessary have MMIO operation, need dummy read to make
> +	 * sure above write reach target.
> +	 */
> +	dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0);
> +	/* Wait for at least 15ns */
> +	ndelay(15);

What does the comment mean? You are reading that register twice, see below.
If you write to that register, you may need to wait 15ns before reading back.
But reading back, waiting and reading back, doesn't make sense to me.

> +
> +	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);
> +}
> +
> +static void dwc_csi_dphy_release_reset(struct dwc_csi_device *csidev)
> +{
> +	/* Release PHY from reset */
> +	dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x1);
> +	/*
> +	 * ndelay is not necessary have MMIO operation, need dummy read to make
> +	 * sure above write reach target.
> +	 */
> +	dwc_csi_read(csidev, CSI2RX_DPHY_SHUTDOWNZ);
> +	ndelay(5);
> +	dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x1);
> +	dwc_csi_read(csidev, CSI2RX_DPHY_RSTZ);
> +	ndelay(5);
> +}
> +
> +static int dwc_csi_device_init(struct dwc_csi_device *csidev)
> +{
> +	struct device *dev = csidev->dev;
> +	union phy_configure_opts opts;
> +	u32 phy_stopstate;
> +	u32 val;
> +	int ret;
> +
> +	ret = dwc_csi_get_dphy_configuration(csidev, &opts);
> +	if (ret)
> +		return ret;
> +
> +	ret = phy_set_mode(csidev->phy, PHY_MODE_MIPI_DPHY);
> +	if (ret)
> +		return ret;
> +
> +	ret = phy_configure(csidev->phy, &opts);
> +	if (ret)
> +		return ret;
> +
> +	dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0);
> +	dwc_csi_dphy_prep(csidev);
> +	dwc_csi_write(csidev, CSI2RX_N_LANES, CSI2RX_N_LANES_N_LANES(opts.mipi_dphy.lanes));
> +	ret = phy_power_on(csidev->phy);
> +	dwc_csi_dphy_release_reset(csidev);
> +	dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0x1);
> +
> +	if (ret)
> +		return ret;
> +
> +	/* Check if lanes are in stop state */
> +	phy_stopstate = CSI2RX_DPHY_STOPSTATE_CLK_LANE;
> +	phy_stopstate |= GENMASK(csidev->bus.num_data_lanes - 1, 0);
> +	ret = readl_poll_timeout(csidev->regs + CSI2RX_DPHY_STOPSTATE,
> +				 val, (val & phy_stopstate) != phy_stopstate,
> +				 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;
> +
> +	phy_power_off(csidev->phy);
> +	phy_exit(csidev->phy);
> +	dwc_csi_ipi_disable(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 void dwc_csi_clear_counters(struct dwc_csi_device *csidev)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < DWC_NUM_EVENTS; ++i)
> +		csidev->events[i].counter = 0;
> +}
> +
> +static void dwc_csi_log_counters(struct dwc_csi_device *csidev)
> +{
> +	unsigned int i;
> +	int counter;
> +
> +	for (i = 0; i < DWC_NUM_EVENTS; ++i) {
> +		counter = csidev->events[i].counter;
> +		if (counter > 0)
> +			dev_info(csidev->dev, "%s events: %d\n",
> +				 csidev->events[i].name,
> +				 counter);
> +	}
> +}
> +
> +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_DATA_IDS_1_DT),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DATA_IDS_2_DT),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DATA_IDS_1_VC),
> +		DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DATA_IDS_2_VC),
> +		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 int __dwc_csi_subdev_set_routing(struct v4l2_subdev *sd,
> +					struct v4l2_subdev_state *state,
> +					struct v4l2_subdev_krouting *routing)
> +{
> +	int ret;
> +
> +	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
> +		return -EINVAL;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	return v4l2_subdev_set_routing_with_fmt(sd, state, routing,
> +						&dwc_csi_default_fmt);
> +}
> +
> +static int dwc_csi_subdev_init_state(struct v4l2_subdev *sd,
> +				     struct v4l2_subdev_state *sd_state)
> +{
> +	struct v4l2_subdev_route routes[] = {
> +		{
> +			.sink_pad = DWC_CSI2RX_PAD_SINK,
> +			.sink_stream = 0,
> +			.source_pad = DWC_CSI2RX_PAD_SOURCE,
> +			.source_stream = 0,
> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> +		},
> +	};
> +
> +	struct v4l2_subdev_krouting routing = {
> +		.num_routes = ARRAY_SIZE(routes),
> +		.routes = routes,
> +	};
> +
> +	return __dwc_csi_subdev_set_routing(sd, sd_state, &routing);
> +}
> +
> +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)
> +{
> +	/*
> +	 * 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 = v4l2_subdev_state_get_format(sd_state, code->pad,
> +						   code->stream);
> +		code->code = fmt->code;
> +		return 0;
> +	}
> +
> +	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_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 v4l2_subdev_get_fmt(sd, sd_state, sdformat);
> +
> +	/*
> +	 * 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);
> +
> +	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 = v4l2_subdev_state_get_format(sd_state, sdformat->pad,
> +					   sdformat->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	*fmt = sdformat->format;
> +
> +	/* Set default code if user set an invalid value */
> +	fmt->code = csi_fmt->code;
> +
> +	/* Propagate the format from sink stream to source stream */
> +	fmt = v4l2_subdev_state_get_opposite_stream_format(sd_state, sdformat->pad,
> +							   sdformat->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	*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;
> +
> +	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 source_fd;
> +	struct v4l2_subdev_route *route;
> +	struct v4l2_subdev_state *state;
> +	int ret;
> +
> +	if (pad != DWC_CSI2RX_PAD_SOURCE)
> +		return -EINVAL;
> +
> +	memset(fd, 0, sizeof(*fd));
> +
> +	ret = v4l2_subdev_call(csidev->source_sd, pad, get_frame_desc,
> +			       csidev->remote_pad, &source_fd);
> +	if (ret < 0) {
> +		dev_info(csidev->dev,
> +			 "Remote sub-device on pad %d should implement .get_frame_desc! Forcing VC = 0 and DT = %x\n",
> +			 pad, csidev->csi_fmt->data_type);

Is this dev_info or dev_warn? Maybe Laurent or other V4L folks can give
some hints.

> +		fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
> +		fd->num_entries = 1;
> +		fd->entry[0].pixelcode = csidev->csi_fmt->code;
> +		fd->entry[0].bus.csi2.vc = 0;
> +		fd->entry[0].bus.csi2.dt = csidev->csi_fmt->data_type;
> +
> +		return 0;
> +	}
> +
> +	fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
> +
> +	state = v4l2_subdev_lock_and_get_active_state(sd);
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct v4l2_mbus_frame_desc_entry *entry = NULL;
> +		unsigned int i;
> +
> +		if (route->source_pad != pad)
> +			continue;
> +
> +		for (i = 0; i < source_fd.num_entries; ++i) {
> +			if (source_fd.entry[i].stream == route->sink_stream) {
> +				entry = &source_fd.entry[i];
> +				break;
> +			}
> +		}
> +
> +		if (!entry) {
> +			dev_err(csidev->dev,
> +				"Failed to find stream from source frames desc\n");
> +			ret = -EPIPE;
> +			goto out_unlock;
> +		}
> +
> +		fd->entry[fd->num_entries].stream = route->source_stream;
> +		fd->entry[fd->num_entries].flags = entry->flags;
> +		fd->entry[fd->num_entries].length = entry->length;
> +		fd->entry[fd->num_entries].pixelcode = entry->pixelcode;
> +		fd->entry[fd->num_entries].bus.csi2.vc = entry->bus.csi2.vc;
> +		fd->entry[fd->num_entries].bus.csi2.dt = entry->bus.csi2.dt;
> +
> +		fd->num_entries++;
> +	}
> +
> +out_unlock:
> +	v4l2_subdev_unlock_state(state);
> +	return ret;
> +}
> +
> +static int dwc_csi_set_routing(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state,
> +			       enum v4l2_subdev_format_whence which,
> +			       struct v4l2_subdev_krouting *routing)
> +{
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&
> +	    media_entity_is_streaming(&sd->entity))
> +		return -EBUSY;
> +
> +	return __dwc_csi_subdev_set_routing(sd, state, routing);
> +}
> +
> +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_enable_streams(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state, u32 pad,
> +				  u64 streams_mask)
> +{
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	if (!csidev->source_sd) {
> +		dev_err(csidev->dev, "Sensor don't link with CSIS pad\n");

s/don't/doesn't/

> +		return -EPIPE;
> +	}
> +
> +	if (!csidev->enabled_streams) {
> +		ret = pm_runtime_resume_and_get(csidev->dev);
> +		if (ret < 0)
> +			return ret;
> +
> +		ret = v4l2_ctrl_handler_setup(&csidev->ctrl_handler);
> +		if (ret < 0)
> +			goto err_runtime_put;
> +
> +		dwc_csi_clear_counters(csidev);
> +
> +		ret = dwc_csi_start_stream(csidev);
> +		if (ret < 0)
> +			goto err_runtime_put;
> +
> +		dwc_csi_dump_regs(csidev);
> +		dwc_csi_log_counters(csidev);

Are you sure you want to dump all the registers every time you start
a stream?

> +	}
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, DWC_CSI2RX_PAD_SOURCE,
> +						       DWC_CSI2RX_PAD_SINK,
> +						       &streams_mask);
> +
> +	dev_dbg(csidev->dev, "remote sd: %s pad: %u, sink_stream:0x%llx\n",
> +		csidev->source_sd->name, csidev->remote_pad, sink_streams);
> +
> +	ret = v4l2_subdev_enable_streams(csidev->source_sd, csidev->remote_pad,
> +					 sink_streams);
> +	if (ret)
> +		return ret;
> +
> +	csidev->enabled_streams |= streams_mask;
> +
> +	return 0;
> +
> +err_runtime_put:
> +	pm_runtime_put(csidev->dev);
> +	return ret;
> +}
> +
> +static int dwc_csi_disable_streams(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state, u32 pad,
> +				   u64 streams_mask)
> +{
> +	struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, DWC_CSI2RX_PAD_SOURCE,
> +						       DWC_CSI2RX_PAD_SINK,
> +						       &streams_mask);
> +
> +	ret = v4l2_subdev_disable_streams(csidev->source_sd, csidev->remote_pad,
> +					  sink_streams);
> +	if (ret)
> +		return ret;
> +
> +	csidev->enabled_streams &= ~streams_mask;
> +
> +	if (!csidev->enabled_streams) {
> +		dwc_csi_stop_stream(csidev);
> +		dwc_csi_log_counters(csidev);
> +		pm_runtime_put(csidev->dev);
> +	}
> +
> +	return 0;
> +}
> +
> +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 = {
> +	.enum_mbus_code	= dwc_csi_subdev_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = dwc_csi_subdev_set_fmt,
> +	.get_frame_desc = dwc_csi_get_frame_desc,
> +	.set_routing = dwc_csi_set_routing,
> +	.enable_streams = dwc_csi_enable_streams,
> +	.disable_streams = dwc_csi_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops dwc_csi_subdev_ops = {
> +	.core  = &dwc_csi_subdev_core_ops,
> +	.pad   = &dwc_csi_subdev_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops dwc_csi_internal_ops = {
> +	.init_state = dwc_csi_subdev_init_state,
> +};
> +
> +/*
> + * 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->source_sd)
> +			return -EBUSY;
> +
> +		csidev->source_sd = remote_sd;
> +		csidev->remote_pad = remote_pad->index;
> +	} else {
> +		csidev->source_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 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_connection *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_connection *asd;
> +	struct fwnode_handle *ep;
> +	unsigned int i;
> +	int ret;
> +
> +	v4l2_async_subdev_nf_init(&csidev->notifier, &csidev->sd);
> +
> +	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_connection);
> +	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_nf_register(&csidev->notifier);
> +	if (ret)
> +		goto err_notifier_clean;
> +
> +	ret = v4l2_async_register_subdev(&csidev->sd);
> +	if (ret)
> +		goto err_unreg_notifier;
> +
> +	return ret;
> +
> +err_unreg_notifier:
> +	v4l2_async_nf_unregister(&csidev->notifier);
> +err_notifier_clean:
> +	v4l2_async_nf_cleanup(&csidev->notifier);
> +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;

ctrl->val is s32 which not supposed to wrap. IIRC this is undefined behavior.
Just set pg_pattern if strl->val is non-zero.
You only active test pattern when stream is started. Does it make sense to
allow setting this control only while streaming is disabled?

Thanks, I'll give it a try soon.

Best regards
Alexander

> +		csidev->pg_enable = (ctrl->val) ? true : false;
> +		break;
> +	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;
> +}
> +
> +static void dwc_csi_controls_cleanup(void *data)
> +{
> +	struct dwc_csi_device *csidev = data;
> +
> +	v4l2_ctrl_handler_free(&csidev->ctrl_handler);
> +}
> +
> +/*
> + * 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);
> +
> +	clk_bulk_disable_unprepare(csidev->num_clks, csidev->clks);
> +
> +	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);
> +
> +	return clk_bulk_prepare_enable(csidev->num_clks, csidev->clks);
> +}
> +
> +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)
> +};
> +
> +static irqreturn_t dwc_csi_irq_handler(int irq, void *priv)
> +{
> +	struct dwc_csi_device *csidev = priv;
> +	u32 status;
> +	int i;
> +
> +	/* Hardware auto clean after read */
> +	status = dwc_csi_read(csidev, CSI2RX_INT_ST_MAIN);
> +
> +	if (status & DWC_EVENT_MASK) {
> +		for (i = 0; i < DWC_NUM_EVENTS; ++i) {
> +			struct dwc_csi_event *event = &csidev->events[i];
> +
> +			if (status & event->mask)
> +				event->counter++;
> +		}
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static inline void dwc_csi_param_init(struct dwc_csi_device *csidev)
> +{
> +	csidev->csi_fmt = &dwc_csi_formats[0];
> +}
> +
> +static int dwc_csi_subdev_init(struct dwc_csi_device *csidev)
> +{
> +	struct v4l2_subdev *sd = &csidev->sd;
> +	int ret;
> +
> +	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->internal_ops = &dwc_csi_internal_ops;
> +
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
> +		     V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_STREAMS;
> +	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;
> +
> +	ret = media_entity_pads_init(&csidev->sd.entity, DWC_CSI2RX_PADS_NUM,
> +				     csidev->pads);
> +	if (ret) {
> +		dev_err(csidev->dev, "Failed to init pads\n");
> +		return ret;
> +	}
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		media_entity_cleanup(&sd->entity);
> +
> +	return ret;
> +}
> +
> +static void dwc_csi_subdev_cleanup(void *data)
> +{
> +	struct dwc_csi_device *csidev = data;
> +
> +	v4l2_subdev_cleanup(&csidev->sd);
> +	media_entity_cleanup(&csidev->sd.entity);
> +}
> +
> +static int dwc_csi_device_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct dwc_csi_device *csidev;
> +	int irq;
> +	int ret;
> +
> +	csidev = devm_kzalloc(dev, sizeof(*csidev), GFP_KERNEL);
> +	if (!csidev)
> +		return -ENOMEM;
> +
> +	mutex_init(&csidev->lock);
> +
> +	csidev->dev = dev;
> +	memcpy(csidev->events, dwc_events, sizeof(dwc_events));
> +
> +	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);
> +	}
> +
> +	csidev->phy = devm_phy_get(dev, "rx");
> +	if (IS_ERR(csidev->phy))
> +		return dev_err_probe(dev, PTR_ERR(csidev->phy),
> +				     "Failed to get DPHY Rx\n");
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return dev_err_probe(dev, irq, "Failed to get IRQ\n");
> +
> +	ret = devm_request_irq(dev, irq, dwc_csi_irq_handler, 0,
> +			       dev_name(dev), csidev);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "Failed to request IRQ\n");
> +
> +	csidev->num_clks = devm_clk_bulk_get_all(dev, &csidev->clks);
> +
> +	dwc_csi_param_init(csidev);
> +
> +	ret = dwc_csi_subdev_init(csidev);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "Failed to initialize subdev\n");
> +
> +	ret = devm_add_action_or_reset(dev, dwc_csi_subdev_cleanup, csidev);
> +	if (ret)
> +		return ret;
> +
> +	ret = dwc_csi_controls_init(csidev);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to initialize controls\n");
> +
> +	ret = devm_add_action_or_reset(dev, dwc_csi_controls_cleanup, csidev);
> +	if (ret)
> +		return ret;
> +
> +	platform_set_drvdata(pdev, &csidev->sd);
> +
> +	ret = dwc_csi_async_register(csidev);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "Async register failed\n");
> +
> +	pm_runtime_enable(dev);
> +
> +	return 0;
> +}
> +
> +static void 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);
> +
> +	fwnode_handle_put(csidev->sd.fwnode);
> +	mutex_destroy(&csidev->lock);
> +
> +	pm_runtime_set_suspended(&pdev->dev);
> +}
> +
> +static const struct of_device_id dwc_csi_device_of_match[] = {
> +	{ .compatible = "snps,dw-mipi-csi2-v150" },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, dwc_csi_device_of_match);
> +
> +static struct platform_driver dwc_csi_device_driver = {
> +	.driver = {
> +		.owner          = THIS_MODULE,
> +		.name           = "dwc-mipi-csi2",
> +		.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-csi2");
> +MODULE_AUTHOR("NXP Semiconductor, Inc.");
> 
> 


-- 
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/



Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ