On Tue, Dec 17, 2024 at 02:40:25AM +0200, Dmitry Baryshkov wrote: > 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 | 5 + > drivers/gpu/drm/drm_connector_hdmi_codec.c | 189 +++++++++++++++++++++++++++++ > include/drm/drm_connector.h | 125 +++++++++++++++++++ > 4 files changed, 320 insertions(+) > > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile > index 19fb370fbc56772077973c864df71e4b8e0bf99b..46e6dbc355afe4015ff2327ba04211cdc011a0b7 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..a6e2f27c3f7ff9d57cf19c055870001daff4c676 > --- /dev/null > +++ b/drivers/gpu/drm/drm_connector_hdmi_codec.c > @@ -0,0 +1,189 @@ > +// 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_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_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_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_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_audio_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 > + * @funcs: callbacks for this 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_audio_init(struct drm_connector *connector, > + struct device *hdmi_codec_dev, > + const struct drm_connector_hdmi_codec_funcs *funcs, > + 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; > + > + if (!funcs || > + !funcs->prepare || > + !funcs->audio_shutdown) > + return -EINVAL; > + > + connector->hdmi_codec.funcs = funcs; > + connector->hdmi_codec.dai_port = dai_port; > + > + 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_audio_init); > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h > index 1e2b25e204cb523d61d30f5409faa059bf2b86eb..536d604b6fb27368c43805a2ecf57fac06b3d472 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,53 @@ 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); With the naming comment I made on the previous version addressed, and assuming it's been tested on a pi3 and pi4, Acked-by: Maxime Ripard <mripard@xxxxxxxxxx> Maxime
Attachment:
signature.asc
Description: PGP signature