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            |   5 +
 drivers/gpu/drm/drm_connector_hdmi_codec.c | 185 +++++++++++++++++++++++++++++
 include/drm/drm_connector.h                |  80 +++++++++++++
 4 files changed, 271 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 
bbdaaf7022b62d84594a29f1b60144920903a99a..4abfbded962bf45b793a2bd5b1b5c4d9f478a1f7
 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -33,6 +33,7 @@
 #include <drm/drm_sysfs.h>
 #include <drm/drm_utils.h>
 
+#include <linux/platform_device.h>
 #include <linux/property.h>
 #include <linux/uaccess.h>
 
@@ -280,6 +281,7 @@ static int __drm_connector_init(struct drm_device *dev,
        mutex_init(&connector->eld_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;
@@ -632,6 +634,8 @@ void drm_connector_cleanup(struct drm_connector *connector)
                    DRM_CONNECTOR_REGISTERED))
                drm_connector_unregister(connector);
 
+       platform_device_unregister(connector->hdmi_codec.codec_pdev);
+
        if (connector->privacy_screen) {
                drm_privacy_screen_put(connector->privacy_screen);
                connector->privacy_screen = NULL;
@@ -670,6 +674,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..1934fb53b4d128434559970c9fea548bbc4bedda
--- /dev/null
+++ b/drivers/gpu/drm/drm_connector_hdmi_codec.c
@@ -0,0 +1,185 @@
+// 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>
+
+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.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.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;
+
+       mutex_lock(&connector->eld_mutex);
+       memcpy(buf, connector->eld, min(sizeof(connector->eld), len));
+       mutex_unlock(&connector->eld_mutex);
+
+       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_init - Initialize HDMI Codec device for the DRM 
connector
+ * @connector: A pointer to the connector to allocate codec for
+ * @hdmi_codec_dev: device to be used as a parent for the HDMI Codec
+ * @max_i2s_playback_channels: maximum number of playback I2S channels
+ * @spdif_playback: set if HDMI codec has S/PDIF playback port
+ * @dai_port: sound DAI port, -1 if it is not enabled
+ *
+ * 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 device *hdmi_codec_dev,
+                                 unsigned int max_i2s_playback_channels,
+                                 bool spdif_playback,
+                                 int dai_port)
+{
+       struct hdmi_codec_pdata codec_pdata = {
+               .ops = &drm_connector_hdmi_codec_ops,
+               .max_i2s_channels = max_i2s_playback_channels,
+               .i2s = !!max_i2s_playback_channels,
+               .spdif = spdif_playback,
+               .no_i2s_capture = true,
+               .no_spdif_capture = true,
+               .data = connector,
+       };
+       struct platform_device *pdev;
+
+       connector->hdmi_codec.dai_port = dai_port;
+
+       if (!connector->hdmi.funcs->codec_funcs->prepare ||
+           !connector->hdmi.funcs->codec_funcs->audio_shutdown)
+               return -EINVAL;
+
+       pdev = platform_device_register_data(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;
+}
+EXPORT_SYMBOL(drm_connector_hdmi_codec_init);
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 
1e2b25e204cb523d61d30f5409faa059bf2b86eb..1d113c0ceec7ce8196a412d7c00a1737175c6fbe
 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,17 @@ struct drm_cmdline_mode {
        bool tv_mode_specified;
 };
 
+struct drm_connector_hdmi_codec {
+       struct platform_device *codec_pdev;
+
+       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 dai_port;
+};
+
 /*
  * struct drm_connector_hdmi - DRM Connector HDMI-related structure
  */
@@ -2121,6 +2188,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)
@@ -2148,12 +2220,20 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
                             struct i2c_adapter *ddc,
                             unsigned long supported_formats,
                             unsigned int max_bpc);
+int drm_connector_hdmi_codec_init(struct drm_connector *connector,
+                                 struct device *hdmi_codec_dev,
+                                 unsigned int max_i2s_playback_channels,
+                                 bool spdif_playback,
+                                 int sound_dai_port);
 void drm_connector_attach_edid_property(struct drm_connector *connector);
 int drm_connector_register(struct drm_connector *connector);
 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