From nobody Thu Apr 2 20:28:24 2026 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 22CF9426EC7; Thu, 26 Mar 2026 17:31:48 +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=1774546315; cv=none; b=h7DugPNnh/Jc3H0hN56pX7OVFyEaGA9ACO2H7nOvvk63KzuOmMRPgGpzN73IyGupgHVf+LSeAQth1fRhwlzzwD5Zcyo8mqrIirZur1pP7aJs4Kzl1BCbAmaYfp9ERfD5dl/aQ97vr0hEK+o0iKPEQJNImzI6d/rAQgr4V15bSqc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774546315; c=relaxed/simple; bh=6S6X6w6unuVLgPGbQxKbzXT/phbegI3vC2Fyg0CPYNg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ZZ/ob3IvpiueP47B0aceKoR1IkWvRzy2YuD7/4vYPxUdsGXvVyc7B5qbNlBPCxFNhR+oe1MCPbCN9u/zL3zF7jY7+EUJVoTNVNNwJR0eDLVb+b14eUBOa9mI7ZrSSoWGVcIj3qJ1qwaJiBjaRYdjnAqSFZVHZCXXHl11/Uefd9g= 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=n/LqlNsK; 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 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b="n/LqlNsK" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1774546299; bh=6S6X6w6unuVLgPGbQxKbzXT/phbegI3vC2Fyg0CPYNg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=n/LqlNsK0TStOQobeLQFpjno9z/GIJvfVSSU+pFEKI6oUzBcpfxEnRcWVFQprCn+O TZefqhWiZvnMkL8NWKwbugc1yOCMxZELJ3LITSe4C3SfKWGCiuKXfOizYAXL/V9Aoh WOFyqL1P4sA/eEilUhwYG6d/3ySox9e42OhkqrrFkQhgNnH707wLBYmB5ZlPfqXd2E oaDuYf2cc8NY8TR6qlUXH/e95c0ZiH4g+yxtzmtcf8dtkvjL6K27pG7901NgFPcjS1 +T36Qq747rWsJccndqZNza892GrYy2xbFt2wEsCbuzOUA1gKWQrUcEUJs18dL3IKwo UlCIxxyk5nhmA== Received: from jupiter.universe (dyndsl-091-248-208-149.ewe-ip-backbone.de [91.248.208.149]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) (Authenticated sender: sre) by bali.collaboradmins.com (Postfix) with ESMTPSA id 05E2517E6197; Thu, 26 Mar 2026 18:31:39 +0100 (CET) Received: by jupiter.universe (Postfix, from userid 1000) id 4D05048003D; Thu, 26 Mar 2026 18:31:38 +0100 (CET) From: Sebastian Reichel Date: Thu, 26 Mar 2026 18:31:34 +0100 Subject: [PATCH 10/10] drm/bridge: synopsys: dw-dp: Add audio support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260326-synopsys-dw-dp-improvements-v1-10-501849162290@collabora.com> References: <20260326-synopsys-dw-dp-improvements-v1-0-501849162290@collabora.com> In-Reply-To: <20260326-synopsys-dw-dp-improvements-v1-0-501849162290@collabora.com> To: Sandy Huang , =?utf-8?q?Heiko_St=C3=BCbner?= , Andy Yan , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , Andrzej Hajda , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: Damon Ding , Dmitry Baryshkov , Alexey Charkov , dri-devel@lists.freedesktop.org, linux-rockchip@lists.infradead.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, kernel@collabora.com, Sebastian Reichel X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=8786; i=sebastian.reichel@collabora.com; h=from:subject:message-id; bh=6S6X6w6unuVLgPGbQxKbzXT/phbegI3vC2Fyg0CPYNg=; b=owJ4nAFtApL9kA0DAAoB2O7X88g7+poByyZiAGnFbXpvByZfOmHKat1/ps0xUz9oHrPUciQg0 eVtIttocHoN+YkCMwQAAQoAHRYhBO9mDQdGP4tyanlUE9ju1/PIO/qaBQJpxW16AAoJENju1/PI O/qaescP/ju7HzPDSpER9MK0kBVpGo7FZ3rqhnwiBdCzY34xZUh28y4kqYiSE98xMvTWhRj/Zlx A1o+fr+ujAblgphCeIma1XdzVA14msksbYPx4y2/OYktoxvbXI3KpEQ64Sb+cua0de4Ysapx/ng tQZ0+/SJJH90xe+p0oTeatoQSllt7TKvYfiUNAmSRM3keXL7YgGVJupUOVWnbl9HKYZ3NMxYrir HVzDnWsHQCbPDbZkFtsY7RJwvOBRUHQoTdTNv0PxLnrCQXB5x92KYe/hZu+pOptYi3DfvP7LOJx aaJid00+pc067PHV23/WaizLJigXcSpAB8EhX/NdXj9hCZ91BsGpNsL1kMHJBrRB8K9JZqTWgA+ gtoxNm5EFKn9V6v5jWYxXfcRh5Yz2g/y9vFN94cE5bC8fILTHIcGnMCmWsj28sGMMEaNGEGhIjz TwynUvFCtSwZVEnYzQ/lttTD4RO9dFYS52Jd4l9Oq+7g4mVLQRdXwqbLEFDTSBT9mqvcJdjyE1q ITebgI02Z6hKs6sbghBIlNk0xI3AroxanKyau/vhooN0/R4Y0WlZBM68GcngIl1cVbFOypLh9IQ GvwrXRqa7C8t0jPYvAimTxAMBpkuvIrJMiPc0RMYlYgFwlrxKN0ioXwSe2KdBFnYlHMYcqS0yCt Zb9bQrmw4iyLjnjsVnjgh+A== X-Developer-Key: i=sebastian.reichel@collabora.com; a=openpgp; fpr=EF660D07463F8B726A795413D8EED7F3C83BFA9A Implement audio support for the Synopsys DesignWare DisplayPort controller. Signed-off-by: Sebastian Reichel --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 197 ++++++++++++++++++++++++++++= +++- 1 file changed, 196 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/brid= ge/synopsys/dw-dp.c index 9ee9e3b9d912..add22acea719 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -23,17 +23,21 @@ #include #include #include +#include #include #include #include #include #include =20 +#include + #define DW_DP_VERSION_NUMBER 0x0000 #define DW_DP_VERSION_TYPE 0x0004 #define DW_DP_ID 0x0008 =20 #define DW_DP_CONFIG_REG1 0x0100 +#define AUDIO_SELECT GENMASK(2, 1) #define DW_DP_CONFIG_REG2 0x0104 #define DW_DP_CONFIG_REG3 0x0108 =20 @@ -110,6 +114,10 @@ #define HBR_MODE_ENABLE BIT(10) #define AUDIO_DATA_WIDTH GENMASK(9, 5) #define AUDIO_DATA_IN_EN GENMASK(4, 1) +#define AUDIO_DATA_IN_EN_CHANNEL12 BIT(0) +#define AUDIO_DATA_IN_EN_CHANNEL34 BIT(1) +#define AUDIO_DATA_IN_EN_CHANNEL56 BIT(2) +#define AUDIO_DATA_IN_EN_CHANNEL78 BIT(3) #define AUDIO_INF_SELECT BIT(0) =20 #define DW_DP_SDP_VERTICAL_CTRL 0x0500 @@ -253,6 +261,8 @@ =20 #define SDP_REG_BANK_SIZE 16 =20 +#define DW_DP_SDP_VERSION 0x12 + struct dw_dp_link_caps { bool enhanced_framing; bool tps3_supported; @@ -305,6 +315,19 @@ struct dw_dp_hotplug { bool long_hpd; }; =20 +enum dw_dp_audio_interface_support { + DW_DP_AUDIO_I2S_ONLY =3D 0, + DW_DP_AUDIO_SPDIF_ONLY =3D 1, + DW_DP_AUDIO_I2S_AND_SPDIF =3D 2, + DW_DP_AUDIO_NONE =3D 3, +}; + +enum dw_dp_audio_interface { + DW_DP_AUDIO_I2S =3D 0, + DW_DP_AUDIO_SPDIF =3D 1, + DW_DP_AUDIO_UNUSED, +}; + struct dw_dp { struct drm_bridge bridge; struct device *dev; @@ -320,6 +343,8 @@ struct dw_dp { int irq; struct work_struct hpd_work; struct dw_dp_hotplug hotplug; + enum dw_dp_audio_interface audio_interface; + int audio_channels; /* Serialize hpd status access */ struct mutex irq_lock; =20 @@ -1844,6 +1869,163 @@ static void dw_dp_bridge_oob_notify(struct drm_brid= ge *bridge, dev_err_once(dp->dev, "Missing platform handler for OOB HPD handling\n"); } =20 +static int dw_dp_audio_infoframe_send(struct dw_dp *dp) +{ + struct hdmi_audio_infoframe frame; + struct dw_dp_sdp sdp; + int ret; + + ret =3D hdmi_audio_infoframe_init(&frame); + if (ret < 0) + return ret; + + frame.coding_type =3D HDMI_AUDIO_CODING_TYPE_STREAM; + frame.sample_frequency =3D HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; + frame.sample_size =3D HDMI_AUDIO_SAMPLE_SIZE_STREAM; + frame.channels =3D dp->audio_channels; + + ret =3D hdmi_audio_infoframe_pack_for_dp(&frame, &sdp.base, DW_DP_SDP_VER= SION); + if (ret < 0) + return ret; + + sdp.flags =3D DW_DP_SDP_VERTICAL_INTERVAL; + + return dw_dp_send_sdp(dp, &sdp); +} + +static int dw_dp_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct dw_dp *dp =3D bridge_to_dp(bridge); + u8 audio_data_in_en, supported_audio_interfaces; + u32 cfg1; + int ret; + + pm_runtime_get_sync(dp->dev); + + dp->audio_channels =3D params->cea.channels; + switch (params->cea.channels) { + case 1: + case 2: + audio_data_in_en =3D AUDIO_DATA_IN_EN_CHANNEL12; + break; + case 8: + audio_data_in_en =3D AUDIO_DATA_IN_EN_CHANNEL12 | + AUDIO_DATA_IN_EN_CHANNEL34 | + AUDIO_DATA_IN_EN_CHANNEL56 | + AUDIO_DATA_IN_EN_CHANNEL78; + break; + default: + dev_err(dp->dev, "invalid audio channels %d\n", dp->audio_channels); + return -EINVAL; + } + + switch (daifmt->fmt) { + case HDMI_SPDIF: + dp->audio_interface =3D DW_DP_AUDIO_SPDIF; + break; + case HDMI_I2S: + /* + * It is recommended to use SPDIF instead of I2S, since I2S mode requires + * manually inserting PCUV control bits from userspace and this is done + * automatically in hardware for SPDIF mode. + */ + dp->audio_interface =3D DW_DP_AUDIO_I2S; + break; + default: + dev_err(dp->dev, "invalid DAI format %d\n", daifmt->fmt); + return -EINVAL; + } + + regmap_read(dp->regmap, DW_DP_CONFIG_REG1, &cfg1); + supported_audio_interfaces =3D FIELD_GET(AUDIO_SELECT, cfg1); + + if (supported_audio_interfaces !=3D DW_DP_AUDIO_I2S_AND_SPDIF && + supported_audio_interfaces !=3D dp->audio_interface) { + dev_err(dp->dev, "unsupported DAI %d\n", daifmt->fmt); + return -EINVAL; + } + + clk_prepare_enable(dp->spdif_clk); + clk_prepare_enable(dp->i2s_clk); + + regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, + AUDIO_DATA_IN_EN | NUM_CHANNELS | AUDIO_DATA_WIDTH | + AUDIO_INF_SELECT | HBR_MODE_ENABLE, + FIELD_PREP(AUDIO_DATA_IN_EN, audio_data_in_en) | + FIELD_PREP(NUM_CHANNELS, dp->audio_channels - 1) | + FIELD_PREP(AUDIO_DATA_WIDTH, params->sample_width) | + FIELD_PREP(AUDIO_INF_SELECT, dp->audio_interface) | + FIELD_PREP(HBR_MODE_ENABLE, 0)); + + /* Wait for inf switch */ + usleep_range(20, 40); + + if (dp->audio_interface =3D=3D DW_DP_AUDIO_I2S) + clk_disable_unprepare(dp->spdif_clk); + else if (dp->audio_interface =3D=3D DW_DP_AUDIO_SPDIF) + clk_disable_unprepare(dp->i2s_clk); + + /* + * Send audio stream during vertical and horizontal blanking periods. + * Send out audio timestamp SDP once per video frame during the vertical + * blanking period + */ + regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL, + EN_AUDIO_STREAM_SDP | EN_AUDIO_TIMESTAMP_SDP, + FIELD_PREP(EN_AUDIO_STREAM_SDP, 1) | + FIELD_PREP(EN_AUDIO_TIMESTAMP_SDP, 1)); + regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL, + EN_AUDIO_STREAM_SDP, + FIELD_PREP(EN_AUDIO_STREAM_SDP, 1)); + + ret =3D dw_dp_audio_infoframe_send(dp); + if (ret < 0) + dev_err(dp->dev, "failed to send audio infoframe\n"); + + dev_dbg(dp->dev, "audio start with %d channels using DAI=3D%d\n", + dp->audio_channels, dp->audio_interface); + + return 0; +} + +static void dw_dp_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_dp *dp =3D bridge_to_dp(bridge); + + dev_dbg(dp->dev, "audio shutdown\n"); + + /* Disable all audio streams */ + regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_DATA_IN_EN, + FIELD_PREP(AUDIO_DATA_IN_EN, 0)); + + if (dp->audio_interface =3D=3D DW_DP_AUDIO_SPDIF) + clk_disable_unprepare(dp->spdif_clk); + else if (dp->audio_interface =3D=3D DW_DP_AUDIO_I2S) + clk_disable_unprepare(dp->i2s_clk); + + dp->audio_interface =3D DW_DP_AUDIO_UNUSED; + + pm_runtime_put_autosuspend(dp->dev); +} + +static int dw_dp_audio_mute_stream(struct drm_bridge *bridge, + struct drm_connector *connector, + bool enable, int direction) +{ + struct dw_dp *dp =3D bridge_to_dp(bridge); + + dev_dbg(dp->dev, "audio %smute\n", enable ? "" : "un"); + + regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_MUTE, + FIELD_PREP(AUDIO_MUTE, enable)); + + return 0; +} + static const struct drm_bridge_funcs dw_dp_bridge_funcs =3D { .atomic_duplicate_state =3D dw_dp_bridge_atomic_duplicate_state, .atomic_destroy_state =3D drm_atomic_helper_bridge_destroy_state, @@ -1858,6 +2040,10 @@ static const struct drm_bridge_funcs dw_dp_bridge_fu= ncs =3D { .edid_read =3D dw_dp_bridge_edid_read, .detach =3D dw_dp_bridge_detach, .oob_notify =3D dw_dp_bridge_oob_notify, + + .dp_audio_prepare =3D dw_dp_audio_prepare, + .dp_audio_shutdown =3D dw_dp_audio_shutdown, + .dp_audio_mute_stream =3D dw_dp_audio_mute_stream, }; =20 static int dw_dp_link_retrain(struct dw_dp *dp) @@ -2084,10 +2270,19 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct= drm_encoder *encoder, } =20 bridge->of_node =3D dev->of_node; - bridge->ops =3D DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP= _HPD; + bridge->ops =3D DRM_BRIDGE_OP_DP_AUDIO | + DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD; bridge->type =3D DRM_MODE_CONNECTOR_DisplayPort; bridge->ycbcr_420_allowed =3D true; =20 + dp->audio_interface =3D DW_DP_AUDIO_UNUSED; + bridge->hdmi_audio_dev =3D dev; + bridge->hdmi_audio_max_i2s_playback_channels =3D 8; + bridge->hdmi_audio_dai_port =3D 1; + bridge->hdmi_audio_spdif_playback =3D true; + ret =3D devm_drm_bridge_add(dev, bridge); if (ret) return ERR_PTR(ret); --=20 2.53.0