From: Alexander Stein <alexander.stein@ew.tq-group.com>
To: linux-media@vger.kernel.org, devicetree@vger.kernel.org,
linux-imx@nxp.com, 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
Date: Tue, 04 Jul 2023 11:23:11 +0200 [thread overview]
Message-ID: <10303134.nUPlyArG6x@steina-w> (raw)
In-Reply-To: <20230703113734.762307-3-guoniu.zhou@oss.nxp.com>
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
--
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/
next prev parent reply other threads:[~2023-07-04 9:24 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-07-03 11:37 [PATCH 0/2] media: nxp: add i.MX93 MIPI CSI-2 support guoniu.zhou
2023-07-03 11:37 ` [PATCH 1/2] media: dt-bindings: Add binding doc for i.MX93 MIPI CSI-2 guoniu.zhou
2023-07-04 8:38 ` Alexander Stein
2023-07-05 1:36 ` G.N. Zhou (OSS)
2023-07-05 21:23 ` Laurent Pinchart
2023-07-06 10:08 ` G.N. Zhou (OSS)
2023-07-04 16:53 ` Conor Dooley
2023-07-05 1:30 ` G.N. Zhou (OSS)
2023-07-05 21:08 ` Conor Dooley
2023-07-03 11:37 ` [PATCH 2/2] media: nxp: add driver for i.MX93 MIPI CSI-2 controller and D-PHY guoniu.zhou
2023-07-04 9:23 ` Alexander Stein [this message]
2023-07-05 3:52 ` G.N. Zhou (OSS)
2023-07-05 6:50 ` Alexander Stein
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=10303134.nUPlyArG6x@steina-w \
--to=alexander.stein@ew.tq-group.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=guoniu.zhou@oss.nxp.com \
--cc=jacopo.mondi@ideasonboard.com \
--cc=krzysztof.kozlowski+dt@linaro.org \
--cc=laurent.pinchart@ideasonboard.com \
--cc=linux-imx@nxp.com \
--cc=linux-media@vger.kernel.org \
--cc=mchehab@kernel.org \
--cc=robh+dt@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.