Hello, CC'ing Niklas, for a question down below. On Wed, Sep 13, 2023 at 08:36:18PM +0000, Sakari Ailus wrote: > Hi Guonio, > > Thanks for the patch. Looks pretty good overall, but please see comments > below... > > On Mon, Jul 10, 2023 at 02:03:52PM +0800, guoniu.zhou@xxxxxxxxxxx wrote: > > From: "Guoniu.zhou" <guoniu.zhou@xxxxxxx> > > > > The MIPI CSI-2 controller and MIPI Rx D-PHY found on i.MX93 originate > > from Synopsys. MIPI CSI-2 controller implements the CSI-2 protocol on > > host side. MIPI 2-lane Rx D-PHY module implement the physical layer > > for the MIPI D-PHY interface. Lane operation ranging from 80 Mbps to > > 1.5Gbps in forward direction. > > > > Add V4L2 subdev driver support both for CSI-2 controller and D-PHY > > since the PHY is wrapped by the CSI-2 controller and only expose a > > control interface to the CSI-2 controller. > > > > Signed-off-by: Guoniu.zhou <guoniu.zhou@xxxxxxx> > > --- > > MAINTAINERS | 10 + > > drivers/media/platform/nxp/Kconfig | 11 + > > drivers/media/platform/nxp/Makefile | 3 + > > drivers/media/platform/nxp/dwc-mipi-csi2.c | 1354 ++++++++++++++++++++ > > drivers/media/platform/nxp/dwc-mipi-csi2.h | 299 +++++ > > drivers/media/platform/nxp/dwc-mipi-dphy.c | 295 +++++ > > 6 files changed, 1972 insertions(+) > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index c83475103a25..349d981f9c24 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -15189,6 +15189,16 @@ S: Maintained > > F: Documentation/devicetree/bindings/sound/nxp,tfa989x.yaml > > F: sound/soc/codecs/tfa989x.c > > > > +NXP i.MX93 MIPI CSI-2 V4L2 DRIVER > > +M: G.N. Zhou (OSS) <guoniu.zhou@xxxxxxxxxxx> > > +R: NXP Linux Team <linux-imx@xxxxxxx> > > +L: linux-media@xxxxxxxxxxxxxxx > > +S: Maintained > > +F: Documentation/devicetree/bindings/media/nxp,dwc-mipi-csi2.yaml > > +F: drivers/media/platform/nxp/dwc-mipi-csi2.c > > +F: drivers/media/platform/nxp/dwc-mipi-csi2.h > > +F: drivers/media/platform/nxp/dwc-mipi-dphy.c > > + > > NZXT-KRAKEN2 HARDWARE MONITORING DRIVER > > M: Jonas Malaco <jonas@xxxxxxxxxxxx> > > L: linux-hwmon@xxxxxxxxxxxxxxx > > diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig > > index a0ca6b297fb8..4b8b713022d4 100644 > > --- a/drivers/media/platform/nxp/Kconfig > > +++ b/drivers/media/platform/nxp/Kconfig > > @@ -30,6 +30,17 @@ config VIDEO_IMX_MIPI_CSIS > > > > source "drivers/media/platform/nxp/imx8-isi/Kconfig" > > > > +config VIDEO_DWC_MIPI_CSIS > > + tristate "DesignWare Cores MIPI CSI-2 receiver found on i.MX93" > > + depends on ARCH_MXC || COMPILE_TEST > > + depends on VIDEO_DEV > > + select MEDIA_CONTROLLER > > + select V4L2_FWNODE > > + select VIDEO_V4L2_SUBDEV_API > > + help > > + Video4Linux2 sub-device driver for the DesignWare Cores MIPI > > + CSI-2 receiver used on i.MX93. > > You seem to have D-PHY handling embedded in the same driver. > > Would it be possible to use the phy framework instead? I believe there are > other drivers that already do this. I think that would be difficult, as the D-PHY isn't memory-mapped to the CPU. > > + > > # mem2mem drivers > > > > config VIDEO_IMX_PXP > > diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile > > index b8e672b75fed..07f43795dc16 100644 > > --- a/drivers/media/platform/nxp/Makefile > > +++ b/drivers/media/platform/nxp/Makefile > > @@ -4,6 +4,9 @@ obj-y += dw100/ > > obj-y += imx-jpeg/ > > obj-y += imx8-isi/ > > > > +dwc-mipi-csis-y := dwc-mipi-csi2.o dwc-mipi-dphy.o > > + > > +obj-$(CONFIG_VIDEO_DWC_MIPI_CSIS) += dwc-mipi-csis.o I like that this is a generic driver for the Synopsys CSI-2 host controller. Should it then be placed in drivers/media/platform/synopsys/ ? > > obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o > > obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o > > obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o > > diff --git a/drivers/media/platform/nxp/dwc-mipi-csi2.c b/drivers/media/platform/nxp/dwc-mipi-csi2.c > > new file mode 100644 > > index 000000000000..4a7154b471c5 > > --- /dev/null > > +++ b/drivers/media/platform/nxp/dwc-mipi-csi2.c > > @@ -0,0 +1,1354 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Copyright 2023 NXP > > + * > > + */ > > + > > +#include <linux/bits.h> > > +#include <linux/clk.h> > > +#include <linux/errno.h> > > +#include <linux/iopoll.h> > > +#include <linux/kernel.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include <linux/of_device.h> > > +#include <linux/platform_device.h> > > +#include <linux/pm_runtime.h> > > + > > +#include <media/v4l2-event.h> > > +#include <media/mipi-csi2.h> > > + > > +#include "dwc-mipi-csi2.h" > > + > > +#define DWC_MIPI_CSIS_DRIVER_NAME "dwc-mipi-csi2" > > + > > +#define DWC_CSI2RX_DEF_MBUS_CODE MEDIA_BUS_FMT_UYVY8_1X16 > > +#define DWC_CSI2RX_DEF_PIX_WIDTH 1920U > > +#define DWC_CSI2RX_DEF_PIX_HEIGHT 1080U > > +#define DWC_CSI2RX_MAX_PIX_WIDTH 0xffff > > +#define DWC_CSI2RX_MAX_PIX_HEIGHT 0xffff > > + > > +enum imx93_csi_clks { > > + PER, > > + PIXEL, > > + PHY_CFG, > > +}; > > + > > +enum model { > > + DWC_CSI2RX_IMX93, > > +}; > > + > > +enum dwc_csi2rx_intf { > > + DWC_CSI2RX_INTF_IDI, > > + DWC_CSI2RX_INTF_IPI, > > +}; > > + > > +struct dwc_csi_plat_data { > > + enum model model; > > + enum dwc_csi2rx_intf intf; > > + > > + const struct clk_bulk_data *clks; > > + u32 num_clks; > > + > > + const struct dwc_csi_event *events; > > + u32 num_events; > > + u32 events_mask; > > +}; > > + > > +/* ----------------------------------------------------------------------------- > > + * Events > > + */ > > + > > +struct dwc_csi_event { > > + u32 mask; > > + const char * const name; > > + unsigned int counter; > > +}; > > + > > +static struct dwc_csi_event mxc_imx93_events[] = { > > const. > > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_IPI, "IPI Interface Fatal Error" }, > > + { CSI2RX_INT_ST_MAIN_ERR_PHY, "PHY Error" }, > > + { CSI2RX_INT_ST_MAIN_ERR_ECC, "Header Single Bit Error" }, > > + { CSI2RX_INT_ST_MAIN_ERR_DID, "Data ID Error" }, > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_PLD_CRC, "Payload CRC Fatal Error" }, > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_CRC_FRAME, "Frame CRC Fatal Error" }, > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_SEQ_FRAME, "Frame Sequence Fatal Error" }, > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL, "Frame Boundaries Fatal Error" }, > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_PKT, "Packet Construction Fatal Error" }, > > + { CSI2RX_INT_ST_MAIN_FATAL_ERR_PHY, "PHY Fatal Error" }, > > +}; > > + > > +/* ----------------------------------------------------------------------------- > > + * Format helpers > > + */ > > + > > +/* List of supported pixel formats for the subdev */ > > +static const struct dwc_csi_pix_format dwc_csi_formats[] = { > > + /* YUV formats */ > > + { > > + .code = MEDIA_BUS_FMT_UYVY8_1X16, > > + .output = MEDIA_BUS_FMT_UYVY8_1X16, > > + .data_type = MIPI_CSI2_DT_YUV422_8B, > > + .width = 16, > > + }, > > + /* RGB formats */ > > + { > > + .code = MEDIA_BUS_FMT_RGB565_1X16, > > + .output = MEDIA_BUS_FMT_RGB565_1X16, > > + .data_type = MIPI_CSI2_DT_RGB565, > > + .width = 16, > > + }, { > > + .code = MEDIA_BUS_FMT_BGR888_1X24, > > + .output = MEDIA_BUS_FMT_RGB888_1X24, > > + .data_type = MIPI_CSI2_DT_RGB888, > > + .width = 24, > > + }, > > + /* RAW (Bayer and greyscale) formats. */ > > + { > > + .code = MEDIA_BUS_FMT_SBGGR8_1X8, > > + .output = MEDIA_BUS_FMT_SBGGR8_1X8, > > + .data_type = MIPI_CSI2_DT_RAW8, > > + .width = 8, > > + }, { > > + .code = MEDIA_BUS_FMT_SGBRG8_1X8, > > + .output = MEDIA_BUS_FMT_SGBRG8_1X8, > > + .data_type = MIPI_CSI2_DT_RAW8, > > + .width = 8, > > + }, { > > + .code = MEDIA_BUS_FMT_SGRBG8_1X8, > > + .output = MEDIA_BUS_FMT_SGRBG8_1X8, > > + .data_type = MIPI_CSI2_DT_RAW8, > > + .width = 8, > > + }, { > > + .code = MEDIA_BUS_FMT_SRGGB8_1X8, > > + .output = MEDIA_BUS_FMT_SRGGB8_1X8, > > + .data_type = MIPI_CSI2_DT_RAW8, > > + .width = 8, > > + }, { > > + .code = MEDIA_BUS_FMT_Y8_1X8, > > + .output = MEDIA_BUS_FMT_Y8_1X8, > > + .data_type = MIPI_CSI2_DT_RAW8, > > + .width = 8, > > + }, { > > + .code = MEDIA_BUS_FMT_SBGGR10_1X10, > > + .output = MEDIA_BUS_FMT_SBGGR10_1X10, > > + .data_type = MIPI_CSI2_DT_RAW10, > > + .width = 10, > > + }, { > > + .code = MEDIA_BUS_FMT_SGBRG10_1X10, > > + .output = MEDIA_BUS_FMT_SGBRG10_1X10, > > + .data_type = MIPI_CSI2_DT_RAW10, > > + .width = 10, > > + }, { > > + .code = MEDIA_BUS_FMT_SGRBG10_1X10, > > + .output = MEDIA_BUS_FMT_SGRBG10_1X10, > > + .data_type = MIPI_CSI2_DT_RAW10, > > + .width = 10, > > + }, { > > + .code = MEDIA_BUS_FMT_SRGGB10_1X10, > > + .output = MEDIA_BUS_FMT_SRGGB10_1X10, > > + .data_type = MIPI_CSI2_DT_RAW10, > > + .width = 10, > > + }, { > > + .code = MEDIA_BUS_FMT_Y10_1X10, > > + .output = MEDIA_BUS_FMT_Y10_1X10, > > + .data_type = MIPI_CSI2_DT_RAW10, > > + .width = 10, > > + }, { > > + .code = MEDIA_BUS_FMT_SBGGR12_1X12, > > + .output = MEDIA_BUS_FMT_SBGGR12_1X12, > > + .data_type = MIPI_CSI2_DT_RAW12, > > + .width = 12, > > + }, { > > + .code = MEDIA_BUS_FMT_SGBRG12_1X12, > > + .output = MEDIA_BUS_FMT_SGBRG12_1X12, > > + .data_type = MIPI_CSI2_DT_RAW12, > > + .width = 12, > > + }, { > > + .code = MEDIA_BUS_FMT_SGRBG12_1X12, > > + .output = MEDIA_BUS_FMT_SGRBG12_1X12, > > + .data_type = MIPI_CSI2_DT_RAW12, > > + .width = 12, > > + }, { > > + .code = MEDIA_BUS_FMT_SRGGB12_1X12, > > + .output = MEDIA_BUS_FMT_SRGGB12_1X12, > > + .data_type = MIPI_CSI2_DT_RAW12, > > + .width = 12, > > + }, { > > + .code = MEDIA_BUS_FMT_Y12_1X12, > > + .output = MEDIA_BUS_FMT_Y12_1X12, > > + .data_type = MIPI_CSI2_DT_RAW12, > > + .width = 12, > > + }, { > > + .code = MEDIA_BUS_FMT_SBGGR14_1X14, > > + .output = MEDIA_BUS_FMT_SBGGR14_1X14, > > + .data_type = MIPI_CSI2_DT_RAW14, > > + .width = 14, > > + }, { > > + .code = MEDIA_BUS_FMT_SGBRG14_1X14, > > + .output = MEDIA_BUS_FMT_SGBRG14_1X14, > > + .data_type = MIPI_CSI2_DT_RAW14, > > + .width = 14, > > + }, { > > + .code = MEDIA_BUS_FMT_SGRBG14_1X14, > > + .output = MEDIA_BUS_FMT_SGRBG14_1X14, > > + .data_type = MIPI_CSI2_DT_RAW14, > > + .width = 14, > > + }, { > > + .code = MEDIA_BUS_FMT_SRGGB14_1X14, > > + .output = MEDIA_BUS_FMT_SRGGB14_1X14, > > + .data_type = MIPI_CSI2_DT_RAW14, > > + .width = 14, > > + } > > +}; > > + > > +static const struct v4l2_mbus_framefmt dwc_csi_default_fmt = { > > + .code = DWC_CSI2RX_DEF_MBUS_CODE, > > + .width = DWC_CSI2RX_DEF_PIX_WIDTH, > > + .height = DWC_CSI2RX_DEF_PIX_HEIGHT, > > + .field = V4L2_FIELD_NONE, > > + .colorspace = V4L2_COLORSPACE_SMPTE170M, > > + .xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SMPTE170M), > > + .ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SMPTE170M), > > + .quantization = V4L2_QUANTIZATION_LIM_RANGE, > > +}; > > + > > +static const struct dwc_csi_pix_format *find_csi_format(u32 code) > > +{ > > + int i; > > unsigned int? > > > + > > + for (i = 0; i < ARRAY_SIZE(dwc_csi_formats); i++) > > + if (code == dwc_csi_formats[i].code) > > + return &dwc_csi_formats[i]; > > + return NULL; > > Could you always return a valid format? This makes it easier for the caller > to handle the return value. > > > +} > > + > > +/* ----------------------------------------------------------------------------- > > + * DWC MIPI CSI-2 Host Controller Hardware operation > > + */ > > + > > +static int dwc_csi_device_pg_enable(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_pix_format *csi_fmt = csidev->csi_fmt; > > + struct v4l2_mbus_framefmt *format; > > + u32 val; > > + > > + if (!csidev->pg_enable) > > + return 0; > > + > > + if (!csi_fmt) { > > + dev_err(csidev->dev, "CSI pixel format is NULL\n"); > > + return -EINVAL; > > + } This should never happen, the driver should ensure that a valid format is configured at all times. > > + > > + format = &csidev->format_mbus[DWC_CSI2RX_PAD_SINK]; > > + > > + if (csi_fmt->data_type != MIPI_CSI2_DT_RGB888) { > > + dev_err(csidev->dev, "Pattern generator only support RGB888\n"); Make this a dev_dbg() as it can get triggered by userspace. > > + return -EINVAL; Pipeline validation at stream on time typically returns -EPIPE. > > + } > > + > > + val = CSI2RX_PPI_PG_PATTERN_HRES_HRES(format->width); > > + dwc_csi_write(csidev, CSI2RX_PPI_PG_PATTERN_HRES, val); > > + > > + val = CSI2RX_PPI_PG_PATTERN_VRES_VRES(format->height); > > + dwc_csi_write(csidev, CSI2RX_PPI_PG_PATTERN_VRES, val); > > + > > + val = CSI2RX_PPI_PG_CONFIG_DATA_TYPE(csi_fmt->data_type); > > + val |= CSI2RX_PPI_PG_CONFIG_VIR_CHAN(0); > > + val |= CSI2RX_PPI_PG_CONFIG_PG_MODE(csidev->pg_pattern); > > + dwc_csi_write(csidev, CSI2RX_PPI_PG_CONFIG, val); > > + > > + /* > > + * Select line start packets to construct vertical > > + * timing information for IPI interface > > + **/ Double ** > > + val = CSI2RX_IPI_ADV_FEATURES_SYNC_EVENT_MODE; > > + val |= CSI2RX_IPI_ADV_FEATURES_SYNC_LS_PKT; > > + val |= CSI2RX_IPI_ADV_FEATURES_LINE_EVENT_SEL; > > + dwc_csi_write(csidev, CSI2RX_IPI_ADV_FEATURES, val); > > + > > + val = CSI2RX_PPI_PG_ENABLE_EN; > > + dwc_csi_write(csidev, CSI2RX_PPI_PG_ENABLE, val); > > + > > + return 0; > > +} > > + > > +static void dwc_csi_device_pg_disable(struct dwc_csi_device *csidev) > > +{ > > + dwc_csi_write(csidev, CSI2RX_PPI_PG_ENABLE, 0); > > +} > > + > > +static void dwc_csi_ipi_enable(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + u32 val; > > + > > + if (pdata->intf != DWC_CSI2RX_INTF_IPI) > > + return; The driver supports the IPI interface only, I would drop these checks as well as the intf field. > > + > > + /* Memory is automatically flushed at each Frame Start */ > > + val = CSI2RX_IPI_MEM_FLUSH_AUTO; > > + dwc_csi_write(csidev, CSI2RX_IPI_MEM_FLUSH, val); > > + > > + /* Enable IPI */ > > + val = dwc_csi_read(csidev, CSI2RX_IPI_MODE); > > + val |= CSI2RX_IPI_MODE_ENABLE; > > + dwc_csi_write(csidev, CSI2RX_IPI_MODE, val); > > +} > > + > > +static void dwc_csi_ipi_disable(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + > > + if (pdata->intf != DWC_CSI2RX_INTF_IPI) > > + return; > > + > > + dwc_csi_write(csidev, CSI2RX_IPI_MODE, 0); > > +} > > + > > +static void dwc_csi_device_ipi_config(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_pix_format *csi_fmt = csidev->csi_fmt; > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + u32 val; > > + > > + if (pdata->intf != DWC_CSI2RX_INTF_IPI) > > + return; > > + > > + /* Do IPI soft reset */ > > + dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x0); > > + dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x1); > > + > > + /* Select virtual channel and data type to be processed by IPI */ > > + val = CSI2RX_IPI_DATA_TYPE_DT(csi_fmt->data_type); > > + dwc_csi_write(csidev, CSI2RX_IPI_DATA_TYPE, val); > > + > > + /* Set virtual channel 0 as default */ > > + val = CSI2RX_IPI_VCID_VC(0); > > + dwc_csi_write(csidev, CSI2RX_IPI_VCID, val); > > + > > + /* > > + * Select IPI camera timing mode and allow the pixel stream > > + * to be non-continuous when pixel interface FIFO is empty > > + */ > > + val = dwc_csi_read(csidev, CSI2RX_IPI_MODE); > > + val &= ~CSI2RX_IPI_MODE_CONTROLLER; > > + val &= ~CSI2RX_IPI_MODE_COLOR_MODE16; > > + val |= CSI2RX_IPI_MODE_CUT_THROUGH; > > + dwc_csi_write(csidev, CSI2RX_IPI_MODE, val); > > +} > > + > > +static void dwc_csi_device_reset(struct dwc_csi_device *csidev) > > +{ > > + /* Reset mipi csi host, active low */ > > + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0); > > + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 1); > > +} > > + > > +static void dwc_csi_device_startup(struct dwc_csi_device *csidev) > > +{ > > + /* Release DWC_mipi_csi2_host from reset */ > > + dwc_csi_device_reset(csidev); > > + > > + /* Apply PHY Reset */ > > + dphy_rx_reset(csidev); > > + > > + /* Release PHY test codes from reset */ > > + dphy_rx_test_code_reset(csidev); > > +} > > + > > +static int dwc_csi_device_init(struct dwc_csi_device *csidev) > > +{ > > + struct device *dev = csidev->dev; > > + u32 val; > > + int ret; > > + > > + /* Release Synopsys DPHY test codes from reset */ > > + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0); > > + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0); > > + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0); > > + > > + /* Set testclr=1'b1 */ > > + val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0); > > + val |= CSI2RX_DPHY_TEST_CTRL0_TEST_CLR; > > + dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > + > > + /* Wait for at least 15ns */ > > + ndelay(15); > > + > > + /* Configure the PHY frequency range */ > > + ret = dphy_rx_get_hsfreqrange(csidev); > > + if (ret < 0) > > + return ret; > > + > > + dphy_rx_test_code_config(csidev); > > + dphy_rx_test_code_dump(csidev); > > + > > + /* Config the number of active lanes */ > > + val = CSI2RX_N_LANES_N_LANES(csidev->bus.num_data_lanes - 1); > > + dwc_csi_write(csidev, CSI2RX_N_LANES, val); > > + > > + /* Release PHY from reset */ > > + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x1); > > + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x1); > > + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0x1); > > + > > + /* Check if lanes are in stop state */ > > + ret = readl_poll_timeout(csidev->regs + CSI2RX_DPHY_STOPSTATE, > > + val, val != 0x10003, 10, 10000); > > + if (ret) { > > + dev_err(dev, "Lanes are not in stop state(%#x)\n", val); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static void dwc_csi_device_hs_rx_start(struct dwc_csi_device *csidev) > > +{ > > + dwc_csi_ipi_enable(csidev); > > +} > > + > > +static int dwc_csi_device_hs_rx_stop(struct dwc_csi_device *csidev) > > +{ > > + struct device *dev = csidev->dev; > > + u32 val; > > + > > + dwc_csi_ipi_disable(csidev); > > + dphy_rx_power_off(csidev); > > + > > + /* Check clock lanes are not in High Speed Mode */ > > + val = dwc_csi_read(csidev, CSI2RX_DPHY_RX_STATUS); > > + if (val & CSI2RX_DPHY_RX_STATUS_CLK_LANE_HS) { > > + dev_err(dev, "Clock lanes are still in HS mode\n"); > > + return -EINVAL; > > + } > > + > > + return 0; > > +} > > + > > +static void dwc_csi_device_enable_interrupts(struct dwc_csi_device *csidev, bool on) > > +{ > > + /* Define errors to be enabled */ > > + dwc_csi_write(csidev, CSI2RX_INT_MSK_DPHY_FATAL, on ? 0x3 : 0); > > + dwc_csi_write(csidev, CSI2RX_INT_MSK_PKT_FATAL, on ? 0x3 : 0); > > + dwc_csi_write(csidev, CSI2RX_INT_MSK_DPHY, on ? 0x30003 : 0); > > + dwc_csi_write(csidev, CSI2RX_INT_MSK_IPI_FATAL, on ? 0x7f : 0); > > +} > > + > > +static int dwc_csi_clk_enable(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + > > + return clk_bulk_prepare_enable(pdata->num_clks, csidev->clks); > > +} > > + > > +static void dwc_csi_clk_disable(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + > > + clk_bulk_disable_unprepare(pdata->num_clks, csidev->clks); > > +} > > + > > +static int dwc_csi_clk_get(struct dwc_csi_device *csidev) > > +{ > > + const struct dwc_csi_plat_data *pdata = csidev->pdata; > > + unsigned int size; > > + int ret; > > + > > + size = pdata->num_clks * sizeof(*csidev->clks); > > + > > + csidev->clks = devm_kmalloc(csidev->dev, size, GFP_KERNEL); > > devm_kmalloc_array()? > > > + if (!csidev->clks) > > + return -ENOMEM; > > + > > + memcpy(csidev->clks, pdata->clks, size); > > + > > + ret = devm_clk_bulk_get(csidev->dev, pdata->num_clks, csidev->clks); > > + if (ret < 0) { > > + dev_err(csidev->dev, "Failed to acquire clocks: %d\n", ret); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +/* ----------------------------------------------------------------------------- > > + * Debug > > + */ > > + > > +static void dwc_csi_clear_counters(struct dwc_csi_device *csidev) > > +{ > > + unsigned long flags; > > + unsigned int i; > > + > > + spin_lock_irqsave(&csidev->slock, flags); > > + > > + for (i = 0; i < csidev->pdata->num_events; ++i) > > + csidev->events[i].counter = 0; > > + > > + spin_unlock_irqrestore(&csidev->slock, flags); > > +} > > + > > +static void dwc_csi_log_counters(struct dwc_csi_device *csidev) > > +{ > > + unsigned int num_events = csidev->pdata->num_events; > > + unsigned long flags; > > + unsigned int i; > > + > > + spin_lock_irqsave(&csidev->slock, flags); > > + > > + for (i = 0; i < num_events; ++i) { > > + if (csidev->events[i].counter > 0) > > + dev_info(csidev->dev, "%s events: %d\n", > > + csidev->events[i].name, > > + csidev->events[i].counter); > > + } > > + > > + spin_unlock_irqrestore(&csidev->slock, flags); > > +} > > + > > +static void dwc_csi_dump_regs(struct dwc_csi_device *csidev) > > +{ > > +#define DWC_MIPI_CSIS_DEBUG_REG(name) {name, #name} > > + static const struct { > > + u32 offset; > > + const char * const name; > > + } registers[] = { > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_VERSION), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_N_LANES), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_HOST_RESETN), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_MAIN), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_SHUTDOWNZ), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_RSTZ), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_RX_STATUS), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_STOPSTATE), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_TEST_CTRL0), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_TEST_CTRL1), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_PATTERN_VRES), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_PATTERN_HRES), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_CONFIG), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_ENABLE), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_STATUS), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_MODE), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_VCID), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_DATA_TYPE), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_MEM_FLUSH), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_SOFTRSTN), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_ADV_FEATURES), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_DPHY_FATAL), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_PKT_FATAL), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_DPHY_FATAL), > > + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_IPI_FATAL), > > + }; > > + > > Extra newline. > > > + unsigned int i; > > + u32 cfg; > > + > > + dev_dbg(csidev->dev, "--- REGISTERS ---\n"); > > + > > + for (i = 0; i < ARRAY_SIZE(registers); i++) { > > + cfg = dwc_csi_read(csidev, registers[i].offset); > > + dev_dbg(csidev->dev, "%14s[0x%02x]: 0x%08x\n", > > + registers[i].name, registers[i].offset, cfg); > > + } > > +} > > + > > +/* ----------------------------------------------------------------------------- > > + * V4L2 subdev operations > > + */ > > + > > +static inline struct dwc_csi_device * > > +sd_to_dwc_csi_device(struct v4l2_subdev *sdev) > > Please call this "sd" or "subdev" (instead of sdev). > > > +{ > > + return container_of(sdev, struct dwc_csi_device, sd); > > +} > > + > > +static struct v4l2_mbus_framefmt * > > +dwc_csi_get_pad_format(struct dwc_csi_device *csidev, > > + struct v4l2_subdev_state *sd_state, > > + enum v4l2_subdev_format_whence which, > > + unsigned int pad) > > +{ > > + if (which == V4L2_SUBDEV_FORMAT_TRY) > > + return v4l2_subdev_get_try_format(&csidev->sd, sd_state, pad); > > + > > + return &csidev->format_mbus[pad]; > > Please use sub-device state. See e.g. To be precise, this is about storing the subdev active state in a v4l2_subdev_state structure managed by the subdev core. It should simplify the driver. > <URL:https://git.linuxtv.org/sailus/media_tree.git/commit/?h=metadata&id=9e6a17a9750e220a93e0eb5d24c4af4c27d5fe3a> > for an example. I believe there are other examples in the tree already. > > > +} > > + > > +static int dwc_csi_subdev_init_cfg(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + struct v4l2_mbus_framefmt *fmt_sink; > > + struct v4l2_mbus_framefmt *fmt_source; > > + > > + fmt_sink = dwc_csi_get_pad_format(csidev, sd_state, > > + V4L2_SUBDEV_FORMAT_TRY, > > + DWC_CSI2RX_PAD_SINK); > > + *fmt_sink = dwc_csi_default_fmt; > > + > > + fmt_source = dwc_csi_get_pad_format(csidev, sd_state, > > + V4L2_SUBDEV_FORMAT_TRY, > > + DWC_CSI2RX_PAD_SOURCE); > > + *fmt_source = *fmt_sink; > > + > > + return 0; > > +} > > + > > +static int dwc_csi_subdev_enum_mbus_code(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_mbus_code_enum *code) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + > > + /* > > + * The CSIS can't transcode in any way, the source format is identical > > + * to the sink format. > > + */ > > + if (code->pad == DWC_CSI2RX_PAD_SOURCE) { > > + struct v4l2_mbus_framefmt *fmt; > > + > > + if (code->index > 0) > > + return -EINVAL; > > + > > + fmt = dwc_csi_get_pad_format(csidev, sd_state, code->which, > > + code->pad); > > + code->code = fmt->code; > > + return 0; > > + } > > + > > + if (code->pad != DWC_CSI2RX_PAD_SINK) > > + return -EINVAL; > > Do you have more than two pads? The caller will have validated the pad > field already. > > > + > > + if (code->index >= ARRAY_SIZE(dwc_csi_formats)) > > + return -EINVAL; > > + > > + code->code = dwc_csi_formats[code->index].code; > > + > > + return 0; > > +} > > + > > +static int dwc_csi_subdev_get_fmt(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_format *sdformat) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + struct v4l2_mbus_framefmt *fmt; > > + > > + fmt = dwc_csi_get_pad_format(csidev, sd_state, sdformat->which, > > + sdformat->pad); > > + > > + mutex_lock(&csidev->lock); > > + sdformat->format = *fmt; > > + mutex_unlock(&csidev->lock); > > + > > + return 0; > > +} > > + > > +static int dwc_csi_subdev_set_fmt(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_format *sdformat) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + struct dwc_csi_pix_format const *csi_fmt; > > + struct v4l2_mbus_framefmt *fmt; > > + unsigned int align; > > + > > + /* > > + * The CSIS can't transcode in any way, the source format can't be > > + * modified. > > + */ > > + if (sdformat->pad == DWC_CSI2RX_PAD_SOURCE) > > + return dwc_csi_subdev_get_fmt(sd, sd_state, sdformat); > > + > > + if (sdformat->pad != DWC_CSI2RX_PAD_SINK) > > + return -EINVAL; > > Ditto. > > > + > > + /* > > + * Validate the media bus code and clamp and align the size. > > + * > > + * The total number of bits per line must be a multiple of 8. We thus > > + * need to align the width for formats that are not multiples of 8 > > + * bits. > > + */ > > + csi_fmt = find_csi_format(sdformat->format.code); > > + if (!csi_fmt) > > + csi_fmt = &dwc_csi_formats[0]; > > + > > + switch (csi_fmt->width % 8) { > > + case 0: > > + align = 0; > > + break; > > + case 4: > > + align = 1; > > + break; > > + case 2: > > + case 6: > > + align = 2; > > + break; > > + default: > > + /* 1, 3, 5, 7 */ > > + align = 3; > > + break; > > + } > > + > > + v4l_bound_align_image(&sdformat->format.width, 1, > > + DWC_CSI2RX_MAX_PIX_WIDTH, align, > > + &sdformat->format.height, 1, > > + DWC_CSI2RX_MAX_PIX_HEIGHT, 0, 0); > > + > > + fmt = dwc_csi_get_pad_format(csidev, sd_state, sdformat->which, > > + sdformat->pad); > > + > > + mutex_lock(&csidev->lock); > > + > > + fmt->code = csi_fmt->code; > > + fmt->width = sdformat->format.width; > > + fmt->height = sdformat->format.height; > > + fmt->colorspace = sdformat->format.colorspace; > > + fmt->quantization = sdformat->format.quantization; > > + fmt->xfer_func = sdformat->format.xfer_func; > > + fmt->ycbcr_enc = sdformat->format.ycbcr_enc; > > + > > + sdformat->format = *fmt; > > + > > + /* Propagate the format from sink to source. */ > > + fmt = dwc_csi_get_pad_format(csidev, sd_state, sdformat->which, > > + DWC_CSI2RX_PAD_SOURCE); > > + *fmt = sdformat->format; > > + > > + /* The format on the source pad might change due to unpacking. */ > > + fmt->code = csi_fmt->output; > > + > > + /* Store the CSIS format descriptor for active formats. */ > > + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) > > + csidev->csi_fmt = csi_fmt; > > + > > + mutex_unlock(&csidev->lock); > > + > > + return 0; > > +} > > + > > +static int dwc_csi_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, > > + struct v4l2_mbus_frame_desc *fd) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + struct v4l2_mbus_frame_desc_entry *entry = &fd->entry[0]; > > + > > + if (pad != DWC_CSI2RX_PAD_SOURCE) > > + return -EINVAL; > > + > > + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL; > > + fd->num_entries = 1; Does the CSI-2 receiver support capturing embedded data ? > > + > > + memset(entry, 0, sizeof(*entry)); > > + > > + mutex_lock(&csidev->lock); > > + > > + entry->flags = 0; > > + entry->pixelcode = csidev->csi_fmt->code; > > + entry->bus.csi2.vc = 0; > > + entry->bus.csi2.dt = csidev->csi_fmt->data_type; > > + > > + mutex_unlock(&csidev->lock); > > + > > + return 0; > > +} > > + > > +static int dwc_csi_start_stream(struct dwc_csi_device *csidev) > > +{ > > + int ret; > > + > > + dwc_csi_device_startup(csidev); > > + > > + ret = dwc_csi_device_init(csidev); > > + if (ret) > > + return ret; > > + > > + dwc_csi_device_ipi_config(csidev); > > + > > + ret = dwc_csi_device_pg_enable(csidev); > > + if (ret) > > + return ret; > > + > > + dwc_csi_device_hs_rx_start(csidev); > > + > > + dwc_csi_device_enable_interrupts(csidev, true); > > + > > + return 0; > > +} > > + > > +static void dwc_csi_stop_stream(struct dwc_csi_device *csidev) > > +{ > > + dwc_csi_device_enable_interrupts(csidev, false); > > + dwc_csi_device_hs_rx_stop(csidev); > > + dwc_csi_device_pg_disable(csidev); > > +} > > + > > +static int dwc_csi_subdev_s_stream(struct v4l2_subdev *sd, int enable) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + int ret; > > + > > + if (!csidev->sensor_sd) { > > + dev_err(csidev->dev, "Sensor don't link with CSIS pad\n"); > > + return -EPIPE; > > + } As the driver supports a single connected sensor (it adds a single entry to the notifier at probe time), you can simplify this by assigning sensor_sd in the .bound() handler, making the link immutable, and dropping the check here as well as dropping the .link_setup() handler. > > + > > + mutex_lock(&csidev->lock); > > + > > + if (!enable) { > > + dwc_csi_stop_stream(csidev); > > + dwc_csi_log_counters(csidev); > > + pm_runtime_put(csidev->dev); > > + goto sd_stream; > > + } An } else { here would avoid the goto above. > > + > > + ret = pm_runtime_resume_and_get(csidev->dev); > > + if (ret < 0) > > + goto unlocked; > > + > > + ret = __v4l2_ctrl_handler_setup(&csidev->ctrl_handler); > > + if (ret < 0) { > > + pm_runtime_put(csidev->dev); > > + goto unlocked; > > + } > > + > > + dwc_csi_clear_counters(csidev); > > + > > + /* CSIS HW configuration */ > > + ret = dwc_csi_start_stream(csidev); > > + if (ret) { > > + pm_runtime_put(csidev->dev); > > + goto unlocked; > > + } > > + > > + dwc_csi_dump_regs(csidev); > > + > > +sd_stream: > > + /* > > + * when enable CSI pattern generator, the clock source of > > + * pattern generator will be from external sensor, so it > > + * also need to enable external sensor clock. That makes the test pattern generator less useful :-( If you need a sensor anyway, what are the use cases for using the TPG ? > > + */ > > + v4l2_subdev_call(csidev->sensor_sd, video, s_stream, enable); > > + dwc_csi_log_counters(csidev); > > +unlocked: s/unlocked/unlock/ > > + mutex_unlock(&csidev->lock); > > + return ret; > > +} > > + > > +static int dwc_csi_subdev_log_status(struct v4l2_subdev *sd) > > +{ > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + > > + dwc_csi_log_counters(csidev); > > + return 0; > > +} > > + > > +static const struct v4l2_subdev_core_ops dwc_csi_subdev_core_ops = { > > + .log_status = dwc_csi_subdev_log_status, > > + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, > > + .unsubscribe_event = v4l2_event_subdev_unsubscribe, > > +}; > > + > > +static const struct v4l2_subdev_pad_ops dwc_csi_subdev_pad_ops = { > > + .init_cfg = dwc_csi_subdev_init_cfg, > > + .enum_mbus_code = dwc_csi_subdev_enum_mbus_code, > > + .get_fmt = dwc_csi_subdev_get_fmt, > > + .set_fmt = dwc_csi_subdev_set_fmt, > > + .get_frame_desc = dwc_csi_get_frame_desc, > > +}; > > + > > +static const struct v4l2_subdev_video_ops dwc_csi_subdev_video_ops = { > > + .s_stream = dwc_csi_subdev_s_stream, > > +}; > > + > > +static const struct v4l2_subdev_ops dwc_csi_subdev_ops = { > > + .core = &dwc_csi_subdev_core_ops, > > + .pad = &dwc_csi_subdev_pad_ops, > > + .video = &dwc_csi_subdev_video_ops, > > +}; > > + > > +/* ----------------------------------------------------------------------------- > > + * Media entity operations > > + */ > > + > > +static int dwc_csi_link_setup(struct media_entity *entity, > > + const struct media_pad *local_pad, > > + const struct media_pad *remote_pad, u32 flags) > > +{ > > + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + struct v4l2_subdev *remote_sd; > > + > > + dev_dbg(csidev->dev, "link setup %s -> %s", remote_pad->entity->name, > > + local_pad->entity->name); > > + > > + /* We only care about the link to the source. */ > > + if (!(local_pad->flags & MEDIA_PAD_FL_SINK)) > > + return 0; > > + > > + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); > > + > > + if (flags & MEDIA_LNK_FL_ENABLED) { > > + if (csidev->sensor_sd) > > + return -EBUSY; > > + > > + csidev->sensor_sd = remote_sd; > > + csidev->remote_pad = remote_pad->index; > > + } else { > > + csidev->sensor_sd = NULL; > > + } > > + > > + return 0; > > +} > > + > > +static int dwc_csi_link_validate(struct media_link *link) > > +{ > > + struct media_pad *sink_pad = link->sink; > > + struct v4l2_subdev *sink_sd; > > + struct dwc_csi_device *csidev; > > + > > + sink_sd = media_entity_to_v4l2_subdev(sink_pad->entity); > > + csidev = sd_to_dwc_csi_device(sink_sd); > > + > > + dev_dbg(csidev->dev, "entity name:%s pad index=%d\n", > > + sink_sd->name, sink_pad->index); > > + > > + /* > > + * Skip link validate when pattern enabled since the soruce s/soruce/source/ > > + * data will be from CSI pattern generator, not sensor. > > + */ > > + if (csidev->pg_enable && sink_pad->index == DWC_CSI2RX_PAD_SINK) > > + return 0; > > + > > + return v4l2_subdev_link_validate(link); > > +} > > + > > +static const struct media_entity_operations dwc_csi_entity_ops = { > > + .link_setup = dwc_csi_link_setup, > > + .link_validate = dwc_csi_link_validate, > > + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, > > +}; > > + > > +/* ----------------------------------------------------------------------------- > > + * Async subdev notifier > > + */ > > + > > +static inline struct dwc_csi_device * > > +notifier_to_dwc_csi_device(struct v4l2_async_notifier *n) > > +{ > > + return container_of(n, struct dwc_csi_device, notifier); > > +} > > + > > +static int dwc_csi_notify_bound(struct v4l2_async_notifier *notifier, > > + struct v4l2_subdev *sd, > > + struct v4l2_async_subdev *asd) > > There have been changes since you posted the patch, the equivalent struct > is now called v4l2_async_connection. > > > +{ > > + struct dwc_csi_device *csidev = notifier_to_dwc_csi_device(notifier); > > + struct media_pad *sink = &csidev->sd.entity.pads[DWC_CSI2RX_PAD_SINK]; > > + > > + return v4l2_create_fwnode_links_to_pad(sd, sink, 0); > > +} > > + > > +static const struct v4l2_async_notifier_operations dwc_csi_notify_ops = { > > + .bound = dwc_csi_notify_bound, > > +}; > > + > > +static int dwc_csi_async_register(struct dwc_csi_device *csidev) > > +{ > > + struct v4l2_fwnode_endpoint vep = { > > + .bus_type = V4L2_MBUS_CSI2_DPHY, > > + }; > > + struct v4l2_async_subdev *asd; > > + struct fwnode_handle *ep; > > + unsigned int i; > > + int ret; > > + > > + v4l2_async_nf_init(&csidev->notifier); > > + > > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csidev->dev), 0, 0, > > + FWNODE_GRAPH_ENDPOINT_NEXT); > > + if (!ep) > > + return -ENOTCONN; > > + > > + ret = v4l2_fwnode_endpoint_parse(ep, &vep); > > + if (ret) > > + goto err_parse; > > + > > + for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) { > > + if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) { > > + dev_err(csidev->dev, > > + "data lanes reordering is not supported"); > > + ret = -EINVAL; > > + goto err_parse; > > + } > > + } > > + > > + csidev->bus = vep.bus.mipi_csi2; > > + > > + dev_dbg(csidev->dev, "data lanes: %d\n", csidev->bus.num_data_lanes); > > + dev_dbg(csidev->dev, "flags: 0x%08x\n", csidev->bus.flags); > > + > > + asd = v4l2_async_nf_add_fwnode_remote(&csidev->notifier, ep, > > + struct v4l2_async_subdev); > > + if (IS_ERR(asd)) { > > + ret = PTR_ERR(asd); > > + goto err_parse; > > + } > > + > > + fwnode_handle_put(ep); > > + > > + csidev->notifier.ops = &dwc_csi_notify_ops; > > + > > + ret = v4l2_async_subdev_nf_register(&csidev->sd, &csidev->notifier); > > + if (ret) > > + return ret; > > + > > + return v4l2_async_register_subdev(&csidev->sd); > > + > > +err_parse: > > + fwnode_handle_put(ep); > > + > > + return ret; > > +} > > + > > +/* ----------------------------------------------------------------------------- > > + * Pattern Generator Controller operations > > + */ > > + > > +static const char * const test_pattern_menu[] = { > > + "Disabled", > > + "Vertical Color Bars", > > + "Horizontal Color Bars", > > +}; > > + > > +static inline struct dwc_csi_device *ctrl_to_csidev(struct v4l2_ctrl *ctrl) > > +{ > > + return container_of(ctrl->handler, struct dwc_csi_device, ctrl_handler); > > +} > > + > > +static int dwc_csi_s_ctrl(struct v4l2_ctrl *ctrl) > > +{ > > + struct dwc_csi_device *csidev = ctrl_to_csidev(ctrl); > > + int ret = 0; > > + > > + switch (ctrl->id) { > > + case V4L2_CID_TEST_PATTERN: > > + /* Pattern index start from 0 */ > > + csidev->pg_pattern = ctrl->val - 1; > > + csidev->pg_enable = (ctrl->val) ? true : false; > > + break; > > You could already return here. Ret would become redundant. Up to you. > > > + default: > > + ret = -EINVAL; > > + break; > > + } > > + > > + return ret; > > +} > > + > > +static const struct v4l2_ctrl_ops dwc_csi_ctrl_ops = { > > + .s_ctrl = dwc_csi_s_ctrl, > > +}; > > + > > +static int dwc_csi_controls_init(struct dwc_csi_device *csidev) > > +{ > > + struct v4l2_ctrl_handler *handler = &csidev->ctrl_handler; > > + int ret; > > + > > + v4l2_ctrl_handler_init(handler, 1); > > + > > + /* Use driver mutex lock for the ctrl lock */ > > + handler->lock = &csidev->lock; > > + > > + v4l2_ctrl_new_std_menu_items(handler, &dwc_csi_ctrl_ops, > > + V4L2_CID_TEST_PATTERN, > > + ARRAY_SIZE(test_pattern_menu) - 1, > > + 0, 0, test_pattern_menu); > > + > > + if (handler->error) { > > + ret = handler->error; > > + v4l2_ctrl_handler_free(handler); > > + return ret; > > + } > > + > > + csidev->sd.ctrl_handler = handler; > > + return 0; > > +} > > +/* ----------------------------------------------------------------------------- > > + * Suspend/resume > > + */ > > + > > +static int dwc_csi_system_suspend(struct device *dev) > > +{ > > + return pm_runtime_force_suspend(dev); > > +} > > + > > +static int dwc_csi_system_resume(struct device *dev) > > +{ > > + int ret; > > + > > + ret = pm_runtime_force_resume(dev); > > + if (ret < 0) { > > + dev_err(dev, "force resume %s failed!\n", dev_name(dev)); > > + return ret; > > + } > > + > > + return 0; > > +} These shouldn't be needed, the device connected to the CSI-2 RX output should stop the pipeline during system suspend and resume it during system resume. > > + > > +static int dwc_csi_runtime_suspend(struct device *dev) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + > > + dwc_csi_clk_disable(csidev); This is the only caller fo the dwc_csi_clk_disable() function, I would inline it here. Same for dwc_csi_clk_enable() below. > > + > > + return 0; > > +} > > + > > +static int dwc_csi_runtime_resume(struct device *dev) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + int ret; > > + > > + ret = dwc_csi_clk_enable(csidev); > > + if (ret < 0) > > + return ret; > > + > > + return 0; return dwc_csi_clk_enable(csidev); > > +} > > + > > +static const struct dev_pm_ops dwc_csi_device_pm_ops = { > > + SET_SYSTEM_SLEEP_PM_OPS(dwc_csi_system_suspend, dwc_csi_system_resume) > > + SET_RUNTIME_PM_OPS(dwc_csi_runtime_suspend, dwc_csi_runtime_resume, NULL) > > +}; > > + > > +/* ----------------------------------------------------------------------------- > > + * IRQ handling > > + */ > > + > > +static irqreturn_t dwc_csi_irq_handler(int irq, void *priv) > > +{ > > + struct dwc_csi_device *csidev = priv; > > + unsigned long flags; > > + u32 status; > > + int i; > > + > > + status = dwc_csi_read(csidev, CSI2RX_INT_ST_MAIN); > > + > > + spin_lock_irqsave(&csidev->slock, flags); > > + > > + if (status & csidev->pdata->events_mask) { > > + for (i = 0; i < csidev->pdata->num_events; ++i) { > > + struct dwc_csi_event *event = &csidev->events[i]; > > + > > + if (status & event->mask) > > + event->counter++; > > + } > > + } > > + > > + spin_unlock_irqrestore(&csidev->slock, flags); > > + > > + return IRQ_HANDLED; > > +} > > + > > +/* ----------------------------------------------------------------------------- > > + * Probe/remove & platform driver > > + */ > > + > > +static int dwc_csi_param_init(struct dwc_csi_device *csidev) > > +{ > > + int i; > > unsigned int? > > > + > > + /* Initialize the same format for pads of CSIS entity */ > > + for (i = 0; i < DWC_CSI2RX_PADS_NUM; ++i) > > + csidev->format_mbus[i] = dwc_csi_default_fmt; > > + > > + csidev->csi_fmt = &dwc_csi_formats[0]; > > + > > + return 0; > > +} > > + > > +static int dwc_csi_event_init(struct dwc_csi_device *csidev) > > +{ > > + unsigned int size = csidev->pdata->num_events > > + * sizeof(*csidev->events); > > + > > + csidev->events = devm_kzalloc(csidev->dev, size, GFP_KERNEL); > > + if (!csidev->events) > > + return -ENOMEM; > > + > > + memcpy(csidev->events, csidev->pdata->events, size); > > + > > + return 0; > > +} > > + > > +static int dwc_csi_subdev_init(struct dwc_csi_device *csidev) > > +{ > > + struct v4l2_subdev *sd = &csidev->sd; > > + > > + v4l2_subdev_init(sd, &dwc_csi_subdev_ops); > > + sd->owner = THIS_MODULE; > > + snprintf(sd->name, sizeof(sd->name), "csidev-%s", dev_name(csidev->dev)); > > + > > + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | > > + V4L2_SUBDEV_FL_HAS_EVENTS; > > + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; > > + sd->entity.ops = &dwc_csi_entity_ops; > > + > > + sd->dev = csidev->dev; > > + > > + csidev->pads[DWC_CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK; > > + csidev->pads[DWC_CSI2RX_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; > > + > > + return media_entity_pads_init(&csidev->sd.entity, DWC_CSI2RX_PADS_NUM, > > + csidev->pads); > > +} > > + > > +static int dwc_csi_device_probe(struct platform_device *pdev) > > +{ > > + struct device *dev = &pdev->dev; > > + struct dwc_csi_device *csidev; > > + unsigned long cfg_rate; > > + int irq; > > + int ret; > > + > > + csidev = devm_kzalloc(dev, sizeof(*csidev), GFP_KERNEL); > > + if (!csidev) > > + return -ENOMEM; > > + > > + mutex_init(&csidev->lock); > > + spin_lock_init(&csidev->slock); > > + > > + csidev->dev = dev; > > + csidev->pdata = of_device_get_match_data(dev); > > + > > + csidev->regs = devm_platform_ioremap_resource(pdev, 0); > > + if (IS_ERR(csidev->regs)) { > > + dev_err(dev, "Failed to get DWC csi2 register map\n"); > > + return PTR_ERR(csidev->regs); > > + } > > + > > + irq = platform_get_irq(pdev, 0); > > + if (irq < 0) { > > + dev_err(dev, "Failed to get IRQ (%d)\n", irq); > > + return irq; > > + } > > + > > + ret = devm_request_irq(dev, irq, dwc_csi_irq_handler, 0, > > + dev_name(dev), csidev); > > + if (ret < 0) { > > + dev_err(dev, "Failed to request IRQ (%d)\n", ret); > > + return ret; > > + } > > + > > + ret = dwc_csi_clk_get(csidev); > > + if (ret < 0) { > > + dev_err(dev, "Failed to get clocks\n"); > > + return ret; > > + } > > + > > + /* cfgclkfreqrange[5:0] = round[(cfg_clk(MHz) - 17) * 4] */ > > + cfg_rate = clk_get_rate(csidev->clks[PHY_CFG].clk); > > + if (!cfg_rate) { > > + dev_err(dev, "Failed to get phy_cfg clock rate\n"); > > + return -EINVAL; > > + } > > + > > + csidev->cfgclkfreqrange = ((cfg_rate / 1000000) - 17) * 4; > > Please use div_u64() here. This won't work on some archs otherwise. > > > + > > + ret = dwc_csi_param_init(csidev); > > + if (ret < 0) > > + return ret; > > + > > + ret = dwc_csi_event_init(csidev); > > + if (ret < 0) > > + return ret; > > + > > + ret = dwc_csi_subdev_init(csidev); > > + if (ret < 0) { > > + dev_err(dev, "Failed to initialize subdev\n"); > > + return ret; > > + } > > + > > + ret = dwc_csi_controls_init(csidev); > > + if (ret) { > > + dev_err(dev, "Failed to initialize controls\n"); > > + return ret; > > + } > > + > > + platform_set_drvdata(pdev, &csidev->sd); > > + > > + ret = dwc_csi_async_register(csidev); > > + if (ret < 0) { > > + dev_err(dev, "Async register failed: %d\n", ret); > > + return ret; > > + } > > + > > + pm_runtime_enable(dev); > > + > > + return 0; > > +} > > + > > +static int dwc_csi_device_remove(struct platform_device *pdev) > > +{ > > + struct v4l2_subdev *sd = platform_get_drvdata(pdev); > > + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd); > > + > > + v4l2_async_nf_unregister(&csidev->notifier); > > + v4l2_async_nf_cleanup(&csidev->notifier); > > + v4l2_async_unregister_subdev(&csidev->sd); > > + > > + pm_runtime_disable(&pdev->dev); > > + > > + media_entity_cleanup(&csidev->sd.entity); > > + fwnode_handle_put(csidev->sd.fwnode); > > + mutex_destroy(&csidev->lock); > > + > > + pm_runtime_set_suspended(&pdev->dev); > > + return 0; > > +} > > + > > +static const struct clk_bulk_data mxc_imx93_clks[] = { > > + { .id = "per" }, > > + { .id = "pixel" }, > > + { .id = "phy_cfg" }, > > +}; > > + > > +static const struct dwc_csi_plat_data mxc_imx93_data = { > > + .model = DWC_CSI2RX_IMX93, > > + .intf = DWC_CSI2RX_INTF_IPI, > > + .clks = mxc_imx93_clks, > > + .num_clks = ARRAY_SIZE(mxc_imx93_clks), > > + .events = mxc_imx93_events, > > + .num_events = ARRAY_SIZE(mxc_imx93_events), > > + .events_mask = 0x500ff, > > +}; > > + > > +static const struct of_device_id dwc_csi_device_of_match[] = { > > + { .compatible = "fsl,imx93-mipi-csi2", .data = &mxc_imx93_data }, > > + { /* sentinel */ }, > > +}; > > +MODULE_DEVICE_TABLE(of, dwc_csi_device_of_match); > > + > > +static struct platform_driver dwc_csi_device_driver = { > > + .driver = { > > + .owner = THIS_MODULE, > > + .name = DWC_MIPI_CSIS_DRIVER_NAME, > > + .of_match_table = dwc_csi_device_of_match, > > + .pm = &dwc_csi_device_pm_ops, > > + }, > > + .probe = dwc_csi_device_probe, > > + .remove = dwc_csi_device_remove, > > +}; > > + > > +module_platform_driver(dwc_csi_device_driver); > > + > > +MODULE_DESCRIPTION("DesignWare Core MIPI CSI2 driver"); > > +MODULE_LICENSE("GPL"); > > +MODULE_ALIAS("platform:" DWC_MIPI_CSIS_DRIVER_NAME); > > diff --git a/drivers/media/platform/nxp/dwc-mipi-csi2.h b/drivers/media/platform/nxp/dwc-mipi-csi2.h > > new file mode 100644 > > index 000000000000..470023787c25 > > --- /dev/null > > +++ b/drivers/media/platform/nxp/dwc-mipi-csi2.h > > @@ -0,0 +1,299 @@ > > +/* SPDX-License-Identifier: GPL-2.0 */ > > +/* > > + * Copyright 2023 NXP > > + */ > > + > > +#ifndef __DWC_MIPI_CSI2_H__ > > +#define __DWC_MIPI_CSI2_H__ > > + > > +#include <linux/device.h> > > +#include <linux/delay.h> > > +#include <linux/io.h> > > + > > +#include <media/v4l2-ctrls.h> > > +#include <media/v4l2-device.h> > > +#include <media/v4l2-fwnode.h> > > +#include <media/v4l2-mc.h> > > +#include <media/v4l2-subdev.h> > > I don't think you need many of these headers here. Please move to the .c > file those you don't need here. > > > + > > +/* MIPI CSI-2 Host Controller Registers Define */ > > + Niklas, is it me, or do the registers here look very familiar ? The R-Car V4H may have the same CSI-2 RX. Should we have a single driver ? > > +/* Core Version */ > > +#define CSI2RX_VERSION 0x0 > > + > > +/* Number of Lanes */ > > +#define CSI2RX_N_LANES 0x4 > > +#define CSI2RX_N_LANES_N_LANES(x) ((x) & 0x7) > > + > > +/* Logic Reset */ > > +#define CSI2RX_HOST_RESETN 0x8 > > +#define CSI2RX_HOST_RESETN_ENABLE BIT(0) > > + > > +/* Main Interrupt Status */ > > +#define CSI2RX_INT_ST_MAIN 0xc > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_IPI BIT(18) > > +#define CSI2RX_INT_ST_MAIN_ERR_PHY BIT(16) > > +#define CSI2RX_INT_ST_MAIN_ERR_ECC BIT(7) > > +#define CSI2RX_INT_ST_MAIN_ERR_DID BIT(6) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PLD_CRC BIT(5) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_CRC_FRAME BIT(4) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_SEQ_FRAME BIT(3) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL BIT(2) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PKT BIT(1) > > +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PHY BIT(0) > > + > > +/* PHY Shutdown */ > > +#define CSI2RX_DPHY_SHUTDOWNZ 0x40 > > +#define CSI2RX_DPHY_SHUTDOWNZ_ENABLE BIT(0) > > + > > +/* DPHY Reset */ > > +#define CSI2RX_DPHY_RSTZ 0x44 > > +#define CSI2RX_DPHY_RSTZ_ENABLE BIT(0) > > + > > +/* RX PHY Status */ > > +#define CSI2RX_DPHY_RX_STATUS 0x48 > > +#define CSI2RX_DPHY_RX_STATUS_CLK_LANE_HS BIT(17) > > +#define CSI2RX_DPHY_RX_STATUS_CLK_LANE_ULP BIT(16) > > +#define CSI2RX_DPHY_RX_STATUS_DATA_LANE1_ULP BIT(1) > > +#define CSI2RX_DPHY_RX_STATUS_DATA_LANE0_ULP BIT(0) > > + > > +/* STOP STATE PHY Status */ > > +#define CSI2RX_DPHY_STOPSTATE 0x4c > > +#define CSI2RX_DPHY_STOPSTATE_CLK_LANE BIT(16) > > +#define CSI2RX_DPHY_STOPSTATE_DATA_LANE1 BIT(1) > > +#define CSI2RX_DPHY_STOPSTATE_DATA_LANE0 BIT(0) > > + > > +/* DPHY Test and Control Interface 1 */ > > +#define CSI2RX_DPHY_TEST_CTRL0 0x50 > > +#define CSI2RX_DPHY_TEST_CTRL0_TEST_CLR BIT(0) > > +#define CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN BIT(1) > > + > > +/* DPHY Test and Control Interface 2 */ > > +#define CSI2RX_DPHY_TEST_CTRL1 0x54 > > +#define CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(x) ((x) & 0xff) > > +#define CSI2RX_DPHY_TEST_CTRL1_TEST_DOUT(x) (((x) & 0xff00) >> 8) > > +#define CSI2RX_DPHY_TEST_CTRL1_TEST_EN BIT(16) > > + > > +/* Pattern Generator vertical Resolution */ > > +#define CSI2RX_PPI_PG_PATTERN_VRES 0x60 > > +#define CSI2RX_PPI_PG_PATTERN_VRES_VRES(x) ((x) & 0xffff) > > + > > +/* Pattern Generator horizontal Resolution */ > > +#define CSI2RX_PPI_PG_PATTERN_HRES 0x64 > > +#define CSI2RX_PPI_PG_PATTERN_HRES_HRES(x) ((x) & 0xffff) > > + > > +/* Pattern Generator */ > > +#define CSI2RX_PPI_PG_CONFIG 0x68 > > +#define CSI2RX_PPI_PG_CONFIG_DATA_TYPE(x) (((x) & 0x3f) << 8) > > +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN(x) (((x) & 0x3) << 14) > > +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX(x) (((x) & 0x3) << 16) > > +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX_2_EN BIT(18) > > +#define CSI2RX_PPI_PG_CONFIG_PG_MODE(x) (x) > > + > > +/* Pattern Generator Enable */ > > +#define CSI2RX_PPI_PG_ENABLE 0x6c > > +#define CSI2RX_PPI_PG_ENABLE_EN BIT(0) > > + > > +/* Pattern Generator Status */ > > +#define CSI2RX_PPI_PG_STATUS 0x70 > > +#define CSI2RX_PPI_PG_STATUS_ACTIVE BIT(0) > > + > > +/* IPI Mode */ > > +#define CSI2RX_IPI_MODE 0x80 > > +#define CSI2RX_IPI_MODE_ENABLE BIT(24) > > +#define CSI2RX_IPI_MODE_CUT_THROUGH BIT(16) > > +#define CSI2RX_IPI_MODE_COLOR_MODE16 BIT(8) > > +#define CSI2RX_IPI_MODE_CONTROLLER BIT(1) > > + > > +/* IPI Virtual Channel */ > > +#define CSI2RX_IPI_VCID 0x84 > > +#define CSI2RX_IPI_VCID_VC(x) ((x) & 0x3) > > +#define CSI2RX_IPI_VCID_VC_0_1(x) (((x) & 0x3) << 2) > > +#define CSI2RX_IPI_VCID_VC_2 BIT(4) > > + > > +/* IPI Data Type */ > > +#define CSI2RX_IPI_DATA_TYPE 0x88 > > +#define CSI2RX_IPI_DATA_TYPE_DT(x) ((x) & 0x3f) > > +#define CSI2RX_IPI_DATA_TYPE_EMB_DATA_EN BIT(8) > > + > > +/* IPI Flush Memory */ > > +#define CSI2RX_IPI_MEM_FLUSH 0x8c > > +#define CSI2RX_IPI_MEM_FLUSH_AUTO BIT(8) > > + > > +/* IPI HSA */ > > +#define CSI2RX_IPI_HSA_TIME 0x90 > > +#define CSI2RX_IPI_HSA_TIME_VAL(x) ((x) & 0xfff) > > + > > +/* IPI HBP */ > > +#define CSI2RX_IPI_HBP_TIME 0x94 > > +#define CSI2RX_IPI_HBP_TIME_VAL(x) ((x) & 0xfff) > > + > > +/* IPI HSD */ > > +#define CSI2RX_IPI_HSD_TIME 0x98 > > +#define CSI2RX_IPI_HSD_TIME_VAL(x) ((x) & 0xfff) > > + > > +/* IPI HLINE */ > > +#define CSI2RX_IPI_HLINE_TIME 0x9C > > +#define CSI2RX_IPI_HLINE_TIME_VAL(x) ((x) & 0x3fff) > > + > > +/* IPI Soft Reset */ > > +#define CSI2RX_IPI_SOFTRSTN 0xa0 > > + > > +/* IPI Advanced Features */ > > +#define CSI2RX_IPI_ADV_FEATURES 0xac > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_EVENT_MODE BIT(24) > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_EMBEDDED_PKT BIT(21) > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_BLANKING_PKT BIT(20) > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_NULL_PKT BIT(19) > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_LS_PKT BIT(18) > > +#define CSI2RX_IPI_ADV_FEATURES_SYNC_VIDEO_PKT BIT(17) > > +#define CSI2RX_IPI_ADV_FEATURES_LINE_EVENT_SEL BIT(16) > > +#define CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE(x) (((x) & 0x3f) << 8) > > +#define CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE_EN BIT(0) > > + > > +/* IPI VSA */ > > +#define CSI2RX_IPI_VSA_LINES 0xb0 > > +#define CSI2RX_IPI_VSA_LINES_VAL(x) ((x) & 0x3ff) > > + > > +/* IPI VBP */ > > +#define CSI2RX_IPI_VBP_LINES 0xb4 > > +#define CSI2RX_IPI_VBP_LINES_VAL(x) ((x) & 0x3ff) > > + > > +/* IPI VFP */ > > +#define CSI2RX_IPI_VFP_LINES 0xb8 > > +#define CSI2RX_IPI_VFP_LINES_VAL(x) ((x) & 0x3ff) > > + > > +/* IPI VACTIVE */ > > +#define CSI2RX_IPI_VACTIVE_LINES 0xbc > > +#define CSI2RX_IPI_VACTIVE_LINES_VAL(x) ((x) & 0x3fff) > > + > > +/* Fatal Interruption Caused by PHY */ > > +#define CSI2RX_INT_ST_DPHY_FATAL 0xe0 > > +#define CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE1 BIT(1) > > +#define CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE0 BIT(0) > > + > > +/* Mask for Fatal Interruption Caused by PHY */ > > +#define CSI2RX_INT_MSK_DPHY_FATAL 0xe4 > > +#define CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE1 BIT(1) > > +#define CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE0 BIT(0) > > + > > +/* Force for Fatal Interruption Caused by PHY */ > > +#define CSI2RX_INT_FORCE_DPHY_FATAL 0xe8 > > + > > +/* Fatal Interruption Caused During Packet Construction */ > > +#define CSI2RX_INT_ST_PKT_FATAL 0xf0 > > +#define CSI2RX_INT_ST_PKT_FATAL_ERR_PAYLOAD BIT(1) > > +#define CSI2RX_INT_ST_PKT_FATAL_ERR_ECC BIT(0) > > + > > +/* Mask for Fatal Interruption Caused During Packet Construction */ > > +#define CSI2RX_INT_MSK_PKT_FATAL 0xf4 > > +#define CSI2RX_INT_MSK_PKT_FATAL_ERR_PAYLOAD BIT(1) > > +#define CSI2RX_INT_MSK_PKT_FATAL_ERR_ECC BIT(0) > > + > > +/* Force for Fatal Interruption Caused During Packet Construction */ > > +#define CSI2RX_INT_FORCE_PKT_FATAL 0xf8 > > + > > +/* Interruption Caused by PHY */ > > +#define CSI2RX_INT_ST_DPHY 0x110 > > +#define CSI2RX_INT_ST_DPHY_ERR_ESC_LANE1 BIT(17) > > +#define CSI2RX_INT_ST_DPHY_ERR_ESC_LANE0 BIT(16) > > +#define CSI2RX_INT_ST_DPHY_ERR_SOT_LANE1 BIT(1) > > +#define CSI2RX_INT_ST_DPHY_ERR_SOT_LANE0 BIT(0) > > + > > +/* Mask for Interruption Caused by PHY */ > > +#define CSI2RX_INT_MSK_DPHY 0x114 > > +#define CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE1 BIT(17) > > +#define CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE0 BIT(16) > > +#define CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE1 BIT(1) > > +#define CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE0 BIT(0) > > + > > +/* Force for Interruption Caused by PHY */ > > +#define CSI2RX_INT_FORCE_DPHY 0x118 > > + > > +/* Fatal Interruption Caused by IPI Interface */ > > +#define CSI2RX_INT_ST_IPI_FATAL 0x140 > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_PD_FIFO_OVERFLOW BIT(6) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_OVERFLOW BIT(5) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_HLINE_TIME BIT(4) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_NOT_EMPTY BIT(3) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FRAME_SYNC BIT(2) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_IF_FIFO_OVERFLOW BIT(1) > > +#define CSI2RX_INT_ST_IPI_FATAL_ERR_IF_FIFO_UNDERFLOW BIT(0) > > + > > +/* Mask for Fatal Interruption Caused by IPI Interface */ > > +#define CSI2RX_INT_MSK_IPI_FATAL 0x144 > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_PD_FIFO_OVERFLOW BIT(6) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_OVERFLOW BIT(5) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_HLINE_TIME BIT(4) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_NOT_EMPTY BIT(3) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FRAME_SYNC BIT(2) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_IF_FIFO_OVERFLOW BIT(1) > > +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_IF_FIFO_UNDERFLOW BIT(0) > > + > > +/* Force for Fatal Interruption Caused by IPI Interface */ > > +#define CSI2RX_INT_FORCE_IPI_FATAL 0x148 > > + > > +/* Data De-Scrambling */ > > +#define CSI2RX_SCRAMBLING 0x300 > > + > > +/* De-scrambler Seed for Lane 1 */ > > +#define CSI2RX_SCRAMBLING_SEED1 0x304 > > + > > +/* De-scrambler Seed for Lane 2 */ > > +#define CSI2RX_SCRAMBLING_SEED2 0x308 > > + > > +#define dwc_csi_write(csidev, reg, val) writel((val), csidev->regs + (reg)) > > +#define dwc_csi_read(csidev, reg) readl(csidev->regs + (reg)) > > + > > +#define DWC_CSI2RX_PAD_SINK 0 > > +#define DWC_CSI2RX_PAD_SOURCE 1 > > +#define DWC_CSI2RX_PADS_NUM 2 > > + > > +struct dwc_csi_pix_format { > > + u32 code; > > + u32 output; > > + u32 data_type; > > + u8 width; > > +}; > > + > > +struct dwc_csi_device { > > + struct device *dev; > > + void __iomem *regs; > > + struct clk_bulk_data *clks; > > + const struct dwc_csi_plat_data *pdata; > > + > > + struct v4l2_subdev sd; > > + struct v4l2_async_notifier notifier; > > + struct v4l2_subdev *sensor_sd; > > + struct v4l2_ctrl_handler ctrl_handler; > > + struct media_pad pads[DWC_CSI2RX_PADS_NUM]; > > + u16 remote_pad; > > + > > + struct v4l2_mbus_config_mipi_csi2 bus; > > + u32 cfgclkfreqrange; > > + u32 hsfreqrange; > > + > > + spinlock_t slock; /* Protect events */ > > + struct mutex lock; > > + > > + struct dwc_csi_event *events; > > + const struct dwc_csi_pix_format *csi_fmt; > > + struct v4l2_mbus_framefmt format_mbus[DWC_CSI2RX_PADS_NUM]; > > + > > + /* Used for pattern generator */ > > + bool pg_enable; > > + enum { > > + PATTERN_DISABLED, > > + PATTERN_VERTICAL, > > + PATTERN_HORIZONTAL, > > + } pg_pattern; > > +}; > > + > > +void dphy_rx_reset(struct dwc_csi_device *csidev); > > +void dphy_rx_test_code_reset(struct dwc_csi_device *csidev); > > +void dphy_rx_test_code_config(struct dwc_csi_device *csidev); > > +void dphy_rx_power_off(struct dwc_csi_device *csidev); > > +void dphy_rx_test_code_dump(struct dwc_csi_device *csidev); > > +int dphy_rx_get_hsfreqrange(struct dwc_csi_device *csidev); > > + > > +#endif /* __DWC_MIPI_CSI2_H__ */ > > diff --git a/drivers/media/platform/nxp/dwc-mipi-dphy.c b/drivers/media/platform/nxp/dwc-mipi-dphy.c > > new file mode 100644 > > index 000000000000..9b9f2f01a31c > > --- /dev/null > > +++ b/drivers/media/platform/nxp/dwc-mipi-dphy.c > > @@ -0,0 +1,295 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Copyright 2023 NXP > > + */ > > + > > +#include "dwc-mipi-csi2.h" > > + > > +/* > > + * DPHY testcode used to configure Rx DPHY > > + */ > > + > > +/* System configuration 0 */ > > +#define DPHY_RX_SYS_0 0x01 > > +#define HSFREQRANGE_OVR_EN_RW BIT(5) > > + > > +/* System configuration 1 */ > > +#define DPHY_RX_SYS_1 0x02 > > +#define HSFREQRANGE_OVR_RW(x) ((x) & 0x7F) > > +#define TIMEBASE_OVR_EN_RW BIT(7) > > + > > +/* System configuration 2 */ > > +#define DPHY_RX_SYS_2 0x03 > > +#define TIMEBASE_OVR_RW(x) ((x) & 0xFF) > > + > > +struct dwc_csi_mbps_reg { > > + u16 mbps; > > + u16 reg; > > +}; > > + > > +/* > > + * Bit rate to high speed frequency range map table > > + */ > > +static const struct dwc_csi_mbps_reg dwc_csi_mbps_table[] = { > > + { .mbps = 80, .reg = 0x00 }, > > + { .mbps = 90, .reg = 0x10 }, > > + { .mbps = 100, .reg = 0x20 }, > > + { .mbps = 110, .reg = 0x30 }, > > + { .mbps = 120, .reg = 0x01 }, > > + { .mbps = 130, .reg = 0x11 }, > > + { .mbps = 140, .reg = 0x21 }, > > + { .mbps = 150, .reg = 0x31 }, > > + { .mbps = 160, .reg = 0x02 }, > > + { .mbps = 170, .reg = 0x12 }, > > + { .mbps = 180, .reg = 0x22 }, > > + { .mbps = 190, .reg = 0x32 }, > > + { .mbps = 205, .reg = 0x03 }, > > + { .mbps = 220, .reg = 0x13 }, > > + { .mbps = 235, .reg = 0x23 }, > > + { .mbps = 250, .reg = 0x33 }, > > + { .mbps = 275, .reg = 0x04 }, > > + { .mbps = 300, .reg = 0x14 }, > > + { .mbps = 325, .reg = 0x25 }, > > + { .mbps = 350, .reg = 0x35 }, > > + { .mbps = 400, .reg = 0x05 }, > > + { .mbps = 450, .reg = 0x16 }, > > + { .mbps = 500, .reg = 0x26 }, > > + { .mbps = 550, .reg = 0x37 }, > > + { .mbps = 600, .reg = 0x07 }, > > + { .mbps = 650, .reg = 0x18 }, > > + { .mbps = 700, .reg = 0x28 }, > > + { .mbps = 750, .reg = 0x39 }, > > + { .mbps = 800, .reg = 0x09 }, > > + { .mbps = 850, .reg = 0x19 }, > > + { .mbps = 900, .reg = 0x29 }, > > + { .mbps = 950, .reg = 0x3a }, > > + { .mbps = 1000, .reg = 0x0a }, > > + { .mbps = 1050, .reg = 0x1a }, > > + { .mbps = 1100, .reg = 0x2a }, > > + { .mbps = 1150, .reg = 0x3b }, > > + { .mbps = 1200, .reg = 0x0b }, > > + { .mbps = 1250, .reg = 0x1b }, > > + { .mbps = 1300, .reg = 0x2b }, > > + { .mbps = 1350, .reg = 0x3c }, > > + { .mbps = 1400, .reg = 0x0c }, > > + { .mbps = 1450, .reg = 0x1c }, > > + { .mbps = 1500, .reg = 0x2c }, > > + { /* sentinel */ }, > > +}; > > + > > +static inline void dphy_rx_test_ctrl_set(struct dwc_csi_device *csidev, > > + u32 offset, u32 mask, u32 code) > > +{ > > + u32 val; > > + > > + val = dwc_csi_read(csidev, offset); > > + val &= ~(mask); > > + val |= code; > > + dwc_csi_write(csidev, offset, val); > > +} > > + > > +static inline void dphy_rx_test_ctrl_clr(struct dwc_csi_device *csidev, > > + u32 offset, u32 code) > > +{ > > + u32 val; > > + > > + val = dwc_csi_read(csidev, offset); > > + val &= ~(code); > > + dwc_csi_write(csidev, offset, val); > > +} > > + > > +static u8 dphy_rx_test_ctrl_get(struct dwc_csi_device *csidev, u32 offset) > > +{ > > + u32 val; > > + > > + val = dwc_csi_read(csidev, offset); > > + val = CSI2RX_DPHY_TEST_CTRL1_TEST_DOUT(val); > > + > > + return (u8)val; > > +} > > + > > +static void dphy_rx_write(struct dwc_csi_device *csidev, u8 addr, u8 value) > > +{ > > + u32 val; > > + > > + /* > > + * Set PHY_TST_CTRL1, bit[16] and write PHY_TST_CTRL1, > > + * bit[7:0] with test code address > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL1_TEST_EN; > > + val |= CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(addr); > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL1, 0x100ff, val); > > + > > + /* > > + * Set and clear PHY_TST_CTRL0, bit[1] > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN; > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL0, 0x2, val); > > + dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > + > > + /* > > + * Write PHY_TST_CTRL1, bit[7:0] with test code content > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(value); > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL1, 0xff, val); > > + > > + /* > > + * Clear PHY_TST_CTRL1, bit[16] > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL1_TEST_EN; > > + dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL1, val); > > + > > + /* > > + * Set and clear PHY_TST_CTRL0, bit[1] > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN; > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL0, 0x2, val); > > + dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > +} > > + > > +static int dphy_rx_read(struct dwc_csi_device *csidev, u8 addr) > > +{ > > + u32 val; > > + > > + /* > > + * Set PHY_TST_CTRL1, bit[16] and write PHY_TST_CTRL1, > > + * bit[7:0] with test code address > > + */ > > + val = CSI2RX_DPHY_TEST_CTRL1_TEST_EN; > > + val |= CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(addr); > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL1, 0x100ff, val); > > + > > + /* Set and clear PHY_TST_CTRL0, bit[1] */ > > + val = CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN; > > + dphy_rx_test_ctrl_set(csidev, CSI2RX_DPHY_TEST_CTRL0, 0x2, val); > > + dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > + > > + /* Read PHY_TST_CTRL1, bit[15:8] with the test code content */ > > + val = dphy_rx_test_ctrl_get(csidev, CSI2RX_DPHY_TEST_CTRL1); > > + > > + /* Clear PHY_TST_CTRL1, bit[16] */ > > + dphy_rx_test_ctrl_clr(csidev, CSI2RX_DPHY_TEST_CTRL1, > > + CSI2RX_DPHY_TEST_CTRL1_TEST_EN); > > + > > + return val; > > +} > > + > > +void dphy_rx_reset(struct dwc_csi_device *csidev) > > +{ > > + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0); > > + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0); > > + ndelay(15); > > + > > + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x1); > > + ndelay(5); > > + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x1); > > +} > > + > > +void dphy_rx_test_code_reset(struct dwc_csi_device *csidev) > > +{ > > + u32 val; > > + > > + /* Set PHY_TST_CTRL0, bit[0] */ > > + val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0); > > + val |= CSI2RX_DPHY_TEST_CTRL0_TEST_CLR; > > + dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > + > > + /* Clear PHY_TST_CTRL0, bit[0] */ > > + val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0); > > + val &= ~CSI2RX_DPHY_TEST_CTRL0_TEST_CLR; > > + dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > +} > > + > > +void dphy_rx_test_code_config(struct dwc_csi_device *csidev) > > +{ > > + u32 val; > > + u8 dphy_val; > > + > > + /* Set testclr=1'b0 */ > > + val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0); > > + val &= ~CSI2RX_DPHY_TEST_CTRL0_TEST_CLR; > > + dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val); > > + > > + /* Enable hsfreqrange_ovr_en and set hsfreqrange */ > > + dphy_rx_write(csidev, DPHY_RX_SYS_0, HSFREQRANGE_OVR_EN_RW); > > + dphy_rx_write(csidev, DPHY_RX_SYS_1, > > + HSFREQRANGE_OVR_RW(csidev->hsfreqrange)); > > + > > + /* Enable timebase_ovr_en */ > > + dphy_val = dphy_rx_read(csidev, DPHY_RX_SYS_1); > > + dphy_val |= TIMEBASE_OVR_EN_RW; > > + dphy_rx_write(csidev, DPHY_RX_SYS_1, dphy_val); > > + > > + /* Set cfgclkfreqrange */ > > + dphy_rx_write(csidev, DPHY_RX_SYS_2, > > + TIMEBASE_OVR_RW(csidev->cfgclkfreqrange + 0x44)); > > +} > > + > > +void dphy_rx_power_off(struct dwc_csi_device *csidev) > > +{ > > + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0); > > + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0); > > +} > > + > > +void dphy_rx_test_code_dump(struct dwc_csi_device *csidev) > > +{ > > +#define DPHY_DEBUG_REG(name) {name, #name} > > + static const struct { > > + u32 offset; > > + const char * const name; > > + } test_codes[] = { > > + DPHY_DEBUG_REG(DPHY_RX_SYS_0), > > + DPHY_DEBUG_REG(DPHY_RX_SYS_1), > > + DPHY_DEBUG_REG(DPHY_RX_SYS_2), > > + }; > > + unsigned int i; > > + > > + for (i = 0; i < ARRAY_SIZE(test_codes); i++) > > + dev_dbg(csidev->dev, "%14s[0x%02x]: 0x%02x\n", > > + test_codes[i].name, test_codes[i].offset, > > + dphy_rx_read(csidev, test_codes[i].offset)); > > +} > > + > > +int dphy_rx_get_hsfreqrange(struct dwc_csi_device *csidev) > > +{ > > + struct v4l2_subdev *source = csidev->sensor_sd; > > + const struct dwc_csi_mbps_reg *value; > > + const struct dwc_csi_mbps_reg *prev_value = NULL; > > + s64 link_freq; > > + u32 mbsp; > > + > > + link_freq = v4l2_get_link_freq(source->ctrl_handler, > > + csidev->csi_fmt->width, > > + csidev->bus.num_data_lanes * 2); > > + if (link_freq < 0) { > > + dev_err(csidev->dev, "Unable to obtain link frequency: %d\n", > > + (int)link_freq); > > + return link_freq; > > + } > > + > > + mbsp = link_freq * 2 / 1000000; > > + > > + if (mbsp < 80 || mbsp > 1500) { > > + dev_dbg(csidev->dev, "Out-of-bound lane rate %u\n", mbsp); > > + return -EINVAL; > > + } > > + > > + for (value = dwc_csi_mbps_table; value->mbps; value++) { > > + if (value->mbps >= mbsp) > > + break; > > + prev_value = value; > > + } > > + > > + if (prev_value && > > + ((mbsp - prev_value->mbps) <= (value->mbps - mbsp))) > > + value = prev_value; > > + > > + if (!value->mbps) { > > + dev_err(csidev->dev, "Unsupported PHY speed (%u Mbps)", mbsp); > > + return -ERANGE; > > + } > > + > > + csidev->hsfreqrange = value->reg; > > + > > + return 0; > > +} -- Regards, Laurent Pinchart