[PATCH RFC 3/5] drm/connector: implement generic HDMI codec helpers

From: Dmitry Baryshkov
Date: Sat Jun 15 2024 - 13:54:07 EST


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.baryshkov@xxxxxxxxxx>
---
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/drm_connector.c | 8 ++
drivers/gpu/drm/drm_connector_hdmi_codec.c | 157 +++++++++++++++++++++++++++++
include/drm/drm_connector.h | 33 ++++++
4 files changed, 199 insertions(+)

diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 68cc9258ffc4..e113a6eade23 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -45,6 +45,7 @@ drm-y := \
drm_client_modeset.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 3d73a981004c..66d6e9487339 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;
@@ -529,6 +530,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 = drmm_connector_hdmi_codec_alloc(dev, connector, hdmi_funcs->codec_ops);
+ if (ret)
+ return ret;
+ }
+
return 0;
}
EXPORT_SYMBOL(drmm_connector_hdmi_init);
@@ -665,6 +672,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 000000000000..a3a7ad117f6f
--- /dev/null
+++ b/drivers/gpu/drm/drm_connector_hdmi_codec.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2024 Linaro Ltd
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_connector.h>
+#include <drm/drm_managed.h>
+
+#include <sound/hdmi-codec.h>
+
+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 void drm_connector_hdmi_codec_cleanup_action(struct drm_device *dev,
+ void *ptr)
+{
+ struct platform_device *pdev = ptr;
+
+ platform_device_unregister(pdev);
+}
+
+/**
+ * drmm_connector_hdmi_alloc - Allocate HDMI Codec device for the DRM connector
+ * @dev: DRM device
+ * @connector: A pointer to the connector to allocate codec for
+ * @ops: callbacks for this connector
+ *
+ * Create a HDMI codec device to be used with the specified connector.
+ *
+ * Cleanup is automatically handled with in a DRM-managed action.
+ *
+ * The connector structure should be allocated with drmm_kzalloc().
+ *
+ * Returns:
+ * Zero on success, error code on failure.
+ */
+int drmm_connector_hdmi_codec_alloc(struct drm_device *dev,
+ struct drm_connector *connector,
+ const struct hdmi_codec_ops *base_ops)
+{
+ struct hdmi_codec_pdata codec_pdata = {};
+ struct platform_device *pdev;
+ struct hdmi_codec_ops *ops;
+ int ret;
+
+ ops = drmm_kmalloc(dev, sizeof(*ops), GFP_KERNEL);
+ if (!ops)
+ return -ENOMEM;
+
+ *ops = *base_ops;
+
+ ops->get_eld = drm_connector_hdmi_codec_get_eld;
+ ops->hook_plugged_cb = drm_connector_hdmi_codec_hook_plugged_cb;
+
+ codec_pdata.ops = 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.parent_dev,
+ HDMI_CODEC_DRV_NAME,
+ PLATFORM_DEVID_AUTO,
+ &codec_pdata, sizeof(codec_pdata));
+ if (IS_ERR(pdev))
+ return PTR_ERR(pdev);
+
+ ret = drmm_add_action_or_reset(dev, drm_connector_hdmi_codec_cleanup_action, pdev);
+ if (ret)
+ return ret;
+
+ connector->hdmi_codec.codec_pdev = pdev;
+
+ return 0;
+}
+EXPORT_SYMBOL(drmm_connector_hdmi_codec_alloc);
+
+/**
+ * drmm_connector_hdmi_codec_free - rollback drmm_connector_hdmi_codec_alloc
+ * @dev: DRM device
+ * @hdmi_codec: A pointer to the HDMI codec data
+ *
+ * Rollback the drmm_connector_hdmi_codec_alloc() and free allocated data.
+ * While this function should not be necessary for a typical driver, DRM bridge
+ * drivers have to call it from the remove callback if the bridge uses
+ * Connector's HDMI Codec interface.
+ */
+void drmm_connector_hdmi_codec_free(struct drm_device *dev,
+ struct drm_connector_hdmi_codec *hdmi_codec)
+{
+ drmm_release_action(dev, drm_connector_hdmi_codec_cleanup_action,
+ hdmi_codec->codec_pdev);
+}
+EXPORT_SYMBOL(drmm_connector_hdmi_codec_free);
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index f750765d8fbc..0eb8d8ed9495 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -46,6 +46,7 @@ struct drm_property_blob;
struct drm_printer;
struct drm_privacy_screen;
struct edid;
+struct hdmi_codec_ops;
struct i2c_adapter;

enum drm_connector_force {
@@ -1199,6 +1200,8 @@ struct drm_connector_hdmi_funcs {
int (*write_infoframe)(struct drm_connector *connector,
enum hdmi_infoframe_type type,
const u8 *buffer, size_t len);
+
+ const struct hdmi_codec_ops *codec_ops;
};

/**
@@ -1706,6 +1709,22 @@ struct drm_connector_hdmi {
} infoframes;
};

+struct drm_connector_hdmi_codec {
+ struct device *parent_dev;
+ struct platform_device *codec_pdev;
+
+ const struct drm_connector_hdmi_codec_funcs *funcs;
+
+ 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;
+};
+
/**
* struct drm_connector - central DRM connector control structure
*
@@ -2119,6 +2138,12 @@ struct drm_connector {
* @hdmi: HDMI-related variable and properties.
*/
struct drm_connector_hdmi hdmi;
+
+ /**
+ * @hdmi_codec: HDMI codec properties and variables. Also might be used
+ * for DisplayPort audio.
+ */
+ struct drm_connector_hdmi_codec hdmi_codec;
};

#define obj_to_connector(x) container_of(x, struct drm_connector, base)
@@ -2152,6 +2177,14 @@ void drm_connector_unregister(struct drm_connector *connector);
int drm_connector_attach_encoder(struct drm_connector *connector,
struct drm_encoder *encoder);

+int drmm_connector_hdmi_codec_alloc(struct drm_device *dev,
+ struct drm_connector *connector,
+ const struct hdmi_codec_ops *ops);
+void drmm_connector_hdmi_codec_free(struct drm_device *dev,
+ struct drm_connector_hdmi_codec *codec);
+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.2