Files
LibreELEC.tv/packages/linux/patches/rockchip/rockchip-0106-WIP-FRL-drm-rockchip-dw_hdmi_qp-Add-HDMI-2.1-FRL-sup.patch
Christian Hewitt 5b2b97c29c linux: update rockchip to Linux 6.17-rc6
Signed-off-by: Christian Hewitt <christianshewitt@gmail.com>
2025-09-16 15:18:29 +00:00

370 lines
12 KiB
Diff

From fd0b25ea4aa5b8e6f4dac412beb261a83a994104 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Thu, 3 Jul 2025 12:44:04 +0300
Subject: [PATCH 106/110] [WIP-FRL] drm/rockchip: dw_hdmi_qp: Add HDMI 2.1 FRL
support
Extend ->enc_init() hooks of {rk3576,rk3588}_hdmi_ctrl_ops to enable
HDMI 2.1 FRL operation mode.
Additionally, add dw_hdmi_qp_rockchip_link_setup() helper to handle PHY
configuration when switching between TMDS and FRL.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 243 ++++++++++++++++--
1 file changed, 215 insertions(+), 28 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
index c18c0a9bf06c..69e9a76d4656 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
@@ -64,12 +64,17 @@
#define RK3588_HDMI0_HPD_INT_CLR BIT(12)
#define RK3588_HDMI1_HPD_INT_MSK BIT(15)
#define RK3588_HDMI1_HPD_INT_CLR BIT(14)
+
#define RK3588_GRF_SOC_CON7 0x031c
#define RK3588_SET_HPD_PATH_MASK GENMASK(13, 12)
#define RK3588_GRF_SOC_STATUS1 0x0384
#define RK3588_HDMI0_LEVEL_INT BIT(16)
#define RK3588_HDMI1_LEVEL_INT BIT(24)
+
#define RK3588_GRF_VO1_CON3 0x000c
+#define RK3588_GRF_VO1_CON4 0x0010
+#define RK3588_HDMI21_MASK BIT(0)
+
#define RK3588_GRF_VO1_CON6 0x0018
#define RK3588_COLOR_DEPTH_MASK GENMASK(7, 4)
#define RK3588_8BPC 0x0
@@ -81,6 +86,8 @@
#define RK3588_SDAIN_MASK BIT(10)
#define RK3588_MODE_MASK BIT(11)
#define RK3588_I2S_SEL_MASK BIT(13)
+
+#define RK3588_GRF_VO1_CON7 0x001c
#define RK3588_GRF_VO1_CON9 0x0024
#define RK3588_HDMI0_GRANT_SEL BIT(10)
#define RK3588_HDMI1_GRANT_SEL BIT(12)
@@ -88,6 +95,7 @@
#define HIWORD_UPDATE(val, mask) ((val) | (mask) << 16)
#define HOTPLUG_DEBOUNCE_MS 150
#define MAX_HDMI_PORT_NUM 2
+#define HDMI20_MAX_TMDSRATE 600000000
struct rockchip_hdmi_qp {
struct device *dev;
@@ -96,6 +104,7 @@ struct rockchip_hdmi_qp {
struct rockchip_encoder encoder;
struct dw_hdmi_qp *hdmi;
struct phy *phy;
+ struct dw_hdmi_qp_link_config link_cfg;
struct gpio_desc *tmds_enable_gpio;
struct delayed_work hpd_work;
int port_id;
@@ -120,16 +129,119 @@ static struct rockchip_hdmi_qp *to_rockchip_hdmi_qp(struct drm_encoder *encoder)
static void dw_hdmi_qp_rockchip_encoder_enable(struct drm_encoder *encoder)
{
struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder);
+ const struct dw_hdmi_qp_link_config *link_cfg = &hdmi->link_cfg;
struct drm_crtc *crtc = encoder->crtc;
+ struct rockchip_crtc_state *rks;
- /* Unconditionally switch to TMDS as FRL is not yet supported */
- gpiod_set_value(hdmi->tmds_enable_gpio, 1);
+ gpiod_set_value(hdmi->tmds_enable_gpio, !link_cfg->frl_enabled);
if (!crtc || !crtc->state)
return;
+ rks = to_rockchip_crtc_state(crtc->state);
+
if (hdmi->ctrl_ops->enc_init)
- hdmi->ctrl_ops->enc_init(hdmi, to_rockchip_crtc_state(crtc->state));
+ hdmi->ctrl_ops->enc_init(hdmi, rks);
+
+ dev_dbg(hdmi->dev, "%s port=%d tmds=%llu bpc=%u frl_enabled=%d frl_rate_forced=%u frl_rate=%u\n", __func__,
+ hdmi->port_id, hdmi->tmds_char_rate, rks->output_bpc,
+ link_cfg->frl_enabled, link_cfg->frl_rate_forced,
+ link_cfg->frl_rate_per_lane * link_cfg->frl_lanes);
+}
+
+static int
+dw_hdmi_qp_rockchip_calc_frl_lane_cfg(struct rockchip_hdmi_qp *hdmi,
+ u8 frl_rate, u8 *rate_per_lane, u8 *lanes)
+{
+ switch (frl_rate) {
+ case 48:
+ *rate_per_lane = 12;
+ *lanes = 4;
+ break;
+ case 40:
+ *rate_per_lane = 10;
+ *lanes = 4;
+ break;
+ case 32:
+ *rate_per_lane = 8;
+ *lanes = 4;
+ break;
+ case 24:
+ *rate_per_lane = 6;
+ *lanes = 4;
+ break;
+ case 18:
+ *rate_per_lane = 6;
+ *lanes = 3;
+ break;
+ case 9:
+ *rate_per_lane = 3;
+ *lanes = 3;
+ break;
+ default:
+ dev_err(hdmi->dev, "%s frl rate %u is out of range, set to 40G\n",
+ __func__, frl_rate);
+
+ *rate_per_lane = 10;
+ *lanes = 4;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int dw_hdmi_qp_rockchip_link_setup(struct rockchip_hdmi_qp *hdmi,
+ struct rockchip_crtc_state *rks,
+ struct dw_hdmi_qp_link_config *new_link_cfg,
+ unsigned long long tmds_char_rate,
+ unsigned int bpc)
+{
+ struct dw_hdmi_qp_link_config *link_cfg = &hdmi->link_cfg;
+ union phy_configure_opts phy_cfg = {};
+ int ret = 0;
+
+ if (link_cfg->frl_enabled && new_link_cfg->frl_enabled &&
+ link_cfg->frl_lanes == new_link_cfg->frl_lanes &&
+ link_cfg->frl_rate_per_lane == new_link_cfg->frl_rate_per_lane &&
+ rks->output_bpc == bpc)
+ goto check_tmds_rate;
+
+ if (!link_cfg->frl_enabled && !new_link_cfg->frl_enabled &&
+ hdmi->tmds_char_rate == tmds_char_rate && rks->output_bpc == bpc)
+ return 0;
+
+ if (new_link_cfg->frl_enabled) {
+ phy_cfg.hdmi.frl.lanes = new_link_cfg->frl_lanes;
+ phy_cfg.hdmi.frl.rate_per_lane = new_link_cfg->frl_rate_per_lane;
+ phy_set_mode_ext(hdmi->phy, PHY_MODE_HDMI, PHY_HDMI_MODE_FRL);
+ } else {
+ phy_cfg.hdmi.tmds_char_rate = tmds_char_rate;
+ phy_set_mode_ext(hdmi->phy, PHY_MODE_HDMI, PHY_HDMI_MODE_TMDS);
+ }
+
+ phy_cfg.hdmi.bpc = bpc;
+
+ ret = phy_configure(hdmi->phy, &phy_cfg);
+ if (ret) {
+ dev_err(hdmi->dev, "Failed to configure phy: %d\n", ret);
+ return ret;
+ }
+
+ *link_cfg = *new_link_cfg;
+ rks->output_type = DRM_MODE_CONNECTOR_HDMIA;
+ rks->output_bpc = bpc;
+
+ dev_dbg(hdmi->dev,
+ "%s tmds=%llu bpc=%u, frl_enabled=%d frl_rate_forced=%u frl_rate_per_lane=%u frl_lanes=%u\n",
+ __func__, tmds_char_rate, bpc,
+ link_cfg->frl_enabled, link_cfg->frl_rate_forced,
+ link_cfg->frl_rate_per_lane, link_cfg->frl_lanes);
+
+check_tmds_rate:
+ if (hdmi->tmds_char_rate != tmds_char_rate)
+ hdmi->tmds_char_rate = tmds_char_rate;
+
+ return ret;
}
static int
@@ -137,41 +249,60 @@ dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
+ const struct drm_display_info *info = &conn_state->connector->display_info;
+ struct rockchip_crtc_state *rks = to_rockchip_crtc_state(crtc_state);
struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder);
- struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
- union phy_configure_opts phy_cfg = {};
+ struct dw_hdmi_qp_link_config link_cfg = {};
int ret;
+ //TODO: sync impl with dw_hdmi_qp_rk3588_force_link_rate()
+ if (conn_state->hdmi.tmds_char_rate > HDMI20_MAX_TMDSRATE) {
+ link_cfg.frl_rate_per_lane = info->hdmi.max_frl_rate_per_lane;
+ link_cfg.frl_lanes = info->hdmi.max_lanes;
+ link_cfg.frl_rate_forced = hdmi->link_cfg.frl_rate_forced;
+
+ if (link_cfg.frl_rate_forced) {
+ u8 rate_per_lane, frl_lanes;
+ dw_hdmi_qp_rockchip_calc_frl_lane_cfg(hdmi, link_cfg.frl_rate_forced,
+ &rate_per_lane, &frl_lanes);
+
+ if (link_cfg.frl_rate_per_lane > rate_per_lane)
+ link_cfg.frl_rate_per_lane = rate_per_lane;
+ if (link_cfg.frl_lanes > frl_lanes)
+ link_cfg.frl_lanes = frl_lanes;
+ } else {
+ if (link_cfg.frl_rate_per_lane > 12)
+ link_cfg.frl_rate_per_lane = 12;
+ if (link_cfg.frl_lanes > 4)
+ link_cfg.frl_lanes = 4;
+ }
+
+ link_cfg.frl_enabled = true;
+ } else {
+ link_cfg.frl_enabled = false;
+ }
+
+ ret = dw_hdmi_qp_rockchip_link_setup(hdmi, rks, &link_cfg,
+ conn_state->hdmi.tmds_char_rate,
+ conn_state->hdmi.output_bpc);
+ if (ret)
+ return ret;
+
switch (conn_state->hdmi.output_format) {
case HDMI_COLORSPACE_YUV420:
- s->output_mode = ROCKCHIP_OUT_MODE_YUV420;
+ rks->output_mode = ROCKCHIP_OUT_MODE_YUV420;
break;
default:
- s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+ rks->output_mode = ROCKCHIP_OUT_MODE_AAAA;
}
- if (hdmi->tmds_char_rate == conn_state->hdmi.tmds_char_rate &&
- s->output_bpc == conn_state->hdmi.output_bpc)
- return 0;
-
- phy_cfg.hdmi.tmds_char_rate = conn_state->hdmi.tmds_char_rate;
- phy_cfg.hdmi.bpc = conn_state->hdmi.output_bpc;
-
- ret = phy_configure(hdmi->phy, &phy_cfg);
- if (!ret) {
- hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate;
- s->output_type = DRM_MODE_CONNECTOR_HDMIA;
- s->output_bpc = conn_state->hdmi.output_bpc;
- /*
- * TODO: Adapt for vop2_convert_csc_mode() which uses v4l2_colorspace
- * instead of drm_colorspace.
- */
- s->color_space = rockchip_drm_colorimetry_to_v4l_colorspace(conn_state->colorspace);
- } else {
- dev_err(hdmi->dev, "Failed to configure phy: %d\n", ret);
- }
+ /*
+ * TODO: Adapt for vop2_convert_csc_mode() which uses v4l2_colorspace
+ * instead of drm_colorspace.
+ */
+ rks->color_space = rockchip_drm_colorimetry_to_v4l_colorspace(conn_state->colorspace);
- return ret;
+ return 0;
}
static const struct
@@ -222,11 +353,55 @@ static void dw_hdmi_qp_rk3588_setup_hpd(struct dw_hdmi_qp *dw_hdmi, void *data)
regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val);
}
+static struct dw_hdmi_qp_link_config *
+dw_hdmi_qp_rk3588_get_link_cfg(struct dw_hdmi_qp *dw_hdmi, void *data)
+{
+ struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
+
+ return &hdmi->link_cfg;
+}
+
+static int dw_hdmi_qp_rk3588_force_link_rate(struct dw_hdmi_qp *dw_hdmi,
+ void *data, u8 frl_rate)
+{
+ struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
+ struct dw_hdmi_qp_link_config link_cfg = hdmi->link_cfg;
+ struct drm_crtc *crtc = hdmi->encoder.encoder.crtc;
+ struct rockchip_crtc_state *rks;
+ u8 rate_per_lane, frl_lanes;
+
+ if (!frl_rate)
+ return 0;
+
+ if (!crtc || !crtc->state) {
+ dev_err(hdmi->dev, "Failed to force link rate: missing crtc\n");
+ return -EINVAL;
+ }
+
+ dw_hdmi_qp_rockchip_calc_frl_lane_cfg(hdmi, frl_rate, &rate_per_lane,
+ &frl_lanes);
+
+ if (link_cfg.frl_rate_per_lane > rate_per_lane)
+ link_cfg.frl_rate_per_lane = rate_per_lane;
+
+ if (link_cfg.frl_lanes > frl_lanes)
+ link_cfg.frl_lanes = frl_lanes;
+
+ link_cfg.frl_rate_forced = frl_rate;
+ link_cfg.frl_enabled = true;
+
+ rks = to_rockchip_crtc_state(crtc->state);
+
+ return dw_hdmi_qp_rockchip_link_setup(hdmi, rks, &link_cfg, 0, rks->output_bpc);
+}
+
static const struct dw_hdmi_qp_phy_ops rk3588_hdmi_phy_ops = {
.init = dw_hdmi_qp_rk3588_phy_init,
.disable = dw_hdmi_qp_rk3588_phy_disable,
.read_hpd = dw_hdmi_qp_rk3588_read_hpd,
.setup_hpd = dw_hdmi_qp_rk3588_setup_hpd,
+ .get_link_cfg = dw_hdmi_qp_rk3588_get_link_cfg,
+ .force_link_rate = dw_hdmi_qp_rk3588_force_link_rate,
};
static enum drm_connector_status
@@ -258,6 +433,8 @@ static const struct dw_hdmi_qp_phy_ops rk3576_hdmi_phy_ops = {
.disable = dw_hdmi_qp_rk3588_phy_disable,
.read_hpd = dw_hdmi_qp_rk3576_read_hpd,
.setup_hpd = dw_hdmi_qp_rk3576_setup_hpd,
+ .get_link_cfg = dw_hdmi_qp_rk3588_get_link_cfg,
+ .force_link_rate = dw_hdmi_qp_rk3588_force_link_rate,
};
static void dw_hdmi_qp_rk3588_hpd_work(struct work_struct *work)
@@ -408,8 +585,12 @@ static void dw_hdmi_qp_rk3588_io_init(struct rockchip_hdmi_qp *hdmi)
static void dw_hdmi_qp_rk3576_enc_init(struct rockchip_hdmi_qp *hdmi,
struct rockchip_crtc_state *state)
{
+ struct dw_hdmi_qp_link_config *link_cfg = &hdmi->link_cfg;
u32 val;
+ val = FIELD_PREP_WM16(RK3576_HDMI_FRL_MOD, link_cfg->frl_enabled);
+ regmap_write(hdmi->vo_regmap, RK3576_VO0_GRF_SOC_CON1, val);
+
if (state->output_bpc == 10)
val = HIWORD_UPDATE(FIELD_PREP(RK3576_COLOR_DEPTH_MASK, RK3576_10BPC),
RK3576_COLOR_DEPTH_MASK);
@@ -428,8 +609,14 @@ static void dw_hdmi_qp_rk3576_enc_init(struct rockchip_hdmi_qp *hdmi,
static void dw_hdmi_qp_rk3588_enc_init(struct rockchip_hdmi_qp *hdmi,
struct rockchip_crtc_state *state)
{
+ struct dw_hdmi_qp_link_config *link_cfg = &hdmi->link_cfg;
u32 val;
+ val = FIELD_PREP_WM16(RK3588_HDMI21_MASK, link_cfg->frl_enabled);
+ regmap_write(hdmi->vo_regmap,
+ hdmi->port_id ? RK3588_GRF_VO1_CON7 : RK3588_GRF_VO1_CON4,
+ val);
+
if (state->output_bpc == 10)
val = HIWORD_UPDATE(FIELD_PREP(RK3588_COLOR_DEPTH_MASK, RK3588_10BPC),
RK3588_COLOR_DEPTH_MASK);
--
2.34.1