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 74B00EB364A for ; Tue, 3 Mar 2026 01:38:31 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id F2FDB10E08C; Tue, 3 Mar 2026 01:38:28 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=collabora.com header.i=@collabora.com header.b="FRoqzzqY"; dkim-atps=neutral Received: from bali.collaboradmins.com (bali.collaboradmins.com [148.251.105.195]) by gabe.freedesktop.org (Postfix) with ESMTPS id 9D66110E08C for ; Tue, 3 Mar 2026 01:38:27 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1772501906; bh=RlXgUeG6ILKXHZT+XMRADRMmrFnvGfSMeH4tJx9lZbU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=FRoqzzqY+aSZoO/W1lwklPn+Y6BtVnwboznO7KPMyVVTdGaEXP7nA3UBsEewbVSJ0 Q3PEOrNwMteSWWLEBsLWCDeE7htWd2U8IqS/3L2KQU8xc+n5bn80Q7Ji9cSjQMdn/A UTAJdRqFpqmJOUhWXnDVmT2MLdMsBmE/ZByR8P13bmQm5+dm/al6alP0tJlgTRg35n 03NABPqogFfKj7tNhUC5uQLpC8mCpNocYT8XyQK7dCWPNtZluoezHM/X1I/rNpGZFB HfL/ciQtf/epNh5LOzfAusWUn2e1tCzeWFBBWA9BFdp2NEIWN43hvz3LUqpd8zh97u aWOMf+vnE7IOw== Received: from localhost (unknown [86.123.23.225]) (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 19B5517E129E; Tue, 3 Mar 2026 02:38:26 +0100 (CET) From: Cristian Ciocaltea Date: Tue, 03 Mar 2026 03:38:09 +0200 Subject: [PATCH v4 3/4] drm/bridge: dw-hdmi-qp: Add high TMDS clock ratio and scrambling support MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260303-dw-hdmi-qp-scramb-v4-3-317d3b8bd219@collabora.com> References: <20260303-dw-hdmi-qp-scramb-v4-0-317d3b8bd219@collabora.com> In-Reply-To: <20260303-dw-hdmi-qp-scramb-v4-0-317d3b8bd219@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.14.3 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" Add support for HDMI 2.0 display modes, e.g. 4K@60Hz, by permitting TMDS character rates above the 340 MHz limit of HDMI 1.4b. Hence, provide the required SCDC management, including the high TMDS clock ratio and scrambling setup, and filter out the HDMI 2.1 modes. Tested-by: Diederik de Haas Tested-by: Maud Spierings Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 167 ++++++++++++++++++++++++--- 1 file changed, 150 insertions(+), 17 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..e40f16a364ed 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,8 +42,10 @@ #define DDC_SEGMENT_ADDR 0x30 #define HDMI14_MAX_TMDSCLK 340000000 +#define HDMI20_MAX_TMDSRATE 600000000 -#define SCRAMB_POLL_DELAY_MS 3000 +#define SCDC_MIN_SOURCE_VERSION 0x1 +#define SCRAMB_POLL_DELAY_MS 5000 /* * Unless otherwise noted, entries in this table are 100% optimization. @@ -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,98 @@ 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 void dw_hdmi_qp_set_scramb(struct dw_hdmi_qp *hdmi) +{ + dev_dbg(hdmi->dev, "set scrambling\n"); + + drm_scdc_set_high_tmds_clock_ratio(hdmi->curr_conn, true); + drm_scdc_set_scrambling(hdmi->curr_conn, true); + + schedule_delayed_work(&hdmi->scramb_work, + msecs_to_jiffies(SCRAMB_POLL_DELAY_MS)); +} + +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 (!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) +{ + u8 ver; + + if (!dw_hdmi_qp_supports_scrambling(&hdmi->curr_conn->display_info)) + return; + + drm_scdc_readb(hdmi->bridge.ddc, SCDC_SINK_VERSION, &ver); + drm_scdc_writeb(hdmi->bridge.ddc, SCDC_SOURCE_VERSION, + min_t(u8, ver, SCDC_MIN_SOURCE_VERSION)); + + dw_hdmi_qp_set_scramb(hdmi); + dw_hdmi_qp_write(hdmi, 1, SCRAMB_CONFIG0); + + hdmi->scramb_enabled = true; + + /* 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"); + + 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 +861,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,13 +871,56 @@ 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"); + +retry: + ret = drm_bridge_helper_reset_crtc(&hdmi->bridge, ctx); + if (ret == -EDEADLK) { + drm_modeset_backoff(ctx); + goto retry; + } else if (ret) { + dev_err(hdmi->dev, "Failed to reset crtc: %d\n", ret); + } + + return ret; +} + +static int dw_hdmi_qp_bridge_detect(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; if (hdmi->no_hpd) { @@ -808,7 +931,15 @@ 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) + dw_hdmi_qp_reset_crtc(hdmi, connector, ctx); + + return status; } static const struct drm_edid * @@ -832,12 +963,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 +1328,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, .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 +1418,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.52.0