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