[PATCH 2/2] phy: rockchip: samsung-hdptx: Add support for FRL TxFFE level control

Cristian Ciocaltea
2026-03-28T13:55:22Z

During HDMI 2.1 FRL link training, the source may need to incrementally
raise the TxFFE level in response to persistent link failures reported
by the sink during LTS3. The phy_configure_opts_hdmi struct now carries
ffe_level and set_ffe_level fields to convey such an update
independently of a full rate reconfiguration.

Wire up the optional TxFFE control in the Samsung HDPTX PHY driver.

Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 74 +++++++++++++++++++++--
1 file changed, 69 insertions(+), 5 deletions(-)

diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 3bde7fbb34b1..c4669853ad0e 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -333,6 +333,7 @@
#define FRL_3G3L_RATE 900000000
#define FRL_6G3L_RATE 1800000000
#define FRL_8G4L_RATE 3200000000
+#define FRL_FFE_MAX_LEVEL 3

enum dp_link_rate {
DP_BW_RBR,
@@ -466,6 +467,16 @@ static const struct ropll_config rk_hdptx_tmds_ropll_cfg[] = {
{ 25175000ULL, 84, 84, 1, 1, 15, 1, 168, 1, 16, 4, 1, 1, },
};

+static const struct ffe_config {
+ u8 pre_shoot;
+ u8 de_emphasis;
+} rk_hdptx_frl_ffe_cfg[FRL_FFE_MAX_LEVEL + 1] = {
+ { 0x3, 0x4 },
+ { 0x3, 0x6 },
+ { 0x3, 0x8 },
+ { 0x3, 0x9 },
+};
+
static const struct reg_sequence rk_hdptx_common_cmn_init_seq[] = {
REG_SEQ0(CMN_REG(0009), 0x0c),
REG_SEQ0(CMN_REG(000a), 0x83),
@@ -1321,6 +1332,45 @@ static int rk_hdptx_tmds_ropll_mode_config(struct rk_hdptx_phy *hdptx)
return rk_hdptx_post_enable_lane(hdptx);
}

+static int rk_hdptx_frl_ffe_config(struct rk_hdptx_phy *hdptx, u8 ffe_level)
+{
+ u8 val;
+
+ if (ffe_level > FRL_FFE_MAX_LEVEL)
+ return -EINVAL;
+
+ val = rk_hdptx_frl_ffe_cfg[ffe_level].pre_shoot;
+
+ regmap_update_bits(hdptx->regmap, LANE_REG(0305),
+ LN_TX_DRV_PRE_LVL_CTRL_MASK,
+ FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0405),
+ LN_TX_DRV_PRE_LVL_CTRL_MASK,
+ FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0505),
+ LN_TX_DRV_PRE_LVL_CTRL_MASK,
+ FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0605),
+ LN_TX_DRV_PRE_LVL_CTRL_MASK,
+ FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK, val));
+
+ val = rk_hdptx_frl_ffe_cfg[ffe_level].de_emphasis;
+
+ regmap_update_bits(hdptx->regmap, LANE_REG(0304),
+ LN_TX_DRV_POST_LVL_CTRL_MASK,
+ FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0404),
+ LN_TX_DRV_POST_LVL_CTRL_MASK,
+ FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0504),
+ LN_TX_DRV_POST_LVL_CTRL_MASK,
+ FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0604),
+ LN_TX_DRV_POST_LVL_CTRL_MASK,
+ FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK, val));
+ return 0;
+}
+
static void rk_hdptx_dp_reset(struct rk_hdptx_phy *hdptx)
{
reset_control_assert(hdptx->rsts[RST_LANE].rstc);
@@ -1730,6 +1780,13 @@ static int rk_hdptx_phy_verify_hdmi_config(struct rk_hdptx_phy *hdptx,
unsigned long long frl_rate = 100000000ULL * hdmi_in->frl.lanes *
hdmi_in->frl.rate_per_lane;

+ if (hdmi_in->frl.set_ffe_level) {
+ if (hdmi_in->frl.ffe_level > FRL_FFE_MAX_LEVEL)
+ return -EINVAL;
+
+ return 0;
+ }
+
switch (hdmi_in->frl.rate_per_lane) {
case 3:
case 6:
@@ -2076,11 +2133,18 @@ static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opt
if (ret) {
dev_err(hdptx->dev, "invalid hdmi params for phy configure\n");
} else {
- hdptx->pll_config_dirty = true;
-
- dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__,
- hdptx->hdmi_cfg.mode ? "FRL" : "TMDS",
- hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc);
+ if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL &&
+ opts->hdmi.frl.set_ffe_level) {
+ dev_dbg(hdptx->dev, "%s ffe_level=%u\n", __func__,
+ opts->hdmi.frl.ffe_level);
+ ret = rk_hdptx_frl_ffe_config(hdptx, opts->hdmi.frl.ffe_level);
+ } else {
+ hdptx->pll_config_dirty = true;
+
+ dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__,
+ hdptx->hdmi_cfg.mode ? "FRL" : "TMDS",
+ hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc);
+ }
}

return ret;

--
2.52.0