From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 27484FF8867 for ; Sun, 26 Apr 2026 00:20:41 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id B978510E4E3; Sun, 26 Apr 2026 00:20:34 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=collabora.com header.i=@collabora.com header.b="jEH4WUdJ"; dkim-atps=neutral Received: from bali.collaboradmins.com (bali.collaboradmins.com [148.251.105.195]) by gabe.freedesktop.org (Postfix) with ESMTPS id 74A5310E4D2 for ; Sun, 26 Apr 2026 00:20:32 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1777162831; bh=HLgbrmIkpMH53FmQgE4bCcWpqN55/CnA4PL79ufqAp0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=jEH4WUdJOiMuhU4aCrAowqQ5snidqD5+mfb+mhpwkC7eV3GlYu0aih6Co8t8DOJNa MmBGGgvrMzHcm+BUrww8RuExGr+Peg4WZ7smiRoLXU+g7f9+VFs4FsW0F01PgQwonb vlaX9F3Wp/uqlCJlUmbKD2lMyXEKkw7l0plGYjSRqYco6te8jfEgkDHk1cReTnzXql 78mMd1kkIacwr9E3XUBxuXuqMlfFQrRMZQIqDw/SxQySpaAl0hAia85IeLEC/Fqn6v rgT+8K2zOOQtHclBfNlTOd647z+JcxXsvzh5rH0l0NFwSYPB4jYPlfVMOvxkxM0dRW STQJBzChdAX0Q== Received: from localhost (unknown [100.64.0.241]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (prime256v1) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: cristicc) by bali.collaboradmins.com (Postfix) with ESMTPSA id 10F8317E1428; Sun, 26 Apr 2026 02:20:31 +0200 (CEST) From: Cristian Ciocaltea Date: Sun, 26 Apr 2026 03:20:17 +0300 Subject: [PATCH v5 05/10] drm/bridge: dw-hdmi-qp: Add HDMI 2.0 SCDC scrambling and high TMDS clock ratio support MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260426-dw-hdmi-qp-scramb-v5-5-d778e70c317b@collabora.com> References: <20260426-dw-hdmi-qp-scramb-v5-0-d778e70c317b@collabora.com> In-Reply-To: <20260426-dw-hdmi-qp-scramb-v5-0-d778e70c317b@collabora.com> To: Andrzej Hajda , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Simona Vetter , Sandy Huang , =?utf-8?q?Heiko_St=C3=BCbner?= , Andy Yan Cc: kernel@collabora.com, dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, Diederik de Haas , Maud Spierings X-Mailer: b4 0.15.1 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Enable HDMI 2.0 display modes (e.g. 4K@60Hz) by adding SCDC management for the high TMDS clock ratio and scrambling, required when the TMDS character rate exceeds the 340 MHz HDMI 1.4b limit. A periodic work item monitors the sink's scrambling status to recover from sink-side resets. On hotplug detect, if SCDC scrambling state is out of sync with the driver, trigger a CRTC reset to re-establish the link. Reject modes requiring TMDS rates above 600 MHz, as those fall in the HDMI 2.1 FRL domain which is not supported. In no_hpd configurations, further restrict to 340 MHz since SCDC requires a connected sink. Tested-by: Diederik de Haas Tested-by: Maud Spierings Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 188 ++++++++++++++++++++++++--- 1 file changed, 172 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index d649a1cf07f5..c482a8e7da25 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd. * Copyright (c) 2024 Collabora Ltd. + * Copyright (c) 2025 Amazon.com, Inc. or its affiliates. * * Author: Algea Cao * Author: Cristian Ciocaltea @@ -21,9 +22,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -39,7 +42,9 @@ #define DDC_SEGMENT_ADDR 0x30 #define HDMI14_MAX_TMDSCLK 340000000 +#define HDMI20_MAX_TMDSRATE 600000000 +#define SCDC_MAX_SOURCE_VERSION 0x1 #define SCRAMB_POLL_DELAY_MS 3000 /* @@ -164,6 +169,11 @@ struct dw_hdmi_qp { } phy; unsigned long ref_clk_rate; + + struct drm_connector *curr_conn; + struct delayed_work scramb_work; + bool scramb_enabled; + struct regmap *regm; int main_irq; @@ -749,28 +759,124 @@ static struct i2c_adapter *dw_hdmi_qp_i2c_adapter(struct dw_hdmi_qp *hdmi) return adap; } +static bool dw_hdmi_qp_supports_scrambling(struct drm_display_info *display) +{ + if (!display->is_hdmi) + return false; + + return display->hdmi.scdc.supported && + display->hdmi.scdc.scrambling.supported; +} + +static int dw_hdmi_qp_set_scramb(struct dw_hdmi_qp *hdmi) +{ + bool done; + + dev_dbg(hdmi->dev, "set scrambling\n"); + + done = drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, true); + if (!done) + return -EIO; + + done = drm_scdc_set_scrambling(hdmi->curr_conn, true); + if (!done) { + drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, false); + return -EIO; + } + + schedule_delayed_work(&hdmi->scramb_work, + msecs_to_jiffies(SCRAMB_POLL_DELAY_MS)); + return 0; +} + +static void dw_hdmi_qp_scramb_work(struct work_struct *work) +{ + struct dw_hdmi_qp *hdmi = container_of(to_delayed_work(work), + struct dw_hdmi_qp, + scramb_work); + if (READ_ONCE(hdmi->scramb_enabled) && + !drm_scdc_get_scrambling_status(hdmi->curr_conn)) + dw_hdmi_qp_set_scramb(hdmi); +} + +static void dw_hdmi_qp_enable_scramb(struct dw_hdmi_qp *hdmi) +{ + int ret; + u8 ver; + + if (!dw_hdmi_qp_supports_scrambling(&hdmi->curr_conn->display_info)) + return; + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_SINK_VERSION, &ver); + if (ret) { + dev_err(hdmi->dev, "Failed to read SCDC_SINK_VERSION: %d\n", ret); + return; + } + + ret = drm_scdc_writeb(hdmi->bridge.ddc, SCDC_SOURCE_VERSION, + min_t(u8, ver, SCDC_MAX_SOURCE_VERSION)); + if (ret) { + dev_err(hdmi->dev, "Failed to write SCDC_SOURCE_VERSION: %d\n", ret); + return; + } + + WRITE_ONCE(hdmi->scramb_enabled, true); + + ret = dw_hdmi_qp_set_scramb(hdmi); + if (ret) { + hdmi->scramb_enabled = false; + return; + } + + dw_hdmi_qp_write(hdmi, 1, SCRAMB_CONFIG0); + + /* Wait at least 1 ms before resuming TMDS transmission */ + usleep_range(1000, 5000); +} + +static void dw_hdmi_qp_disable_scramb(struct dw_hdmi_qp *hdmi) +{ + if (!hdmi->scramb_enabled) + return; + + dev_dbg(hdmi->dev, "disable scrambling\n"); + + WRITE_ONCE(hdmi->scramb_enabled, false); + cancel_delayed_work_sync(&hdmi->scramb_work); + + dw_hdmi_qp_write(hdmi, 0, SCRAMB_CONFIG0); + + if (hdmi->curr_conn->status == connector_status_connected) { + drm_scdc_set_scrambling(hdmi->curr_conn, false); + drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, false); + } +} + static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_state *state) { struct dw_hdmi_qp *hdmi = bridge->driver_private; struct drm_connector_state *conn_state; - struct drm_connector *connector; unsigned int op_mode; - connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); - if (WARN_ON(!connector)) + hdmi->curr_conn = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + if (WARN_ON(!hdmi->curr_conn)) return; - conn_state = drm_atomic_get_new_connector_state(state, connector); + conn_state = drm_atomic_get_new_connector_state(state, hdmi->curr_conn); if (WARN_ON(!conn_state)) return; - if (connector->display_info.is_hdmi) { + if (hdmi->curr_conn->display_info.is_hdmi) { dev_dbg(hdmi->dev, "%s mode=HDMI %s rate=%llu bpc=%u\n", __func__, drm_hdmi_connector_get_output_format_name(conn_state->hdmi.output_format), conn_state->hdmi.tmds_char_rate, conn_state->hdmi.output_bpc); op_mode = 0; hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; + + if (conn_state->hdmi.tmds_char_rate > HDMI14_MAX_TMDSCLK) + dw_hdmi_qp_enable_scramb(hdmi); } else { dev_dbg(hdmi->dev, "%s mode=DVI\n", __func__); op_mode = OPMODE_DVI; @@ -781,7 +887,7 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, dw_hdmi_qp_mod(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0); dw_hdmi_qp_mod(hdmi, op_mode, OPMODE_DVI, LINK_CONFIG0); - drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); + drm_atomic_helper_connector_hdmi_update_infoframes(hdmi->curr_conn, state); } static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, @@ -791,14 +897,49 @@ static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, hdmi->tmds_char_rate = 0; + dw_hdmi_qp_disable_scramb(hdmi); + + hdmi->curr_conn = NULL; hdmi->phy.ops->disable(hdmi, hdmi->phy.data); } -static enum drm_connector_status -dw_hdmi_qp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) +static int dw_hdmi_qp_reset_crtc(struct dw_hdmi_qp *hdmi, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx) +{ + u8 config; + int ret; + + ret = drm_scdc_readb(hdmi->bridge.ddc, SCDC_TMDS_CONFIG, &config); + if (ret < 0) { + dev_err(hdmi->dev, "Failed to read TMDS config: %d\n", ret); + return ret; + } + + if (!!(config & SCDC_SCRAMBLING_ENABLE) == hdmi->scramb_enabled) + return 0; + + drm_atomic_helper_connector_hdmi_hotplug(connector, + connector_status_connected); + /* + * Conform to HDMI 2.0 spec by ensuring scrambled data is not sent + * before configuring the sink scrambling, as well as suspending any + * TMDS transmission while changing the TMDS clock rate in the sink. + */ + + dev_dbg(hdmi->dev, "resetting crtc\n"); + + return drm_bridge_helper_reset_crtc(&hdmi->bridge, ctx); +} + +static int dw_hdmi_qp_bridge_detect_ctx(struct drm_bridge *bridge, + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx) { struct dw_hdmi_qp *hdmi = bridge->driver_private; + enum drm_connector_status status; const struct drm_edid *drm_edid; + int ret; if (hdmi->no_hpd) { drm_edid = drm_edid_read_ddc(connector, bridge->ddc); @@ -808,7 +949,20 @@ dw_hdmi_qp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connec return connector_status_disconnected; } - return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + status = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + + dev_dbg(hdmi->dev, "%s status=%d scramb=%d\n", __func__, + status, hdmi->scramb_enabled); + + if (status == connector_status_connected && hdmi->scramb_enabled) { + ret = dw_hdmi_qp_reset_crtc(hdmi, connector, ctx); + if (ret == -EDEADLK) + return ret; + if (ret < 0) + status = connector_status_unknown; + } + + return status; } static const struct drm_edid * @@ -832,12 +986,12 @@ dw_hdmi_qp_bridge_tmds_char_rate_valid(const struct drm_bridge *bridge, { struct dw_hdmi_qp *hdmi = bridge->driver_private; - /* - * TODO: when hdmi->no_hpd is 1 we must not support modes that - * require scrambling, including every mode with a clock above - * HDMI14_MAX_TMDSCLK. - */ - if (rate > HDMI14_MAX_TMDSCLK) { + if (hdmi->no_hpd && rate > HDMI14_MAX_TMDSCLK) { + dev_dbg(hdmi->dev, "Unsupported TMDS char rate in no_hpd mode: %lld\n", rate); + return MODE_CLOCK_HIGH; + } + + if (rate > HDMI20_MAX_TMDSRATE) { dev_dbg(hdmi->dev, "Unsupported TMDS char rate: %lld\n", rate); return MODE_CLOCK_HIGH; } @@ -1197,7 +1351,7 @@ static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { .atomic_reset = drm_atomic_helper_bridge_reset, .atomic_enable = dw_hdmi_qp_bridge_atomic_enable, .atomic_disable = dw_hdmi_qp_bridge_atomic_disable, - .detect = dw_hdmi_qp_bridge_detect, + .detect_ctx = dw_hdmi_qp_bridge_detect_ctx, .edid_read = dw_hdmi_qp_bridge_edid_read, .hdmi_tmds_char_rate_valid = dw_hdmi_qp_bridge_tmds_char_rate_valid, .hdmi_clear_avi_infoframe = dw_hdmi_qp_bridge_clear_avi_infoframe, @@ -1287,6 +1441,8 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, if (IS_ERR(hdmi)) return ERR_CAST(hdmi); + INIT_DELAYED_WORK(&hdmi->scramb_work, dw_hdmi_qp_scramb_work); + hdmi->dev = dev; regs = devm_platform_ioremap_resource(pdev, 0); -- 2.53.0