| Message ID | 20260731-dw-hdmi-qp-scramb-v10-7-294364b2cf15@collabora.com (mailing list archive) |
|---|---|
| State | New |
| Headers |
Return-Path: <linux-sunxi+bounces-24830-sunxi=pue.re@lists.linux.dev> X-Original-To: noreply@patchwork.local Delivered-To: noreply@patchwork.local Received: from sto.lore.kernel.org (sto.lore.kernel.org [172.232.135.74]) by mxe881.netcup.net (Postfix) with ESMTPS id 3BFF91C1D58 for <noreply@patchwork.local>; Fri, 31 Jul 2026 18:33:41 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=collabora.com; spf=pass (sender IP is 172.232.135.74) smtp.mailfrom=linux-sunxi+bounces-24830-noreply=patchwork.local@lists.linux.dev smtp.helo=sto.lore.kernel.org Received-SPF: pass (mxe881: domain of lists.linux.dev designates 172.232.135.74 as permitted sender) client-ip=172.232.135.74; envelope-from=linux-sunxi+bounces-24830-noreply=patchwork.local@lists.linux.dev; helo=sto.lore.kernel.org; Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sto.lore.kernel.org (Postfix) with ESMTP id 0D4D230C2A69 for <noreply@patchwork.local>; Fri, 31 Jul 2026 16:23:23 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 91E574657EE; Fri, 31 Jul 2026 16:19:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b="n2HWz8SG" X-Original-To: linux-sunxi@lists.linux.dev Received: from bali.collaboradmins.com (bali.collaboradmins.com [148.251.105.195]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6CB384582ED for <linux-sunxi@lists.linux.dev>; Fri, 31 Jul 2026 16:19:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.251.105.195 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785514788; cv=none; b=NjYgPeKMpgGw+K7PnbssW9Kqjf3r8sd6gqEkpEilTj8qkmdgINOAJ3mzbaycg6Hjl7XFXrn3hmNFw+PqiozVpfKad23HLt7/+a6Z0OoltlR/S9jajlvk/+DPe4c+ey0fnQlnxMOYv/ECvyf5LaWYj7uw2SG/k4K/exGcvu+16b4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785514788; c=relaxed/simple; bh=J1vxmseoLPQQnv2CmyOezLJSX3z1Sy0R9f4GOVZ1xq4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=B9i1UJH56OUZDt1T+0GXsfqm00Qm4LRUi7DGBqWW3GxE3nobhGVVVYUkWJAdvLQsdfCAiK2t4Cv8/XC/5rGbFelpLJnvdJipyK/RGbeUIQs0MigGwUd8xQDUAKGkLwtbp0zbaARl1X7I9+CJ8zI4Sd5l+n8DSprk2aza9c+Dbro= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=collabora.com; spf=pass smtp.mailfrom=collabora.com; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b=n2HWz8SG; arc=none smtp.client-ip=148.251.105.195 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=collabora.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=collabora.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1785514759; bh=J1vxmseoLPQQnv2CmyOezLJSX3z1Sy0R9f4GOVZ1xq4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=n2HWz8SG6RstpcZ9zFQnUXMQH9z1/0e7VaWgppUHpn4sJQk3gXGV+Dw4pWZlu/4ns Ce3Xed+/KWrQycb1w+i4KJZbj3pgdSOvHvTIevkqPKsF9ySjaiZMtEOA+1DJK2xCjh xKxt5PmqsOPbGVSbj1YB3kqzMHM+UyofBhfpjSSNSdbnihZV7fA4T9q7Snuiya98VE EO06t4ZptZELQB7msWLgicEXbMcqasWI14b+0kwtkr6m/rAsa4IKnDhMHXi1ook0tG xx2rY0xS1HmQbsbQJONAB/vEPlQ2Q+KT1C3Zq4QfuTIVA4IMqzY/ScE4gRquQa0S3E cMFPHF1936ymw== Received: from localhost (unknown [100.64.0.241]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange secp256r1 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: cristicc) by bali.collaboradmins.com (Postfix) with ESMTPSA id C7AA817E08F7; Fri, 31 Jul 2026 18:19:19 +0200 (CEST) From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com> Date: Fri, 31 Jul 2026 19:19:14 +0300 Subject: [PATCH v10 07/69] drm/connector: Add HDMI 2.0 scrambler infrastructure Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: <linux-sunxi.lists.linux.dev> List-Subscribe: <mailto:linux-sunxi+subscribe@lists.linux.dev> List-Unsubscribe: <mailto:linux-sunxi+unsubscribe@lists.linux.dev> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260731-dw-hdmi-qp-scramb-v10-7-294364b2cf15@collabora.com> References: <20260731-dw-hdmi-qp-scramb-v10-0-294364b2cf15@collabora.com> In-Reply-To: <20260731-dw-hdmi-qp-scramb-v10-0-294364b2cf15@collabora.com> To: Andrzej Hajda <andrzej.hajda@intel.com>, Neil Armstrong <neil.armstrong@linaro.org>, Robert Foss <rfoss@kernel.org>, Laurent Pinchart <Laurent.pinchart@ideasonboard.com>, Jonas Karlman <jonas@kwiboo.se>, Jernej Skrabec <jernej.skrabec@gmail.com>, Luca Ceresoli <luca.ceresoli@bootlin.com>, Maarten Lankhorst <maarten.lankhorst@linux.intel.com>, Maxime Ripard <mripard@kernel.org>, Thomas Zimmermann <tzimmermann@suse.de>, David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>, Chen-Yu Tsai <wens@kernel.org>, Samuel Holland <samuel@sholland.org>, Dave Stevenson <dave.stevenson@raspberrypi.com>, =?utf-8?q?Ma=C3=ADra_Canal?= <mcanal@igalia.com>, Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>, Sandy Huang <hjc@rock-chips.com>, =?utf-8?q?Heiko_St=C3=BCbner?= <heiko@sntech.de>, Andy Yan <andy.yan@rock-chips.com>, Algea Cao <algea.cao@rock-chips.com>, Daniel Stone <daniels@collabora.com>, Liu Ying <victor.liu@nxp.com>, Phong LE <ple@baylibre.com> Cc: kernel@collabora.com, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-arm-kernel@lists.infradead.org, linux-sunxi@lists.linux.dev, linux-rockchip@lists.infradead.org, Maud Spierings <maud_spierings@hotmail.com>, Diederik de Haas <diederik@cknow-tech.com> X-Mailer: b4 0.15.2 X-Rspamd-Server: rspamd-worker-8404 X-Spamd-Result: default: False [-2.16 / 15.00]; BAYES_HAM(-5.50)[100.00%]; RBL_SENDERSCORE(2.00)[172.232.135.74:from]; SUSPICIOUS_RECIPS(1.50)[]; MAILLIST(-0.15)[generic]; MIME_GOOD(-0.10)[text/plain]; BAD_REP_POLICIES(0.10)[]; HAS_LIST_UNSUB(-0.01)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[collabora.com:email,collabora.com:dkim,sto.lore.kernel.org:rdns,sto.lore.kernel.org:helo,cknow-tech.com:email]; TAGGED_RCPT(0.00)[]; PRECEDENCE_BULK(0.00)[]; RCPT_COUNT_TWELVE(0.00)[32]; FREEMAIL_CC(0.00)[collabora.com,vger.kernel.org,lists.freedesktop.org,lists.infradead.org,lists.linux.dev,hotmail.com,cknow-tech.com]; FROM_HAS_DN(0.00)[]; MID_RHS_MATCH_FROM(0.00)[]; RCVD_COUNT_FIVE(0.00)[5]; R_DKIM_ALLOW(0.00)[collabora.com:s=mail]; FROM_NEQ_ENVFROM(0.00)[cristian.ciocaltea@collabora.com,linux-sunxi@lists.linux.dev]; DMARC_POLICY_ALLOW(0.00)[collabora.com,none]; FREEMAIL_TO(0.00)[intel.com,linaro.org,kernel.org,ideasonboard.com,kwiboo.se,gmail.com,bootlin.com,linux.intel.com,suse.de,ffwll.ch,sholland.org,raspberrypi.com,igalia.com,rock-chips.com,sntech.de,collabora.com,nxp.com,baylibre.com]; DKIM_TRACE(0.00)[collabora.com:+]; TO_DN_SOME(0.00)[]; R_SPF_ALLOW(0.00)[+ip4:172.232.135.74]; FORGED_SENDER_MAILLIST(0.00)[]; FORGED_RECIPIENTS_MAILLIST(0.00)[]; RCVD_TLS_LAST(0.00)[]; ARC_ALLOW(0.00)[subspace.kernel.org:s=arc-20240116:i=1]; MIME_TRACE(0.00)[0:+]; TAGGED_FROM(0.00)[bounces-24830-noreply=patchwork.local]; ASN(0.00)[asn:63949, ipnet:172.232.128.0/19, country:SG]; RCVD_VIA_SMTP_AUTH(0.00)[] X-Rspamd-Queue-Id: 3BFF91C1D58 X-MORS-Enabled: yes X-MORS-DOMAIN: patchwork.local X-MORS-HOSTING: hosting172546 X-MORS-USER: hosting172546 X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= |
| Series |
Add HDMI 2.0 support to DW HDMI QP TX
|
|
Commit Message
Cristian Ciocaltea
July 31, 2026, 4:19 p.m. UTC
Add the connector-level infrastructure to support HDMI 2.0 scrambling:
- A drm_connector_hdmi_scrambler_supported() helper to report whether
the source supports the scrambling capability, based on the presence
of the newly introduced .scrambler_{enable|disable}() callbacks in
drm_connector_hdmi_funcs are mandatory
- A scrambler_needed flag to be managed by the hdmi state helpers based
on the negotiated TMDS character rate and the source/sink scrambling
capabilities
- A scrambler_enabled flag to track whether scrambling is currently
active
- A delayed work item (scdc_work) to monitor sink-side scrambling status
and retry the setup if the sink resets it
- A scdc_work_initialized flag to support lazy initialization of the
work item on the first scrambling enable and guard the teardown paths
These are intended to be used by SCDC scrambling helpers to coordinate
scrambling setup and teardown between the source driver and the DRM
core.
Tested-by: Maud Spierings <maud_spierings@hotmail.com>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # NanoPC-T6 LTS, Rock 5B
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/drm_connector.c | 31 ++++++++++++---
include/drm/drm_connector.h | 83 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 109 insertions(+), 5 deletions(-)
Comments
On Fri, Jul 31, 2026 at 07:19:14PM +0300, Cristian Ciocaltea wrote: > Add the connector-level infrastructure to support HDMI 2.0 scrambling: > > - A drm_connector_hdmi_scrambler_supported() helper to report whether > the source supports the scrambling capability, based on the presence > of the newly introduced .scrambler_{enable|disable}() callbacks in > drm_connector_hdmi_funcs are mandatory > - A scrambler_needed flag to be managed by the hdmi state helpers based > on the negotiated TMDS character rate and the source/sink scrambling > capabilities > - A scrambler_enabled flag to track whether scrambling is currently > active > - A delayed work item (scdc_work) to monitor sink-side scrambling status > and retry the setup if the sink resets it > - A scdc_work_initialized flag to support lazy initialization of the > work item on the first scrambling enable and guard the teardown paths > > These are intended to be used by SCDC scrambling helpers to coordinate > scrambling setup and teardown between the source driver and the DRM > core. > > Tested-by: Maud Spierings <maud_spierings@hotmail.com> > Tested-by: Diederik de Haas <diederik@cknow-tech.com> # NanoPC-T6 LTS, Rock 5B > Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com> > --- > drivers/gpu/drm/drm_connector.c | 31 ++++++++++++--- > include/drm/drm_connector.h | 83 +++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 109 insertions(+), 5 deletions(-) > > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c > index 4721cdeafc84..a18410faf040 100644 > --- a/drivers/gpu/drm/drm_connector.c > +++ b/drivers/gpu/drm/drm_connector.c > @@ -622,12 +622,29 @@ int drmm_connector_hdmi_init(struct drm_device *dev, > * default with the actual controller capability. A value of zero keeps > * the limit inferred from supported_hdmi_ver. > */ > - if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) > + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) { > + if (!hdmi_funcs->scrambler_enable || !hdmi_funcs->scrambler_disable) { > + drm_err(dev, "Scrambler callbacks missing for HDMI 2.x\n"); > + return -EINVAL; > + } > + > connector->hdmi.max_tmds_char_rate = HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ; > - else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) > - connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; > - else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) > - connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; > + } else { > + /* > + * Scrambler callbacks are only valid for connectors advertising > + * HDMI 2.0 capability. drm_connector_hdmi_scrambler_supported() > + * relies on their presence to report scrambling support. > + */ > + if (hdmi_funcs->scrambler_enable || hdmi_funcs->scrambler_disable) { > + drm_err(dev, "Scrambler callbacks unexpected for HDMI 1.x\n"); > + return -EINVAL; > + } > + > + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) > + connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; > + else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) > + connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; > + } I'd put it into a separate test (possibly earlier). Merging both the tmds rate default and the scrambler callbacks check makes it messier than it would be if we had two separate tests. > if (hdmi_funcs->supported_tmds_char_rate) { > if (hdmi_funcs->supported_tmds_char_rate > connector->hdmi.max_tmds_char_rate) { > @@ -635,6 +652,7 @@ int drmm_connector_hdmi_init(struct drm_device *dev, > connector->hdmi.max_tmds_char_rate); > return -EINVAL; > } > + > connector->hdmi.max_tmds_char_rate = hdmi_funcs->supported_tmds_char_rate; > } > > @@ -918,6 +936,9 @@ void drm_connector_cleanup(struct drm_connector *connector) > DRM_CONNECTOR_REGISTERED)) > drm_connector_unregister(connector); > > + if (connector->hdmi.scdc_work_initialized) > + cancel_delayed_work_sync(&connector->hdmi.scdc_work); > + > platform_device_unregister(connector->hdmi_audio.codec_pdev); > > if (connector->privacy_screen) { > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h > index a6de3e63b462..89a140d6f064 100644 > --- a/include/drm/drm_connector.h > +++ b/include/drm/drm_connector.h > @@ -28,6 +28,7 @@ > #include <linux/ctype.h> > #include <linux/hdmi.h> > #include <linux/notifier.h> > +#include <linux/workqueue.h> > #include <drm/drm_mode_object.h> > #include <drm/drm_util.h> > #include <drm/drm_property.h> > @@ -1131,6 +1132,17 @@ struct drm_connector_hdmi_state { > * @tmds_char_rate: TMDS Character Rate, in Hz. > */ > unsigned long long tmds_char_rate; > + > + /** > + * @scrambler_needed: Whether HDMI 2.0 SCDC scrambling is required > + * for the negotiated mode/bpc/format. > + * > + * Computed by drm_atomic_helper_connector_hdmi_check() according to > + * the HDMI 2.0 specification: scrambling is mandatory above a 340 MHz > + * TMDS character rate. Optional scrambling at lower rates is > + * deliberately not requested by the helper. > + */ > + bool scrambler_needed; > }; > > /** > @@ -1481,6 +1493,36 @@ struct drm_connector_hdmi_funcs { > */ > const struct drm_edid *(*read_edid)(struct drm_connector *connector); > > + /** > + * @scrambler_enable: > + * > + * The callback is invoked via @drm_connector_hdmi_enable_scrambling > + * during commit to setup SCDC scrambling and high TMDS clock ratio on > + * the source side. > + * > + * The @scrambler_enable callback is mandatory if HDMI 2.0 is to be > + * supported. > + * > + * Returns: > + * 0 on success, a negative error code otherwise > + */ > + int (*scrambler_enable)(struct drm_connector *connector); > + > + /** > + * @scrambler_disable: > + * > + * The callback is invoked via @drm_connector_hdmi_disable_scrambling > + * during commit to tear down SCDC scrambling and high TMDS clock ratio > + * on the source side. > + * > + * The @scrambler_disable callback is mandatory if HDMI 2.0 is to be > + * supported. > + * > + * Returns: > + * 0 on success, a negative error code otherwise > + */ > + int (*scrambler_disable)(struct drm_connector *connector); > + > /** > * @avi: > * > @@ -2111,6 +2153,25 @@ struct drm_connector_hdmi { > */ > unsigned long long max_tmds_char_rate; > > + /** > + * @scrambler_enabled: Tracks whether HDMI 2.0 scrambler is currently enabled. > + */ > + bool scrambler_enabled; > + > + /** > + * @scdc_work: Work item currently used to monitor sink-side scrambling > + * status and retry setup if the sink resets it. > + */ > + struct delayed_work scdc_work; > + > + /** > + * @scdc_work_initialized: Tracks whether @scdc_work has been set up via > + * INIT_DELAYED_WORK(). The work item is initialized lazily on the first > + * scrambling enable, so this guards the teardown paths against touching > + * an uninitialized work item. > + */ > + bool scdc_work_initialized; > + Why should we track whether it's initialized or not? I'd always initialize it, but only ever schedule something if we're using the scrambler. Maxime
On 8/19/26 1:12 PM, Maxime Ripard wrote: > On Fri, Jul 31, 2026 at 07:19:14PM +0300, Cristian Ciocaltea wrote: >> Add the connector-level infrastructure to support HDMI 2.0 scrambling: >> >> - A drm_connector_hdmi_scrambler_supported() helper to report whether >> the source supports the scrambling capability, based on the presence >> of the newly introduced .scrambler_{enable|disable}() callbacks in >> drm_connector_hdmi_funcs are mandatory >> - A scrambler_needed flag to be managed by the hdmi state helpers based >> on the negotiated TMDS character rate and the source/sink scrambling >> capabilities >> - A scrambler_enabled flag to track whether scrambling is currently >> active >> - A delayed work item (scdc_work) to monitor sink-side scrambling status >> and retry the setup if the sink resets it >> - A scdc_work_initialized flag to support lazy initialization of the >> work item on the first scrambling enable and guard the teardown paths >> >> These are intended to be used by SCDC scrambling helpers to coordinate >> scrambling setup and teardown between the source driver and the DRM >> core. >> >> Tested-by: Maud Spierings <maud_spierings@hotmail.com> >> Tested-by: Diederik de Haas <diederik@cknow-tech.com> # NanoPC-T6 LTS, Rock 5B >> Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com> >> --- >> drivers/gpu/drm/drm_connector.c | 31 ++++++++++++--- >> include/drm/drm_connector.h | 83 +++++++++++++++++++++++++++++++++++++++++ >> 2 files changed, 109 insertions(+), 5 deletions(-) >> >> diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c >> index 4721cdeafc84..a18410faf040 100644 >> --- a/drivers/gpu/drm/drm_connector.c >> +++ b/drivers/gpu/drm/drm_connector.c >> @@ -622,12 +622,29 @@ int drmm_connector_hdmi_init(struct drm_device *dev, >> * default with the actual controller capability. A value of zero keeps >> * the limit inferred from supported_hdmi_ver. >> */ >> - if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) >> + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) { >> + if (!hdmi_funcs->scrambler_enable || !hdmi_funcs->scrambler_disable) { >> + drm_err(dev, "Scrambler callbacks missing for HDMI 2.x\n"); >> + return -EINVAL; >> + } >> + >> connector->hdmi.max_tmds_char_rate = HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ; >> - else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) >> - connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; >> - else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) >> - connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; >> + } else { >> + /* >> + * Scrambler callbacks are only valid for connectors advertising >> + * HDMI 2.0 capability. drm_connector_hdmi_scrambler_supported() >> + * relies on their presence to report scrambling support. >> + */ >> + if (hdmi_funcs->scrambler_enable || hdmi_funcs->scrambler_disable) { >> + drm_err(dev, "Scrambler callbacks unexpected for HDMI 1.x\n"); >> + return -EINVAL; >> + } >> + >> + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) >> + connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; >> + else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) >> + connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; >> + } > > I'd put it into a separate test (possibly earlier). Merging both the > tmds rate default and the scrambler callbacks check makes it messier > than it would be if we had two separate tests. Ack. How about the following? if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) connector->hdmi.max_tmds_char_rate = HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ; else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; if (hdmi_funcs->supported_tmds_char_rate) { if (hdmi_funcs->supported_tmds_char_rate > connector->hdmi.max_tmds_char_rate) { drm_err(dev, "Enforced max_tmds_char_rate exceeds %llu spec limit\n", connector->hdmi.max_tmds_char_rate); return -EINVAL; } connector->hdmi.max_tmds_char_rate = hdmi_funcs->supported_tmds_char_rate; } if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) { if (!hdmi_funcs->scrambler_enable || !hdmi_funcs->scrambler_disable) { drm_err(dev, "Scrambler callbacks missing for HDMI 2.x\n"); return -EINVAL; } } else { /* * Scrambler callbacks are only valid for connectors advertising * HDMI 2.0 capability. drm_connector_hdmi_scrambler_supported() * relies on their presence to report scrambling support. */ if (hdmi_funcs->scrambler_enable || hdmi_funcs->scrambler_disable) { drm_err(dev, "Scrambler callbacks unexpected for HDMI 1.x\n"); return -EINVAL; } } >> if (hdmi_funcs->supported_tmds_char_rate) { >> if (hdmi_funcs->supported_tmds_char_rate > connector->hdmi.max_tmds_char_rate) { >> @@ -635,6 +652,7 @@ int drmm_connector_hdmi_init(struct drm_device *dev, >> connector->hdmi.max_tmds_char_rate); >> return -EINVAL; >> } >> + >> connector->hdmi.max_tmds_char_rate = hdmi_funcs->supported_tmds_char_rate; >> } [...] >> + /** >> + * @scdc_work: Work item currently used to monitor sink-side scrambling >> + * status and retry setup if the sink resets it. >> + */ >> + struct delayed_work scdc_work; >> + >> + /** >> + * @scdc_work_initialized: Tracks whether @scdc_work has been set up via >> + * INIT_DELAYED_WORK(). The work item is initialized lazily on the first >> + * scrambling enable, so this guards the teardown paths against touching >> + * an uninitialized work item. >> + */ >> + bool scdc_work_initialized; >> + > > Why should we track whether it's initialized or not? I'd always > initialize it, but only ever schedule something if we're using the > scrambler. Having this initialized in the connector would lead to a module dependency cycle. Currently the work function lives in drm_hdmi_helper.c, which is built into drm_display_helper module: static void drm_connector_hdmi_scdc_work(struct work_struct *work) { [...] if (READ_ONCE(connector->hdmi.scrambler_enabled) && !drm_scdc_get_scrambling_status(connector)) drm_connector_hdmi_try_scrambling_setup(connector); [...] } int drm_connector_hdmi_enable_scrambling(struct drm_connector *connector, const struct drm_connector_state *conn_state) { [...] if (!hdmi->scdc_work_initialized) { INIT_DELAYED_WORK(&hdmi->scdc_work, drm_connector_hdmi_scdc_work); hdmi->scdc_work_initialized = true; } [...] } If we move INIT_DELAYED_WORK() into the connector (i.e. in drm.ko), the work function has to be reachable from there. The following attempts to accomplish that would fail: - Keep the work function in drm_hdmi_helper.c and export it from drm_display_helper. - Move the work function into drm_connector.c and export drm_connector_hdmi_try_scrambling_setup(), or a wrapper function, from drm_display_helper. Either way drm module ends up depending on drm_display_helper, which already depends on drm: depmod: ERROR: Cycle detected: drm_display_helper -> drm -> drm_display_helper My previous approach provided the work function in the connector, and a callback set by the scrambling helper: /* Part of drm module */ struct drm_connector_hdmi { [...] void (*scdc_cb)(struct drm_connector *connector); [...] } static void drm_connector_hdmi_scdc_work(struct work_struct *work) { [...] if (hdmi->scdc_cb) hdmi->scdc_cb(connector); [...] } /* Part of drm_display_helper */ int drm_connector_hdmi_enable_scrambling() { [...] hdmi->scdc_cb = drm_scdc_monitor_scrambler; [...] } Since it didn't get positive feedback, I ended up with lazy initialization instead. I think it's the better of the two, as it keeps all the implementation logic inside the helper module rather than splitting it across drm and drm_display_helper. Thanks, Cristian
On Wed, Aug 19, 2026 at 10:33:04PM +0300, Cristian Ciocaltea wrote: > On 8/19/26 1:12 PM, Maxime Ripard wrote: > > On Fri, Jul 31, 2026 at 07:19:14PM +0300, Cristian Ciocaltea wrote: > >> Add the connector-level infrastructure to support HDMI 2.0 scrambling: > >> > >> - A drm_connector_hdmi_scrambler_supported() helper to report whether > >> the source supports the scrambling capability, based on the presence > >> of the newly introduced .scrambler_{enable|disable}() callbacks in > >> drm_connector_hdmi_funcs are mandatory > >> - A scrambler_needed flag to be managed by the hdmi state helpers based > >> on the negotiated TMDS character rate and the source/sink scrambling > >> capabilities > >> - A scrambler_enabled flag to track whether scrambling is currently > >> active > >> - A delayed work item (scdc_work) to monitor sink-side scrambling status > >> and retry the setup if the sink resets it > >> - A scdc_work_initialized flag to support lazy initialization of the > >> work item on the first scrambling enable and guard the teardown paths > >> > >> These are intended to be used by SCDC scrambling helpers to coordinate > >> scrambling setup and teardown between the source driver and the DRM > >> core. > >> > >> Tested-by: Maud Spierings <maud_spierings@hotmail.com> > >> Tested-by: Diederik de Haas <diederik@cknow-tech.com> # NanoPC-T6 LTS, Rock 5B > >> Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com> > >> --- > >> drivers/gpu/drm/drm_connector.c | 31 ++++++++++++--- > >> include/drm/drm_connector.h | 83 +++++++++++++++++++++++++++++++++++++++++ > >> 2 files changed, 109 insertions(+), 5 deletions(-) > >> > >> diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c > >> index 4721cdeafc84..a18410faf040 100644 > >> --- a/drivers/gpu/drm/drm_connector.c > >> +++ b/drivers/gpu/drm/drm_connector.c > >> @@ -622,12 +622,29 @@ int drmm_connector_hdmi_init(struct drm_device *dev, > >> * default with the actual controller capability. A value of zero keeps > >> * the limit inferred from supported_hdmi_ver. > >> */ > >> - if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) > >> + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) { > >> + if (!hdmi_funcs->scrambler_enable || !hdmi_funcs->scrambler_disable) { > >> + drm_err(dev, "Scrambler callbacks missing for HDMI 2.x\n"); > >> + return -EINVAL; > >> + } > >> + > >> connector->hdmi.max_tmds_char_rate = HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ; > >> - else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) > >> - connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; > >> - else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) > >> - connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; > >> + } else { > >> + /* > >> + * Scrambler callbacks are only valid for connectors advertising > >> + * HDMI 2.0 capability. drm_connector_hdmi_scrambler_supported() > >> + * relies on their presence to report scrambling support. > >> + */ > >> + if (hdmi_funcs->scrambler_enable || hdmi_funcs->scrambler_disable) { > >> + drm_err(dev, "Scrambler callbacks unexpected for HDMI 1.x\n"); > >> + return -EINVAL; > >> + } > >> + > >> + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) > >> + connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; > >> + else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) > >> + connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; > >> + } > > > > I'd put it into a separate test (possibly earlier). Merging both the > > tmds rate default and the scrambler callbacks check makes it messier > > than it would be if we had two separate tests. > > Ack. How about the following? > > if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) > connector->hdmi.max_tmds_char_rate = HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ; > else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) > connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; > else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) > connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; > > if (hdmi_funcs->supported_tmds_char_rate) { > if (hdmi_funcs->supported_tmds_char_rate > connector->hdmi.max_tmds_char_rate) { > drm_err(dev, "Enforced max_tmds_char_rate exceeds %llu spec limit\n", > connector->hdmi.max_tmds_char_rate); > return -EINVAL; > } > > connector->hdmi.max_tmds_char_rate = hdmi_funcs->supported_tmds_char_rate; > } > > if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) { > if (!hdmi_funcs->scrambler_enable || !hdmi_funcs->scrambler_disable) { > drm_err(dev, "Scrambler callbacks missing for HDMI 2.x\n"); > return -EINVAL; > } > } else { > /* > * Scrambler callbacks are only valid for connectors advertising > * HDMI 2.0 capability. drm_connector_hdmi_scrambler_supported() > * relies on their presence to report scrambling support. > */ > if (hdmi_funcs->scrambler_enable || hdmi_funcs->scrambler_disable) { > drm_err(dev, "Scrambler callbacks unexpected for HDMI 1.x\n"); > return -EINVAL; > } > } I don't think we need the else clause at all. It's not valid, but it's also not creating any issue. I'd move that second check earlier together with the infoframe callbacks checks and so on too. > > >> if (hdmi_funcs->supported_tmds_char_rate) { > >> if (hdmi_funcs->supported_tmds_char_rate > connector->hdmi.max_tmds_char_rate) { > >> @@ -635,6 +652,7 @@ int drmm_connector_hdmi_init(struct drm_device *dev, > >> connector->hdmi.max_tmds_char_rate); > >> return -EINVAL; > >> } > >> + > >> connector->hdmi.max_tmds_char_rate = hdmi_funcs->supported_tmds_char_rate; > >> } > > [...] > > >> + /** > >> + * @scdc_work: Work item currently used to monitor sink-side scrambling > >> + * status and retry setup if the sink resets it. > >> + */ > >> + struct delayed_work scdc_work; > >> + > >> + /** > >> + * @scdc_work_initialized: Tracks whether @scdc_work has been set up via > >> + * INIT_DELAYED_WORK(). The work item is initialized lazily on the first > >> + * scrambling enable, so this guards the teardown paths against touching > >> + * an uninitialized work item. > >> + */ > >> + bool scdc_work_initialized; > >> + > > > > Why should we track whether it's initialized or not? I'd always > > initialize it, but only ever schedule something if we're using the > > scrambler. > > Having this initialized in the connector would lead to a module dependency > cycle. > > Currently the work function lives in drm_hdmi_helper.c, which is built into > drm_display_helper module: > > static void drm_connector_hdmi_scdc_work(struct work_struct *work) > { > [...] > if (READ_ONCE(connector->hdmi.scrambler_enabled) && > !drm_scdc_get_scrambling_status(connector)) > drm_connector_hdmi_try_scrambling_setup(connector); > [...] > } > > int drm_connector_hdmi_enable_scrambling(struct drm_connector *connector, > const struct drm_connector_state *conn_state) > { > > [...] > if (!hdmi->scdc_work_initialized) { > INIT_DELAYED_WORK(&hdmi->scdc_work, > drm_connector_hdmi_scdc_work); > hdmi->scdc_work_initialized = true; > } > [...] > } > > If we move INIT_DELAYED_WORK() into the connector (i.e. in drm.ko), the work > function has to be reachable from there. The following attempts to accomplish > that would fail: > > - Keep the work function in drm_hdmi_helper.c and export it from > drm_display_helper. > > - Move the work function into drm_connector.c and export > drm_connector_hdmi_try_scrambling_setup(), or a wrapper function, from > drm_display_helper. An alternative could be to move drm_connector_hdmi_init to drm_hdmi_helper.c, no? But yeah, if we can't let's keep it like that Maxime
On 8/20/26 7:56 PM, Maxime Ripard wrote: > On Wed, Aug 19, 2026 at 10:33:04PM +0300, Cristian Ciocaltea wrote: >> On 8/19/26 1:12 PM, Maxime Ripard wrote: >>> On Fri, Jul 31, 2026 at 07:19:14PM +0300, Cristian Ciocaltea wrote: >>>> Add the connector-level infrastructure to support HDMI 2.0 scrambling: >>>> >>>> - A drm_connector_hdmi_scrambler_supported() helper to report whether >>>> the source supports the scrambling capability, based on the presence >>>> of the newly introduced .scrambler_{enable|disable}() callbacks in >>>> drm_connector_hdmi_funcs are mandatory >>>> - A scrambler_needed flag to be managed by the hdmi state helpers based >>>> on the negotiated TMDS character rate and the source/sink scrambling >>>> capabilities >>>> - A scrambler_enabled flag to track whether scrambling is currently >>>> active >>>> - A delayed work item (scdc_work) to monitor sink-side scrambling status >>>> and retry the setup if the sink resets it >>>> - A scdc_work_initialized flag to support lazy initialization of the >>>> work item on the first scrambling enable and guard the teardown paths >>>> >>>> These are intended to be used by SCDC scrambling helpers to coordinate >>>> scrambling setup and teardown between the source driver and the DRM >>>> core. >>>> >>>> Tested-by: Maud Spierings <maud_spierings@hotmail.com> >>>> Tested-by: Diederik de Haas <diederik@cknow-tech.com> # NanoPC-T6 LTS, Rock 5B >>>> Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com> >>>> --- >>>> drivers/gpu/drm/drm_connector.c | 31 ++++++++++++--- >>>> include/drm/drm_connector.h | 83 +++++++++++++++++++++++++++++++++++++++++ >>>> 2 files changed, 109 insertions(+), 5 deletions(-) >>>> >>>> diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c >>>> index 4721cdeafc84..a18410faf040 100644 >>>> --- a/drivers/gpu/drm/drm_connector.c >>>> +++ b/drivers/gpu/drm/drm_connector.c >>>> @@ -622,12 +622,29 @@ int drmm_connector_hdmi_init(struct drm_device *dev, >>>> * default with the actual controller capability. A value of zero keeps >>>> * the limit inferred from supported_hdmi_ver. >>>> */ >>>> - if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) >>>> + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) { >>>> + if (!hdmi_funcs->scrambler_enable || !hdmi_funcs->scrambler_disable) { >>>> + drm_err(dev, "Scrambler callbacks missing for HDMI 2.x\n"); >>>> + return -EINVAL; >>>> + } >>>> + >>>> connector->hdmi.max_tmds_char_rate = HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ; >>>> - else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) >>>> - connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; >>>> - else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) >>>> - connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; >>>> + } else { >>>> + /* >>>> + * Scrambler callbacks are only valid for connectors advertising >>>> + * HDMI 2.0 capability. drm_connector_hdmi_scrambler_supported() >>>> + * relies on their presence to report scrambling support. >>>> + */ >>>> + if (hdmi_funcs->scrambler_enable || hdmi_funcs->scrambler_disable) { >>>> + drm_err(dev, "Scrambler callbacks unexpected for HDMI 1.x\n"); >>>> + return -EINVAL; >>>> + } >>>> + >>>> + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) >>>> + connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; >>>> + else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) >>>> + connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; >>>> + } >>> >>> I'd put it into a separate test (possibly earlier). Merging both the >>> tmds rate default and the scrambler callbacks check makes it messier >>> than it would be if we had two separate tests. >> >> Ack. How about the following? >> >> if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) >> connector->hdmi.max_tmds_char_rate = HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ; >> else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) >> connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; >> else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) >> connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; >> >> if (hdmi_funcs->supported_tmds_char_rate) { >> if (hdmi_funcs->supported_tmds_char_rate > connector->hdmi.max_tmds_char_rate) { >> drm_err(dev, "Enforced max_tmds_char_rate exceeds %llu spec limit\n", >> connector->hdmi.max_tmds_char_rate); >> return -EINVAL; >> } >> >> connector->hdmi.max_tmds_char_rate = hdmi_funcs->supported_tmds_char_rate; >> } >> >> if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) { >> if (!hdmi_funcs->scrambler_enable || !hdmi_funcs->scrambler_disable) { >> drm_err(dev, "Scrambler callbacks missing for HDMI 2.x\n"); >> return -EINVAL; >> } >> } else { >> /* >> * Scrambler callbacks are only valid for connectors advertising >> * HDMI 2.0 capability. drm_connector_hdmi_scrambler_supported() >> * relies on their presence to report scrambling support. >> */ >> if (hdmi_funcs->scrambler_enable || hdmi_funcs->scrambler_disable) { >> drm_err(dev, "Scrambler callbacks unexpected for HDMI 1.x\n"); >> return -EINVAL; >> } >> } > > I don't think we need the else clause at all. It's not valid, but it's > also not creating any issue. As discussed a while ago, we used to have a scrambler_supported flag, inferred from supported_hdmi_ver, which allowed helpers to verify the capability when needed. That flag has now been removed and replaced by drm_connector_hdmi_scrambler_supported(), which relies exclusively on the presence of the scrambler callbacks to report whether the capability is supported. If we don't ensure that these callbacks are *not* set for HDMI 1.x cases, one could set supported_hdmi_ver to HDMI_VERSION_1_4, for example, while still providing the scrambler_{enable,disable} funcs. This would lead to an inconsistency between the maximum TMDS character rate inferred from supported_hdmi_ver and the capability reported by drm_connector_hdmi_scrambler_supported(). > I'd move that second check earlier together with the infoframe callbacks > checks and so on too. Ack. >>>> if (hdmi_funcs->supported_tmds_char_rate) { >>>> if (hdmi_funcs->supported_tmds_char_rate > connector->hdmi.max_tmds_char_rate) { >>>> @@ -635,6 +652,7 @@ int drmm_connector_hdmi_init(struct drm_device *dev, >>>> connector->hdmi.max_tmds_char_rate); >>>> return -EINVAL; >>>> } >>>> + >>>> connector->hdmi.max_tmds_char_rate = hdmi_funcs->supported_tmds_char_rate; >>>> } >> >> [...] >> >>>> + /** >>>> + * @scdc_work: Work item currently used to monitor sink-side scrambling >>>> + * status and retry setup if the sink resets it. >>>> + */ >>>> + struct delayed_work scdc_work; >>>> + >>>> + /** >>>> + * @scdc_work_initialized: Tracks whether @scdc_work has been set up via >>>> + * INIT_DELAYED_WORK(). The work item is initialized lazily on the first >>>> + * scrambling enable, so this guards the teardown paths against touching >>>> + * an uninitialized work item. >>>> + */ >>>> + bool scdc_work_initialized; >>>> + >>> >>> Why should we track whether it's initialized or not? I'd always >>> initialize it, but only ever schedule something if we're using the >>> scrambler. >> >> Having this initialized in the connector would lead to a module dependency >> cycle. >> >> Currently the work function lives in drm_hdmi_helper.c, which is built into >> drm_display_helper module: >> >> static void drm_connector_hdmi_scdc_work(struct work_struct *work) >> { >> [...] >> if (READ_ONCE(connector->hdmi.scrambler_enabled) && >> !drm_scdc_get_scrambling_status(connector)) >> drm_connector_hdmi_try_scrambling_setup(connector); >> [...] >> } >> >> int drm_connector_hdmi_enable_scrambling(struct drm_connector *connector, >> const struct drm_connector_state *conn_state) >> { >> >> [...] >> if (!hdmi->scdc_work_initialized) { >> INIT_DELAYED_WORK(&hdmi->scdc_work, >> drm_connector_hdmi_scdc_work); >> hdmi->scdc_work_initialized = true; >> } >> [...] >> } >> >> If we move INIT_DELAYED_WORK() into the connector (i.e. in drm.ko), the work >> function has to be reachable from there. The following attempts to accomplish >> that would fail: >> >> - Keep the work function in drm_hdmi_helper.c and export it from >> drm_display_helper. >> >> - Move the work function into drm_connector.c and export >> drm_connector_hdmi_try_scrambling_setup(), or a wrapper function, from >> drm_display_helper. > > An alternative could be to move drm_connector_hdmi_init to > drm_hdmi_helper.c, no? I haven't considered this option so far, as I believe it would also require some refactoring to get right - for example, moving HDMI-related initialization from the generic drm_connector_init_only() to drm_connector_hdmi_init(), and splitting drm_connector_cleanup() into a dedicated drm_connector_hdmi_cleanup() utility. > But yeah, if we can't let's keep it like that Should I proceed with this refactoring, or would it be better to postpone it until I send out the HDMI 2.1 patches, to avoid expanding this series even further? Thanks, Cristian
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 4721cdeafc84..a18410faf040 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -622,12 +622,29 @@ int drmm_connector_hdmi_init(struct drm_device *dev, * default with the actual controller capability. A value of zero keeps * the limit inferred from supported_hdmi_ver. */ - if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) { + if (!hdmi_funcs->scrambler_enable || !hdmi_funcs->scrambler_disable) { + drm_err(dev, "Scrambler callbacks missing for HDMI 2.x\n"); + return -EINVAL; + } + connector->hdmi.max_tmds_char_rate = HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ; - else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) - connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; - else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) - connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; + } else { + /* + * Scrambler callbacks are only valid for connectors advertising + * HDMI 2.0 capability. drm_connector_hdmi_scrambler_supported() + * relies on their presence to report scrambling support. + */ + if (hdmi_funcs->scrambler_enable || hdmi_funcs->scrambler_disable) { + drm_err(dev, "Scrambler callbacks unexpected for HDMI 1.x\n"); + return -EINVAL; + } + + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) + connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; + else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) + connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; + } if (hdmi_funcs->supported_tmds_char_rate) { if (hdmi_funcs->supported_tmds_char_rate > connector->hdmi.max_tmds_char_rate) { @@ -635,6 +652,7 @@ int drmm_connector_hdmi_init(struct drm_device *dev, connector->hdmi.max_tmds_char_rate); return -EINVAL; } + connector->hdmi.max_tmds_char_rate = hdmi_funcs->supported_tmds_char_rate; } @@ -918,6 +936,9 @@ void drm_connector_cleanup(struct drm_connector *connector) DRM_CONNECTOR_REGISTERED)) drm_connector_unregister(connector); + if (connector->hdmi.scdc_work_initialized) + cancel_delayed_work_sync(&connector->hdmi.scdc_work); + platform_device_unregister(connector->hdmi_audio.codec_pdev); if (connector->privacy_screen) { diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index a6de3e63b462..89a140d6f064 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -28,6 +28,7 @@ #include <linux/ctype.h> #include <linux/hdmi.h> #include <linux/notifier.h> +#include <linux/workqueue.h> #include <drm/drm_mode_object.h> #include <drm/drm_util.h> #include <drm/drm_property.h> @@ -1131,6 +1132,17 @@ struct drm_connector_hdmi_state { * @tmds_char_rate: TMDS Character Rate, in Hz. */ unsigned long long tmds_char_rate; + + /** + * @scrambler_needed: Whether HDMI 2.0 SCDC scrambling is required + * for the negotiated mode/bpc/format. + * + * Computed by drm_atomic_helper_connector_hdmi_check() according to + * the HDMI 2.0 specification: scrambling is mandatory above a 340 MHz + * TMDS character rate. Optional scrambling at lower rates is + * deliberately not requested by the helper. + */ + bool scrambler_needed; }; /** @@ -1481,6 +1493,36 @@ struct drm_connector_hdmi_funcs { */ const struct drm_edid *(*read_edid)(struct drm_connector *connector); + /** + * @scrambler_enable: + * + * The callback is invoked via @drm_connector_hdmi_enable_scrambling + * during commit to setup SCDC scrambling and high TMDS clock ratio on + * the source side. + * + * The @scrambler_enable callback is mandatory if HDMI 2.0 is to be + * supported. + * + * Returns: + * 0 on success, a negative error code otherwise + */ + int (*scrambler_enable)(struct drm_connector *connector); + + /** + * @scrambler_disable: + * + * The callback is invoked via @drm_connector_hdmi_disable_scrambling + * during commit to tear down SCDC scrambling and high TMDS clock ratio + * on the source side. + * + * The @scrambler_disable callback is mandatory if HDMI 2.0 is to be + * supported. + * + * Returns: + * 0 on success, a negative error code otherwise + */ + int (*scrambler_disable)(struct drm_connector *connector); + /** * @avi: * @@ -2111,6 +2153,25 @@ struct drm_connector_hdmi { */ unsigned long long max_tmds_char_rate; + /** + * @scrambler_enabled: Tracks whether HDMI 2.0 scrambler is currently enabled. + */ + bool scrambler_enabled; + + /** + * @scdc_work: Work item currently used to monitor sink-side scrambling + * status and retry setup if the sink resets it. + */ + struct delayed_work scdc_work; + + /** + * @scdc_work_initialized: Tracks whether @scdc_work has been set up via + * INIT_DELAYED_WORK(). The work item is initialized lazily on the first + * scrambling enable, so this guards the teardown paths against touching + * an uninitialized work item. + */ + bool scdc_work_initialized; + /** * @funcs: HDMI connector Control Functions and controller capabilities */ @@ -2702,6 +2763,28 @@ drm_connector_is_unregistered(struct drm_connector *connector) DRM_CONNECTOR_UNREGISTERED; } +/** + * drm_connector_hdmi_scrambler_supported - does connector support HDMI + * scrambling? + * @connector: DRM connector + * + * Checks whether or not @connector driver supports HDMI scrambling. When + * controller advertises HDMI_VERSION_2_0 support, the + * @drm_connector_hdmi_funcs.scrambler_enable and + * @drm_connector_hdmi_funcs.scrambler_disable callbacks are mandatory and + * enforced during drmm_connector_hdmi_init(). + * + * Returns: + * True if the connector supports scrambling, false otherwise. + */ +static inline bool +drm_connector_hdmi_scrambler_supported(struct drm_connector *connector) +{ + return connector->hdmi.funcs && + connector->hdmi.funcs->scrambler_enable && + connector->hdmi.funcs->scrambler_disable; +} + void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, enum drm_connector_status status); enum drm_connector_color_format