// SPDX-License-Identifier: MIT/* * Copyright 2026 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Authors: AMD */ #include "amdgpu.h"#include "amdgpu_dm.h"#include "amdgpu_dm_audio.h"#include "dm_helpers.h"#include "dc.h" #include <linux/component.h>#include <drm/drm_atomic.h>#include <drm/drm_audio_component.h>#include <drm/drm_connector.h>#include <drm/drm_edid.h>#include <drm/drm_eld.h> #include "dc/inc/core_types.h" static int amdgpu_dm_audio_component_get_eld(struct device *kdev, int port, int pipe, bool *enabled, unsigned char *buf, int max_bytes){ struct drm_device *dev = dev_get_drvdata(kdev); struct amdgpu_device *adev = drm_to_adev(dev); struct drm_connector *connector; struct drm_connector_list_iter conn_iter; struct amdgpu_dm_connector *aconnector; int ret = 0; *enabled = false; mutex_lock(&adev->dm.audio_lock); drm_connector_list_iter_begin(dev, &conn_iter); drm_for_each_connector_iter(connector, &conn_iter) { if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) continue; aconnector = to_amdgpu_dm_connector(connector); if (aconnector->audio_inst != port) continue; *enabled = true; mutex_lock(&connector->eld_mutex); ret = drm_eld_size(connector->eld); memcpy(buf, connector->eld, min(max_bytes, ret)); mutex_unlock(&connector->eld_mutex); break; } drm_connector_list_iter_end(&conn_iter); mutex_unlock(&adev->dm.audio_lock); drm_dbg_kms(adev_to_drm(adev), "Get ELD : idx=%d ret=%d en=%d\n", port, ret, *enabled); return ret;} static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = { .get_eld = amdgpu_dm_audio_component_get_eld,}; STATIC_IFN_KUNIT int amdgpu_dm_audio_component_bind(struct device *kdev, struct device *hda_kdev, void *data){ struct drm_device *dev = dev_get_drvdata(kdev); struct amdgpu_device *adev = drm_to_adev(dev); struct drm_audio_component *acomp = data; acomp->ops = &amdgpu_dm_audio_component_ops; acomp->dev = kdev; adev->dm.audio_component = acomp; return 0;}EXPORT_IF_KUNIT(amdgpu_dm_audio_component_bind); STATIC_IFN_KUNIT void amdgpu_dm_audio_component_unbind(struct device *kdev, struct device *hda_kdev, void *data){ struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev)); struct drm_audio_component *acomp = data; acomp->ops = NULL; acomp->dev = NULL; adev->dm.audio_component = NULL;}EXPORT_IF_KUNIT(amdgpu_dm_audio_component_unbind); static const struct component_ops amdgpu_dm_audio_component_bind_ops = { .bind = amdgpu_dm_audio_component_bind, .unbind = amdgpu_dm_audio_component_unbind,}; STATIC_IFN_KUNITvoid amdgpu_dm_audio_init_pins(struct amdgpu_device *adev, int audio_count, const unsigned int *inst_array){ int i; adev->mode_info.audio.num_pins = audio_count; for (i = 0; i < audio_count; i++) { adev->mode_info.audio.pin[i].channels = -1; adev->mode_info.audio.pin[i].rate = -1; adev->mode_info.audio.pin[i].bits_per_sample = -1; adev->mode_info.audio.pin[i].status_bits = 0; adev->mode_info.audio.pin[i].category_code = 0; adev->mode_info.audio.pin[i].connected = false; adev->mode_info.audio.pin[i].id = inst_array[i]; adev->mode_info.audio.pin[i].offset = 0; }}EXPORT_IF_KUNIT(amdgpu_dm_audio_init_pins); int amdgpu_dm_audio_init(struct amdgpu_device *adev){ unsigned int inst_array[MAX_AUDIOS]; int audio_count; int i, ret; if (!amdgpu_audio) return 0; adev->mode_info.audio.enabled = true; audio_count = adev->dm.dc->res_pool->audio_count; for (i = 0; i < audio_count; i++) inst_array[i] = adev->dm.dc->res_pool->audios[i]->inst; amdgpu_dm_audio_init_pins(adev, audio_count, inst_array); ret = component_add(adev->dev, &amdgpu_dm_audio_component_bind_ops); if (ret < 0) return ret; adev->dm.audio_registered = true; return 0;}EXPORT_IF_KUNIT(amdgpu_dm_audio_init); void amdgpu_dm_audio_fini(struct amdgpu_device *adev){ if (!amdgpu_audio) return; if (!adev->mode_info.audio.enabled) return; if (adev->dm.audio_registered) { component_del(adev->dev, &amdgpu_dm_audio_component_bind_ops); adev->dm.audio_registered = false; } /* TODO: Disable audio? */ adev->mode_info.audio.enabled = false;}EXPORT_IF_KUNIT(amdgpu_dm_audio_fini); void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin){ struct drm_audio_component *acomp = adev->dm.audio_component; if (acomp && acomp->audio_ops && acomp->audio_ops->pin_eld_notify) { drm_dbg_kms(adev_to_drm(adev), "Notify ELD: %d\n", pin); acomp->audio_ops->pin_eld_notify(acomp->audio_ops->audio_ptr, pin, -1); }}EXPORT_IF_KUNIT(amdgpu_dm_audio_eld_notify); void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, const struct drm_connector *drm_connector, const struct dc_sink *dc_sink){ int i = 0; int cea_revision = 0; const struct dc_edid_caps *edid_caps = &dc_sink->edid_caps; audio_info->manufacture_id = edid_caps->manufacturer_id; audio_info->product_id = edid_caps->product_id; cea_revision = drm_connector->display_info.cea_rev; strscpy(audio_info->display_name, edid_caps->display_name, AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS); if (cea_revision >= 3) { audio_info->mode_count = edid_caps->audio_mode_count; for (i = 0; i < audio_info->mode_count; ++i) { audio_info->modes[i].format_code = (enum audio_format_code) (edid_caps->audio_modes[i].format_code); audio_info->modes[i].channel_count = edid_caps->audio_modes[i].channel_count; audio_info->modes[i].sample_rates.all = edid_caps->audio_modes[i].sample_rate; audio_info->modes[i].sample_size = edid_caps->audio_modes[i].sample_size; } } audio_info->flags.all = edid_caps->speaker_flags; /* TODO: We only check for the progressive mode, check for interlace mode too */ if (drm_connector->latency_present[0]) { audio_info->video_latency = drm_connector->video_latency[0]; audio_info->audio_latency = drm_connector->audio_latency[0]; } /* TODO: For DP, video and audio latency should be calculated from DPCD caps */ }EXPORT_IF_KUNIT(amdgpu_dm_fill_audio_info); void amdgpu_dm_commit_audio(struct drm_device *dev, struct drm_atomic_commit *state){ struct amdgpu_device *adev = drm_to_adev(dev); struct amdgpu_dm_connector *aconnector; struct drm_connector *connector; struct drm_connector_state *old_con_state, *new_con_state; struct drm_crtc_state *new_crtc_state; struct dm_crtc_state *new_dm_crtc_state; const struct dc_stream_status *status; int i, inst; /* Notify device removals. */ for_each_oldnew_connector_in_state(state, connector, old_con_state, new_con_state, i) { if (old_con_state->crtc != new_con_state->crtc) { /* CRTC changes require notification. */ goto notify; } if (!new_con_state->crtc) continue; new_crtc_state = drm_atomic_get_new_crtc_state( state, new_con_state->crtc); if (!new_crtc_state) continue; if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) continue; notify: if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) continue; aconnector = to_amdgpu_dm_connector(connector); mutex_lock(&adev->dm.audio_lock); inst = aconnector->audio_inst; aconnector->audio_inst = -1; mutex_unlock(&adev->dm.audio_lock); amdgpu_dm_audio_eld_notify(adev, inst); } /* Notify audio device additions. */ for_each_new_connector_in_state(state, connector, new_con_state, i) { if (!new_con_state->crtc) continue; new_crtc_state = drm_atomic_get_new_crtc_state( state, new_con_state->crtc); if (!new_crtc_state) continue; if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) continue; new_dm_crtc_state = to_dm_crtc_state(new_crtc_state); if (!new_dm_crtc_state->stream) continue; status = dc_stream_get_status(new_dm_crtc_state->stream); if (!status) continue; if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) continue; aconnector = to_amdgpu_dm_connector(connector); mutex_lock(&adev->dm.audio_lock); inst = status->audio_inst; aconnector->audio_inst = inst; mutex_unlock(&adev->dm.audio_lock); amdgpu_dm_audio_eld_notify(adev, inst); }}EXPORT_IF_KUNIT(amdgpu_dm_commit_audio); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)int amdgpu_dm_audio_get_param(void){ return amdgpu_audio;}EXPORT_IF_KUNIT(amdgpu_dm_audio_get_param); void amdgpu_dm_audio_set_param(int val){ amdgpu_audio = val;}EXPORT_IF_KUNIT(amdgpu_dm_audio_set_param);#endif
// SPDX-License-Identifier: MIT
/*
* Copyright 2026 Advanced Micro Devices, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: AMD
*/
#include "amdgpu.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_audio.h"
#include "dm_helpers.h"
#include "dc.h"
#include <linux/component.h>
#include <drm/drm_atomic.h>
#include <drm/drm_audio_component.h>
#include <drm/drm_connector.h>
#include <drm/drm_edid.h>
#include <drm/drm_eld.h>
#include "dc/inc/core_types.h"
static int amdgpu_dm_audio_component_get_eld(struct device *kdev, int port,
int pipe, bool *enabled,
unsigned char *buf, int max_bytes)
{
struct drm_device *dev = dev_get_drvdata(kdev);
struct amdgpu_device *adev = drm_to_adev(dev);
struct drm_connector *connector;
struct drm_connector_list_iter conn_iter;
struct amdgpu_dm_connector *aconnector;
int ret = 0;
*enabled = false;
mutex_lock(&adev->dm.audio_lock);
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter) {
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
continue;
aconnector = to_amdgpu_dm_connector(connector);
if (aconnector->audio_inst != port)
continue;
*enabled = true;
mutex_lock(&connector->eld_mutex);
ret = drm_eld_size(connector->eld);
memcpy(buf, connector->eld, min(max_bytes, ret));
mutex_unlock(&connector->eld_mutex);
break;
}
drm_connector_list_iter_end(&conn_iter);
mutex_unlock(&adev->dm.audio_lock);
drm_dbg_kms(adev_to_drm(adev), "Get ELD : idx=%d ret=%d en=%d\n", port, ret, *enabled);
return ret;
}
static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = {
.get_eld = amdgpu_dm_audio_component_get_eld,
};
STATIC_IFN_KUNIT int amdgpu_dm_audio_component_bind(struct device *kdev,
struct device *hda_kdev, void *data)
{
struct drm_device *dev = dev_get_drvdata(kdev);
struct amdgpu_device *adev = drm_to_adev(dev);
struct drm_audio_component *acomp = data;
acomp->ops = &amdgpu_dm_audio_component_ops;
acomp->dev = kdev;
adev->dm.audio_component = acomp;
return 0;
}
EXPORT_IF_KUNIT(amdgpu_dm_audio_component_bind);
STATIC_IFN_KUNIT void amdgpu_dm_audio_component_unbind(struct device *kdev,
struct device *hda_kdev, void *data)
{
struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev));
struct drm_audio_component *acomp = data;
acomp->ops = NULL;
acomp->dev = NULL;
adev->dm.audio_component = NULL;
}
EXPORT_IF_KUNIT(amdgpu_dm_audio_component_unbind);
static const struct component_ops amdgpu_dm_audio_component_bind_ops = {
.bind = amdgpu_dm_audio_component_bind,
.unbind = amdgpu_dm_audio_component_unbind,
};
STATIC_IFN_KUNIT
void amdgpu_dm_audio_init_pins(struct amdgpu_device *adev, int audio_count,
const unsigned int *inst_array)
{
int i;
adev->mode_info.audio.num_pins = audio_count;
for (i = 0; i < audio_count; i++) {
adev->mode_info.audio.pin[i].channels = -1;
adev->mode_info.audio.pin[i].rate = -1;
adev->mode_info.audio.pin[i].bits_per_sample = -1;
adev->mode_info.audio.pin[i].status_bits = 0;
adev->mode_info.audio.pin[i].category_code = 0;
adev->mode_info.audio.pin[i].connected = false;
adev->mode_info.audio.pin[i].id = inst_array[i];
adev->mode_info.audio.pin[i].offset = 0;
}
}
EXPORT_IF_KUNIT(amdgpu_dm_audio_init_pins);
int amdgpu_dm_audio_init(struct amdgpu_device *adev)
{
unsigned int inst_array[MAX_AUDIOS];
int audio_count;
int i, ret;
if (!amdgpu_audio)
return 0;
adev->mode_info.audio.enabled = true;
audio_count = adev->dm.dc->res_pool->audio_count;
for (i = 0; i < audio_count; i++)
inst_array[i] = adev->dm.dc->res_pool->audios[i]->inst;
amdgpu_dm_audio_init_pins(adev, audio_count, inst_array);
ret = component_add(adev->dev, &amdgpu_dm_audio_component_bind_ops);
if (ret < 0)
return ret;
adev->dm.audio_registered = true;
return 0;
}
EXPORT_IF_KUNIT(amdgpu_dm_audio_init);
void amdgpu_dm_audio_fini(struct amdgpu_device *adev)
{
if (!amdgpu_audio)
return;
if (!adev->mode_info.audio.enabled)
return;
if (adev->dm.audio_registered) {
component_del(adev->dev, &amdgpu_dm_audio_component_bind_ops);
adev->dm.audio_registered = false;
}
/* TODO: Disable audio? */
adev->mode_info.audio.enabled = false;
}
EXPORT_IF_KUNIT(amdgpu_dm_audio_fini);
void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin)
{
struct drm_audio_component *acomp = adev->dm.audio_component;
if (acomp && acomp->audio_ops && acomp->audio_ops->pin_eld_notify) {
drm_dbg_kms(adev_to_drm(adev), "Notify ELD: %d\n", pin);
acomp->audio_ops->pin_eld_notify(acomp->audio_ops->audio_ptr,
pin, -1);
}
}
EXPORT_IF_KUNIT(amdgpu_dm_audio_eld_notify);
void amdgpu_dm_fill_audio_info(struct audio_info *audio_info,
const struct drm_connector *drm_connector,
const struct dc_sink *dc_sink)
{
int i = 0;
int cea_revision = 0;
const struct dc_edid_caps *edid_caps = &dc_sink->edid_caps;
audio_info->manufacture_id = edid_caps->manufacturer_id;
audio_info->product_id = edid_caps->product_id;
cea_revision = drm_connector->display_info.cea_rev;
strscpy(audio_info->display_name,
edid_caps->display_name,
AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS);
if (cea_revision >= 3) {
audio_info->mode_count = edid_caps->audio_mode_count;
for (i = 0; i < audio_info->mode_count; ++i) {
audio_info->modes[i].format_code =
(enum audio_format_code)
(edid_caps->audio_modes[i].format_code);
audio_info->modes[i].channel_count =
edid_caps->audio_modes[i].channel_count;
audio_info->modes[i].sample_rates.all =
edid_caps->audio_modes[i].sample_rate;
audio_info->modes[i].sample_size =
edid_caps->audio_modes[i].sample_size;
}
}
audio_info->flags.all = edid_caps->speaker_flags;
/* TODO: We only check for the progressive mode, check for interlace mode too */
if (drm_connector->latency_present[0]) {
audio_info->video_latency = drm_connector->video_latency[0];
audio_info->audio_latency = drm_connector->audio_latency[0];
}
/* TODO: For DP, video and audio latency should be calculated from DPCD caps */
}
EXPORT_IF_KUNIT(amdgpu_dm_fill_audio_info);
void amdgpu_dm_commit_audio(struct drm_device *dev,
struct drm_atomic_commit *state)
{
struct amdgpu_device *adev = drm_to_adev(dev);
struct amdgpu_dm_connector *aconnector;
struct drm_connector *connector;
struct drm_connector_state *old_con_state, *new_con_state;
struct drm_crtc_state *new_crtc_state;
struct dm_crtc_state *new_dm_crtc_state;
const struct dc_stream_status *status;
int i, inst;
/* Notify device removals. */
for_each_oldnew_connector_in_state(state, connector, old_con_state, new_con_state, i) {
if (old_con_state->crtc != new_con_state->crtc) {
/* CRTC changes require notification. */
goto notify;
}
if (!new_con_state->crtc)
continue;
new_crtc_state = drm_atomic_get_new_crtc_state(
state, new_con_state->crtc);
if (!new_crtc_state)
continue;
if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
continue;
notify:
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
continue;
aconnector = to_amdgpu_dm_connector(connector);
mutex_lock(&adev->dm.audio_lock);
inst = aconnector->audio_inst;
aconnector->audio_inst = -1;
mutex_unlock(&adev->dm.audio_lock);
amdgpu_dm_audio_eld_notify(adev, inst);
}
/* Notify audio device additions. */
for_each_new_connector_in_state(state, connector, new_con_state, i) {
if (!new_con_state->crtc)
continue;
new_crtc_state = drm_atomic_get_new_crtc_state(
state, new_con_state->crtc);
if (!new_crtc_state)
continue;
if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
continue;
new_dm_crtc_state = to_dm_crtc_state(new_crtc_state);
if (!new_dm_crtc_state->stream)
continue;
status = dc_stream_get_status(new_dm_crtc_state->stream);
if (!status)
continue;
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
continue;
aconnector = to_amdgpu_dm_connector(connector);
mutex_lock(&adev->dm.audio_lock);
inst = status->audio_inst;
aconnector->audio_inst = inst;
mutex_unlock(&adev->dm.audio_lock);
amdgpu_dm_audio_eld_notify(adev, inst);
}
}
EXPORT_IF_KUNIT(amdgpu_dm_commit_audio);
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
int amdgpu_dm_audio_get_param(void)
{
return amdgpu_audio;
}
EXPORT_IF_KUNIT(amdgpu_dm_audio_get_param);
void amdgpu_dm_audio_set_param(int val)
{
amdgpu_audio = val;
}
EXPORT_IF_KUNIT(amdgpu_dm_audio_set_param);
#endif