Add QCS615-specific DP PHY bring-up sequence, including AUX channel initialization, TX configuration, clock setup, and PHY calibration. These functions follow the QMP combo PHY flow and are integrated into the `qmp_phy_dp_cfg` structure to support full DP PHY mode enablement. Signed-off-by: Xiangxu Yin <xiangxu.yin@xxxxxxxxxxxxxxxx> --- drivers/phy/qualcomm/phy-qcom-qmp-dp-phy.h | 1 + drivers/phy/qualcomm/phy-qcom-qmp-usbc.c | 256 +++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-dp-phy.h b/drivers/phy/qualcomm/phy-qcom-qmp-dp-phy.h index 0ebd405bcaf0cac8215550bfc9b226f30cc43a59..59885616405f878885d0837838a0bac1899fb69f 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-dp-phy.h +++ b/drivers/phy/qualcomm/phy-qcom-qmp-dp-phy.h @@ -25,6 +25,7 @@ #define QSERDES_DP_PHY_AUX_CFG7 0x03c #define QSERDES_DP_PHY_AUX_CFG8 0x040 #define QSERDES_DP_PHY_AUX_CFG9 0x044 +#define QSERDES_DP_PHY_VCO_DIV 0x068 /* QSERDES COM_BIAS_EN_CLKBUFLR_EN bits */ # define QSERDES_V3_COM_BIAS_EN 0x0001 diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c index aefcc520ee0bb3dd116e58222e5e035d1d750714..13228a21644271567d4281169ff1c1f316465d81 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c @@ -645,6 +645,11 @@ static const struct qmp_phy_usb_cfg sdm660_usb3phy_cfg = { .regs = qmp_v3_usb3phy_regs_layout_qcm2290, }; +static void qcs615_qmp_dp_aux_init(struct qmp_usbc *qmp); +static void qcs615_qmp_configure_dp_tx(struct qmp_usbc *qmp); +static int qcs615_qmp_configure_dp_phy(struct qmp_usbc *qmp); +static int qcs615_qmp_calibrate_dp_phy(struct qmp_usbc *qmp); + static const struct qmp_phy_dp_cfg qcs615_dpphy_cfg = { .offsets = &qmp_usbc_dp_offsets_qcs615, @@ -663,6 +668,11 @@ static const struct qmp_phy_dp_cfg qcs615_dpphy_cfg = { .swing_tbl = &qmp_dp_voltage_swing_hbr2_rbr, .pre_emphasis_tbl = &qmp_dp_pre_emphasis_hbr2_rbr, + .dp_aux_init = qcs615_qmp_dp_aux_init, + .configure_dp_tx = qcs615_qmp_configure_dp_tx, + .configure_dp_phy = qcs615_qmp_configure_dp_phy, + .calibrate_dp_phy = qcs615_qmp_calibrate_dp_phy, + .vreg_list = qmp_phy_dp_vreg_l, .num_vregs = ARRAY_SIZE(qmp_phy_dp_vreg_l), }; @@ -778,6 +788,252 @@ static int qmp_usbc_generic_exit(struct phy *phy) return 0; } +static void qcs615_qmp_dp_aux_init(struct qmp_usbc *qmp) +{ + struct qmp_phy_dp_layout *layout = to_dp_layout(qmp); + + writel(DP_PHY_PD_CTL_AUX_PWRDN | + DP_PHY_PD_CTL_LANE_0_1_PWRDN | DP_PHY_PD_CTL_LANE_2_3_PWRDN | + DP_PHY_PD_CTL_PLL_PWRDN, + layout->dp_phy + QSERDES_DP_PHY_PD_CTL); + + writel(DP_PHY_PD_CTL_PWRDN | DP_PHY_PD_CTL_AUX_PWRDN | + DP_PHY_PD_CTL_LANE_0_1_PWRDN | DP_PHY_PD_CTL_LANE_2_3_PWRDN | + DP_PHY_PD_CTL_PLL_PWRDN, + layout->dp_phy + QSERDES_DP_PHY_PD_CTL); + + writel(0x00, layout->dp_phy + QSERDES_DP_PHY_AUX_CFG0); + writel(0x13, layout->dp_phy + QSERDES_DP_PHY_AUX_CFG1); + writel(0x00, layout->dp_phy + QSERDES_DP_PHY_AUX_CFG2); + writel(0x00, layout->dp_phy + QSERDES_DP_PHY_AUX_CFG3); + writel(0x0a, layout->dp_phy + QSERDES_DP_PHY_AUX_CFG4); + writel(0x26, layout->dp_phy + QSERDES_DP_PHY_AUX_CFG5); + writel(0x0a, layout->dp_phy + QSERDES_DP_PHY_AUX_CFG6); + writel(0x03, layout->dp_phy + QSERDES_DP_PHY_AUX_CFG7); + writel(0xbb, layout->dp_phy + QSERDES_DP_PHY_AUX_CFG8); + writel(0x03, layout->dp_phy + QSERDES_DP_PHY_AUX_CFG9); + layout->dp_aux_cfg = 0; + + writel(PHY_AUX_STOP_ERR_MASK | PHY_AUX_DEC_ERR_MASK | + PHY_AUX_SYNC_ERR_MASK | PHY_AUX_ALIGN_ERR_MASK | + PHY_AUX_REQ_ERR_MASK, + layout->dp_phy + QSERDES_V3_DP_PHY_AUX_INTERRUPT_MASK); +} + +static int qcs615_qmp_configure_dp_swing(struct qmp_usbc *qmp) +{ + struct qmp_phy_dp_layout *layout = to_dp_layout(qmp); + struct qmp_phy_dp_cfg *cfg = to_dp_cfg(qmp); + const struct phy_configure_opts_dp *dp_opts = &layout->dp_opts; + void __iomem *tx = layout->dp_tx; + void __iomem *tx2 = layout->dp_tx2; + unsigned int v_level = 0, p_level = 0; + u8 voltage_swing_cfg, pre_emphasis_cfg; + int i; + + if (dp_opts->lanes > 4) { + dev_err(qmp->dev, "Invalid lane_num(%d)\n", dp_opts->lanes); + return -EINVAL; + } + + for (i = 0; i < dp_opts->lanes; i++) { + v_level = max(v_level, dp_opts->voltage[i]); + p_level = max(p_level, dp_opts->pre[i]); + } + + if (v_level > 4 || p_level > 4) { + dev_err(qmp->dev, "Invalid v(%d) | p(%d) level)\n", + v_level, p_level); + return -EINVAL; + } + + voltage_swing_cfg = (*cfg->swing_tbl)[v_level][p_level]; + pre_emphasis_cfg = (*cfg->pre_emphasis_tbl)[v_level][p_level]; + + voltage_swing_cfg |= DP_PHY_TXn_TX_DRV_LVL_MUX_EN; + pre_emphasis_cfg |= DP_PHY_TXn_TX_EMP_POST1_LVL_MUX_EN; + + if (voltage_swing_cfg == 0xff && pre_emphasis_cfg == 0xff) + return -EINVAL; + + writel(voltage_swing_cfg, tx + QSERDES_V3_TX_TX_DRV_LVL); + writel(pre_emphasis_cfg, tx + QSERDES_V3_TX_TX_EMP_POST1_LVL); + writel(voltage_swing_cfg, tx2 + QSERDES_V3_TX_TX_DRV_LVL); + writel(pre_emphasis_cfg, tx2 + QSERDES_V3_TX_TX_EMP_POST1_LVL); + + return 0; +} + +static void qmp_usbc_configure_dp_mode(struct qmp_usbc *qmp) +{ + struct qmp_phy_dp_layout *layout = to_dp_layout(qmp); + bool reverse = (qmp->orientation == TYPEC_ORIENTATION_REVERSE); + u32 val; + + val = DP_PHY_PD_CTL_PWRDN | DP_PHY_PD_CTL_AUX_PWRDN | + DP_PHY_PD_CTL_PLL_PWRDN | DP_PHY_PD_CTL_LANE_0_1_PWRDN | DP_PHY_PD_CTL_LANE_2_3_PWRDN; + + writel(val, layout->dp_phy + QSERDES_DP_PHY_PD_CTL); + + if (reverse) + writel(0xc9, layout->dp_phy + QSERDES_DP_PHY_MODE); + else + writel(0xd9, layout->dp_phy + QSERDES_DP_PHY_MODE); +} + +static int qmp_usbc_configure_dp_clocks(struct qmp_usbc *qmp) +{ + struct qmp_phy_dp_layout *layout = to_dp_layout(qmp); + const struct phy_configure_opts_dp *dp_opts = &layout->dp_opts; + u32 phy_vco_div; + unsigned long pixel_freq; + + switch (dp_opts->link_rate) { + case 1620: + phy_vco_div = 0x1; + pixel_freq = 1620000000UL / 2; + break; + case 2700: + phy_vco_div = 0x1; + pixel_freq = 2700000000UL / 2; + break; + case 5400: + phy_vco_div = 0x2; + pixel_freq = 5400000000UL / 4; + break; + default: + dev_err(qmp->dev, "link rate:%d not supported\n", dp_opts->link_rate); + return -EINVAL; + } + writel(phy_vco_div, layout->dp_phy + QSERDES_DP_PHY_VCO_DIV); + + clk_set_rate(layout->dp_link_hw.clk, dp_opts->link_rate * 100000); + clk_set_rate(layout->dp_pixel_hw.clk, pixel_freq); + + return 0; +} + +static void qcs615_qmp_configure_dp_tx(struct qmp_usbc *qmp) +{ + struct qmp_phy_dp_layout *layout = to_dp_layout(qmp); + void __iomem *tx = layout->dp_tx; + void __iomem *tx2 = layout->dp_tx2; + + /* program default setting first */ + writel(0x2a, tx + QSERDES_V3_TX_TX_DRV_LVL); + writel(0x20, tx + QSERDES_V3_TX_TX_EMP_POST1_LVL); + writel(0x2a, tx2 + QSERDES_V3_TX_TX_DRV_LVL); + writel(0x20, tx2 + QSERDES_V3_TX_TX_EMP_POST1_LVL); + + qcs615_qmp_configure_dp_swing(qmp); +} + +static int qcs615_qmp_configure_dp_phy(struct qmp_usbc *qmp) +{ + struct qmp_phy_dp_layout *layout = to_dp_layout(qmp); + u32 status; + int ret; + + qmp_usbc_configure_dp_mode(qmp); + + writel(0x05, layout->dp_phy + QSERDES_V3_DP_PHY_TX0_TX1_LANE_CTL); + writel(0x05, layout->dp_phy + QSERDES_V3_DP_PHY_TX2_TX3_LANE_CTL); + + ret = qmp_usbc_configure_dp_clocks(qmp); + if (ret) + return ret; + + writel(0x01, layout->dp_phy + QSERDES_DP_PHY_CFG); + writel(0x05, layout->dp_phy + QSERDES_DP_PHY_CFG); + writel(0x01, layout->dp_phy + QSERDES_DP_PHY_CFG); + writel(0x09, layout->dp_phy + QSERDES_DP_PHY_CFG); + + writel(0x20, layout->dp_serdes + QSERDES_COM_RESETSM_CNTRL); + + if (readl_poll_timeout(layout->dp_serdes + QSERDES_COM_C_READY_STATUS, + status, + ((status & BIT(0)) > 0), + 500, + 10000)) { + dev_err(qmp->dev, "C_READY not ready\n"); + return -ETIMEDOUT; + } + + if (readl_poll_timeout(layout->dp_serdes + QSERDES_COM_CMN_STATUS, + status, + ((status & BIT(0)) > 0), + 500, + 10000)){ + dev_err(qmp->dev, "FREQ_DONE not ready\n"); + return -ETIMEDOUT; + } + + if (readl_poll_timeout(layout->dp_serdes + QSERDES_COM_CMN_STATUS, + status, + ((status & BIT(1)) > 0), + 500, + 10000)){ + dev_err(qmp->dev, "PLL_LOCKED not ready\n"); + return -ETIMEDOUT; + } + + writel(0x19, layout->dp_phy + QSERDES_DP_PHY_CFG); + + if (readl_poll_timeout(layout->dp_phy + QSERDES_V3_DP_PHY_STATUS, + status, + ((status & BIT(0)) > 0), + 500, + 10000)){ + dev_err(qmp->dev, "TSYNC_DONE not ready\n"); + return -ETIMEDOUT; + } + + if (readl_poll_timeout(layout->dp_phy + QSERDES_V3_DP_PHY_STATUS, + status, + ((status & BIT(1)) > 0), + 500, + 10000)){ + dev_err(qmp->dev, "PHY_READY not ready\n"); + return -ETIMEDOUT; + } + + writel(0x3f, layout->dp_tx + QSERDES_V3_TX_TRANSCEIVER_BIAS_EN); + writel(0x10, layout->dp_tx + QSERDES_V3_TX_HIGHZ_DRVR_EN); + writel(0x0a, layout->dp_tx + QSERDES_V3_TX_TX_POL_INV); + writel(0x3f, layout->dp_tx2 + QSERDES_V3_TX_TRANSCEIVER_BIAS_EN); + writel(0x10, layout->dp_tx2 + QSERDES_V3_TX_HIGHZ_DRVR_EN); + writel(0x0a, layout->dp_tx2 + QSERDES_V3_TX_TX_POL_INV); + + writel(0x18, layout->dp_phy + QSERDES_DP_PHY_CFG); + writel(0x19, layout->dp_phy + QSERDES_DP_PHY_CFG); + + if (readl_poll_timeout(layout->dp_phy + QSERDES_V3_DP_PHY_STATUS, + status, + ((status & BIT(1)) > 0), + 500, + 10000)){ + dev_err(qmp->dev, "PHY_READY not ready\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int qcs615_qmp_calibrate_dp_phy(struct qmp_usbc *qmp) +{ + static const u8 cfg1_settings[] = {0x13, 0x23, 0x1d}; + struct qmp_phy_dp_layout *layout = to_dp_layout(qmp); + u8 val; + + layout->dp_aux_cfg++; + layout->dp_aux_cfg %= ARRAY_SIZE(cfg1_settings); + val = cfg1_settings[layout->dp_aux_cfg]; + + writel(val, layout->dp_phy + QSERDES_DP_PHY_AUX_CFG1); + + return 0; +} + static int qmp_usbc_usb_power_on(struct phy *phy) { struct qmp_usbc *qmp = phy_get_drvdata(phy); -- 2.34.1