From 4be88c2606ce2d979798f1fb96924ea475ce9c67 Mon Sep 17 00:00:00 2001 From: syafiqnizam25 Date: Sat, 22 Aug 2026 13:57:35 +0800 Subject: [PATCH] media: ipu7: isys: enable C-PHY port A+B aggregation On IPU7P5 a CSI-2 port with more than two lanes is built by aggregating SoC ports A and B, see ipu7_isys_csi_phy_powerup(): if (!is_ipu7(hw_ver) && lanes > 2 && id == PORT_A) { aggregation = true; lanes = 2; } The trigger is PHY-mode agnostic and the port B gpregs are programmed for both modes, but only ipu7_isys_dphy_config() acts on the aggregation flag. ipu7_isys_cphy_config() accepts the same argument and never reads it, so the two PHY macros are never linked: port B is brought up as an unrelated standalone PHY while port A's CSI-2 controller waits for lanes that never arrive coherently. The result is a permanently corrupt stream. Mirror the D-PHY sequence in the C-PHY path: - Set the macro link bit CORE_DIG_RW_COMMON_0[1] on both ports and program the AFE lane forwarding bits in AFE_LANEx_CTRL_2_15[4:3]. D-PHY forces its clock-lane AFE (LANE1) to follower mode on port B; C-PHY has no clock lane, so all AFE lanes carry trio wires on both ports and none is a follower. - Split the trio count across the aggregated macro. A 4-lane D-PHY port is 5 wire pairs; regrouped as C-PHY that is 3 trios (9 of 10 wires), which is also why ipu7_isys_cphy_config() already reports trios = 3 for the native 4-lane port on full IPU7. Port A's three pairs carry trios 0-1 and port B's two pairs carry trio 2. - Propagate the rext calibration result to the secondary port. Only port A runs the calibration (ipu7_isys_phy_ready() returns early for id != 0), so without this port B has no valid termination trim. The D-PHY path already does this; the C-PHY path was missing it. The sensor side must advertise a matching trio count, i.e. three data lanes on the endpoint feeding port A. Signed-off-by: syafiqnizam25 --- .../media/pci/intel/ipu7/ipu7-isys-csi-phy.c | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c b/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c index a56aeec..52288bf 100644 --- a/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c +++ b/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c @@ -748,6 +748,13 @@ static void ipu7_isys_cphy_config(struct ipu7_isys *isys, u8 id, u8 lanes, if (is_ipu7(isys->adev->isp->hw_ver)) trios = 3; + /* + * An aggregated port A+B is a single 5-pair macro carrying 3 trios: + * port A's three pairs hold trios 0-1, port B's two hold trio 2. + */ + if (aggregation && id != PORT_A) + trios = 1; + dwc_phy_write_mask(isys, id, CORE_DIG_RW_COMMON_7, val, 0, 9); dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_DPHY_7, 104, 0, 7); dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_DPHY_8, 16, 0, 7); @@ -873,6 +880,27 @@ static void ipu7_isys_cphy_config(struct ipu7_isys *isys, u8 id, u8 lanes, reg = CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_7 + 0x400 * i; dwc_phy_write_mask(isys, id, reg, cap_prog, 10, 12); } + + if (aggregation) { + dwc_phy_write_mask(isys, id, CORE_DIG_RW_COMMON_0, 1, 1, 1); + + /* + * C-PHY has no clock lane, so unlike D-PHY no AFE lane is the + * shared clock that has to follow port A. + */ + for (i = 0; i < (lanes + 1); i++) { + reg = CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_15 + 0x400 * i; + dwc_phy_write_mask(isys, id, reg, 3, 3, 4); + } + } + + /* Only port A runs rext calibration; other ports reuse its result. */ + if (isys->phy_rext_cal && id) { + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_8, + isys->phy_rext_cal, 0, 3); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_7, + 1, 11, 11); + } } static int ipu7_isys_phy_config(struct ipu7_isys *isys, u8 id, u8 lanes,