[2/2] media: nxp: add driver for i.MX93 MIPI CSI-2 controller and D-PHY
Commit Message
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(+)
Comments
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
@@ -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
@@ -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
@@ -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
new file mode 100644
@@ -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);
new file mode 100644
@@ -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__ */
new file mode 100644
@@ -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));
+}