Re: [PATCH 6/6] media: synopsys: add i.MX93 support
From: Michael Riesch <michael.riesch@collabora.com>
Date: 2026-02-13 10:22:55
Also in:
imx, linux-devicetree, linux-media, linux-rockchip, lkml
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.
quoted hunk ↗ jump to hunk
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@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.
quoted hunk ↗ jump to hunk
+ + 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.
quoted hunk ↗ jump to hunk
} 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);