[PATCH 6/6] media: synopsys: add i.MX93 support

Michael Riesch michael.riesch at collabora.com
Fri Feb 13 02:22:52 PST 2026


Hi Frank,

On 2/10/26 18:11, Frank Li wrote:
> The i.MX93 uses a newer version of the DW CSI-2 controller with a changed
> register layout and an added IPI block.

What does this IPI block do? Please add a short description.

> 
> The reset flow also differs, so add the .assert_reset(), .deassert_reset(),
> and .ipi_enable() callbacks to support it.
> 
> Signed-off-by: Frank Li <Frank.Li at nxp.com>
> ---
>  drivers/media/platform/synopsys/dw-mipi-csi2rx.c | 163 ++++++++++++++++++++++-
>  1 file changed, 156 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c
> index 6a2966c9e3a2eac661fa1f8610c9f021d6e26cf8..5dc55b59d6aeed4b6cb207c8e2ebe0fb3c462644 100644
> --- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c
> +++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c
> @@ -33,10 +33,24 @@ struct dw_mipi_csi2rx_regs {
>  	u32 msk1;
>  	u32 msk2;
>  	u32 control;
> +	u32 phy_tst_ctrl0;
> +	u32 phy_tst_ctrl1;
> +	u32 dphy_rstz;
> +	u32 phy_shutdownz;
> +	u32 ipi_softrstn;
> +	u32 ipi_datatype;
> +	u32 ipi_vcid;
> +	u32 ipi_mode;
> +	u32 ipi_mem_flush;
>  };
>  
> +struct dw_mipi_csi2rx_device;
> +
>  struct dw_mipi_csi2rx_drvdata {
>  	const struct dw_mipi_csi2rx_regs *regs;
> +	void (*dphy_assert_reset)(struct dw_mipi_csi2rx_device *csi2);
> +	void (*dphy_deassert_reset)(struct dw_mipi_csi2rx_device *csi2);
> +	void (*ipi_enable)(struct dw_mipi_csi2rx_device *csi2);
>  };
>  
>  /* Help check wrong access unexisted register at difference IP version */
> @@ -54,6 +68,21 @@ static const struct dw_mipi_csi2rx_regs rk3568_regs = {
>  	.control = DW_REG(0x40),
>  };
>  
> +static const struct dw_mipi_csi2rx_regs imx93_regs = {
> +	.n_lanes = DW_REG(0x4),
> +	.resetn = DW_REG(0x8),
> +	.phy_shutdownz = DW_REG(0x40),
> +	.dphy_rstz = DW_REG(0x44),
> +	.phy_state = DW_REG(0x48),
> +	.phy_tst_ctrl0 = DW_REG(0x50),
> +	.phy_tst_ctrl1 = DW_REG(0x54),
> +	.ipi_mode = DW_REG(0x80),
> +	.ipi_vcid = DW_REG(0x84),
> +	.ipi_datatype = DW_REG(0x88),
> +	.ipi_mem_flush = DW_REG(0x8c),
> +	.ipi_softrstn = DW_REG(0xa0),
> +};
> +
>  static const struct dw_mipi_csi2rx_drvdata rk3568_drvdata = {
>  	.regs = &rk3568_regs,
>  };
> @@ -302,14 +331,30 @@ static int dw_mipi_csi2rx_start(struct dw_mipi_csi2rx_device *csi2)
>  		return -EINVAL;
>  	}
>  
> +	dw_mipi_csi2rx_write(csi2, resetn, 0);
> +
> +	if (csi2->drvdata->dphy_assert_reset)
> +		csi2->drvdata->dphy_assert_reset(csi2);
> +
>  	control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) |
>  		   SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03);
>  
>  	dw_mipi_csi2rx_write(csi2, n_lanes, lanes - 1);
> -	dw_mipi_csi2rx_write(csi2, control, control);
> +
> +	if (dw_reg_exist(csi2, control))

Why? The _write functions check for existance anyway, right?

> +		dw_mipi_csi2rx_write(csi2, control, control);
> +
> +	ret = phy_power_on(csi2->phy);

Does it make sense to continue if phy_power_on fails? Check the return
value and return early.

> +
> +	if (csi2->drvdata->dphy_deassert_reset)
> +		csi2->drvdata->dphy_deassert_reset(csi2);
> +
>  	dw_mipi_csi2rx_write(csi2, resetn, 1);
>  
> -	return phy_power_on(csi2->phy);
> +	if (csi2->drvdata->ipi_enable)
> +		csi2->drvdata->ipi_enable(csi2);
> +
> +	return ret;
>  }
>  
>  static void dw_mipi_csi2rx_stop(struct dw_mipi_csi2rx_device *csi2)
> @@ -317,8 +362,12 @@ static void dw_mipi_csi2rx_stop(struct dw_mipi_csi2rx_device *csi2)
>  	phy_power_off(csi2->phy);
>  
>  	dw_mipi_csi2rx_write(csi2, resetn, 0);
> -	dw_mipi_csi2rx_write(csi2, msk1, ~0);
> -	dw_mipi_csi2rx_write(csi2, msk2, ~0);
> +
> +	if (dw_reg_exist(csi2, msk1))
> +		dw_mipi_csi2rx_write(csi2, msk1, ~0);
> +
> +	if (dw_reg_exist(csi2, msk2))
> +		dw_mipi_csi2rx_write(csi2, msk2, ~0);

Again, the dw_reg_exist checks seem superfluous.

>  }
>  
>  static const struct media_entity_operations dw_mipi_csi2rx_media_ops = {
> @@ -670,10 +719,110 @@ static void dw_mipi_csi2rx_unregister(struct dw_mipi_csi2rx_device *csi2)
>  	v4l2_async_nf_cleanup(&csi2->notifier);
>  }
>  
> +#define DPHY_TEST_CTRL0_TEST_CLR	BIT(0)

All #defines at the top, as already pointed out before. Holds for all
other bit defines, too.

> +
> +static void imx93_csi2rx_dphy_assert_reset(struct dw_mipi_csi2rx_device *csi2)
> +{
> +	u32 val;
> +
> +	/* Release synopsis DPHY test codes from reset */
> +	dw_mipi_csi2rx_write(csi2, dphy_rstz, 0);
> +	dw_mipi_csi2rx_write(csi2, phy_shutdownz, 0);
> +
> +	val = dw_mipi_csi2rx_read(csi2, phy_tst_ctrl0);
> +	val &= ~DPHY_TEST_CTRL0_TEST_CLR;
> +	dw_mipi_csi2rx_write(csi2, phy_tst_ctrl0, val);
> +
> +	/*
> +	 * ndelay is not necessary have MMIO operation, need dummy read to make
> +	 * sure above write reach target.
> +	 */
> +	val = dw_mipi_csi2rx_read(csi2, phy_tst_ctrl0);
> +	/* Wait for at least 15ns */
> +	ndelay(15);
> +	val |= DPHY_TEST_CTRL0_TEST_CLR;
> +	dw_mipi_csi2rx_write(csi2, phy_tst_ctrl0, val);
> +}
> +
> +#define CSI2RX_DPHY_STOPSTATE_CLK_LANE BIT(16)
> +
> +static void imx93_csi2rx_dphy_deassert_reset(struct dw_mipi_csi2rx_device *csi2)
> +{
> +	/* Release PHY from reset */
> +	dw_mipi_csi2rx_write(csi2, phy_shutdownz, 0x1);
> +	/*
> +	 * ndelay is not necessary have MMIO operation, need dummy read to make
> +	 * sure above write reach target.
> +	 */
> +	dw_mipi_csi2rx_read(csi2, phy_shutdownz);
> +	ndelay(5);
> +	dw_mipi_csi2rx_write(csi2, dphy_rstz, 0x1);
> +
> +	dw_mipi_csi2rx_read(csi2, dphy_rstz);
> +	ndelay(5);
> +}
> +
> +#define IPI_VCID_VC(x)			FIELD_PREP(GENMASK(1, 0), (x))
> +#define IPI_VCID_VC_0_1(x)		FIELD_PREP(GENMASK(3, 2), (x))
> +#define IPI_VCID_VC_2			BIT(4)
> +
> +#define IPI_DATA_TYPE_DT(x)		FIELD_PREP(GENMASK(5, 0), (x))
> +#define IPI_DATA_TYPE_EMB_DATA_EN	BIT(8)
> +
> +#define IPI_MODE_CONTROLLER		BIT(1)
> +#define IPI_MODE_COLOR_MODE16		BIT(8)
> +#define IPI_MODE_CUT_THROUGH		BIT(16)
> +#define IPI_MODE_ENABLE			BIT(24)
> +
> +#define IPI_MEM_FLUSH_AUTO		BIT(8)
> +
> +static void imx93_csi2rx_dphy_ipi_enable(struct dw_mipi_csi2rx_device *csi2)
> +{
> +	int dt = csi2->formats->csi_dt;
> +	u32 val;
> +
> +	/* Do IPI soft reset */
> +	dw_mipi_csi2rx_write(csi2, ipi_softrstn, 0x0);
> +	dw_mipi_csi2rx_write(csi2, ipi_softrstn, 0x1);
> +
> +	/* Select virtual channel and data type to be processed by IPI */
> +	val = IPI_DATA_TYPE_DT(dt);
> +	dw_mipi_csi2rx_write(csi2, ipi_datatype, val);
> +
> +	/* Set virtual channel 0 as default */
> +	val  = IPI_VCID_VC(0);
> +	dw_mipi_csi2rx_write(csi2, ipi_vcid, val);
> +
> +	/*
> +	 * Select IPI camera timing mode and allow the pixel stream
> +	 * to be non-continuous when pixel interface FIFO is empty
> +	 */
> +	val = dw_mipi_csi2rx_read(csi2, ipi_mode);
> +	val &= ~IPI_MODE_CONTROLLER;
> +	val &= ~IPI_MODE_COLOR_MODE16;
> +	val |= IPI_MODE_CUT_THROUGH;
> +	dw_mipi_csi2rx_write(csi2, ipi_mode, val);
> +
> +	/* Memory is automatically flushed at each Frame Start */
> +	val = IPI_MEM_FLUSH_AUTO;
> +	dw_mipi_csi2rx_write(csi2, ipi_mem_flush, val);
> +
> +	/* Enable IPI */
> +	val = dw_mipi_csi2rx_read(csi2, ipi_mode);
> +	val |= IPI_MODE_ENABLE;
> +	dw_mipi_csi2rx_write(csi2, ipi_mode, val);
> +}
> +
> +static const struct dw_mipi_csi2rx_drvdata imx93_drvdata = {
> +	.regs = &imx93_regs,
> +	.dphy_assert_reset = imx93_csi2rx_dphy_assert_reset,
> +	.dphy_deassert_reset = imx93_csi2rx_dphy_deassert_reset,
> +	.ipi_enable = imx93_csi2rx_dphy_ipi_enable,
> +};
> +
>  static const struct of_device_id dw_mipi_csi2rx_of_match[] = {
> -	{
> -		.compatible = "rockchip,rk3568-mipi-csi2", .data = &rk3568_drvdata,
> -	},
> +	{ .compatible = "fsl,imx93-mipi-csi2", .data = &imx93_drvdata, },

As pointed out before,
	{
		.compatible = "fsl,imx93-mipi-csi2",
		.data = &imx93_drvdata,
	},

Best regards,
Michael

> +	{ .compatible = "rockchip,rk3568-mipi-csi2", .data = &rk3568_drvdata, },
>  	{}
>  };
>  MODULE_DEVICE_TABLE(of, dw_mipi_csi2rx_of_match);
> 




More information about the Linux-rockchip mailing list