Implement audio support for the Synopsys DesignWare DisplayPort
controller.

Signed-off-by: Sebastian Reichel <[email protected]>
---
 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 7f4f36c61484..f1946f2c945d 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

Reply via email to