Several DRM drivers implement HDMI codec support (despite its name it
applies to both HDMI and DisplayPort drivers). Implement generic
framework to be used by these drivers. This removes a requirement to
implement get_eld() callback and provides default implementation for
codec's plug handling.

The framework is integrated with the DRM HDMI Connector framework, but
can be used by DisplayPort drivers.

Signed-off-by: Dmitry Baryshkov <dmitry.barysh...@linaro.org>
---
 drivers/gpu/drm/Makefile                   |   1 +
 drivers/gpu/drm/drm_connector.c            |  10 ++
 drivers/gpu/drm/drm_connector_hdmi_codec.c | 186 +++++++++++++++++++++++++++++
 drivers/gpu/drm/drm_internal.h             |   5 +
 include/drm/drm_connector.h                |  80 +++++++++++++
 5 files changed, 282 insertions(+)

diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 
1677c1f335fbb0c6114bdb4cc0b12eb407d84564..afdd9268ca23ac7602e73bbe45f3f9cd090a3afd
 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -42,6 +42,7 @@ drm-y := \
        drm_cache.o \
        drm_color_mgmt.o \
        drm_connector.o \
+       drm_connector_hdmi_codec.o \
        drm_crtc.o \
        drm_displayid.o \
        drm_drv.o \
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 
fc35f47e2849ed6786d6223ac9c69e1c359fc648..1a155a9fb401f687e5a88e72faca1e81d944b6d2
 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -279,6 +279,7 @@ static int __drm_connector_init(struct drm_device *dev,
        mutex_init(&connector->mutex);
        mutex_init(&connector->edid_override_mutex);
        mutex_init(&connector->hdmi.infoframes.lock);
+       mutex_init(&connector->hdmi_codec.lock);
        connector->edid_blob_ptr = NULL;
        connector->epoch_counter = 0;
        connector->tile_blob_ptr = NULL;
@@ -533,6 +534,12 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
 
        connector->hdmi.funcs = hdmi_funcs;
 
+       if (connector->hdmi_codec.i2s || connector->hdmi_codec.spdif) {
+               ret = drm_connector_hdmi_codec_init(connector);
+               if (ret)
+                       return ret;
+       }
+
        return 0;
 }
 EXPORT_SYMBOL(drmm_connector_hdmi_init);
@@ -631,6 +638,8 @@ void drm_connector_cleanup(struct drm_connector *connector)
                    DRM_CONNECTOR_REGISTERED))
                drm_connector_unregister(connector);
 
+       drm_connector_hdmi_codec_cleanup(connector);
+
        if (connector->privacy_screen) {
                drm_privacy_screen_put(connector->privacy_screen);
                connector->privacy_screen = NULL;
@@ -669,6 +678,7 @@ void drm_connector_cleanup(struct drm_connector *connector)
                connector->funcs->atomic_destroy_state(connector,
                                                       connector->state);
 
+       mutex_destroy(&connector->hdmi_codec.lock);
        mutex_destroy(&connector->hdmi.infoframes.lock);
        mutex_destroy(&connector->mutex);
 
diff --git a/drivers/gpu/drm/drm_connector_hdmi_codec.c 
b/drivers/gpu/drm/drm_connector_hdmi_codec.c
new file mode 100644
index 
0000000000000000000000000000000000000000..e362852c186109644e9033eb8eaf63b864166d5a
--- /dev/null
+++ b/drivers/gpu/drm/drm_connector_hdmi_codec.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2024 Linaro Ltd
+ */
+
+#include <linux/mutex.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_connector.h>
+#include <drm/drm_device.h>
+
+#include <sound/hdmi-codec.h>
+
+#include "drm_internal.h"
+
+static int drm_connector_hdmi_codec_audio_startup(struct device *dev, void 
*data)
+{
+       struct drm_connector *connector = data;
+       const struct drm_connector_hdmi_codec_funcs *funcs =
+               connector->hdmi.funcs->codec_funcs;
+
+       if (funcs->audio_startup)
+               return funcs->audio_startup(connector);
+
+       return 0;
+}
+
+static int drm_connector_hdmi_codec_prepare(struct device *dev, void *data,
+                                           struct hdmi_codec_daifmt *fmt,
+                                           struct hdmi_codec_params *hparms)
+{
+       struct drm_connector *connector = data;
+       const struct drm_connector_hdmi_codec_funcs *funcs =
+               connector->hdmi.funcs->codec_funcs;
+
+       return funcs->prepare(connector, fmt, hparms);
+}
+
+static void drm_connector_hdmi_codec_audio_shutdown(struct device *dev, void 
*data)
+{
+       struct drm_connector *connector = data;
+       const struct drm_connector_hdmi_codec_funcs *funcs =
+               connector->hdmi.funcs->codec_funcs;
+
+       return funcs->audio_shutdown(connector);
+}
+
+static int drm_connector_hdmi_codec_mute_stream(struct device *dev, void *data,
+                                               bool enable, int direction)
+{
+       struct drm_connector *connector = data;
+       const struct drm_connector_hdmi_codec_funcs *funcs =
+               connector->hdmi.funcs->codec_funcs;
+
+       if (funcs->mute_stream)
+               return funcs->mute_stream(connector, enable, direction);
+
+       return -ENOTSUPP;
+}
+
+static int drm_connector_hdmi_codec_get_dai_id(struct snd_soc_component 
*comment,
+                 struct device_node *endpoint,
+                 void *data)
+{
+       struct drm_connector *connector = data;
+       struct of_endpoint of_ep;
+       int ret;
+
+       if (connector->hdmi_codec.sound_dai_port < 0)
+               return -ENOTSUPP;
+
+       ret = of_graph_parse_endpoint(endpoint, &of_ep);
+       if (ret < 0)
+               return ret;
+
+       if (of_ep.port == connector->hdmi_codec.sound_dai_port)
+               return 0;
+
+       return -EINVAL;
+}
+
+static int drm_connector_hdmi_codec_get_eld(struct device *dev, void *data,
+                                           uint8_t *buf, size_t len)
+{
+       struct drm_connector *connector = data;
+
+       //  FIXME: locking against drm_edid_to_eld ?
+       memcpy(buf, connector->eld, min(sizeof(connector->eld), len));
+
+       return 0;
+}
+
+static int drm_connector_hdmi_codec_hook_plugged_cb(struct device *dev,
+                                                   void *data,
+                                                   hdmi_codec_plugged_cb fn,
+                                                   struct device *codec_dev)
+{
+       struct drm_connector *connector = data;
+
+       mutex_lock(&connector->hdmi_codec.lock);
+
+       connector->hdmi_codec.plugged_cb = fn;
+       connector->hdmi_codec.plugged_cb_dev = codec_dev;
+
+       fn(codec_dev, connector->hdmi_codec.last_state);
+
+       mutex_unlock(&connector->hdmi_codec.lock);
+
+       return 0;
+}
+
+void drm_connector_hdmi_codec_plugged_notify(struct drm_connector *connector,
+                                            bool plugged)
+{
+       mutex_lock(&connector->hdmi_codec.lock);
+
+       connector->hdmi_codec.last_state = plugged;
+
+       if (connector->hdmi_codec.plugged_cb &&
+           connector->hdmi_codec.plugged_cb_dev)
+               
connector->hdmi_codec.plugged_cb(connector->hdmi_codec.plugged_cb_dev,
+                                                
connector->hdmi_codec.last_state);
+
+       mutex_unlock(&connector->hdmi_codec.lock);
+}
+EXPORT_SYMBOL(drm_connector_hdmi_codec_plugged_notify);
+
+static const struct hdmi_codec_ops drm_connector_hdmi_codec_ops = {
+       .audio_startup = drm_connector_hdmi_codec_audio_startup,
+       .prepare = drm_connector_hdmi_codec_prepare,
+       .audio_shutdown = drm_connector_hdmi_codec_audio_shutdown,
+       .mute_stream = drm_connector_hdmi_codec_mute_stream,
+       .get_eld = drm_connector_hdmi_codec_get_eld,
+       .get_dai_id = drm_connector_hdmi_codec_get_dai_id,
+       .hook_plugged_cb = drm_connector_hdmi_codec_hook_plugged_cb,
+};
+
+/**
+ * drm_connector_hdmi_codec_cleanup - Cleanup the HDMI Codec for the connector
+ * @connector: A pointer to the connector to cleanup
+ *
+ * Cleanup the HDMI codec device created for the specified connector.
+ * Can be called even if the codec wasn't allocated.
+ */
+void drm_connector_hdmi_codec_cleanup(struct drm_connector *connector)
+{
+       platform_device_unregister(connector->hdmi_codec.codec_pdev);
+}
+
+/**
+ * drm_connector_hdmi_codec_init - Initialize HDMI Codec device for the DRM 
connector
+ * @connector: A pointer to the connector to allocate codec for
+ *
+ * Create a HDMI codec device to be used with the specified connector.
+ *
+ * Returns:
+ * Zero on success, error code on failure.
+ */
+int drm_connector_hdmi_codec_init(struct drm_connector *connector)
+{
+       struct hdmi_codec_pdata codec_pdata = {};
+       struct platform_device *pdev;
+
+       if (!connector->hdmi.funcs->codec_funcs->prepare ||
+           !connector->hdmi.funcs->codec_funcs->audio_shutdown ||
+           !connector->hdmi_codec.dev)
+               return -EINVAL;
+
+       codec_pdata.ops = &drm_connector_hdmi_codec_ops;
+       codec_pdata.i2s = connector->hdmi_codec.i2s,
+       codec_pdata.spdif = connector->hdmi_codec.spdif,
+       codec_pdata.max_i2s_channels = connector->hdmi_codec.max_i2s_channels,
+       codec_pdata.data = connector;
+
+       pdev = platform_device_register_data(connector->hdmi_codec.dev,
+                                            HDMI_CODEC_DRV_NAME,
+                                            PLATFORM_DEVID_AUTO,
+                                            &codec_pdata, sizeof(codec_pdata));
+       if (IS_ERR(pdev))
+               return PTR_ERR(pdev);
+
+       connector->hdmi_codec.codec_pdev = pdev;
+
+       return 0;
+}
diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h
index 
b2b6a8e49dda46f1cb3b048ef7b28356dd3aaa4e..8ed58e482fac4662b659276e8bc17690e1fdb9b7
 100644
--- a/drivers/gpu/drm/drm_internal.h
+++ b/drivers/gpu/drm/drm_internal.h
@@ -280,4 +280,9 @@ void drm_framebuffer_print_info(struct drm_printer *p, 
unsigned int indent,
                                const struct drm_framebuffer *fb);
 void drm_framebuffer_debugfs_init(struct drm_device *dev);
 
+/* drm_connector_hdmi_codec.c */
+
+int drm_connector_hdmi_codec_init(struct drm_connector *connector);
+void drm_connector_hdmi_codec_cleanup(struct drm_connector *connector);
+
 #endif /* __DRM_INTERNAL_H__ */
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 
e3fa43291f449d70f3b92a00985336c4f2237bc6..b89f791cfa2be86ce1c5aa124c0f071d7c8a6489
 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -46,6 +46,8 @@ struct drm_property_blob;
 struct drm_printer;
 struct drm_privacy_screen;
 struct edid;
+struct hdmi_codec_daifmt;
+struct hdmi_codec_params;
 struct i2c_adapter;
 
 enum drm_connector_force {
@@ -1141,6 +1143,52 @@ struct drm_connector_state {
        struct drm_connector_hdmi_state hdmi;
 };
 
+struct drm_connector_hdmi_codec_funcs {
+       /**
+        * @audio_startup:
+        *
+        * Called when ASoC starts an audio stream setup. The
+        * @hdmi_audio_startup is optional.
+        *
+        * Returns:
+        * 0 on success, a negative error code otherwise
+        */
+       int (*audio_startup)(struct drm_connector *connector);
+
+       /**
+        * @prepare:
+        * Configures HDMI-encoder for audio stream. Can be called
+        * multiple times for each setup. Mandatory.
+        *
+        * Returns:
+        * 0 on success, a negative error code otherwise
+        */
+       int (*prepare)(struct drm_connector *connector,
+                      struct hdmi_codec_daifmt *fmt,
+                      struct hdmi_codec_params *hparms);
+       /**
+        * @audio_shutdown:
+        *
+        * Shut down the audio stream. Mandatory.
+        *
+        * Returns:
+        * 0 on success, a negative error code otherwise
+        */
+       void (*audio_shutdown)(struct drm_connector *connector);
+
+       /**
+        * @mute_stream:
+        *
+        * Mute/unmute HDMI audio stream. The @mute_stream callback is
+        * optional.
+        *
+        * Returns:
+        * 0 on success, a negative error code otherwise
+        */
+       int (*mute_stream)(struct drm_connector *connector,
+                          bool enable, int direction);
+};
+
 /**
  * struct drm_connector_hdmi_funcs - drm_hdmi_connector control functions
  */
@@ -1198,6 +1246,14 @@ struct drm_connector_hdmi_funcs {
        int (*write_infoframe)(struct drm_connector *connector,
                               enum hdmi_infoframe_type type,
                               const u8 *buffer, size_t len);
+
+       /**
+        * @codec_funcs:
+        *
+        * Implementation of the HDMI codec functionality to be used by the DRM
+        * HDMI Codec framework.
+        */
+       const struct drm_connector_hdmi_codec_funcs *codec_funcs;
 };
 
 /**
@@ -1660,6 +1716,22 @@ struct drm_cmdline_mode {
        bool tv_mode_specified;
 };
 
+struct drm_connector_hdmi_codec {
+       struct platform_device *codec_pdev;
+       struct device *dev;
+
+       struct mutex lock; /* protects last_state and plugged_cb */
+       void (*plugged_cb)(struct device *dev, bool plugged);
+       struct device *plugged_cb_dev;
+       bool last_state;
+
+       int max_i2s_channels;
+       uint i2s: 1;
+       uint spdif: 1;
+
+       int sound_dai_port;
+};
+
 /*
  * struct drm_connector_hdmi - DRM Connector HDMI-related structure
  */
@@ -2118,6 +2190,11 @@ struct drm_connector {
         * @hdmi: HDMI-related variable and properties.
         */
        struct drm_connector_hdmi hdmi;
+
+       /**
+        * @hdmi_codec: HDMI codec properties and non-DRM state.
+        */
+       struct drm_connector_hdmi_codec hdmi_codec;
 };
 
 #define obj_to_connector(x) container_of(x, struct drm_connector, base)
@@ -2151,6 +2228,9 @@ void drm_connector_unregister(struct drm_connector 
*connector);
 int drm_connector_attach_encoder(struct drm_connector *connector,
                                      struct drm_encoder *encoder);
 
+void drm_connector_hdmi_codec_plugged_notify(struct drm_connector *connector,
+                                            bool plugged);
+
 void drm_connector_cleanup(struct drm_connector *connector);
 
 static inline unsigned int drm_connector_index(const struct drm_connector 
*connector)

-- 
2.39.5

Reply via email to