Hi Alexander, Thanks for your comments. > -----Original Message----- > From: Alexander Stein <alexander.stein@xxxxxxxxxxxxxxx> > Sent: 2023年7月4日 17:23 > To: linux-media@xxxxxxxxxxxxxxx; devicetree@xxxxxxxxxxxxxxx; dl-linux-imx > <linux-imx@xxxxxxx>; G.N. Zhou (OSS) <guoniu.zhou@xxxxxxxxxxx> > Cc: mchehab@xxxxxxxxxx; laurent.pinchart@xxxxxxxxxxxxxxxx; > robh+dt@xxxxxxxxxx; krzysztof.kozlowski+dt@xxxxxxxxxx; conor+dt@xxxxxxxxxx; > jacopo.mondi@xxxxxxxxxxxxxxxx > Subject: Re: [PATCH 2/2] media: nxp: add driver for i.MX93 MIPI CSI-2 controller > and D-PHY > > Caution: This is an external email. Please take care when clicking links or opening > attachments. When in doubt, report the message using the 'Report this email' > button > > > Hi Guoniu, > > thanks for posting this patch. > > Am Montag, 3. Juli 2023, 13:37:34 CEST schrieb guoniu.zhou@xxxxxxxxxxx: > > 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 | 1384 > ++++++++++++++++++++ > > drivers/media/platform/nxp/dwc-mipi-csi2.h | 289 ++++ > > drivers/media/platform/nxp/dwc-mipi-dphy.c | 195 +++ > > 6 files changed, 1892 insertions(+) > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index c83475103a25..349d981f9c24 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -15189,6 +15189,16 @@ S: Maintained > > F: Documentation/devicetree/bindings/sound/nxp,tfa989x.yaml > > F: sound/soc/codecs/tfa989x.c > > > > +NXP i.MX93 MIPI CSI-2 V4L2 DRIVER > > +M: G.N. Zhou (OSS) <guoniu.zhou@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. > > + > > # mem2mem drivers > > > > config VIDEO_IMX_PXP > > diff --git a/drivers/media/platform/nxp/Makefile > > b/drivers/media/platform/nxp/Makefile index b8e672b75fed..07f43795dc16 > > 100644 > > --- a/drivers/media/platform/nxp/Makefile > > +++ b/drivers/media/platform/nxp/Makefile > > @@ -4,6 +4,9 @@ obj-y += dw100/ > > obj-y += imx-jpeg/ > > obj-y += imx8-isi/ > > > > +dwc-mipi-csis-y := dwc-mipi-csi2.o dwc-mipi-dphy.o > > + > > +obj-$(CONFIG_VIDEO_DWC_MIPI_CSIS) += dwc-mipi-csis.o > > obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o > > obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o > > obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o > > diff --git a/drivers/media/platform/nxp/dwc-mipi-csi2.c > > b/drivers/media/platform/nxp/dwc-mipi-csi2.c new file mode 100644 > > index 000000000000..f03a23d9ef71 > > --- /dev/null > > +++ b/drivers/media/platform/nxp/dwc-mipi-csi2.c > > @@ -0,0 +1,1384 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Copyright 2023 NXP > > + * > > + */ > > + > > +#include <linux/bits.h> > > +#include <linux/clk.h> > > +#include <linux/errno.h> > > +#include <linux/iopoll.h> > > +#include <linux/kernel.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include <linux/of_device.h> > > +#include <linux/platform_device.h> > > +#include <linux/pm_runtime.h> > > + > > +#include <media/mipi-csi2.h> > > + > > +#include "dwc-mipi-csi2.h" > > + > > +#define DWC_MIPI_CSIS_DRIVER_NAME "dwc-mipi-csi2" > > + > > +#define DWC_CSI2RX_DEF_MBUS_CODE > MEDIA_BUS_FMT_UYVY8_1X16 > > +#define DWC_CSI2RX_DEF_PIX_WIDTH 1920U > > +#define DWC_CSI2RX_DEF_PIX_HEIGHT 1080U > > +#define DWC_CSI2RX_MAX_PIX_WIDTH 0xffff > > +#define DWC_CSI2RX_MAX_PIX_HEIGHT 0xffff > > + > > +/* Set default high speed frequency range to 1.5Gbps */ > > +#define DPHY_DEFAULT_FREQRANGE 0x2c > > + > > +enum imx93_csi_clks { > > + PER, > > + PIXEL, > > + PHY_CFG, > > +}; > > + > > +enum model { > > + DWC_CSI2RX_IMX93, > > +}; > > + > > +enum dwc_csi2rx_intf { > > + DWC_CSI2RX_INTF_IDI, > > This is unused, what is it intented for? DesignWare Core MIPI CSI-2 support both IDI and IPI interface. For i.MX93 it use IPI as interface with ISI(gasket) and I reserved IDI here on the one hand support full features of the MIPI CSI-2 IP as more as possible, on the other hand, NXP i.MX95 MIPI CSI-2 remove IPI and use IDI as the interface. > > > + DWC_CSI2RX_INTF_IPI, > > +}; > > + > > +struct dwc_csi_plat_data { > > + enum model model; > > + enum dwc_csi2rx_intf intf; > > + > > + const struct clk_bulk_data *clks; > > + u32 num_clks; > > + > > + const struct dwc_csi_event *events; > > + u32 num_events; > > + u32 events_mask; > > +}; > > + > > +/* > > --------------------------------------------------------------------------- > > -- + * Events > > + */ > > + > > +struct dwc_csi_event { > > + u32 mask; > > + const char * const name; > > + unsigned int counter; > > +}; > > + > > +static struct dwc_csi_event mxc_imx93_events[] = { > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_IPI, "IPI Interface Fatal Error" }, > > + { CSI2RX_INT_ST_MAIN_ERR_PHY, "PHY Error" }, > > + { CSI2RX_INT_ST_MAIN_ERR_ECC, "Header Single Bit Error" }, > > + { CSI2RX_INT_ST_MAIN_ERR_DID, "Data ID Error" }, > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_PLD_CRC, "Payload CRC Fatal > Error" }, > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_CRC_FRAME, "Frame CRC Fatal > Error" }, > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_SEQ_FRAME, "Frame Sequence > Fatal > Error" }, > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL, "Frame > Boundaries Fatal > > Error" }, + { CSI2RX_INT_ST_MAIN_FATAL_ERR_PKT, "Packet Construction > Fatal > > Error" }, + { CSI2RX_INT_ST_MAIN_FATAL_ERR_PHY, "PHY Fatal Error" }, > > +}; > > + > > +/* > > --------------------------------------------------------------------------- > > -- + * Format helpers > > + */ > > + > > +struct dwc_csi_pix_format { > > + u32 code; > > + u32 output; > > + u32 data_type; > > + u8 width; > > +}; > > + > > +/* List of supported pixel formats for the subdev */ > > +static const struct dwc_csi_pix_format dwc_csi_formats[] = { > > + /* YUV formats */ > > + { > > + .code = MEDIA_BUS_FMT_UYVY8_1X16, > > + .output = MEDIA_BUS_FMT_UYVY8_1X16, > > + .data_type = MIPI_CSI2_DT_YUV422_8B, > > + .width = 16, > > + }, > > + /* RGB formats */ > > + { > > + .code = MEDIA_BUS_FMT_RGB565_1X16, > > + .output = MEDIA_BUS_FMT_RGB565_1X16, > > + .data_type = MIPI_CSI2_DT_RGB565, > > + .width = 16, > > + }, { > > + .code = MEDIA_BUS_FMT_BGR888_1X24, > > + .output = MEDIA_BUS_FMT_RGB888_1X24, > > + .data_type = MIPI_CSI2_DT_RGB888, > > + .width = 24, > > + }, > > + /* RAW (Bayer and greyscale) formats. */ > > + { > > + .code = MEDIA_BUS_FMT_SBGGR8_1X8, > > + .output = MEDIA_BUS_FMT_SBGGR8_1X8, > > + .data_type = MIPI_CSI2_DT_RAW8, > > + .width = 8, > > + }, { > > + .code = MEDIA_BUS_FMT_SGBRG8_1X8, > > + .output = MEDIA_BUS_FMT_SGBRG8_1X8, > > + .data_type = MIPI_CSI2_DT_RAW8, > > + .width = 8, > > + }, { > > + .code = MEDIA_BUS_FMT_SGRBG8_1X8, > > + .output = MEDIA_BUS_FMT_SGRBG8_1X8, > > + .data_type = MIPI_CSI2_DT_RAW8, > > + .width = 8, > > + }, { > > + .code = MEDIA_BUS_FMT_SRGGB8_1X8, > > + .output = MEDIA_BUS_FMT_SRGGB8_1X8, > > + .data_type = MIPI_CSI2_DT_RAW8, > > + .width = 8, > > + }, { > > + .code = MEDIA_BUS_FMT_Y8_1X8, > > + .output = MEDIA_BUS_FMT_Y8_1X8, > > + .data_type = MIPI_CSI2_DT_RAW8, > > + .width = 8, > > + }, { > > + .code = MEDIA_BUS_FMT_SBGGR10_1X10, > > + .output = MEDIA_BUS_FMT_SBGGR10_1X10, > > + .data_type = MIPI_CSI2_DT_RAW10, > > + .width = 10, > > + }, { > > + .code = MEDIA_BUS_FMT_SGBRG10_1X10, > > + .output = MEDIA_BUS_FMT_SGBRG10_1X10, > > + .data_type = MIPI_CSI2_DT_RAW10, > > + .width = 10, > > + }, { > > + .code = MEDIA_BUS_FMT_SGRBG10_1X10, > > + .output = MEDIA_BUS_FMT_SGRBG10_1X10, > > + .data_type = MIPI_CSI2_DT_RAW10, > > + .width = 10, > > + }, { > > + .code = MEDIA_BUS_FMT_SRGGB10_1X10, > > + .output = MEDIA_BUS_FMT_SRGGB10_1X10, > > + .data_type = MIPI_CSI2_DT_RAW10, > > + .width = 10, > > + }, { > > + .code = MEDIA_BUS_FMT_Y10_1X10, > > + .output = MEDIA_BUS_FMT_Y10_1X10, > > + .data_type = MIPI_CSI2_DT_RAW10, > > + .width = 10, > > + }, { > > + .code = MEDIA_BUS_FMT_SBGGR12_1X12, > > + .output = MEDIA_BUS_FMT_SBGGR12_1X12, > > + .data_type = MIPI_CSI2_DT_RAW12, > > + .width = 12, > > + }, { > > + .code = MEDIA_BUS_FMT_SGBRG12_1X12, > > + .output = MEDIA_BUS_FMT_SGBRG12_1X12, > > + .data_type = MIPI_CSI2_DT_RAW12, > > + .width = 12, > > + }, { > > + .code = MEDIA_BUS_FMT_SGRBG12_1X12, > > + .output = MEDIA_BUS_FMT_SGRBG12_1X12, > > + .data_type = MIPI_CSI2_DT_RAW12, > > + .width = 12, > > + }, { > > + .code = MEDIA_BUS_FMT_SRGGB12_1X12, > > + .output = MEDIA_BUS_FMT_SRGGB12_1X12, > > + .data_type = MIPI_CSI2_DT_RAW12, > > + .width = 12, > > + }, { > > + .code = MEDIA_BUS_FMT_Y12_1X12, > > + .output = MEDIA_BUS_FMT_Y12_1X12, > > + .data_type = MIPI_CSI2_DT_RAW12, > > + .width = 12, > > + }, { > > + .code = MEDIA_BUS_FMT_SBGGR14_1X14, > > + .output = MEDIA_BUS_FMT_SBGGR14_1X14, > > + .data_type = MIPI_CSI2_DT_RAW14, > > + .width = 14, > > + }, { > > + .code = MEDIA_BUS_FMT_SGBRG14_1X14, > > + .output = MEDIA_BUS_FMT_SGBRG14_1X14, > > + .data_type = MIPI_CSI2_DT_RAW14, > > + .width = 14, > > + }, { > > + .code = MEDIA_BUS_FMT_SGRBG14_1X14, > > + .output = MEDIA_BUS_FMT_SGRBG14_1X14, > > + .data_type = MIPI_CSI2_DT_RAW14, > > + .width = 14, > > + }, { > > + .code = MEDIA_BUS_FMT_SRGGB14_1X14, > > + .output = MEDIA_BUS_FMT_SRGGB14_1X14, > > + .data_type = MIPI_CSI2_DT_RAW14, > > + .width = 14, > > + } > > +}; > > + > > +static const struct v4l2_mbus_framefmt dwc_csi_default_fmt = { > > + .code = DWC_CSI2RX_DEF_MBUS_CODE, > > + .width = DWC_CSI2RX_DEF_PIX_WIDTH, > > + .height = DWC_CSI2RX_DEF_PIX_HEIGHT, > > + .field = V4L2_FIELD_NONE, > > + .colorspace = V4L2_COLORSPACE_SMPTE170M, > > + .xfer_func = > V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SMPTE170M), > > + .ycbcr_enc = > V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SMPTE170M), > > + .quantization = V4L2_QUANTIZATION_LIM_RANGE, > > +}; > > + > > +static const struct dwc_csi_pix_format *find_csi_format(u32 code) > > +{ > > + int i; > > + > > + for (i = 0; i < ARRAY_SIZE(dwc_csi_formats); i++) > > + if (code == dwc_csi_formats[i].code) > > + return &dwc_csi_formats[i]; > > + return NULL; > > +} > > + > > +/* > > --------------------------------------------------------------------------- > > -- + * DWC MIPI CSI-2 Host Controller Hardware operation > > + */ > > + > > +static int dwc_csi_device_pg_enable(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_pix_format *csi_fmt = csidev->csi_fmt; > > + struct v4l2_mbus_framefmt *format; > > + u32 val; > > + > > + if (!csidev->pg_enable) > > + return 0; > > + > > + if (!csi_fmt) { > > + dev_err(csidev->dev, "CSI pixel format is NULL\n"); > > + return -EINVAL; > > + } > > + > > + format = &csidev->format_mbus[DWC_CSI2RX_PAD_SINK]; > > + > > + if (csi_fmt->data_type != MIPI_CSI2_DT_RGB888) { > > + dev_err(csidev->dev, "Pattern generator only support > RGB888\n"); > > + return -EINVAL; > > + } > > + > > + val = CSI2RX_PPI_PG_PATTERN_HRES_HRES(format->width); > > + dwc_csi_write(csidev, CSI2RX_PPI_PG_PATTERN_HRES, val); > > + > > + val = CSI2RX_PPI_PG_PATTERN_VRES_VRES(format->height); > > + dwc_csi_write(csidev, CSI2RX_PPI_PG_PATTERN_VRES, val); > > + > > + val = CSI2RX_PPI_PG_CONFIG_DATA_TYPE(csi_fmt->data_type); > > + val |= CSI2RX_PPI_PG_CONFIG_VIR_CHAN(0); > > + val |= CSI2RX_PPI_PG_CONFIG_PG_MODE(csidev->pg_pattern); > > + dwc_csi_write(csidev, CSI2RX_PPI_PG_CONFIG, val); > > + > > + /* > > + * Select line start packets to construct vertical > > + * timing information for IPI interface > > + **/ > > + val = CSI2RX_IPI_ADV_FEATURES_SYNC_EVENT_MODE; > > + val |= CSI2RX_IPI_ADV_FEATURES_SYNC_LS_PKT; > > + val |= CSI2RX_IPI_ADV_FEATURES_LINE_EVENT_SEL; > > + dwc_csi_write(csidev, CSI2RX_IPI_ADV_FEATURES, val); > > + > > + val = CSI2RX_PPI_PG_ENABLE_EN; > > + dwc_csi_write(csidev, CSI2RX_PPI_PG_ENABLE, val); > > + > > + return 0; > > +} > > + > > +static void dwc_csi_device_pg_disable(struct dwc_csi_device *csidev) > > +{ > > + dwc_csi_write(csidev, CSI2RX_PPI_PG_ENABLE, 0); > > + csidev->pg_enable = false; > > +} > > + > > +static void dwc_csi_ipi_enable(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + u32 val; > > + > > + if (pdata->intf != DWC_CSI2RX_INTF_IPI) > > + return; > > + > > + /* Memory is automatically flushed at each Frame Start */ > > + val = CSI2RX_IPI_MEM_FLUSH_AUTO; > > + dwc_csi_write(csidev, CSI2RX_IPI_MEM_FLUSH, val); > > + > > + /* Enable IPI */ > > + val = dwc_csi_read(csidev, CSI2RX_IPI_MODE); > > + val |= CSI2RX_IPI_MODE_ENABLE; > > + dwc_csi_write(csidev, CSI2RX_IPI_MODE, val); > > +} > > + > > +static void dwc_csi_ipi_disable(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + > > + if (pdata->intf != DWC_CSI2RX_INTF_IPI) > > + return; > > + > > + dwc_csi_write(csidev, CSI2RX_IPI_MODE, 0); > > +} > > + > > +static void dwc_csi_device_ipi_config(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_pix_format *csi_fmt = csidev->csi_fmt; > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + u32 val; > > + > > + if (pdata->intf != DWC_CSI2RX_INTF_IPI) > > + return; > > + > > + /* Do IPI soft reset */ > > + dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x0); > > + dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x1); > > + > > + /* Select virtual channel and data type to be processed by IPI */ > > + val = CSI2RX_IPI_DATA_TYPE_DT(csi_fmt->data_type); > > + dwc_csi_write(csidev, CSI2RX_IPI_DATA_TYPE, val); > > + > > + /* Set virtual channel 0 as default */ > > + val = CSI2RX_IPI_VCID_VC(0); > > + dwc_csi_write(csidev, CSI2RX_IPI_VCID, val); > > + > > + /* > > + * Select IPI camera timing mode and allow the pixel stream > > + * to be non-continuous when pixel interface FIFO is empty > > + */ > > + val = dwc_csi_read(csidev, CSI2RX_IPI_MODE); > > + val &= ~CSI2RX_IPI_MODE_CONTROLLER; > > + val &= ~CSI2RX_IPI_MODE_COLOR_MODE16; > > + val |= CSI2RX_IPI_MODE_CUT_THROUGH; > > + dwc_csi_write(csidev, CSI2RX_IPI_MODE, val); > > +} > > + > > +static void dwc_csi_device_reset(struct dwc_csi_device *csidev) > > +{ > > + /* Reset mipi csi host, active low */ > > + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0); > > + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 1); > > +} > > + > > +static void dwc_csi_device_startup(struct dwc_csi_device *csidev) > > +{ > > + /* Release DWC_mipi_csi2_host from reset */ > > + dwc_csi_device_reset(csidev); > > + > > + /* Apply PHY Reset */ > > + dphy_rx_reset(csidev); > > + > > + /* Release PHY test codes from reset */ > > + dphy_rx_test_code_reset(csidev); > > +} > > + > > +static int dwc_csi_device_init(struct dwc_csi_device *csidev) > > +{ > > + struct device *dev = csidev->dev; > > + u32 val; > > + int ret; > > + > > + /* Release Synopsys DPHY test codes from reset */ > > + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0); > > + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0); > > + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0); > > + > > + /* Set testclr=1'b1 */ > > + val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0); > > + val |= CSI2RX_DPHY_TEST_CTRL0_TEST_CLR; > > + dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > + > > + /* Wait for at least 15ns */ > > + ndelay(15); > > + > > + /* Configure the PHY frequency range */ > > + dphy_rx_test_code_config(csidev); > > + dphy_rx_test_code_dump(csidev); > > + > > + /* Config the number of active lanes */ > > + val = CSI2RX_N_LANES_N_LANES(csidev->bus.num_data_lanes - 1); > > + dwc_csi_write(csidev, CSI2RX_N_LANES, val); > > + > > + /* Release PHY from reset */ > > + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x1); > > + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x1); > > + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0x1); > > + > > + /* Check if lanes are in stop state */ > > + ret = readl_poll_timeout(csidev->regs + CSI2RX_DPHY_STOPSTATE, > > + val, val != 0x10003, 10, 10000); > > + if (ret) { > > + dev_err(dev, "Lanes are not in stop state(%#x)\n", val); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static void dwc_csi_device_hs_rx_start(struct dwc_csi_device *csidev) > > +{ > > + dwc_csi_ipi_enable(csidev); > > +} > > + > > +static int dwc_csi_device_hs_rx_stop(struct dwc_csi_device *csidev) > > +{ > > + struct device *dev = csidev->dev; > > + u32 val; > > + > > + dwc_csi_ipi_disable(csidev); > > + dphy_rx_power_off(csidev); > > + > > + /* Check clock lanes are not in High Speed Mode */ > > + val = dwc_csi_read(csidev, CSI2RX_DPHY_RX_STATUS); > > + if (val & CSI2RX_DPHY_RX_STATUS_CLK_LANE_HS) { > > + dev_err(dev, "Clock lanes are still in HS mode\n"); > > + return -EINVAL; > > + } > > + > > + return 0; > > +} > > + > > +static void dwc_csi_device_enable_interrupts(struct dwc_csi_device *csidev, > > bool on) +{ > > + /* Define errors to be enabled */ > > + dwc_csi_write(csidev, CSI2RX_INT_MSK_DPHY_FATAL, on ? 0x3 : 0); > > + dwc_csi_write(csidev, CSI2RX_INT_MSK_PKT_FATAL, on ? 0x3 : 0); > > + dwc_csi_write(csidev, CSI2RX_INT_MSK_DPHY, on ? 0x30003 : 0); > > + dwc_csi_write(csidev, CSI2RX_INT_MSK_IPI_FATAL, on ? 0x7f : 0); > > +} > > + > > +static int dwc_csi_clk_enable(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + > > + return clk_bulk_prepare_enable(pdata->num_clks, csidev->clks); > > +} > > + > > +static void dwc_csi_clk_disable(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + > > + clk_bulk_disable_unprepare(pdata->num_clks, csidev->clks); > > +} > > + > > +static int dwc_csi_clk_get(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + unsigned int size; > > + int ret; > > + > > + size = pdata->num_clks * sizeof(*csidev->clks); > > + > > + csidev->clks = devm_kmalloc(csidev->dev, size, GFP_KERNEL); > > + if (!csidev->clks) > > + return -ENOMEM; > > + > > + memcpy(csidev->clks, pdata->clks, size); > > + > > + ret = devm_clk_bulk_get(csidev->dev, pdata->num_clks, csidev->clks); > > + if (ret < 0) { > > + dev_err(csidev->dev, "Failed to acquire clocks: %d\n", > ret); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +/* > > --------------------------------------------------------------------------- > > -- + * Debug > > + */ > > + > > +static void dwc_csi_clear_counters(struct dwc_csi_device *csidev) > > +{ > > + unsigned long flags; > > + unsigned int i; > > + > > + spin_lock_irqsave(&csidev->slock, flags); > > + > > + for (i = 0; i < csidev->pdata->num_events; ++i) > > + csidev->events[i].counter = 0; > > + > > + spin_unlock_irqrestore(&csidev->slock, flags); > > +} > > + > > +static void dwc_csi_log_counters(struct dwc_csi_device *csidev) > > +{ > > + unsigned int num_events = csidev->pdata->num_events; > > + unsigned long flags; > > + unsigned int i; > > + > > + spin_lock_irqsave(&csidev->slock, flags); > > + > > + for (i = 0; i < num_events; ++i) { > > + if (csidev->events[i].counter > 0) > > + dev_info(csidev->dev, "%s events: %d\n", > > + csidev->events[i].name, > > + csidev->events[i].counter); > > + } > > + > > + spin_unlock_irqrestore(&csidev->slock, flags); > > +} > > + > > +static void dwc_csi_dump_regs(struct dwc_csi_device *csidev) > > +{ > > +#define DWC_MIPI_CSIS_DEBUG_REG(name) {name, > #name} > > + static const struct { > > + u32 offset; > > + const char * const name; > > + } registers[] = { > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_VERSION), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_N_LANES), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_HOST_RESETN), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_MAIN), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_SHUTDOWNZ), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_RSTZ), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_RX_STATUS), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_STOPSTATE), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_TEST_CTRL0), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_TEST_CTRL1), > > + > DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_PATTERN_VRES), > > + > DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_PATTERN_HRES), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_CONFIG), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_ENABLE), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_STATUS), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_MODE), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_VCID), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_DATA_TYPE), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_MEM_FLUSH), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_SOFTRSTN), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_ADV_FEATURES), > > + > DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_DPHY_FATAL), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_PKT_FATAL), > > + > DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_DPHY_FATAL), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_IPI_FATAL), > > + }; > > + > > + unsigned int i; > > + u32 cfg; > > + > > + dev_dbg(csidev->dev, "--- REGISTERS ---\n"); > > + > > + for (i = 0; i < ARRAY_SIZE(registers); i++) { > > + cfg = dwc_csi_read(csidev, registers[i].offset); > > + dev_dbg(csidev->dev, "%14s[0x%02x]: 0x%08x\n", > > + registers[i].name, registers[i].offset, cfg); > > + } > > +} > > + > > +/* > > --------------------------------------------------------------------------- > > -- + * V4L2 subdev operations > > + */ > > + > > +static inline struct dwc_csi_device * > > +sd_to_dwc_csi_device(struct v4l2_subdev *sdev) > > +{ > > + return container_of(sdev, struct dwc_csi_device, sd); > > +} > > + > > +static struct v4l2_mbus_framefmt * > > +dwc_csi_get_pad_format(struct dwc_csi_device *csidev, > > + struct v4l2_subdev_state *sd_state, > > + enum v4l2_subdev_format_whence which, > > + unsigned int pad) > > +{ > > + if (which == V4L2_SUBDEV_FORMAT_TRY) > > + return v4l2_subdev_get_try_format(&csidev->sd, sd_state, > pad); > > + > > + return &csidev->format_mbus[pad]; > > +} > > + > > +static int dwc_csi_subdev_init_cfg(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state > *sd_state) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + struct v4l2_mbus_framefmt *fmt_sink; > > + struct v4l2_mbus_framefmt *fmt_source; > > + > > + fmt_sink = dwc_csi_get_pad_format(csidev, sd_state, > > + V4L2_SUBDEV_FORMAT_TRY, > > + DWC_CSI2RX_PAD_SINK); > > + *fmt_sink = dwc_csi_default_fmt; > > + > > + fmt_source = dwc_csi_get_pad_format(csidev, sd_state, > > + > V4L2_SUBDEV_FORMAT_TRY, > > + > DWC_CSI2RX_PAD_SOURCE); > > + *fmt_source = *fmt_sink; > > + > > + return 0; > > +} > > + > > +static int dwc_csi_subdev_enum_mbus_code(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state > *sd_state, > > + struct > v4l2_subdev_mbus_code_enum *code) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + > > + /* > > + * The CSIS can't transcode in any way, the source format is > identical > > + * to the sink format. > > + */ > > + if (code->pad == DWC_CSI2RX_PAD_SOURCE) { > > + struct v4l2_mbus_framefmt *fmt; > > + > > + if (code->index > 0) > > + return -EINVAL; > > + > > + fmt = dwc_csi_get_pad_format(csidev, sd_state, code- > >which, > > + code->pad); > > + code->code = fmt->code; > > + return 0; > > + } > > + > > + if (code->pad != DWC_CSI2RX_PAD_SINK) > > + return -EINVAL; > > + > > + if (code->index >= ARRAY_SIZE(dwc_csi_formats)) > > + return -EINVAL; > > + > > + code->code = dwc_csi_formats[code->index].code; > > + > > + return 0; > > +} > > + > > +static int dwc_csi_subdev_get_fmt(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_format > *sdformat) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + struct v4l2_mbus_framefmt *fmt; > > + > > + fmt = dwc_csi_get_pad_format(csidev, sd_state, sdformat->which, > > + sdformat->pad); > > + > > + mutex_lock(&csidev->lock); > > + sdformat->format = *fmt; > > + mutex_unlock(&csidev->lock); > > + > > + return 0; > > +} > > + > > +static int dwc_csi_subdev_set_fmt(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_format > *sdformat) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + struct dwc_csi_pix_format const *csi_fmt; > > + struct v4l2_mbus_framefmt *fmt; > > + unsigned int align; > > + > > + /* > > + * The CSIS can't transcode in any way, the source format can't be > > + * modified. > > + */ > > + if (sdformat->pad == DWC_CSI2RX_PAD_SOURCE) > > + return dwc_csi_subdev_get_fmt(sd, sd_state, sdformat); > > + > > + if (sdformat->pad != DWC_CSI2RX_PAD_SINK) > > + return -EINVAL; > > + > > + /* > > + * Validate the media bus code and clamp and align the size. > > + * > > + * The total number of bits per line must be a multiple of 8. We > thus > > + * need to align the width for formats that are not multiples of 8 > > + * bits. > > + */ > > + csi_fmt = find_csi_format(sdformat->format.code); > > + if (!csi_fmt) > > + csi_fmt = &dwc_csi_formats[0]; > > + > > + switch (csi_fmt->width % 8) { > > + case 0: > > + align = 0; > > + break; > > + case 4: > > + align = 1; > > + break; > > + case 2: > > + case 6: > > + align = 2; > > + break; > > + default: > > + /* 1, 3, 5, 7 */ > > + align = 3; > > + break; > > + } > > Is this switch-case actually necessary? If the bits per line have to be a > multiple of 8, IMHO calling v4l_bound_align_image() with walign=3 should be > enough for all cases. Yes it's. walign=3 can cover all cases as you said but can't handle precise control and cause unnecessary memory waste. > > > + v4l_bound_align_image(&sdformat->format.width, 1, > > + DWC_CSI2RX_MAX_PIX_WIDTH, align, > > + &sdformat->format.height, 1, > > + DWC_CSI2RX_MAX_PIX_HEIGHT, 0, 0); > > + > > + fmt = dwc_csi_get_pad_format(csidev, sd_state, sdformat->which, > > + sdformat->pad); > > + > > + mutex_lock(&csidev->lock); > > + > > + fmt->code = csi_fmt->code; > > + fmt->width = sdformat->format.width; > > + fmt->height = sdformat->format.height; > > + fmt->colorspace = sdformat->format.colorspace; > > + fmt->quantization = sdformat->format.quantization; > > + fmt->xfer_func = sdformat->format.xfer_func; > > + fmt->ycbcr_enc = sdformat->format.ycbcr_enc; > > + > > + sdformat->format = *fmt; > > + > > + /* Propagate the format from sink to source. */ > > + fmt = dwc_csi_get_pad_format(csidev, sd_state, sdformat->which, > > + DWC_CSI2RX_PAD_SOURCE); > > + *fmt = sdformat->format; > > + > > + /* The format on the source pad might change due to unpacking. */ > > + fmt->code = csi_fmt->output; > > + > > + /* Store the CSIS format descriptor for active formats. */ > > + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) > > + csidev->csi_fmt = csi_fmt; > > + > > + mutex_unlock(&csidev->lock); > > + > > + return 0; > > +} > > + > > +static int dwc_csi_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, > > + struct v4l2_mbus_frame_desc *fd) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + struct v4l2_mbus_frame_desc_entry *entry = &fd->entry[0]; > > + > > + if (pad != DWC_CSI2RX_PAD_SOURCE) > > + return -EINVAL; > > + > > + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL; > > + fd->num_entries = 1; > > + > > + memset(entry, 0, sizeof(*entry)); > > + > > + mutex_lock(&csidev->lock); > > + > > + entry->flags = 0; > > + entry->pixelcode = csidev->csi_fmt->code; > > + entry->bus.csi2.vc = 0; > > + entry->bus.csi2.dt = csidev->csi_fmt->data_type; > > + > > + mutex_unlock(&csidev->lock); > > + > > + return 0; > > +} > > + > > +static int dwc_csi_start_stream(struct dwc_csi_device *csidev) > > +{ > > + int ret; > > + > > + dwc_csi_device_startup(csidev); > > + > > + ret = dwc_csi_device_init(csidev); > > + if (ret) > > + return ret; > > + > > + dwc_csi_device_ipi_config(csidev); > > + > > + ret = dwc_csi_device_pg_enable(csidev); > > + if (ret) > > + return ret; > > + > > + dwc_csi_device_hs_rx_start(csidev); > > + > > + dwc_csi_device_enable_interrupts(csidev, true); > > + > > + return 0; > > +} > > + > > +static void dwc_csi_stop_stream(struct dwc_csi_device *csidev) > > +{ > > + dwc_csi_device_enable_interrupts(csidev, false); > > + dwc_csi_device_hs_rx_stop(csidev); > > + dwc_csi_device_pg_disable(csidev); > > +} > > + > > +static int dwc_csi_subdev_s_stream(struct v4l2_subdev *sd, int enable) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + int ret; > > + > > + if (!csidev->sensor_sd) { > > + dev_err(csidev->dev, "Sensor don't link with CSIS pad\n"); > > + return -EPIPE; > > + } > > + > > + mutex_lock(&csidev->lock); > > + > > + if (!enable) { > > + dwc_csi_stop_stream(csidev); > > + dwc_csi_log_counters(csidev); > > + pm_runtime_put(csidev->dev); > > + goto sd_stream; > > + } > > + > > + ret = pm_runtime_resume_and_get(csidev->dev); > > + if (ret < 0) > > + goto unlocked; > > + > > + dwc_csi_clear_counters(csidev); > > + > > + /* CSIS HW configuration */ > > + ret = dwc_csi_start_stream(csidev); > > + if (ret) { > > + pm_runtime_put(csidev->dev); > > + goto unlocked; > > + } > > + > > + dwc_csi_dump_regs(csidev); > > + > > +sd_stream: > > + /* > > + * when enable CSI pattern generator, the clock source of > > + * pattern generator will be from external sensor, so it > > + * also need to enable external sensor clock. > > + */ > > + v4l2_subdev_call(csidev->sensor_sd, video, s_stream, enable); > > + dwc_csi_log_counters(csidev); > > +unlocked: > > + mutex_unlock(&csidev->lock); > > + return ret; > > +} > > + > > +static const struct v4l2_subdev_pad_ops dwc_csi_subdev_pad_ops = { > > + .init_cfg = dwc_csi_subdev_init_cfg, > > + .enum_mbus_code = dwc_csi_subdev_enum_mbus_code, > > + .get_fmt = dwc_csi_subdev_get_fmt, > > + .set_fmt = dwc_csi_subdev_set_fmt, > > + .get_frame_desc = dwc_csi_get_frame_desc, > > +}; > > + > > +static const struct v4l2_subdev_video_ops dwc_csi_subdev_video_ops = { > > + .s_stream = dwc_csi_subdev_s_stream, > > +}; > > + > > +static const struct v4l2_subdev_ops dwc_csi_subdev_ops = { > > + .pad = &dwc_csi_subdev_pad_ops, > > + .video = &dwc_csi_subdev_video_ops, > > +}; > > + > > +/* > > --------------------------------------------------------------------------- > > -- + * Media entity operations > > + */ > > + > > +static int dwc_csi_link_setup(struct media_entity *entity, > > + const struct media_pad *local_pad, > > + const struct media_pad *remote_pad, u32 > flags) > > +{ > > + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + struct v4l2_subdev *remote_sd; > > + > > + dev_dbg(csidev->dev, "link setup %s -> %s", remote_pad->entity- > >name, > > + local_pad->entity->name); > > + > > + /* We only care about the link to the source. */ > > + if (!(local_pad->flags & MEDIA_PAD_FL_SINK)) > > + return 0; > > + > > + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); > > + > > + if (flags & MEDIA_LNK_FL_ENABLED) { > > + if (csidev->sensor_sd) > > + return -EBUSY; > > + > > + csidev->sensor_sd = remote_sd; > > + csidev->remote_pad = remote_pad->index; > > + } else { > > + csidev->sensor_sd = NULL; > > + } > > + > > + return 0; > > +} > > + > > +static int dwc_csi_link_validate(struct media_link *link) > > +{ > > + struct media_pad *sink_pad = link->sink; > > + struct v4l2_subdev *sink_sd; > > + struct dwc_csi_device *csidev; > > + > > + sink_sd = media_entity_to_v4l2_subdev(sink_pad->entity); > > + csidev = sd_to_dwc_csi_device(sink_sd); > > + > > + dev_dbg(csidev->dev, "entity name:%s pad index=%d\n", > > + sink_sd->name, sink_pad->index); > > + > > + /* > > + * Skip link validate when pattern enabled since the soruce > > + * data will be from CSI pattern generator, not sensor. > > + */ > > + if (csidev->pg_enable && sink_pad->index == > DWC_CSI2RX_PAD_SINK) > > + return 0; > > + > > + return v4l2_subdev_link_validate(link); > > +} > > + > > +static const struct media_entity_operations dwc_csi_entity_ops = { > > + .link_setup = dwc_csi_link_setup, > > + .link_validate = dwc_csi_link_validate, > > + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, > > +}; > > + > > +/* > > --------------------------------------------------------------------------- > > -- + * Async subdev notifier > > + */ > > + > > +static inline struct dwc_csi_device * > > +notifier_to_dwc_csi_device(struct v4l2_async_notifier *n) > > +{ > > + return container_of(n, struct dwc_csi_device, notifier); > > +} > > + > > +static int dwc_csi_notify_bound(struct v4l2_async_notifier *notifier, > > + struct v4l2_subdev *sd, > > + struct v4l2_async_subdev *asd) > > +{ > > + struct dwc_csi_device *csidev = > notifier_to_dwc_csi_device(notifier); > > + struct media_pad *sink = &csidev- > >sd.entity.pads[DWC_CSI2RX_PAD_SINK]; > > + > > + return v4l2_create_fwnode_links_to_pad(sd, sink, 0); > > +} > > + > > +static const struct v4l2_async_notifier_operations dwc_csi_notify_ops = { > > + .bound = dwc_csi_notify_bound, > > +}; > > + > > +static int dwc_csi_async_register(struct dwc_csi_device *csidev) > > +{ > > + struct v4l2_fwnode_endpoint vep = { > > + .bus_type = V4L2_MBUS_CSI2_DPHY, > > + }; > > + struct v4l2_async_subdev *asd; > > + struct fwnode_handle *ep; > > + unsigned int i; > > + int ret; > > + > > + v4l2_async_nf_init(&csidev->notifier); > > + > > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csidev->dev), 0, > 0, > > + > FWNODE_GRAPH_ENDPOINT_NEXT); > > + if (!ep) > > + return -ENOTCONN; > > + > > + ret = v4l2_fwnode_endpoint_parse(ep, &vep); > > + if (ret) > > + goto err_parse; > > + > > + for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) { > > + if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) { > > + dev_err(csidev->dev, > > + "data lanes reordering is not > supported"); > > + ret = -EINVAL; > > + goto err_parse; > > + } > > + } > > + > > + csidev->bus = vep.bus.mipi_csi2; > > + > > + if (fwnode_property_read_u32(ep, "fsl,hsfreqrange", > > + &csidev->hsfreqrange)) > > + csidev->hsfreqrange = DPHY_DEFAULT_FREQRANGE; > > + > > + dev_dbg(csidev->dev, "data lanes: %d\n", csidev- > >bus.num_data_lanes); > > + dev_dbg(csidev->dev, "flags: 0x%08x\n", csidev->bus.flags); > > + dev_dbg(csidev->dev, "high speed frequency range: 0x%X\n", > > csidev->hsfreqrange); + > > + asd = v4l2_async_nf_add_fwnode_remote(&csidev->notifier, ep, > > + struct > v4l2_async_subdev); > > + if (IS_ERR(asd)) { > > + ret = PTR_ERR(asd); > > + goto err_parse; > > + } > > + > > + fwnode_handle_put(ep); > > + > > + csidev->notifier.ops = &dwc_csi_notify_ops; > > + > > + ret = v4l2_async_subdev_nf_register(&csidev->sd, &csidev->notifier); > > + if (ret) > > + return ret; > > I'm not sure which part causes the following message: > > dwc-mipi-csi2 4ae00000.mipi-csi: Consider updating driver dwc-mipi-csi2 to > match on endpoints > > But as this is a new driver, this should be addressed. Sure. Could you help to share the steps about how to reproduce it? > > > + > > + return v4l2_async_register_subdev(&csidev->sd); > > + > > +err_parse: > > + fwnode_handle_put(ep); > > + > > + return ret; > > +} > > + > > +/* > > --------------------------------------------------------------------------- > > -- + * Pattern Generator operations > > + */ > > + > > +static ssize_t pg_enable_show(struct device *dev, > > + struct device_attribute *attr, char *buf) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + > > + return sprintf(buf, "%d\n", csidev->pg_enable); > > +} > > + > > +static ssize_t pg_enable_store(struct device *dev, > > + struct device_attribute *attr, > > + const char *buf, size_t len) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + int ret; > > + u8 val; > > + > > + ret = kstrtou8(buf, 0, &val); > > + if (ret) > > + return ret; > > + > > + csidev->pg_enable = val; > > + return len; > > +} > > +static DEVICE_ATTR_RW(pg_enable); > > + > > +static ssize_t pg_active_show(struct device *dev, > > + struct device_attribute *attr, char *buf) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + u32 val; > > + > > + if (!pm_runtime_get_if_in_use(dev)) { > > + csidev->pg_active = false; > > + goto out; > > + } > > + > > + val = dwc_csi_read(csidev, CSI2RX_PPI_PG_STATUS); > > + csidev->pg_active = val & BIT(0); > > + > > +out: > > + return sprintf(buf, "%d\n", csidev->pg_active); > > +} > > +static DEVICE_ATTR_RO(pg_active); > > + > > +static ssize_t pg_pattern_show(struct device *dev, > > + struct device_attribute *attr, char *buf) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + char temp[16] = "vertical"; > > + > > + if (csidev->pg_pattern == PATTERN_HORIZONTAL) > > + strcpy(temp, "horizontal"); > > + > > + return sprintf(buf, "%s\n", temp); > > +} > > + > > +static ssize_t pg_pattern_store(struct device *dev, > > + struct device_attribute *attr, > > + const char *buf, size_t len) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + char temp[16]; > > + int ret = -EINVAL; > > + > > + if (sscanf(buf, "%s", temp) > 0) { > > + ret = len; > > + if (!strcmp(temp, "horizontal")) > > + csidev->pg_pattern = PATTERN_HORIZONTAL; > > + else if (!strcmp(temp, "vertical")) > > + csidev->pg_pattern = PATTERN_VERTICAL; > > + else > > + ret = -EINVAL; > > + } > > + > > + return ret; > > +} > > +static DEVICE_ATTR_RW(pg_pattern); > > + > > +static void dwc_csi_pattern_generator_init(struct dwc_csi_device *csidev) > > +{ > > + csidev->pg_enable = false; > > + csidev->pg_active = false; > > + csidev->pg_pattern = PATTERN_VERTICAL; > > + > > + device_create_file(csidev->dev, &dev_attr_pg_enable); > > + device_create_file(csidev->dev, &dev_attr_pg_active); > > + device_create_file(csidev->dev, &dev_attr_pg_pattern); > > +} > > + > > +static void dwc_csi_pattern_generator_deinit(struct dwc_csi_device *csidev) > > +{ > > + device_remove_file(csidev->dev, &dev_attr_pg_pattern); > > + device_remove_file(csidev->dev, &dev_attr_pg_active); > > + device_remove_file(csidev->dev, &dev_attr_pg_enable); > > +} > > I get the idea, but isn't using a V4L2_CID_TEST_PATTERN control the better > choice? Good idea, I will update in next version. > > > +/* > > --------------------------------------------------------------------------- > > -- + * Suspend/resume > > + */ > > + > > +static int dwc_csi_system_suspend(struct device *dev) > > +{ > > + return pm_runtime_force_suspend(dev); > > +} > > + > > +static int dwc_csi_system_resume(struct device *dev) > > +{ > > + int ret; > > + > > + ret = pm_runtime_force_resume(dev); > > + if (ret < 0) { > > + dev_err(dev, "force resume %s failed!\n", dev_name(dev)); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static int dwc_csi_runtime_suspend(struct device *dev) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + > > + dwc_csi_clk_disable(csidev); > > + > > + return 0; > > +} > > + > > +static int dwc_csi_runtime_resume(struct device *dev) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + int ret; > > + > > + ret = dwc_csi_clk_enable(csidev); > > + if (ret < 0) > > + return ret; > > + > > + return 0; > > +} > > + > > +static const struct dev_pm_ops dwc_csi_device_pm_ops = { > > + SET_SYSTEM_SLEEP_PM_OPS(dwc_csi_system_suspend, > dwc_csi_system_resume) > > + SET_RUNTIME_PM_OPS(dwc_csi_runtime_suspend, > dwc_csi_runtime_resume, > NULL) > > +}; > > + > > +/* > > --------------------------------------------------------------------------- > > -- + * IRQ handling > > + */ > > + > > +static irqreturn_t dwc_csi_irq_handler(int irq, void *priv) > > +{ > > + struct dwc_csi_device *csidev = priv; > > + unsigned long flags; > > + u32 status; > > + int i; > > + > > + status = dwc_csi_read(csidev, CSI2RX_INT_ST_MAIN); > > + > > + spin_lock_irqsave(&csidev->slock, flags); > > + > > + if (status & csidev->pdata->events_mask) { > > + for (i = 0; i < csidev->pdata->num_events; ++i) { > > + struct dwc_csi_event *event = &csidev- > >events[i]; > > + > > + if (status & event->mask) > > + event->counter++; > > + } > > + } > > + > > + spin_unlock_irqrestore(&csidev->slock, flags); > > + > > + return IRQ_HANDLED; > > +} > > + > > +/* > > --------------------------------------------------------------------------- > > -- + * Probe/remove & platform driver > > + */ > > + > > +static int dwc_csi_param_init(struct dwc_csi_device *csidev) > > +{ > > + int i; > > + > > + /* Initialize the same format for pads of CSIS entity */ > > + for (i = 0; i < DWC_CSI2RX_PADS_NUM; ++i) > > + csidev->format_mbus[i] = dwc_csi_default_fmt; > > + > > + csidev->csi_fmt = &dwc_csi_formats[0]; > > + > > + return 0; > > +} > > + > > +static int dwc_csi_event_init(struct dwc_csi_device *csidev) > > +{ > > + unsigned int size = csidev->pdata->num_events > > + * sizeof(*csidev->events); > > + > > + csidev->events = devm_kzalloc(csidev->dev, size, GFP_KERNEL); > > + if (!csidev->events) > > + return -ENOMEM; > > + > > + memcpy(csidev->events, csidev->pdata->events, size); > > + > > + return 0; > > +} > > + > > +static int dwc_csi_subdev_init(struct dwc_csi_device *csidev) > > +{ > > + struct v4l2_subdev *sd = &csidev->sd; > > + > > + v4l2_subdev_init(sd, &dwc_csi_subdev_ops); > > + sd->owner = THIS_MODULE; > > + snprintf(sd->name, sizeof(sd->name), "csidev-%s", dev_name(csidev- > >dev)); > > + > > + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > > + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; > > + sd->entity.ops = &dwc_csi_entity_ops; > > + > > + sd->dev = csidev->dev; > > + > > + csidev->pads[DWC_CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK; > > + csidev->pads[DWC_CSI2RX_PAD_SOURCE].flags = > MEDIA_PAD_FL_SOURCE; > > + > > + return media_entity_pads_init(&csidev->sd.entity, > DWC_CSI2RX_PADS_NUM, > > + csidev->pads); > > +} > > + > > +static int dwc_csi_device_probe(struct platform_device *pdev) > > +{ > > + struct device *dev = &pdev->dev; > > + struct dwc_csi_device *csidev; > > + unsigned long cfg_rate; > > + int irq; > > + int ret; > > + > > + csidev = devm_kzalloc(dev, sizeof(*csidev), GFP_KERNEL); > > + if (!csidev) > > + return -ENOMEM; > > + > > + mutex_init(&csidev->lock); > > I think you are missing the initialization of csidev->slock here. That's correct. I miss it. Will update in next version. > > + > > + csidev->dev = dev; > > + csidev->pdata = of_device_get_match_data(dev); > > + > > + csidev->regs = devm_platform_ioremap_resource(pdev, 0); > > + if (IS_ERR(csidev->regs)) { > > + dev_err(dev, "Failed to get DWC csi2 register map\n"); > > + return PTR_ERR(csidev->regs); > > + } > > + > > + irq = platform_get_irq(pdev, 0); > > + if (irq < 0) { > > + dev_err(dev, "Failed to get IRQ (%d)\n", irq); > > + return irq; > > + } > > + > > + ret = devm_request_irq(dev, irq, dwc_csi_irq_handler, 0, > > + dev_name(dev), csidev); > > + if (ret < 0) { > > + dev_err(dev, "Failed to request IRQ (%d)\n", ret); > > + return ret; > > + } > > + > > + ret = dwc_csi_clk_get(csidev); > > + if (ret < 0) { > > + dev_err(dev, "Failed to get clocks\n"); > > + return ret; > > + } > > + > > + /* cfgclkfreqrange[5:0] = round[(cfg_clk(MHz) - 17) * 4] */ > > + cfg_rate = clk_get_rate(csidev->clks[PHY_CFG].clk); > > + if (!cfg_rate) { > > + dev_err(dev, "Failed to get phy_cfg clock rate\n"); > > + return -EINVAL; > > + } > > + > > + csidev->cfgclkfreqrange = ((cfg_rate / 1000000) - 17) * 4; > > + > > + ret = dwc_csi_param_init(csidev); > > + if (ret < 0) > > + return ret; > > + > > + ret = dwc_csi_event_init(csidev); > > + if (ret < 0) > > + return ret; > > + > > + ret = dwc_csi_subdev_init(csidev); > > + if (ret < 0) { > > + dev_err(dev, "Failed to initialize subdev\n"); > > + return ret; > > + } > > + > > + platform_set_drvdata(pdev, &csidev->sd); > > + > > + ret = dwc_csi_async_register(csidev); > > + if (ret < 0) { > > + dev_err(dev, "Async register failed: %d\n", ret); > > + return ret; > > + } > > + > > + pm_runtime_enable(dev); > > + > > + dwc_csi_pattern_generator_init(csidev); > > + > > + return 0; > > +} > > + > > +static int dwc_csi_device_remove(struct platform_device *pdev) > > +{ > > + struct v4l2_subdev *sd = platform_get_drvdata(pdev); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + > > + v4l2_async_nf_unregister(&csidev->notifier); > > + v4l2_async_nf_cleanup(&csidev->notifier); > > + v4l2_async_unregister_subdev(&csidev->sd); > > + > > + /* Remove pattern generator device attribute */ > > + dwc_csi_pattern_generator_deinit(csidev); > > + > > + pm_runtime_disable(&pdev->dev); > > + > > + media_entity_cleanup(&csidev->sd.entity); > > + fwnode_handle_put(csidev->sd.fwnode); > > + mutex_destroy(&csidev->lock); > > + > > + pm_runtime_set_suspended(&pdev->dev); > > + return 0; > > +} > > + > > +static const struct clk_bulk_data mxc_imx93_clks[] = { > > + { .id = "per" }, > > + { .id = "pixel" }, > > + { .id = "phy_cfg" }, > > +}; > > + > > +static const struct dwc_csi_plat_data mxc_imx93_data = { > > + .model = DWC_CSI2RX_IMX93, > > + .intf = DWC_CSI2RX_INTF_IPI, > > + .clks = mxc_imx93_clks, > > + .num_clks = ARRAY_SIZE(mxc_imx93_clks), > > + .events = mxc_imx93_events, > > + .num_events = ARRAY_SIZE(mxc_imx93_events), > > + .events_mask = 0x500ff, > > +}; > > + > > +static const struct of_device_id dwc_csi_device_of_match[] = { > > + { .compatible = "fsl,imx93-mipi-csi2", .data = &mxc_imx93_data }, > > + { /* sentinel */ }, > > +}; > > +MODULE_DEVICE_TABLE(of, dwc_csi_device_of_match); > > + > > +static struct platform_driver dwc_csi_device_driver = { > > + .driver = { > > + .owner = THIS_MODULE, > > + .name = DWC_MIPI_CSIS_DRIVER_NAME, > > + .of_match_table = dwc_csi_device_of_match, > > + .pm = &dwc_csi_device_pm_ops, > > + }, > > + .probe = dwc_csi_device_probe, > > + .remove = dwc_csi_device_remove, > > +}; > > + > > +module_platform_driver(dwc_csi_device_driver); > > + > > +MODULE_DESCRIPTION("DesignWare Core MIPI CSI2 driver"); > > +MODULE_LICENSE("GPL"); > > +MODULE_ALIAS("platform:" DWC_MIPI_CSIS_DRIVER_NAME); > > diff --git a/drivers/media/platform/nxp/dwc-mipi-csi2.h > > b/drivers/media/platform/nxp/dwc-mipi-csi2.h new file mode 100644 > > index 000000000000..cdb85d867f22 > > --- /dev/null > > +++ b/drivers/media/platform/nxp/dwc-mipi-csi2.h > > @@ -0,0 +1,289 @@ > > +/* SPDX-License-Identifier: GPL-2.0 */ > > +/* > > + * Copyright 2023 NXP > > + */ > > + > > +#ifndef __DWC_MIPI_CSI2_H__ > > +#define __DWC_MIPI_CSI2_H__ > > + > > +#include <linux/device.h> > > +#include <linux/delay.h> > > +#include <linux/io.h> > > + > > +#include <media/v4l2-device.h> > > +#include <media/v4l2-fwnode.h> > > +#include <media/v4l2-mc.h> > > +#include <media/v4l2-subdev.h> > > + > > +/* MIPI CSI-2 Host Controller Registers Define */ > > + > > +/* Core Version */ > > +#define CSI2RX_VERSION 0x0 > > + > > +/* Number of Lanes */ > > +#define CSI2RX_N_LANES 0x4 > > +#define CSI2RX_N_LANES_N_LANES(x) ((x) & 0x7) > > + > > +/* Logic Reset */ > > +#define CSI2RX_HOST_RESETN 0x8 > > +#define CSI2RX_HOST_RESETN_ENABLE BIT(0) > > + > > +/* Main Interrupt Status */ > > +#define CSI2RX_INT_ST_MAIN 0xc > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_IPI BIT(18) > > +#define CSI2RX_INT_ST_MAIN_ERR_PHY BIT(16) > > +#define CSI2RX_INT_ST_MAIN_ERR_ECC BIT(7) > > +#define CSI2RX_INT_ST_MAIN_ERR_DID BIT(6) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PLD_CRC BIT(5) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_CRC_FRAME > BIT(4) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_SEQ_FRAME > BIT(3) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL BIT(2) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PKT BIT(1) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PHY BIT(0) > > + > > +/* PHY Shutdown */ > > +#define CSI2RX_DPHY_SHUTDOWNZ > 0x40 > > +#define CSI2RX_DPHY_SHUTDOWNZ_ENABLE BIT(0) > > + > > +/* DPHY Reset */ > > +#define CSI2RX_DPHY_RSTZ 0x44 > > +#define CSI2RX_DPHY_RSTZ_ENABLE > BIT(0) > > + > > +/* RX PHY Status */ > > +#define CSI2RX_DPHY_RX_STATUS > 0x48 > > +#define CSI2RX_DPHY_RX_STATUS_CLK_LANE_HS BIT(17) > > +#define CSI2RX_DPHY_RX_STATUS_CLK_LANE_ULP BIT(16) > > +#define CSI2RX_DPHY_RX_STATUS_DATA_LANE1_ULP BIT(1) > > +#define CSI2RX_DPHY_RX_STATUS_DATA_LANE0_ULP BIT(0) > > + > > +/* STOP STATE PHY Status */ > > +#define CSI2RX_DPHY_STOPSTATE 0x4c > > +#define CSI2RX_DPHY_STOPSTATE_CLK_LANE > BIT(16) > > +#define CSI2RX_DPHY_STOPSTATE_DATA_LANE1 BIT(1) > > +#define CSI2RX_DPHY_STOPSTATE_DATA_LANE0 BIT(0) > > + > > +/* DPHY Test and Control Interface 1 */ > > +#define CSI2RX_DPHY_TEST_CTRL0 0x50 > > +#define CSI2RX_DPHY_TEST_CTRL0_TEST_CLR > BIT(0) > > +#define CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN BIT(1) > > + > > +/* DPHY Test and Control Interface 2 */ > > +#define CSI2RX_DPHY_TEST_CTRL1 0x54 > > +#define CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(x) ((x) & 0xff) > > +#define CSI2RX_DPHY_TEST_CTRL1_TEST_DOUT(x) (((x) & 0xff00) > >> 8) > > +#define CSI2RX_DPHY_TEST_CTRL1_TEST_EN > BIT(16) > > + > > +/* Pattern Generator vertical Resolution */ > > +#define CSI2RX_PPI_PG_PATTERN_VRES 0x60 > > +#define CSI2RX_PPI_PG_PATTERN_VRES_VRES(x) ((x) & 0xffff) > > + > > +/* Pattern Generator horizontal Resolution */ > > +#define CSI2RX_PPI_PG_PATTERN_HRES 0x64 > > +#define CSI2RX_PPI_PG_PATTERN_HRES_HRES(x) ((x) & 0xffff) > > + > > +/* Pattern Generator */ > > +#define CSI2RX_PPI_PG_CONFIG 0x68 > > +#define CSI2RX_PPI_PG_CONFIG_DATA_TYPE(x) (((x) & 0x3f) > << > 8) > > +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN(x) (((x) & 0x3) << > 14) > > +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX(x) (((x) & 0x3) > << > 16) > > +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX_2_EN > BIT(18) > > +#define CSI2RX_PPI_PG_CONFIG_PG_MODE(x) (x) > > + > > +/* Pattern Generator Enable */ > > +#define CSI2RX_PPI_PG_ENABLE 0x6c > > +#define CSI2RX_PPI_PG_ENABLE_EN > BIT(0) > > + > > +/* Pattern Generator Status */ > > +#define CSI2RX_PPI_PG_STATUS 0x70 > > +#define CSI2RX_PPI_PG_STATUS_ACTIVE BIT(0) > > + > > +/* IPI Mode */ > > +#define CSI2RX_IPI_MODE 0x80 > > +#define CSI2RX_IPI_MODE_ENABLE > BIT(24) > > +#define CSI2RX_IPI_MODE_CUT_THROUGH BIT(16) > > +#define CSI2RX_IPI_MODE_COLOR_MODE16 BIT(8) > > +#define CSI2RX_IPI_MODE_CONTROLLER BIT(1) > > + > > +/* IPI Virtual Channel */ > > +#define CSI2RX_IPI_VCID 0x84 > > +#define CSI2RX_IPI_VCID_VC(x) ((x) & > 0x3) > > +#define CSI2RX_IPI_VCID_VC_0_1(x) (((x) & 0x3) << > 2) > > +#define CSI2RX_IPI_VCID_VC_2 BIT(4) > > + > > +/* IPI Data Type */ > > +#define CSI2RX_IPI_DATA_TYPE 0x88 > > +#define CSI2RX_IPI_DATA_TYPE_DT(x) ((x) & 0x3f) > > +#define CSI2RX_IPI_DATA_TYPE_EMB_DATA_EN BIT(8) > > + > > +/* IPI Flush Memory */ > > +#define CSI2RX_IPI_MEM_FLUSH 0x8c > > +#define CSI2RX_IPI_MEM_FLUSH_AUTO BIT(8) > > + > > +/* IPI HSA */ > > +#define CSI2RX_IPI_HSA_TIME 0x90 > > +#define CSI2RX_IPI_HSA_TIME_VAL(x) ((x) & 0xfff) > > + > > +/* IPI HBP */ > > +#define CSI2RX_IPI_HBP_TIME 0x94 > > +#define CSI2RX_IPI_HBP_TIME_VAL(x) ((x) & 0xfff) > > + > > +/* IPI HSD */ > > +#define CSI2RX_IPI_HSD_TIME 0x98 > > +#define CSI2RX_IPI_HSD_TIME_VAL(x) ((x) & 0xfff) > > + > > +/* IPI HLINE */ > > +#define CSI2RX_IPI_HLINE_TIME 0x9C > > +#define CSI2RX_IPI_HLINE_TIME_VAL(x) ((x) & 0x3fff) > > + > > +/* IPI Soft Reset */ > > +#define CSI2RX_IPI_SOFTRSTN 0xa0 > > + > > +/* IPI Advanced Features */ > > +#define CSI2RX_IPI_ADV_FEATURES 0xac > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_EVENT_MODE > BIT(24) > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_EMBEDDED_PKT BIT(21) > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_BLANKING_PKT BIT(20) > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_NULL_PKT > BIT(19) > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_LS_PKT BIT(18) > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_VIDEO_PKT > BIT(17) > > +#define CSI2RX_IPI_ADV_FEATURES_LINE_EVENT_SEL > BIT(16) > > +#define CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE(x) (((x) & 0x3f) > << 8) > > +#define CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE_EN BIT(0) > > + > > +/* IPI VSA */ > > +#define CSI2RX_IPI_VSA_LINES 0xb0 > > +#define CSI2RX_IPI_VSA_LINES_VAL(x) ((x) & 0x3ff) > > + > > +/* IPI VBP */ > > +#define CSI2RX_IPI_VBP_LINES 0xb4 > > +#define CSI2RX_IPI_VBP_LINES_VAL(x) ((x) & 0x3ff) > > + > > +/* IPI VFP */ > > +#define CSI2RX_IPI_VFP_LINES 0xb8 > > +#define CSI2RX_IPI_VFP_LINES_VAL(x) ((x) & 0x3ff) > > + > > +/* IPI VACTIVE */ > > +#define CSI2RX_IPI_VACTIVE_LINES 0xbc > > +#define CSI2RX_IPI_VACTIVE_LINES_VAL(x) ((x) & > 0x3fff) > > + > > +/* Fatal Interruption Caused by PHY */ > > +#define CSI2RX_INT_ST_DPHY_FATAL 0xe0 > > +#define CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE1 > BIT(1) > > +#define CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE0 > BIT(0) > > + > > +/* Mask for Fatal Interruption Caused by PHY */ > > +#define CSI2RX_INT_MSK_DPHY_FATAL 0xe4 > > +#define CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE1 > BIT(1) > > +#define CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE0 > BIT(0) > > + > > +/* Force for Fatal Interruption Caused by PHY */ > > +#define CSI2RX_INT_FORCE_DPHY_FATAL 0xe8 > > + > > +/* Fatal Interruption Caused During Packet Construction */ > > +#define CSI2RX_INT_ST_PKT_FATAL 0xf0 > > +#define CSI2RX_INT_ST_PKT_FATAL_ERR_PAYLOAD BIT(1) > > +#define CSI2RX_INT_ST_PKT_FATAL_ERR_ECC BIT(0) > > + > > +/* Mask for Fatal Interruption Caused During Packet Construction */ > > +#define CSI2RX_INT_MSK_PKT_FATAL 0xf4 > > +#define CSI2RX_INT_MSK_PKT_FATAL_ERR_PAYLOAD BIT(1) > > +#define CSI2RX_INT_MSK_PKT_FATAL_ERR_ECC BIT(0) > > + > > +/* Force for Fatal Interruption Caused During Packet Construction */ > > +#define CSI2RX_INT_FORCE_PKT_FATAL 0xf8 > > + > > +/* Interruption Caused by PHY */ > > +#define CSI2RX_INT_ST_DPHY 0x110 > > +#define CSI2RX_INT_ST_DPHY_ERR_ESC_LANE1 BIT(17) > > +#define CSI2RX_INT_ST_DPHY_ERR_ESC_LANE0 BIT(16) > > +#define CSI2RX_INT_ST_DPHY_ERR_SOT_LANE1 BIT(1) > > +#define CSI2RX_INT_ST_DPHY_ERR_SOT_LANE0 BIT(0) > > + > > +/* Mask for Interruption Caused by PHY */ > > +#define CSI2RX_INT_MSK_DPHY 0x114 > > +#define CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE1 BIT(17) > > +#define CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE0 BIT(16) > > +#define CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE1 BIT(1) > > +#define CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE0 BIT(0) > > + > > +/* Force for Interruption Caused by PHY */ > > +#define CSI2RX_INT_FORCE_DPHY > 0x118 > > + > > +/* Fatal Interruption Caused by IPI Interface */ > > +#define CSI2RX_INT_ST_IPI_FATAL 0x140 > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_PD_FIFO_OVERFLOW BIT(6) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_OVERFLOW BIT(5) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_HLINE_TIME BIT(4) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_NOT_EMPTY BIT(3) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FRAME_SYNC > BIT(2) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_IF_FIFO_OVERFLOW BIT(1) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_IF_FIFO_UNDERFLOW > BIT(0) > > + > > +/* Mask for Fatal Interruption Caused by IPI Interface */ > > +#define CSI2RX_INT_MSK_IPI_FATAL 0x144 > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_PD_FIFO_OVERFLOW > BIT(6) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_OVERFLOW BIT(5) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_HLINE_TIME > BIT(4) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_NOT_EMPTY BIT(3) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FRAME_SYNC > BIT(2) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_IF_FIFO_OVERFLOW > BIT(1) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_IF_FIFO_UNDERFLOW > BIT(0) > > + > > +/* Force for Fatal Interruption Caused by IPI Interface */ > > +#define CSI2RX_INT_FORCE_IPI_FATAL 0x148 > > + > > +/* Data De-Scrambling */ > > +#define CSI2RX_SCRAMBLING 0x300 > > + > > +/* De-scrambler Seed for Lane 1 */ > > +#define CSI2RX_SCRAMBLING_SEED1 > 0x304 > > + > > +/* De-scrambler Seed for Lane 2 */ > > +#define CSI2RX_SCRAMBLING_SEED2 > 0x308 > > + > > +#define dwc_csi_write(csidev, reg, val) writel((val), csidev->regs > + (reg)) > > +#define dwc_csi_read(csidev, reg) readl(csidev->regs + (reg)) > > + > > +#define DWC_CSI2RX_PAD_SINK 0 > > +#define DWC_CSI2RX_PAD_SOURCE 1 > > +#define DWC_CSI2RX_PADS_NUM 2 > > + > > +struct dwc_csi_device { > > + struct device *dev; > > + void __iomem *regs; > > + struct clk_bulk_data *clks; > > + const struct dwc_csi_plat_data *pdata; > > + > > + struct v4l2_subdev sd; > > + struct v4l2_async_notifier notifier; > > + struct v4l2_subdev *sensor_sd; > > + struct media_pad pads[DWC_CSI2RX_PADS_NUM]; > > + u16 remote_pad; > > + > > + struct v4l2_mbus_config_mipi_csi2 bus; > > + u32 cfgclkfreqrange; > > + u32 hsfreqrange; > > + > > + spinlock_t slock; /* Protect events */ > > + struct mutex lock; > > + > > + struct dwc_csi_event *events; > > + const struct dwc_csi_pix_format *csi_fmt; > > + struct v4l2_mbus_framefmt format_mbus[DWC_CSI2RX_PADS_NUM]; > > + > > + /* Used for pattern generator */ > > + bool pg_enable; > > + bool pg_active; > > + enum { > > + PATTERN_VERTICAL, > > + PATTERN_HORIZONTAL, > > + } pg_pattern; > > +}; > > + > > +void dphy_rx_reset(struct dwc_csi_device *csidev); > > +void dphy_rx_test_code_reset(struct dwc_csi_device *csidev); > > +void dphy_rx_test_code_config(struct dwc_csi_device *csidev); > > +void dphy_rx_power_off(struct dwc_csi_device *csidev); > > +void dphy_rx_test_code_dump(struct dwc_csi_device *csidev); > > + > > +#endif /* __DWC_MIPI_CSI2_H__ */ > > diff --git a/drivers/media/platform/nxp/dwc-mipi-dphy.c > > b/drivers/media/platform/nxp/dwc-mipi-dphy.c new file mode 100644 > > index 000000000000..cc443f282bb7 > > --- /dev/null > > +++ b/drivers/media/platform/nxp/dwc-mipi-dphy.c > > @@ -0,0 +1,195 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Copyright 2023 NXP > > + */ > > + > > +#include "dwc-mipi-csi2.h" > > + > > +/* > > + * DPHY testcode used to configure Rx DPHY > > + */ > > + > > +/* System configuration 0 */ > > +#define DPHY_RX_SYS_0 0x01 > > +#define HSFREQRANGE_OVR_EN_RW > BIT(5) > > + > > +/* System configuration 1 */ > > +#define DPHY_RX_SYS_1 0x02 > > +#define HSFREQRANGE_OVR_RW(x) ((x) > & > 0x7F) > > +#define TIMEBASE_OVR_EN_RW BIT(7) > > + > > +/* System configuration 2 */ > > +#define DPHY_RX_SYS_2 0x03 > > +#define TIMEBASE_OVR_RW(x) ((x) & 0xFF) > > + > > +static inline void dphy_rx_test_ctrl_set(struct dwc_csi_device *csidev, > > + u32 offset, u32 mask, u32 > code) > > +{ > > + u32 val; > > + > > + val = dwc_csi_read(csidev, offset); > > + val &= ~(mask); > > + val |= code; > > + dwc_csi_write(csidev, offset, val); > > +} > > + > > +static inline void dphy_rx_test_ctrl_clr(struct dwc_csi_device *csidev, > > + u32 offset, u32 code) > > +{ > > + u32 val; > > + > > + val = dwc_csi_read(csidev, offset); > > + val &= ~(code); > > + dwc_csi_write(csidev, offset, val); > > +} > > + > > +static u8 dphy_rx_test_ctrl_get(struct dwc_csi_device *csidev, u32 offset) > > +{ > > + u32 val; > > + > > + val = dwc_csi_read(csidev, offset); > > + val = CSI2RX_DPHY_TEST_CTRL1_TEST_DOUT(val); > > + > > + return (u8)val; > > +} > > +static void dphy_rx_write(struct dwc_csi_device *csidev, u8 addr, u8 value) > > +{ > > + u32 val; > > + > > + /* > > + * Set PHY_TST_CTRL1, bit[16] and write PHY_TST_CTRL1, > > + * bit[7:0] with test code address > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL1_TEST_EN; > > + val |= CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(addr); > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL1, 0x100ff, > val); > > + > > + /* > > + * Set and clear PHY_TST_CTRL0, bit[1] > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN; > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL0, 0x2, val); > > + dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > + > > + /* > > + * Write PHY_TST_CTRL1, bit[7:0] with test code content > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(value); > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL1, 0xff, val); > > + > > + /* > > + * Clear PHY_TST_CTRL1, bit[16] > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL1_TEST_EN; > > + dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL1, val); > > + > > + /* > > + * Set and clear PHY_TST_CTRL0, bit[1] > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN; > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL0, 0x2, val); > > + dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > +} > > + > > +static int dphy_rx_read(struct dwc_csi_device *csidev, u8 addr) > > +{ > > + u32 val; > > + > > + /* > > + * Set PHY_TST_CTRL1, bit[16] and write PHY_TST_CTRL1, > > + * bit[7:0] with test code address > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL1_TEST_EN; > > + val |= CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(addr); > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL1, 0x100ff, > val); > > + > > + /* Set and clear PHY_TST_CTRL0, bit[1] */ > > + val = CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN; > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL0, 0x2, val); > > + dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > + > > + /* Read PHY_TST_CTRL1, bit[15:8] with the test code content */ > > + val = dphy_rx_test_ctrl_get(csidev, CSI2RX_DPHY_TEST_CTRL1); > > + > > + /* Clear PHY_TST_CTRL1, bit[16] */ > > + dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL1, > > + CSI2RX_DPHY_TEST_CTRL1_TEST_EN); > > + > > + return val; > > +} > > + > > +void dphy_rx_reset(struct dwc_csi_device *csidev) > > +{ > > + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0); > > + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0); > > + ndelay(15); > > + > > + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x1); > > + ndelay(5); > > + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x1); > > +} > > + > > +void dphy_rx_test_code_reset(struct dwc_csi_device *csidev) > > +{ > > + u32 val; > > + > > + /* Set PHY_TST_CTRL0, bit[0] */ > > + val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0); > > + val |= CSI2RX_DPHY_TEST_CTRL0_TEST_CLR; > > + dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > + > > + /* Clear PHY_TST_CTRL0, bit[0] */ > > + val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0); > > + val &= ~CSI2RX_DPHY_TEST_CTRL0_TEST_CLR; > > + dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > +} > > + > > +void dphy_rx_test_code_config(struct dwc_csi_device *csidev) > > +{ > > + u32 val; > > + u8 dphy_val; > > + > > + /* Set testclr=1'b0 */ > > + val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0); > > + val &= ~CSI2RX_DPHY_TEST_CTRL0_TEST_CLR; > > + dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > + > > + /* Enable hsfreqrange_ovr_en and set hsfreqrange */ > > + dphy_rx_write(csidev, DPHY_RX_SYS_0, > HSFREQRANGE_OVR_EN_RW); > > + dphy_rx_write(csidev, DPHY_RX_SYS_1, > > + HSFREQRANGE_OVR_RW(csidev->hsfreqrange)); > > + > > + /* Enable timebase_ovr_en */ > > + dphy_val = dphy_rx_read(csidev, DPHY_RX_SYS_1); > > + dphy_val |= TIMEBASE_OVR_EN_RW; > > + dphy_rx_write(csidev, DPHY_RX_SYS_1, dphy_val); > > + > > + /* Set cfgclkfreqrange */ > > + dphy_rx_write(csidev, DPHY_RX_SYS_2, > > + TIMEBASE_OVR_RW(csidev->cfgclkfreqrange + 0x44)); > > RM Rev 2. mentions that depending on cfgclkfreqrange another configuration, > called counter_for_des_en_config_if, also needs to be set. Is this missing > here? It isn't needed from my experiment result. > > > +} > > + > > +void dphy_rx_power_off(struct dwc_csi_device *csidev) > > +{ > > + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0); > > + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0); > > +} > > + > > +void dphy_rx_test_code_dump(struct dwc_csi_device *csidev) > > +{ > > +#define DPHY_DEBUG_REG(name) {name, #name} > > + static const struct { > > + u32 offset; > > + const char * const name; > > + } test_codes[] = { > > + DPHY_DEBUG_REG(DPHY_RX_SYS_0), > > + DPHY_DEBUG_REG(DPHY_RX_SYS_1), > > + DPHY_DEBUG_REG(DPHY_RX_SYS_2), > > + }; > > + unsigned int i; > > + > > + for (i = 0; i < ARRAY_SIZE(test_codes); i++) > > + dev_dbg(csidev->dev, "%14s[0x%02x]: 0x%02x\n", > > + test_codes[i].name, test_codes[i].offset, > > + dphy_rx_read(csidev, test_codes[i].offset)); > > +} > > Could you also provide a complete DT configuration? I tried myself, but I just > ended up in getting errors while trying to use a MIPI-CSI camera > dwc-mipi-csi2 4ae00000.mipi-csi: IPI Interface Fatal Error events: 2800064 > dwc-mipi-csi2 4ae00000.mipi-csi: PHY Error events: 2174 > dwc-mipi-csi2 4ae00000.mipi-csi: IPI Interface Fatal Error events: 2800064 > dwc-mipi-csi2 4ae00000.mipi-csi: PHY Error events: 2174 Sure, I can provide full patches that use i.MX93 platform with AP1302 if you are interested. Will send you in another mails. > > Best regards, > Alexander > -- > TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany > Amtsgericht München, HRB 105018 > Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider > http://www.tq-group.com/ >