From: Sebastian Reichel <sebastian.reichel@collabora.com>
To: "Sandy Huang" <hjc@rock-chips.com>,
"Heiko Stübner" <heiko@sntech.de>,
"Andy Yan" <andy.yan@rock-chips.com>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"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>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Dmitry Baryshkov" <dmitry.baryshkov@oss.qualcomm.com>,
"Luca Ceresoli" <luca.ceresoli@bootlin.com>
Cc: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>,
Damon Ding <damon.ding@rock-chips.com>,
Dmitry Baryshkov <lumag@kernel.org>,
Alexey Charkov <alchark@gmail.com>,
dri-devel@lists.freedesktop.org,
linux-rockchip@lists.infradead.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
kernel@collabora.com, linux-arm-kernel@lists.infradead.org,
Sebastian Reichel <sebastian.reichel@collabora.com>
Subject: [PATCH v2 12/12] drm/bridge: synopsys: dw-dp: Add audio support
Date: Fri, 01 May 2026 00:20:39 +0200 [thread overview]
Message-ID: <20260501-synopsys-dw-dp-improvements-v2-12-d7e7f6bac77f@collabora.com> (raw)
In-Reply-To: <20260501-synopsys-dw-dp-improvements-v2-0-d7e7f6bac77f@collabora.com>
Implement audio support for the Synopsys DesignWare DisplayPort
controller.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 221 +++++++++++++++++++++++++++++++-
1 file changed, 220 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index b0ab2b3d478d..9553bad690d9 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -23,17 +23,21 @@
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/display/drm_dp_helper.h>
+#include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
+#include <sound/hdmi-codec.h>
+
#define DW_DP_VERSION_NUMBER 0x0000
#define DW_DP_VERSION_TYPE 0x0004
#define DW_DP_ID 0x0008
#define DW_DP_CONFIG_REG1 0x0100
+#define AUDIO_SELECT GENMASK(2, 1)
#define DW_DP_CONFIG_REG2 0x0104
#define DW_DP_CONFIG_REG3 0x0108
@@ -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)
#define DW_DP_SDP_VERTICAL_CTRL 0x0500
@@ -253,6 +261,8 @@
#define SDP_REG_BANK_SIZE 16
+#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;
};
+enum dw_dp_audio_interface_support {
+ DW_DP_AUDIO_I2S_ONLY = 0,
+ DW_DP_AUDIO_SPDIF_ONLY = 1,
+ DW_DP_AUDIO_I2S_AND_SPDIF = 2,
+ DW_DP_AUDIO_NONE = 3,
+};
+
+enum dw_dp_audio_interface {
+ DW_DP_AUDIO_I2S = 0,
+ DW_DP_AUDIO_SPDIF = 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;
@@ -1837,6 +1862,186 @@ static void dw_dp_bridge_oob_notify(struct drm_bridge *bridge,
dev_err_once(dp->dev, "Missing platform handler for OOB HPD handling\n");
}
+static int dw_dp_audio_infoframe_send(struct dw_dp *dp)
+{
+ struct hdmi_audio_infoframe frame;
+ struct dw_dp_sdp sdp;
+ int ret;
+
+ ret = hdmi_audio_infoframe_init(&frame);
+ if (ret < 0)
+ return ret;
+
+ frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
+ frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
+ frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
+ frame.channels = dp->audio_channels;
+
+ ret = hdmi_audio_infoframe_pack_for_dp(&frame, &sdp.base, DW_DP_SDP_VERSION);
+ if (ret < 0)
+ return ret;
+
+ sdp.flags = DW_DP_SDP_VERTICAL_INTERVAL;
+
+ return dw_dp_send_sdp(dp, &sdp);
+}
+
+static int dw_dp_audio_startup(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ dev_dbg(dp->dev, "audio startup\n");
+ pm_runtime_get_sync(dp->dev);
+
+ return 0;
+}
+
+static void dw_dp_audio_unprepare(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct dw_dp *dp = bridge_to_dp(bridge);
+
+ /* 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 == DW_DP_AUDIO_SPDIF)
+ clk_disable_unprepare(dp->spdif_clk);
+ else if (dp->audio_interface == DW_DP_AUDIO_I2S)
+ clk_disable_unprepare(dp->i2s_clk);
+
+ dp->audio_interface = DW_DP_AUDIO_UNUSED;
+}
+
+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 = bridge_to_dp(bridge);
+ u8 audio_data_in_en, supported_audio_interfaces;
+ u32 cfg1;
+ int ret;
+
+ /*
+ * prepare might be called multiple times, so release the clocks
+ * from previous calls to keep the calls in balance.
+ */
+ if (dp->audio_interface != DW_DP_AUDIO_UNUSED)
+ dw_dp_audio_unprepare(bridge, connector);
+
+ dp->audio_channels = params->cea.channels;
+ switch (params->cea.channels) {
+ case 1:
+ case 2:
+ audio_data_in_en = AUDIO_DATA_IN_EN_CHANNEL12;
+ break;
+ case 8:
+ audio_data_in_en = 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 = 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 = 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 = FIELD_GET(AUDIO_SELECT, cfg1);
+
+ if (supported_audio_interfaces != DW_DP_AUDIO_I2S_AND_SPDIF &&
+ supported_audio_interfaces != 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 == DW_DP_AUDIO_I2S)
+ clk_disable_unprepare(dp->spdif_clk);
+ else if (dp->audio_interface == 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 = dw_dp_audio_infoframe_send(dp);
+ if (ret < 0)
+ dev_err(dp->dev, "failed to send audio infoframe\n");
+
+ dev_dbg(dp->dev, "audio prepare with %d channels using DAI=%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 = bridge_to_dp(bridge);
+
+ dev_dbg(dp->dev, "audio shutdown\n");
+
+ dw_dp_audio_unprepare(bridge, connector);
+ 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 = 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 = {
.atomic_duplicate_state = dw_dp_bridge_atomic_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
@@ -1850,6 +2055,11 @@ static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
.detect = dw_dp_bridge_detect,
.edid_read = dw_dp_bridge_edid_read,
.oob_notify = dw_dp_bridge_oob_notify,
+
+ .dp_audio_startup = dw_dp_audio_startup,
+ .dp_audio_prepare = dw_dp_audio_prepare,
+ .dp_audio_shutdown = dw_dp_audio_shutdown,
+ .dp_audio_mute_stream = dw_dp_audio_mute_stream,
};
static int dw_dp_link_retrain(struct dw_dp *dp)
@@ -2076,10 +2286,19 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
}
bridge->of_node = dev->of_node;
- bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
+ bridge->ops = DRM_BRIDGE_OP_DP_AUDIO |
+ DRM_BRIDGE_OP_DETECT |
+ DRM_BRIDGE_OP_EDID |
+ DRM_BRIDGE_OP_HPD;
bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
bridge->ycbcr_420_allowed = true;
+ dp->audio_interface = DW_DP_AUDIO_UNUSED;
+ bridge->hdmi_audio_dev = dev;
+ bridge->hdmi_audio_max_i2s_playback_channels = 8;
+ bridge->hdmi_audio_dai_port = 1;
+ bridge->hdmi_audio_spdif_playback = true;
+
ret = devm_drm_bridge_add(dev, bridge);
if (ret)
return ERR_PTR(ret);
--
2.53.0
prev parent reply other threads:[~2026-04-30 22:21 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-30 22:20 [PATCH v2 00/12] Synopsys DisplayPort Controller improvements for Rockchip platforms Sebastian Reichel
2026-04-30 22:20 ` [PATCH v2 01/12] drm/bridge: synopsys: dw-dp: Support unregistering the AUX channel Sebastian Reichel
2026-04-30 22:20 ` [PATCH v2 02/12] drm/rockchip: dw_dp: Release core resources Sebastian Reichel
2026-04-30 22:20 ` [PATCH v2 03/12] drm/bridge: synopsys: dw-dp: Simplify driver data setting Sebastian Reichel
2026-04-30 22:20 ` [PATCH v2 04/12] drm/bridge: synopsys: dw-dp: Support MEDIA_BUS_FMT_FIXED Sebastian Reichel
2026-04-30 22:20 ` [PATCH v2 05/12] drm/bridge: synopsys: dw-dp: Add follow-up bridge support Sebastian Reichel
2026-04-30 22:20 ` [PATCH v2 06/12] drm/bridge: Add out-of-band HPD notify handler Sebastian Reichel
2026-04-30 22:20 ` [PATCH v2 07/12] drm/rockchip: dw_dp: Implement out-of-band HPD handling Sebastian Reichel
2026-04-30 22:20 ` [PATCH v2 08/12] drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD Sebastian Reichel
2026-04-30 22:20 ` [PATCH v2 09/12] drm/bridge: synopsys: dw-dp: Add Runtime PM support Sebastian Reichel
2026-04-30 22:20 ` [PATCH v2 10/12] drm/rockchip: dw_dp: Add runtime " Sebastian Reichel
2026-04-30 22:20 ` [PATCH RFC v2 11/12] dt-bindings: display: rockchip: dw-dp: fix sound DAI cells Sebastian Reichel
2026-04-30 22:20 ` Sebastian Reichel [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260501-synopsys-dw-dp-improvements-v2-12-d7e7f6bac77f@collabora.com \
--to=sebastian.reichel@collabora.com \
--cc=Laurent.pinchart@ideasonboard.com \
--cc=airlied@gmail.com \
--cc=alchark@gmail.com \
--cc=andrzej.hajda@intel.com \
--cc=andy.yan@rock-chips.com \
--cc=conor+dt@kernel.org \
--cc=cristian.ciocaltea@collabora.com \
--cc=damon.ding@rock-chips.com \
--cc=devicetree@vger.kernel.org \
--cc=dmitry.baryshkov@oss.qualcomm.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=heiko@sntech.de \
--cc=hjc@rock-chips.com \
--cc=jernej.skrabec@gmail.com \
--cc=jonas@kwiboo.se \
--cc=kernel@collabora.com \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rockchip@lists.infradead.org \
--cc=luca.ceresoli@bootlin.com \
--cc=lumag@kernel.org \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@kernel.org \
--cc=neil.armstrong@linaro.org \
--cc=rfoss@kernel.org \
--cc=robh@kernel.org \
--cc=simona@ffwll.ch \
--cc=tzimmermann@suse.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox