Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,69 @@
#
# Copyright 2017 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.
#
#
# Makefile for the 'dm' sub-component of DAL.
# It provides the control and status of dm blocks.
ifneq ($(CONFIG_DRM_AMD_DC),)
AMDGPUDM = \
amdgpu_dm.o \
amdgpu_dm_plane.o \
amdgpu_dm_crtc.o \
amdgpu_dm_irq.o \
amdgpu_dm_mst_types.o \
amdgpu_dm_color.o \
amdgpu_dm_services.o \
amdgpu_dm_helpers.o \
amdgpu_dm_pp_smu.o \
amdgpu_dm_psr.o \
amdgpu_dm_replay.o \
amdgpu_dm_quirks.o \
amdgpu_dm_wb.o \
amdgpu_dm_colorop.o \
amdgpu_dm_ism.o
ifdef CONFIG_DRM_AMD_DC_FP
AMDGPUDM += dc_fpu.o
endif
AMDGPUDM += amdgpu_dm_hdcp.o
ifneq ($(CONFIG_DEBUG_FS),)
AMDGPUDM += amdgpu_dm_crc.o amdgpu_dm_debugfs.o
endif
subdir-ccflags-y += -I$(FULL_AMD_DISPLAY_PATH)/dc
AMDGPU_DM = $(addprefix $(AMDDALPATH)/amdgpu_dm/,$(AMDGPUDM))
AMD_DISPLAY_FILES += $(AMDGPU_DM)
endif
# KUnit tests as separate module
ifneq ($(CONFIG_DRM_AMD_DC_KUNIT_TEST),)
obj-y += $(AMDDALPATH)/amdgpu_dm/tests/
# Enable gcov for amdgpu_dm KUnit builds
GCOV_PROFILE := y
endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,118 @@
/* 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
*
*/
#ifndef __AMDGPU_DM_COLOR_H__
#define __AMDGPU_DM_COLOR_H__
#define MAX_DRM_LUT_VALUE 0xFFFF
#define MAX_DRM_LUT32_VALUE 0xFFFFFFFF
#include <linux/types.h>
struct drm_color_lut;
struct drm_color_lut32;
struct drm_color_ctm;
struct drm_color_ctm_3x4;
struct drm_colorop_state;
struct drm_property_blob;
struct dc_gamma;
struct dc_rgb;
struct dc_plane_state;
struct fixed31_32;
struct tetrahedral_params;
struct dc_transfer_func;
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
/*
* Prototypes for functions exposed to KUnit tests. The enum types
* used below (dc_transfer_func_predefined, amdgpu_transfer_function,
* drm_colorop_curve_1d_type) must be defined before this header is
* included — the source file (amdgpu_dm_color.c) ensures this via
* its own includes of dc.h, amdgpu_dm.h, and drm/drm_colorop.h.
*/
struct fixed31_32 amdgpu_dm_fixpt_from_s3132(__u64 x);
bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size);
void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut,
struct dc_gamma *gamma, bool is_legacy);
void __drm_lut32_to_dc_gamma(const struct drm_color_lut32 *lut,
struct dc_gamma *gamma);
void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm,
struct fixed31_32 *matrix);
void __drm_ctm_3x4_to_dc_matrix(const struct drm_color_ctm_3x4 *ctm,
struct fixed31_32 *matrix);
enum dc_transfer_func_predefined
amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf);
enum dc_transfer_func_predefined
amdgpu_colorop_tf_to_dc_tf(enum drm_colorop_curve_1d_type tf);
const struct drm_color_lut *
__extract_blob_lut(const struct drm_property_blob *blob, uint32_t *size);
const struct drm_color_lut32 *
__extract_blob_lut32(const struct drm_property_blob *blob, uint32_t *size);
void __to_dc_lut3d_color(struct dc_rgb *rgb,
const struct drm_color_lut lut,
int bit_precision);
void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut,
uint32_t lut3d_size,
struct tetrahedral_params *params,
bool use_tetrahedral_9,
int bit_depth);
void __to_dc_lut3d_32_color(struct dc_rgb *rgb,
const struct drm_color_lut32 lut,
int bit_precision);
void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut,
uint32_t lut3d_size,
struct tetrahedral_params *params,
bool use_tetrahedral_9,
int bit_depth);
struct dc_3dlut;
void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut3d,
uint32_t drm_lut3d_size,
struct dc_3dlut *lut);
int __set_colorop_3dlut(const struct drm_color_lut32 *drm_lut3d,
uint32_t drm_lut3d_size,
struct dc_3dlut *lut);
void __set_tf_bypass(struct dc_transfer_func *tf);
void __set_tf_distributed_points(struct dc_transfer_func *tf,
enum dc_transfer_func_predefined predefined_tf);
int amdgpu_dm_set_atomic_regamma(struct dc_transfer_func *out_tf,
const struct drm_color_lut *regamma_lut,
uint32_t regamma_size, bool has_rom,
enum dc_transfer_func_predefined tf);
int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *shaper_lut,
bool has_rom,
enum dc_transfer_func_predefined tf,
uint32_t shaper_size,
struct dc_transfer_func *func_shaper);
int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut,
bool has_rom,
enum dc_transfer_func_predefined tf,
uint32_t blend_size,
struct dc_transfer_func *func_blend);
int __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state,
struct drm_colorop_state *colorop_state);
#endif
#endif /* __AMDGPU_DM_COLOR_H__ */
@@ -0,0 +1,237 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2023 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 <drm/drm_print.h>
#include <drm/drm_plane.h>
#include <drm/drm_property.h>
#include <drm/drm_colorop.h>
#include "amdgpu.h"
#include "amdgpu_dm_colorop.h"
#include "amdgpu_dm_kunit_helpers.h"
#include "dc.h"
const u64 amdgpu_dm_supported_degam_tfs =
BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) |
BIT(DRM_COLOROP_1D_CURVE_GAMMA22);
EXPORT_IF_KUNIT(amdgpu_dm_supported_degam_tfs);
const u64 amdgpu_dm_supported_shaper_tfs =
BIT(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_BT2020_OETF) |
BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV);
EXPORT_IF_KUNIT(amdgpu_dm_supported_shaper_tfs);
const u64 amdgpu_dm_supported_blnd_tfs =
BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) |
BIT(DRM_COLOROP_1D_CURVE_GAMMA22);
EXPORT_IF_KUNIT(amdgpu_dm_supported_blnd_tfs);
#define MAX_COLOR_PIPELINE_OPS 10
#define LUT3D_SIZE 17
static const struct drm_colorop_funcs dm_colorop_funcs = {
.destroy = drm_colorop_destroy,
};
STATIC_IFN_KUNIT int
amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane,
bool hw_3d_lut, struct drm_prop_enum_list *list)
{
struct drm_colorop *ops[MAX_COLOR_PIPELINE_OPS];
int ret;
int i = 0;
memset(ops, 0, sizeof(ops));
/* 1D curve - DEGAM TF */
ops[i] = kzalloc_obj(*ops[0]);
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_curve_1d_init(dev, ops[i], plane, &dm_colorop_funcs,
amdgpu_dm_supported_degam_tfs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
list->type = ops[i]->base.id;
i++;
/* Multiplier */
ops[i] = kzalloc_obj(struct drm_colorop);
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_mult_init(dev, ops[i], plane, &dm_colorop_funcs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
drm_colorop_set_next_property(ops[i-1], ops[i]);
i++;
/* 3x4 matrix */
ops[i] = kzalloc_obj(struct drm_colorop);
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_ctm_3x4_init(dev, ops[i], plane,
&dm_colorop_funcs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
drm_colorop_set_next_property(ops[i-1], ops[i]);
i++;
if (hw_3d_lut) {
/* 1D curve - SHAPER TF */
ops[i] = kzalloc_obj(*ops[0]);
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_curve_1d_init(dev, ops[i], plane, &dm_colorop_funcs,
amdgpu_dm_supported_shaper_tfs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
drm_colorop_set_next_property(ops[i-1], ops[i]);
i++;
/* 1D LUT - SHAPER LUT */
ops[i] = kzalloc_obj(*ops[0]);
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_curve_1d_lut_init(dev, ops[i], plane,
&dm_colorop_funcs,
MAX_COLOR_LUT_ENTRIES,
DRM_COLOROP_LUT1D_INTERPOLATION_LINEAR,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
drm_colorop_set_next_property(ops[i-1], ops[i]);
i++;
/* 3D LUT */
ops[i] = kzalloc_obj(*ops[0]);
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_3dlut_init(dev, ops[i], plane,
&dm_colorop_funcs, LUT3D_SIZE,
DRM_COLOROP_LUT3D_INTERPOLATION_TETRAHEDRAL,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
drm_colorop_set_next_property(ops[i-1], ops[i]);
i++;
}
/* 1D curve - BLND TF */
ops[i] = kzalloc_obj(*ops[0]);
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_curve_1d_init(dev, ops[i], plane, &dm_colorop_funcs,
amdgpu_dm_supported_blnd_tfs,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
drm_colorop_set_next_property(ops[i - 1], ops[i]);
i++;
/* 1D LUT - BLND LUT */
ops[i] = kzalloc_obj(struct drm_colorop);
if (!ops[i]) {
ret = -ENOMEM;
goto cleanup;
}
ret = drm_plane_colorop_curve_1d_lut_init(dev, ops[i], plane, &dm_colorop_funcs,
MAX_COLOR_LUT_ENTRIES,
DRM_COLOROP_LUT1D_INTERPOLATION_LINEAR,
DRM_COLOROP_FLAG_ALLOW_BYPASS);
if (ret)
goto cleanup;
drm_colorop_set_next_property(ops[i-1], ops[i]);
list->name = kasprintf(GFP_KERNEL, "Color Pipeline %d", ops[0]->base.id);
return 0;
cleanup:
if (ret == -ENOMEM)
drm_err(dev, "KMS: Failed to allocate colorop\n");
drm_colorop_pipeline_destroy(dev);
return ret;
}
EXPORT_IF_KUNIT(amdgpu_dm_build_default_pipeline);
int amdgpu_dm_initialize_default_pipeline(struct drm_plane *plane, struct drm_prop_enum_list *list)
{
struct drm_device *dev = plane->dev;
struct amdgpu_device *adev = drm_to_adev(dev);
bool hw_3d_lut = adev->dm.dc->caps.color.dpp.hw_3d_lut ||
adev->dm.dc->caps.color.mpc.preblend;
return amdgpu_dm_build_default_pipeline(dev, plane, hw_3d_lut, list);
}
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2023 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
*
*/
#ifndef __AMDGPU_DM_COLOROP_H__
#define __AMDGPU_DM_COLOROP_H__
extern const u64 amdgpu_dm_supported_degam_tfs;
extern const u64 amdgpu_dm_supported_shaper_tfs;
extern const u64 amdgpu_dm_supported_blnd_tfs;
int amdgpu_dm_initialize_default_pipeline(struct drm_plane *plane, struct drm_prop_enum_list *list);
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
int amdgpu_dm_build_default_pipeline(struct drm_device *dev, struct drm_plane *plane,
bool hw_3d_lut, struct drm_prop_enum_list *list);
#endif
#endif /* __AMDGPU_DM_COLOROP_H__*/
@@ -0,0 +1,989 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2015 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 <drm/drm_crtc.h>
#include <drm/drm_vblank.h>
#include "amdgpu.h"
#include "amdgpu_dm.h"
#include "dc.h"
#include "amdgpu_securedisplay.h"
#include "amdgpu_dm_psr.h"
#include "amdgpu_dm_replay.h"
#include "amdgpu_dm_kunit_helpers.h"
static const char *const pipe_crc_sources[] = {
"none",
"crtc",
"crtc dither",
"dprx",
"dprx dither",
"auto",
};
STATIC_IFN_KUNIT
enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source)
{
if (!source || !strcmp(source, "none"))
return AMDGPU_DM_PIPE_CRC_SOURCE_NONE;
if (!strcmp(source, "auto") || !strcmp(source, "crtc"))
return AMDGPU_DM_PIPE_CRC_SOURCE_CRTC;
if (!strcmp(source, "dprx"))
return AMDGPU_DM_PIPE_CRC_SOURCE_DPRX;
if (!strcmp(source, "crtc dither"))
return AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER;
if (!strcmp(source, "dprx dither"))
return AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER;
return AMDGPU_DM_PIPE_CRC_SOURCE_INVALID;
}
EXPORT_IF_KUNIT(dm_parse_crc_source);
STATIC_IFN_KUNIT
bool dm_is_crc_source_crtc(enum amdgpu_dm_pipe_crc_source src)
{
return (src == AMDGPU_DM_PIPE_CRC_SOURCE_CRTC) ||
(src == AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER);
}
EXPORT_IF_KUNIT(dm_is_crc_source_crtc);
STATIC_IFN_KUNIT
bool dm_is_crc_source_dprx(enum amdgpu_dm_pipe_crc_source src)
{
return (src == AMDGPU_DM_PIPE_CRC_SOURCE_DPRX) ||
(src == AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER);
}
EXPORT_IF_KUNIT(dm_is_crc_source_dprx);
STATIC_IFN_KUNIT
bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src)
{
return (src == AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER) ||
(src == AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER) ||
(src == AMDGPU_DM_PIPE_CRC_SOURCE_NONE);
}
EXPORT_IF_KUNIT(dm_need_crc_dither);
const char *const *amdgpu_dm_crtc_get_crc_sources(struct drm_crtc *crtc,
size_t *count)
{
*count = ARRAY_SIZE(pipe_crc_sources);
return pipe_crc_sources;
}
#ifdef CONFIG_DRM_AMD_SECURE_DISPLAY
static void update_phy_id_mapping(struct amdgpu_device *adev)
{
struct drm_device *ddev = adev_to_drm(adev);
struct amdgpu_display_manager *dm = &adev->dm;
struct drm_connector *connector;
struct amdgpu_dm_connector *aconnector;
struct amdgpu_dm_connector *sort_connector[AMDGPU_DM_MAX_CRTC] = {NULL};
struct drm_connector_list_iter iter;
uint8_t idx = 0, idx_2 = 0, connector_cnt = 0;
dm->secure_display_ctx.phy_mapping_updated = false;
mutex_lock(&ddev->mode_config.mutex);
drm_connector_list_iter_begin(ddev, &iter);
drm_for_each_connector_iter(connector, &iter) {
if (connector->status != connector_status_connected)
continue;
if (idx >= AMDGPU_DM_MAX_CRTC) {
drm_warn(adev_to_drm(adev),
"%s connected connectors exceed max crtc\n",
__func__);
mutex_unlock(&ddev->mode_config.mutex);
return;
}
aconnector = to_amdgpu_dm_connector(connector);
sort_connector[idx] = aconnector;
idx++;
connector_cnt++;
}
drm_connector_list_iter_end(&iter);
/* sort connectors by link_enc_hw_instance first */
for (idx = connector_cnt; idx > 1 ; idx--) {
for (idx_2 = 0; idx_2 < (idx - 1); idx_2++) {
if (sort_connector[idx_2]->dc_link->link_enc_hw_inst >
sort_connector[idx_2 + 1]->dc_link->link_enc_hw_inst)
swap(sort_connector[idx_2], sort_connector[idx_2 + 1]);
}
}
/*
* Sort mst connectors by RAD. mst connectors with the same enc_hw_instance are already
* sorted together above.
*/
for (idx = 0; idx < connector_cnt; /*Do nothing*/) {
if (sort_connector[idx]->mst_root) {
uint8_t i, j, k;
uint8_t mst_con_cnt = 1;
for (idx_2 = (idx + 1); idx_2 < connector_cnt; idx_2++) {
if (sort_connector[idx_2]->mst_root == sort_connector[idx]->mst_root)
mst_con_cnt++;
else
break;
}
for (i = mst_con_cnt; i > 1; i--) {
for (j = idx; j < (idx + i - 2); j++) {
int mstb_lct = sort_connector[j]->mst_output_port->parent->lct;
int next_mstb_lct = sort_connector[j + 1]->mst_output_port->parent->lct;
u8 *rad;
u8 *next_rad;
bool swap = false;
/* Sort by mst tree depth first. Then compare RAD if depth is the same*/
if (mstb_lct > next_mstb_lct) {
swap = true;
} else if (mstb_lct == next_mstb_lct) {
if (mstb_lct == 1) {
if (sort_connector[j]->mst_output_port->port_num > sort_connector[j + 1]->mst_output_port->port_num)
swap = true;
} else if (mstb_lct > 1) {
rad = sort_connector[j]->mst_output_port->parent->rad;
next_rad = sort_connector[j + 1]->mst_output_port->parent->rad;
for (k = 0; k < mstb_lct - 1; k++) {
int shift = (k % 2) ? 0 : 4;
int port_num = (rad[k / 2] >> shift) & 0xf;
int next_port_num = (next_rad[k / 2] >> shift) & 0xf;
if (port_num > next_port_num) {
swap = true;
break;
}
}
} else {
DRM_ERROR("MST LCT shouldn't be set as < 1");
mutex_unlock(&ddev->mode_config.mutex);
return;
}
}
if (swap)
swap(sort_connector[j], sort_connector[j + 1]);
}
}
idx += mst_con_cnt;
} else {
idx++;
}
}
/* Complete sorting. Assign relavant result to dm->secure_display_ctx.phy_id_mapping[]*/
memset(dm->secure_display_ctx.phy_id_mapping, 0, sizeof(dm->secure_display_ctx.phy_id_mapping));
for (idx = 0; idx < connector_cnt; idx++) {
aconnector = sort_connector[idx];
dm->secure_display_ctx.phy_id_mapping[idx].assigned = true;
dm->secure_display_ctx.phy_id_mapping[idx].is_mst = false;
dm->secure_display_ctx.phy_id_mapping[idx].enc_hw_inst = aconnector->dc_link->link_enc_hw_inst;
if (sort_connector[idx]->mst_root) {
dm->secure_display_ctx.phy_id_mapping[idx].is_mst = true;
dm->secure_display_ctx.phy_id_mapping[idx].lct = aconnector->mst_output_port->parent->lct;
dm->secure_display_ctx.phy_id_mapping[idx].port_num = aconnector->mst_output_port->port_num;
memcpy(dm->secure_display_ctx.phy_id_mapping[idx].rad,
aconnector->mst_output_port->parent->rad, sizeof(aconnector->mst_output_port->parent->rad));
}
}
mutex_unlock(&ddev->mode_config.mutex);
dm->secure_display_ctx.phy_id_mapping_cnt = connector_cnt;
dm->secure_display_ctx.phy_mapping_updated = true;
}
static bool get_phy_id(struct amdgpu_display_manager *dm,
struct amdgpu_dm_connector *aconnector, uint8_t *phy_id)
{
int idx, idx_2;
bool found = false;
/*
* Assume secure display start after all connectors are probed. The connection
* config is static as well
*/
if (!dm->secure_display_ctx.phy_mapping_updated) {
DRM_WARN("%s Should update the phy id table before get it's value", __func__);
return false;
}
for (idx = 0; idx < dm->secure_display_ctx.phy_id_mapping_cnt; idx++) {
if (!dm->secure_display_ctx.phy_id_mapping[idx].assigned) {
DRM_ERROR("phy_id_mapping[%d] should be assigned", idx);
return false;
}
if (aconnector->dc_link->link_enc_hw_inst ==
dm->secure_display_ctx.phy_id_mapping[idx].enc_hw_inst) {
if (!dm->secure_display_ctx.phy_id_mapping[idx].is_mst) {
found = true;
goto out;
} else {
/* Could caused by wrongly pass mst root connector */
if (!aconnector->mst_output_port) {
DRM_ERROR("%s Check mst case but connector without a port assigned", __func__);
return false;
}
if (aconnector->mst_root &&
aconnector->mst_root->mst_mgr.mst_primary == NULL) {
DRM_WARN("%s pass in a stale mst connector", __func__);
}
if (aconnector->mst_output_port->parent->lct == dm->secure_display_ctx.phy_id_mapping[idx].lct &&
aconnector->mst_output_port->port_num == dm->secure_display_ctx.phy_id_mapping[idx].port_num) {
if (aconnector->mst_output_port->parent->lct == 1) {
found = true;
goto out;
} else if (aconnector->mst_output_port->parent->lct > 1) {
/* Check RAD */
for (idx_2 = 0; idx_2 < aconnector->mst_output_port->parent->lct - 1; idx_2++) {
int shift = (idx_2 % 2) ? 0 : 4;
int port_num = (aconnector->mst_output_port->parent->rad[idx_2 / 2] >> shift) & 0xf;
int port_num2 = (dm->secure_display_ctx.phy_id_mapping[idx].rad[idx_2 / 2] >> shift) & 0xf;
if (port_num != port_num2)
break;
}
if (idx_2 == aconnector->mst_output_port->parent->lct - 1) {
found = true;
goto out;
}
} else {
DRM_ERROR("lCT should be >= 1");
return false;
}
}
}
}
}
out:
if (found) {
DRM_DEBUG_DRIVER("Associated secure display PHY ID as %d", idx);
*phy_id = idx;
} else {
DRM_WARN("Can't find associated phy ID");
return false;
}
return true;
}
static void amdgpu_dm_set_crc_window_default(struct drm_crtc *crtc, struct dc_stream_state *stream)
{
struct drm_device *drm_dev = crtc->dev;
struct amdgpu_display_manager *dm = &drm_to_adev(drm_dev)->dm;
struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc);
struct amdgpu_dm_connector *aconnector;
bool was_activated;
uint8_t phy_id;
unsigned long flags;
int i;
spin_lock_irqsave(&drm_dev->event_lock, flags);
was_activated = acrtc->dm_irq_params.crc_window_activated;
for (i = 0; i < MAX_CRC_WINDOW_NUM; i++) {
acrtc->dm_irq_params.window_param[i].x_start = 0;
acrtc->dm_irq_params.window_param[i].y_start = 0;
acrtc->dm_irq_params.window_param[i].x_end = 0;
acrtc->dm_irq_params.window_param[i].y_end = 0;
acrtc->dm_irq_params.window_param[i].enable = false;
acrtc->dm_irq_params.window_param[i].update_win = false;
acrtc->dm_irq_params.window_param[i].skip_frame_cnt = 0;
}
acrtc->dm_irq_params.crc_window_activated = false;
spin_unlock_irqrestore(&drm_dev->event_lock, flags);
/* Disable secure_display if it was enabled */
if (was_activated && dm->secure_display_ctx.op_mode == LEGACY_MODE) {
/* stop ROI update on this crtc */
flush_work(&dm->secure_display_ctx.crtc_ctx[crtc->index].notify_ta_work);
flush_work(&dm->secure_display_ctx.crtc_ctx[crtc->index].forward_roi_work);
aconnector = (struct amdgpu_dm_connector *)stream->dm_stream_context;
if (aconnector && get_phy_id(dm, aconnector, &phy_id)) {
if (dm->secure_display_ctx.support_mul_roi)
dc_stream_forward_multiple_crc_window(stream, NULL, phy_id, true);
else
dc_stream_forward_crc_window(stream, NULL, phy_id, true);
} else {
DRM_DEBUG_DRIVER("%s Can't find matching phy id", __func__);
}
}
}
static void amdgpu_dm_crtc_notify_ta_to_read(struct work_struct *work)
{
struct secure_display_crtc_context *crtc_ctx;
struct psp_context *psp;
struct ta_securedisplay_cmd *securedisplay_cmd;
struct drm_crtc *crtc;
struct dc_stream_state *stream;
struct amdgpu_dm_connector *aconnector;
uint8_t phy_inst;
struct amdgpu_display_manager *dm;
struct crc_data crc_cpy[MAX_CRC_WINDOW_NUM];
unsigned long flags;
uint8_t roi_idx = 0;
int ret;
int i;
crtc_ctx = container_of(work, struct secure_display_crtc_context, notify_ta_work);
crtc = crtc_ctx->crtc;
if (!crtc)
return;
psp = &drm_to_adev(crtc->dev)->psp;
if (!psp->securedisplay_context.context.initialized) {
DRM_DEBUG_DRIVER("Secure Display fails to notify PSP TA\n");
return;
}
dm = &drm_to_adev(crtc->dev)->dm;
stream = to_amdgpu_crtc(crtc)->dm_irq_params.stream;
aconnector = (struct amdgpu_dm_connector *)stream->dm_stream_context;
if (!aconnector)
return;
mutex_lock(&crtc->dev->mode_config.mutex);
if (!get_phy_id(dm, aconnector, &phy_inst)) {
DRM_WARN("%s Can't find mapping phy id!", __func__);
mutex_unlock(&crtc->dev->mode_config.mutex);
return;
}
mutex_unlock(&crtc->dev->mode_config.mutex);
spin_lock_irqsave(&crtc->dev->event_lock, flags);
memcpy(crc_cpy, crtc_ctx->crc_info.crc, sizeof(struct crc_data) * MAX_CRC_WINDOW_NUM);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
/* need lock for multiple crtcs to use the command buffer */
mutex_lock(&psp->securedisplay_context.mutex);
/* PSP TA is expected to finish data transmission over I2C within current frame,
* even there are up to 4 crtcs request to send in this frame.
*/
if (dm->secure_display_ctx.support_mul_roi) {
psp_prep_securedisplay_cmd_buf(psp, &securedisplay_cmd,
TA_SECUREDISPLAY_COMMAND__SEND_ROI_CRC_V2);
securedisplay_cmd->securedisplay_in_message.send_roi_crc_v2.phy_id = phy_inst;
for (i = 0; i < MAX_CRC_WINDOW_NUM; i++) {
if (crc_cpy[i].crc_ready)
roi_idx |= 1 << i;
}
securedisplay_cmd->securedisplay_in_message.send_roi_crc_v2.roi_idx = roi_idx;
ret = psp_securedisplay_invoke(psp, TA_SECUREDISPLAY_COMMAND__SEND_ROI_CRC_V2);
} else {
psp_prep_securedisplay_cmd_buf(psp, &securedisplay_cmd,
TA_SECUREDISPLAY_COMMAND__SEND_ROI_CRC);
securedisplay_cmd->securedisplay_in_message.send_roi_crc.phy_id = phy_inst;
ret = psp_securedisplay_invoke(psp, TA_SECUREDISPLAY_COMMAND__SEND_ROI_CRC);
}
if (!ret) {
if (securedisplay_cmd->status != TA_SECUREDISPLAY_STATUS__SUCCESS)
psp_securedisplay_parse_resp_status(psp, securedisplay_cmd->status);
}
mutex_unlock(&psp->securedisplay_context.mutex);
}
static void
amdgpu_dm_forward_crc_window(struct work_struct *work)
{
struct secure_display_crtc_context *crtc_ctx;
struct amdgpu_display_manager *dm;
struct drm_crtc *crtc;
struct dc_stream_state *stream;
struct amdgpu_dm_connector *aconnector;
struct crc_window roi_cpy[MAX_CRC_WINDOW_NUM];
unsigned long flags;
uint8_t phy_id;
crtc_ctx = container_of(work, struct secure_display_crtc_context, forward_roi_work);
crtc = crtc_ctx->crtc;
if (!crtc)
return;
dm = &drm_to_adev(crtc->dev)->dm;
stream = to_amdgpu_crtc(crtc)->dm_irq_params.stream;
aconnector = (struct amdgpu_dm_connector *)stream->dm_stream_context;
if (!aconnector)
return;
mutex_lock(&crtc->dev->mode_config.mutex);
if (!get_phy_id(dm, aconnector, &phy_id)) {
DRM_WARN("%s Can't find mapping phy id!", __func__);
mutex_unlock(&crtc->dev->mode_config.mutex);
return;
}
mutex_unlock(&crtc->dev->mode_config.mutex);
spin_lock_irqsave(&crtc->dev->event_lock, flags);
memcpy(roi_cpy, crtc_ctx->roi, sizeof(struct crc_window) * MAX_CRC_WINDOW_NUM);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
mutex_lock(&dm->dc_lock);
if (dm->secure_display_ctx.support_mul_roi)
dc_stream_forward_multiple_crc_window(stream, roi_cpy,
phy_id, false);
else
dc_stream_forward_crc_window(stream, &roi_cpy[0].rect,
phy_id, false);
mutex_unlock(&dm->dc_lock);
}
bool amdgpu_dm_crc_window_is_activated(struct drm_crtc *crtc)
{
struct drm_device *drm_dev = crtc->dev;
struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc);
bool ret = false;
spin_lock_irq(&drm_dev->event_lock);
ret = acrtc->dm_irq_params.crc_window_activated;
spin_unlock_irq(&drm_dev->event_lock);
return ret;
}
#endif
int
amdgpu_dm_crtc_verify_crc_source(struct drm_crtc *crtc, const char *src_name,
size_t *values_cnt)
{
enum amdgpu_dm_pipe_crc_source source = dm_parse_crc_source(src_name);
if (source < 0) {
DRM_DEBUG_DRIVER("Unknown CRC source %s for CRTC%d\n",
src_name, crtc->index);
return -EINVAL;
}
*values_cnt = 3;
return 0;
}
int amdgpu_dm_crtc_configure_crc_source(struct drm_crtc *crtc,
struct dm_crtc_state *dm_crtc_state,
enum amdgpu_dm_pipe_crc_source source)
{
struct amdgpu_device *adev = drm_to_adev(crtc->dev);
struct dc_stream_state *stream_state = dm_crtc_state->stream;
bool enable = amdgpu_dm_is_valid_crc_source(source);
int ret = 0;
enum crc_poly_mode crc_poly_mode = CRC_POLY_MODE_16;
/* Configuration will be deferred to stream enable. */
if (!stream_state)
return -EINVAL;
mutex_lock(&adev->dm.dc_lock);
/* Notify power module about CRC window active to disable PSR/Replay
* Power module will check caps internally and skip if not supported
*/
if (enable) {
amdgpu_dm_psr_set_event(&adev->dm, stream_state, true,
psr_event_crc_window_active, true);
amdgpu_dm_replay_set_event(&adev->dm, stream_state, true,
replay_event_crc_window_active, true);
}
/* CRC polynomial selection only support for DCN3.6+ except DCN4.0.1 */
if ((amdgpu_ip_version(adev, DCE_HWIP, 0) >= IP_VERSION(3, 6, 0)) &&
(amdgpu_ip_version(adev, DCE_HWIP, 0) != IP_VERSION(4, 0, 1))) {
struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc);
crc_poly_mode = acrtc->dm_irq_params.crc_poly_mode;
}
/* Enable or disable CRTC CRC generation */
if (dm_is_crc_source_crtc(source) || source == AMDGPU_DM_PIPE_CRC_SOURCE_NONE) {
if (!dc_stream_configure_crc(stream_state->ctx->dc,
stream_state, NULL, enable, enable, 0, true, crc_poly_mode)) {
ret = -EINVAL;
goto unlock;
}
}
/* Configure dithering */
if (!dm_need_crc_dither(source)) {
dc_stream_set_dither_option(stream_state, DITHER_OPTION_TRUN8);
dc_stream_set_dyn_expansion(stream_state->ctx->dc, stream_state,
DYN_EXPANSION_DISABLE);
} else {
dc_stream_set_dither_option(stream_state,
DITHER_OPTION_DEFAULT);
dc_stream_set_dyn_expansion(stream_state->ctx->dc, stream_state,
DYN_EXPANSION_AUTO);
}
if (!enable) {
/* Notify power module about CRC window inactive to re-enable PSR/Replay
* Power module will check caps internally and skip if not supported
*/
amdgpu_dm_psr_set_event(&adev->dm, stream_state, false,
psr_event_crc_window_active, false);
amdgpu_dm_replay_set_event(&adev->dm, stream_state, false,
replay_event_crc_window_active, false);
}
unlock:
mutex_unlock(&adev->dm.dc_lock);
return ret;
}
int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name)
{
enum amdgpu_dm_pipe_crc_source source = dm_parse_crc_source(src_name);
enum amdgpu_dm_pipe_crc_source cur_crc_src;
struct drm_crtc_commit *commit;
struct dm_crtc_state *crtc_state;
struct drm_device *drm_dev = crtc->dev;
#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
struct amdgpu_device *adev = drm_to_adev(drm_dev);
struct amdgpu_display_manager *dm = &adev->dm;
#endif
struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc);
struct drm_dp_aux *aux = NULL;
bool enable = false;
bool enabled = false;
int ret = 0;
if (source < 0) {
DRM_DEBUG_DRIVER("Unknown CRC source %s for CRTC%d\n",
src_name, crtc->index);
return -EINVAL;
}
ret = drm_modeset_lock(&crtc->mutex, NULL);
if (ret)
return ret;
spin_lock(&crtc->commit_lock);
commit = list_first_entry_or_null(&crtc->commit_list,
struct drm_crtc_commit, commit_entry);
if (commit)
drm_crtc_commit_get(commit);
spin_unlock(&crtc->commit_lock);
if (commit) {
/*
* Need to wait for all outstanding programming to complete
* in commit tail since it can modify CRC related fields and
* hardware state. Since we're holding the CRTC lock we're
* guaranteed that no other commit work can be queued off
* before we modify the state below.
*/
ret = wait_for_completion_interruptible_timeout(
&commit->hw_done, 10 * HZ);
if (ret < 0)
goto cleanup;
if (ret == 0) {
ret = -ETIMEDOUT;
goto cleanup;
}
}
enable = amdgpu_dm_is_valid_crc_source(source);
crtc_state = to_dm_crtc_state(crtc->state);
spin_lock_irq(&drm_dev->event_lock);
cur_crc_src = acrtc->dm_irq_params.crc_src;
spin_unlock_irq(&drm_dev->event_lock);
/*
* USER REQ SRC | CURRENT SRC | BEHAVIOR
* -----------------------------
* None | None | Do nothing
* None | CRTC | Disable CRTC CRC, set default to dither
* None | DPRX | Disable DPRX CRC, need 'aux', set default to dither
* None | CRTC DITHER | Disable CRTC CRC
* None | DPRX DITHER | Disable DPRX CRC, need 'aux'
* CRTC | XXXX | Enable CRTC CRC, no dither
* DPRX | XXXX | Enable DPRX CRC, need 'aux', no dither
* CRTC DITHER | XXXX | Enable CRTC CRC, set dither
* DPRX DITHER | XXXX | Enable DPRX CRC, need 'aux', set dither
*/
if (dm_is_crc_source_dprx(source) ||
(source == AMDGPU_DM_PIPE_CRC_SOURCE_NONE &&
dm_is_crc_source_dprx(cur_crc_src))) {
struct amdgpu_dm_connector *aconn = NULL;
struct drm_connector *connector;
struct drm_connector_list_iter conn_iter;
drm_connector_list_iter_begin(crtc->dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter) {
if (!connector->state || connector->state->crtc != crtc)
continue;
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
continue;
aconn = to_amdgpu_dm_connector(connector);
break;
}
drm_connector_list_iter_end(&conn_iter);
if (!aconn) {
DRM_DEBUG_DRIVER("No amd connector matching CRTC-%d\n", crtc->index);
ret = -EINVAL;
goto cleanup;
}
aux = (aconn->mst_output_port) ? &aconn->mst_output_port->aux : &aconn->dm_dp_aux.aux;
if (!aux) {
DRM_DEBUG_DRIVER("No dp aux for amd connector\n");
ret = -EINVAL;
goto cleanup;
}
if ((aconn->base.connector_type != DRM_MODE_CONNECTOR_DisplayPort) &&
(aconn->base.connector_type != DRM_MODE_CONNECTOR_eDP)) {
DRM_DEBUG_DRIVER("No DP connector available for CRC source\n");
ret = -EINVAL;
goto cleanup;
}
}
/*
* Reading the CRC requires the vblank interrupt handler to be
* enabled. Keep a reference until CRC capture stops.
*/
enabled = amdgpu_dm_is_valid_crc_source(cur_crc_src);
if (!enabled && enable) {
ret = drm_crtc_vblank_get(crtc);
if (ret)
goto cleanup;
}
#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
/* Reset secure_display when we change crc source from debugfs */
amdgpu_dm_set_crc_window_default(crtc, crtc_state->stream);
#endif
if (amdgpu_dm_crtc_configure_crc_source(crtc, crtc_state, source)) {
ret = -EINVAL;
goto cleanup;
}
if (!enabled && enable) {
if (dm_is_crc_source_dprx(source)) {
if (drm_dp_start_crc(aux, crtc)) {
DRM_DEBUG_DRIVER("dp start crc failed\n");
ret = -EINVAL;
goto cleanup;
}
}
} else if (enabled && !enable) {
drm_crtc_vblank_put(crtc);
if (dm_is_crc_source_dprx(source)) {
if (drm_dp_stop_crc(aux)) {
DRM_DEBUG_DRIVER("dp stop crc failed\n");
ret = -EINVAL;
goto cleanup;
}
}
}
spin_lock_irq(&drm_dev->event_lock);
acrtc->dm_irq_params.crc_src = source;
spin_unlock_irq(&drm_dev->event_lock);
/* Reset crc_skipped on dm state */
crtc_state->crc_skip_count = 0;
#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
/* Initialize phy id mapping table for secure display*/
if (dm->secure_display_ctx.op_mode == LEGACY_MODE &&
!dm->secure_display_ctx.phy_mapping_updated)
update_phy_id_mapping(adev);
#endif
cleanup:
if (commit)
drm_crtc_commit_put(commit);
drm_modeset_unlock(&crtc->mutex);
return ret;
}
/**
* amdgpu_dm_crtc_handle_crc_irq: Report to DRM the CRC on given CRTC.
* @crtc: DRM CRTC object.
*
* This function should be called at the end of a vblank, when the fb has been
* fully processed through the pipe.
*/
void amdgpu_dm_crtc_handle_crc_irq(struct drm_crtc *crtc)
{
struct dm_crtc_state *crtc_state;
struct dc_stream_state *stream_state;
struct drm_device *drm_dev = NULL;
enum amdgpu_dm_pipe_crc_source cur_crc_src;
struct amdgpu_crtc *acrtc = NULL;
uint32_t crcs[3];
unsigned long flags;
if (!crtc || !crtc->state || !crtc->dev)
return;
crtc_state = to_dm_crtc_state(crtc->state);
if (!crtc_state->stream)
return;
stream_state = crtc_state->stream;
acrtc = to_amdgpu_crtc(crtc);
drm_dev = crtc->dev;
spin_lock_irqsave(&drm_dev->event_lock, flags);
cur_crc_src = acrtc->dm_irq_params.crc_src;
spin_unlock_irqrestore(&drm_dev->event_lock, flags);
/* Early return if CRC capture is not enabled. */
if (!amdgpu_dm_is_valid_crc_source(cur_crc_src))
return;
/*
* Since flipping and crc enablement happen asynchronously, we - more
* often than not - will be returning an 'uncooked' crc on first frame.
* Probably because hw isn't ready yet. For added security, skip the
* first two CRC values.
*/
if (crtc_state->crc_skip_count < 2) {
crtc_state->crc_skip_count += 1;
return;
}
if (dm_is_crc_source_crtc(cur_crc_src)) {
if (!dc_stream_get_crc(stream_state->ctx->dc, stream_state, 0,
&crcs[0], &crcs[1], &crcs[2]))
return;
drm_crtc_add_crc_entry(crtc, true,
drm_crtc_accurate_vblank_count(crtc), crcs);
}
}
#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
void amdgpu_dm_crtc_handle_crc_window_irq(struct drm_crtc *crtc)
{
struct drm_device *drm_dev = NULL;
enum amdgpu_dm_pipe_crc_source cur_crc_src;
struct amdgpu_crtc *acrtc = NULL;
struct amdgpu_device *adev = NULL;
struct secure_display_crtc_context *crtc_ctx = NULL;
bool reset_crc_frame_count[MAX_CRC_WINDOW_NUM] = {false};
uint32_t crc_r[MAX_CRC_WINDOW_NUM] = {0};
uint32_t crc_g[MAX_CRC_WINDOW_NUM] = {0};
uint32_t crc_b[MAX_CRC_WINDOW_NUM] = {0};
unsigned long flags1;
bool forward_roi_change = false;
bool notify_ta = false;
struct dc_stream_state *stream_state;
int i;
if (crtc == NULL)
return;
acrtc = to_amdgpu_crtc(crtc);
adev = drm_to_adev(crtc->dev);
drm_dev = crtc->dev;
stream_state = to_dm_crtc_state(crtc->state)->stream;
spin_lock_irqsave(&drm_dev->event_lock, flags1);
cur_crc_src = acrtc->dm_irq_params.crc_src;
/* Early return if CRC capture is not enabled. */
if (!amdgpu_dm_is_valid_crc_source(cur_crc_src) ||
!dm_is_crc_source_crtc(cur_crc_src)) {
spin_unlock_irqrestore(&drm_dev->event_lock, flags1);
return;
}
if (!acrtc->dm_irq_params.crc_window_activated) {
spin_unlock_irqrestore(&drm_dev->event_lock, flags1);
return;
}
crtc_ctx = &adev->dm.secure_display_ctx.crtc_ctx[acrtc->crtc_id];
if (WARN_ON(crtc_ctx->crtc != crtc)) {
/* We have set the crtc when creating secure_display_crtc_context,
* don't expect it to be changed here.
*/
crtc_ctx->crtc = crtc;
}
for (i = 0; i < MAX_CRC_WINDOW_NUM; i++) {
struct crc_params crc_window = {
.windowa_x_start = acrtc->dm_irq_params.window_param[i].x_start,
.windowa_y_start = acrtc->dm_irq_params.window_param[i].y_start,
.windowa_x_end = acrtc->dm_irq_params.window_param[i].x_end,
.windowa_y_end = acrtc->dm_irq_params.window_param[i].y_end,
.windowb_x_start = acrtc->dm_irq_params.window_param[i].x_start,
.windowb_y_start = acrtc->dm_irq_params.window_param[i].y_start,
.windowb_x_end = acrtc->dm_irq_params.window_param[i].x_end,
.windowb_y_end = acrtc->dm_irq_params.window_param[i].y_end,
};
crtc_ctx->roi[i].enable = acrtc->dm_irq_params.window_param[i].enable;
if (!acrtc->dm_irq_params.window_param[i].enable) {
crtc_ctx->crc_info.crc[i].crc_ready = false;
continue;
}
if (acrtc->dm_irq_params.window_param[i].skip_frame_cnt) {
acrtc->dm_irq_params.window_param[i].skip_frame_cnt -= 1;
crtc_ctx->crc_info.crc[i].crc_ready = false;
continue;
}
if (acrtc->dm_irq_params.window_param[i].update_win) {
crtc_ctx->roi[i].rect.x = crc_window.windowa_x_start;
crtc_ctx->roi[i].rect.y = crc_window.windowa_y_start;
crtc_ctx->roi[i].rect.width = crc_window.windowa_x_end -
crc_window.windowa_x_start;
crtc_ctx->roi[i].rect.height = crc_window.windowa_y_end -
crc_window.windowa_y_start;
if (adev->dm.secure_display_ctx.op_mode == LEGACY_MODE)
/* forward task to dmub to update ROI */
forward_roi_change = true;
else if (adev->dm.secure_display_ctx.op_mode == DISPLAY_CRC_MODE)
/* update ROI via dm*/
dc_stream_configure_crc(stream_state->ctx->dc, stream_state,
&crc_window, true, true, i, false, (enum crc_poly_mode)acrtc->dm_irq_params.crc_poly_mode);
reset_crc_frame_count[i] = true;
acrtc->dm_irq_params.window_param[i].update_win = false;
/* Statically skip 1 frame, because we may need to wait below things
* before sending ROI to dmub:
* 1. We defer the work by using system workqueue.
* 2. We may need to wait for dc_lock before accessing dmub.
*/
acrtc->dm_irq_params.window_param[i].skip_frame_cnt = 1;
crtc_ctx->crc_info.crc[i].crc_ready = false;
} else {
if (!dc_stream_get_crc(stream_state->ctx->dc, stream_state, i,
&crc_r[i], &crc_g[i], &crc_b[i]))
DRM_ERROR("Secure Display: fail to get crc from engine %d\n", i);
if (adev->dm.secure_display_ctx.op_mode == LEGACY_MODE)
/* forward task to psp to read ROI/CRC and output via I2C */
notify_ta = true;
else if (adev->dm.secure_display_ctx.op_mode == DISPLAY_CRC_MODE)
/* Avoid ROI window get changed, keep overwriting. */
dc_stream_configure_crc(stream_state->ctx->dc, stream_state,
&crc_window, true, true, i, false, (enum crc_poly_mode)acrtc->dm_irq_params.crc_poly_mode);
/* crc ready for psp to read out */
crtc_ctx->crc_info.crc[i].crc_ready = true;
}
}
spin_unlock_irqrestore(&drm_dev->event_lock, flags1);
if (forward_roi_change)
schedule_work(&crtc_ctx->forward_roi_work);
if (notify_ta)
schedule_work(&crtc_ctx->notify_ta_work);
spin_lock_irqsave(&crtc_ctx->crc_info.lock, flags1);
for (i = 0; i < MAX_CRC_WINDOW_NUM; i++) {
crtc_ctx->crc_info.crc[i].crc_R = crc_r[i];
crtc_ctx->crc_info.crc[i].crc_G = crc_g[i];
crtc_ctx->crc_info.crc[i].crc_B = crc_b[i];
if (!crtc_ctx->roi[i].enable) {
crtc_ctx->crc_info.crc[i].frame_count = 0;
continue;
}
if (reset_crc_frame_count[i] || crtc_ctx->crc_info.crc[i].frame_count == UINT_MAX)
/* Reset the reference frame count after user update the ROI
* or it reaches the maximum value.
*/
crtc_ctx->crc_info.crc[i].frame_count = 0;
else
crtc_ctx->crc_info.crc[i].frame_count += 1;
}
spin_unlock_irqrestore(&crtc_ctx->crc_info.lock, flags1);
}
void amdgpu_dm_crtc_secure_display_create_contexts(struct amdgpu_device *adev)
{
struct secure_display_crtc_context *crtc_ctx = NULL;
int i;
crtc_ctx = kzalloc_objs(struct secure_display_crtc_context,
adev->mode_info.num_crtc);
if (!crtc_ctx) {
adev->dm.secure_display_ctx.crtc_ctx = NULL;
return;
}
for (i = 0; i < adev->mode_info.num_crtc; i++) {
INIT_WORK(&crtc_ctx[i].forward_roi_work, amdgpu_dm_forward_crc_window);
INIT_WORK(&crtc_ctx[i].notify_ta_work, amdgpu_dm_crtc_notify_ta_to_read);
crtc_ctx[i].crtc = &adev->mode_info.crtcs[i]->base;
spin_lock_init(&crtc_ctx[i].crc_info.lock);
}
adev->dm.secure_display_ctx.crtc_ctx = crtc_ctx;
adev->dm.secure_display_ctx.op_mode = DISPLAY_CRC_MODE;
}
#endif
@@ -0,0 +1,161 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2019 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
*
*/
#ifndef AMD_DAL_DEV_AMDGPU_DM_AMDGPU_DM_CRC_H_
#define AMD_DAL_DEV_AMDGPU_DM_AMDGPU_DM_CRC_H_
#include "dc_types.h"
struct drm_crtc;
struct dm_crtc_state;
struct amdgpu_device;
enum amdgpu_dm_pipe_crc_source {
AMDGPU_DM_PIPE_CRC_SOURCE_NONE = 0,
AMDGPU_DM_PIPE_CRC_SOURCE_CRTC,
AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER,
AMDGPU_DM_PIPE_CRC_SOURCE_DPRX,
AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER,
AMDGPU_DM_PIPE_CRC_SOURCE_MAX,
AMDGPU_DM_PIPE_CRC_SOURCE_INVALID = -1,
};
#ifdef CONFIG_DRM_AMD_SECURE_DISPLAY
#define MAX_CRTC 6
enum secure_display_mode {
/* via dmub + psp */
LEGACY_MODE = 0,
/* driver directly */
DISPLAY_CRC_MODE,
SECURE_DISPLAY_MODE_MAX,
};
struct phy_id_mapping {
bool assigned;
bool is_mst;
uint8_t enc_hw_inst;
u8 lct;
u8 port_num;
u8 rad[8];
};
struct crc_data {
uint32_t crc_R;
uint32_t crc_G;
uint32_t crc_B;
uint32_t frame_count;
bool crc_ready;
};
struct crc_info {
struct crc_data crc[MAX_CRC_WINDOW_NUM];
spinlock_t lock;
};
struct crc_window_param {
uint16_t x_start;
uint16_t y_start;
uint16_t x_end;
uint16_t y_end;
/* CRC window is activated or not*/
bool enable;
/* Update crc window during vertical blank or not */
bool update_win;
/* skip reading/writing for few frames */
int skip_frame_cnt;
};
struct secure_display_crtc_context {
/* work to notify PSP TA*/
struct work_struct notify_ta_work;
/* work to forward ROI to dmcu/dmub */
struct work_struct forward_roi_work;
struct drm_crtc *crtc;
/* Region of Interest (ROI) */
struct crc_window roi[MAX_CRC_WINDOW_NUM];
struct crc_info crc_info;
};
struct secure_display_context {
struct secure_display_crtc_context *crtc_ctx;
/* Whether dmub support multiple ROI setting */
bool support_mul_roi;
enum secure_display_mode op_mode;
bool phy_mapping_updated;
int phy_id_mapping_cnt;
struct phy_id_mapping phy_id_mapping[MAX_CRTC];
};
#endif
static inline bool amdgpu_dm_is_valid_crc_source(enum amdgpu_dm_pipe_crc_source source)
{
return (source > AMDGPU_DM_PIPE_CRC_SOURCE_NONE) &&
(source < AMDGPU_DM_PIPE_CRC_SOURCE_MAX);
}
/* amdgpu_dm_crc.c */
#ifdef CONFIG_DEBUG_FS
int amdgpu_dm_crtc_configure_crc_source(struct drm_crtc *crtc,
struct dm_crtc_state *dm_crtc_state,
enum amdgpu_dm_pipe_crc_source source);
int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name);
int amdgpu_dm_crtc_verify_crc_source(struct drm_crtc *crtc,
const char *src_name,
size_t *values_cnt);
const char *const *amdgpu_dm_crtc_get_crc_sources(struct drm_crtc *crtc,
size_t *count);
void amdgpu_dm_crtc_handle_crc_irq(struct drm_crtc *crtc);
#else
#define amdgpu_dm_crtc_set_crc_source NULL
#define amdgpu_dm_crtc_verify_crc_source NULL
#define amdgpu_dm_crtc_get_crc_sources NULL
#define amdgpu_dm_crtc_handle_crc_irq(x)
#endif
#ifdef CONFIG_DRM_AMD_SECURE_DISPLAY
bool amdgpu_dm_crc_window_is_activated(struct drm_crtc *crtc);
void amdgpu_dm_crtc_handle_crc_window_irq(struct drm_crtc *crtc);
void amdgpu_dm_crtc_secure_display_create_contexts(struct amdgpu_device *adev);
#else
#define amdgpu_dm_crc_window_is_activated(x)
#define amdgpu_dm_crtc_handle_crc_window_irq(x)
#define amdgpu_dm_crtc_secure_display_create_contexts(x)
#endif
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source);
bool dm_is_crc_source_crtc(enum amdgpu_dm_pipe_crc_source src);
bool dm_is_crc_source_dprx(enum amdgpu_dm_pipe_crc_source src);
bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src);
#endif
#endif /* AMD_DAL_DEV_AMDGPU_DM_AMDGPU_DM_CRC_H_ */
@@ -0,0 +1,804 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2022 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 <drm/drm_vblank.h>
#include <drm/drm_atomic_helper.h>
#include "dc.h"
#include "amdgpu.h"
#include "amdgpu_dm_psr.h"
#include "amdgpu_dm_replay.h"
#include "amdgpu_dm_crtc.h"
#include "amdgpu_dm_plane.h"
#include "amdgpu_dm_trace.h"
#include "amdgpu_dm_debugfs.h"
#include "modules/inc/mod_power.h"
#define HPD_DETECTION_PERIOD_uS 2000000
#define HPD_DETECTION_TIME_uS 100000
void amdgpu_dm_crtc_handle_vblank(struct amdgpu_crtc *acrtc)
{
struct drm_crtc *crtc = &acrtc->base;
struct drm_device *dev = crtc->dev;
unsigned long flags;
drm_crtc_handle_vblank(crtc);
spin_lock_irqsave(&dev->event_lock, flags);
/* Send completion event for cursor-only commits */
if (acrtc->event && acrtc->pflip_status != AMDGPU_FLIP_SUBMITTED) {
drm_crtc_send_vblank_event(crtc, acrtc->event);
drm_crtc_vblank_put(crtc);
acrtc->event = NULL;
}
spin_unlock_irqrestore(&dev->event_lock, flags);
}
bool amdgpu_dm_crtc_modeset_required(struct drm_crtc_state *crtc_state,
struct dc_stream_state *new_stream,
struct dc_stream_state *old_stream)
{
return crtc_state->active && drm_atomic_crtc_needs_modeset(crtc_state);
}
bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc)
{
return acrtc->dm_irq_params.freesync_config.state ==
VRR_STATE_ACTIVE_VARIABLE ||
acrtc->dm_irq_params.freesync_config.state ==
VRR_STATE_ACTIVE_FIXED;
}
int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable)
{
enum dc_irq_source irq_source;
struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc);
struct amdgpu_device *adev = drm_to_adev(crtc->dev);
int rc;
if (acrtc->otg_inst == -1)
return 0;
irq_source = IRQ_TYPE_VUPDATE + acrtc->otg_inst;
rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY;
DRM_DEBUG_VBL("crtc %d - vupdate irq %sabling: r=%d\n",
acrtc->crtc_id, enable ? "en" : "dis", rc);
return rc;
}
bool amdgpu_dm_crtc_vrr_active(const struct dm_crtc_state *dm_state)
{
return dm_state->freesync_config.state == VRR_STATE_ACTIVE_VARIABLE ||
dm_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED;
}
/**
* amdgpu_dm_crtc_set_static_screen_optimze() - Toggle static screen optimizations.
*
* @dm: display manager
* @stream: DC stream state
* @sso_enable: desired static screen optimization state
* @allow_sr_entry: whether entry into self-refresh mode is allowed
*
* This function uses the static-screen optimization state as the trigger to
* set/clear the Replay and PSR vsync-related events.
*/
void amdgpu_dm_crtc_set_static_screen_optimze(
struct amdgpu_display_manager *dm,
struct dc_stream_state *stream,
bool sso_enable, bool allow_sr_entry)
{
struct dc_link *link = stream->link;
bool set_vsync_event = !sso_enable;
/*
* allow_sr_entry gates only entry. A disable request must still set
* the vsync events to force Replay and PSR1 out and keep them blocked.
*/
if (sso_enable && !allow_sr_entry)
return;
amdgpu_dm_replay_set_event(dm, stream,
set_vsync_event, replay_event_vsync, set_vsync_event);
if (link->psr_settings.psr_version < DC_PSR_VERSION_SU_1)
amdgpu_dm_psr_set_event(dm, stream,
set_vsync_event, psr_event_vsync, set_vsync_event);
}
bool amdgpu_dm_is_headless(struct amdgpu_device *adev)
{
struct drm_connector *connector;
struct drm_connector_list_iter iter;
struct drm_device *dev;
bool is_headless = true;
if (adev == NULL)
return true;
dev = adev->dm.ddev;
drm_connector_list_iter_begin(dev, &iter);
drm_for_each_connector_iter(connector, &iter) {
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
continue;
if (connector->status == connector_status_connected) {
is_headless = false;
break;
}
}
drm_connector_list_iter_end(&iter);
return is_headless;
}
static void amdgpu_dm_idle_worker(struct work_struct *work)
{
struct idle_workqueue *idle_work;
idle_work = container_of(work, struct idle_workqueue, work);
idle_work->dm->idle_workqueue->running = true;
while (idle_work->enable) {
fsleep(HPD_DETECTION_PERIOD_uS);
mutex_lock(&idle_work->dm->dc_lock);
if (!idle_work->dm->dc->idle_optimizations_allowed) {
mutex_unlock(&idle_work->dm->dc_lock);
break;
}
dc_allow_idle_optimizations(idle_work->dm->dc, false);
mutex_unlock(&idle_work->dm->dc_lock);
fsleep(HPD_DETECTION_TIME_uS);
mutex_lock(&idle_work->dm->dc_lock);
if (!amdgpu_dm_is_headless(idle_work->dm->adev) &&
!amdgpu_dm_psr_is_active_allowed(idle_work->dm)) {
mutex_unlock(&idle_work->dm->dc_lock);
break;
}
if (idle_work->enable) {
dc_post_update_surfaces_to_stream(idle_work->dm->dc);
dc_allow_idle_optimizations(idle_work->dm->dc, true);
}
mutex_unlock(&idle_work->dm->dc_lock);
}
idle_work->dm->idle_workqueue->running = false;
}
struct idle_workqueue *idle_create_workqueue(struct amdgpu_device *adev)
{
struct idle_workqueue *idle_work;
idle_work = kzalloc_obj(*idle_work);
if (ZERO_OR_NULL_PTR(idle_work))
return NULL;
idle_work->dm = &adev->dm;
idle_work->enable = false;
idle_work->running = false;
INIT_WORK(&idle_work->work, amdgpu_dm_idle_worker);
return idle_work;
}
static void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work)
{
struct vblank_control_work *vblank_work =
container_of(work, struct vblank_control_work, work);
struct amdgpu_display_manager *dm = vblank_work->dm;
mutex_lock(&dm->dc_lock);
if (vblank_work->enable) {
dm->active_vblank_irq_count++;
amdgpu_dm_ism_commit_event(&vblank_work->acrtc->ism,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED);
} else {
if (dm->active_vblank_irq_count > 0)
dm->active_vblank_irq_count--;
amdgpu_dm_ism_commit_event(&vblank_work->acrtc->ism,
DM_ISM_EVENT_ENTER_IDLE_REQUESTED);
}
mutex_unlock(&dm->dc_lock);
dc_stream_release(vblank_work->stream);
kfree(vblank_work);
}
static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable)
{
struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc);
struct amdgpu_device *adev = drm_to_adev(crtc->dev);
struct dm_crtc_state *acrtc_state = to_dm_crtc_state(crtc->state);
struct amdgpu_display_manager *dm = &adev->dm;
struct vblank_control_work *work;
int irq_type;
int rc = 0;
if (enable && !acrtc->base.enabled) {
drm_dbg_vbl(crtc->dev,
"Reject vblank enable on unconfigured CRTC %d (enabled=%d)\n",
acrtc->crtc_id, acrtc->base.enabled);
return -EINVAL;
}
irq_type = amdgpu_display_crtc_idx_to_irq_type(adev, acrtc->crtc_id);
if (enable && acrtc_state->stream) {
struct dc *dc = adev->dm.dc;
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
struct psr_settings *psr = &acrtc_state->stream->link->psr_settings;
struct replay_settings *pr = &acrtc_state->stream->link->replay_settings;
bool sr_supported = (psr->psr_version != DC_PSR_VERSION_UNSUPPORTED) ||
pr->config.replay_supported;
/*
* IPS & self-refresh feature can cause vblank counter resets between
* vblank disable and enable.
* It may cause system stuck due to waiting for the vblank counter.
* Call this function to estimate missed vblanks by using timestamps and
* update the vblank counter in DRM.
*/
if (dc->caps.ips_support &&
dc->config.disable_ips != DMUB_IPS_DISABLE_ALL &&
sr_supported && vblank->config.disable_immediate)
drm_crtc_vblank_restore(crtc);
}
/*
* On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver
* vblank and pageflip completion events, so enable it whenever vblank
* is enabled. On DCE, vupdate is only needed in VRR mode.
*/
if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) {
if (enable) {
rc = amdgpu_irq_get(adev, &adev->vupdate_irq, irq_type);
drm_dbg_vbl(crtc->dev, "Get vupdate_irq ret=%d\n", rc);
} else {
rc = amdgpu_irq_put(adev, &adev->vupdate_irq, irq_type);
drm_dbg_vbl(crtc->dev, "Put vupdate_irq ret=%d\n", rc);
}
} else if (dc_supports_vrr(dm->dc->ctx->dce_version)) {
if (enable) {
/* vblank irq on -> Only need vupdate irq in vrr mode
* Not ref-counted since we need explicit enable/disable
* for DCE VRR handling
*/
if (amdgpu_dm_crtc_vrr_active(acrtc_state))
rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, true);
} else {
/* vblank irq off -> vupdate irq off */
rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, false);
}
}
if (rc)
return rc;
/*
* VLINE0 (crtc_irq) and GRPH_PFLIP (pageflip_irq) are only used on
* DCE. On DCN, vblank and pageflip completion are delivered from
* VUPDATE_NO_LOCK (enabled above), so don't touch them here.
*/
if (amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) {
/* crtc vblank or vstartup interrupt */
if (enable) {
rc = amdgpu_irq_get(adev, &adev->crtc_irq, irq_type);
drm_dbg_vbl(crtc->dev, "Get crtc_irq ret=%d\n", rc);
} else {
rc = amdgpu_irq_put(adev, &adev->crtc_irq, irq_type);
drm_dbg_vbl(crtc->dev, "Put crtc_irq ret=%d\n", rc);
}
if (rc)
return rc;
/*
* hubp surface flip interrupt
*
* We have no guarantee that the frontend index maps to the same
* backend index - some even map to more than one.
*
* TODO: Use a different interrupt or check DC itself for the mapping.
*/
if (enable) {
rc = amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type);
drm_dbg_vbl(crtc->dev, "Get pageflip_irq ret=%d\n", rc);
} else {
rc = amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type);
drm_dbg_vbl(crtc->dev, "Put pageflip_irq ret=%d\n", rc);
}
if (rc)
return rc;
}
#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY)
/* crtc vline0 interrupt, only available on DCN+ */
if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) {
if (enable) {
rc = amdgpu_irq_get(adev, &adev->vline0_irq, irq_type);
drm_dbg_vbl(crtc->dev, "Get vline0_irq ret=%d\n", rc);
} else {
rc = amdgpu_irq_put(adev, &adev->vline0_irq, irq_type);
drm_dbg_vbl(crtc->dev, "Put vline0_irq ret=%d\n", rc);
}
if (rc)
return rc;
}
#endif
if (amdgpu_in_reset(adev))
return 0;
if (dm->vblank_control_workqueue) {
work = kzalloc_obj(*work, GFP_ATOMIC);
if (!work)
return -ENOMEM;
INIT_WORK(&work->work, amdgpu_dm_crtc_vblank_control_worker);
work->dm = dm;
work->acrtc = acrtc;
work->enable = enable;
if (acrtc_state->stream) {
dc_stream_retain(acrtc_state->stream);
work->stream = acrtc_state->stream;
}
queue_work(dm->vblank_control_workqueue, &work->work);
}
return 0;
}
int amdgpu_dm_crtc_enable_vblank(struct drm_crtc *crtc)
{
return amdgpu_dm_crtc_set_vblank(crtc, true);
}
void amdgpu_dm_crtc_disable_vblank(struct drm_crtc *crtc)
{
amdgpu_dm_crtc_set_vblank(crtc, false);
}
static void amdgpu_dm_crtc_destroy_state(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct dm_crtc_state *cur = to_dm_crtc_state(state);
/* TODO Destroy dc_stream objects are stream object is flattened */
if (cur->stream)
dc_stream_release(cur->stream);
__drm_atomic_helper_crtc_destroy_state(state);
kfree(state);
}
static struct drm_crtc_state *amdgpu_dm_crtc_duplicate_state(struct drm_crtc *crtc)
{
struct dm_crtc_state *state, *cur;
cur = to_dm_crtc_state(crtc->state);
if (WARN_ON(!crtc->state))
return NULL;
state = kzalloc_obj(*state);
if (!state)
return NULL;
__drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
if (cur->stream) {
state->stream = cur->stream;
dc_stream_retain(state->stream);
}
state->active_planes = cur->active_planes;
state->vrr_infopacket = cur->vrr_infopacket;
state->abm_level = cur->abm_level;
state->vrr_supported = cur->vrr_supported;
state->freesync_config = cur->freesync_config;
state->cm_has_degamma = cur->cm_has_degamma;
state->cm_is_degamma_srgb = cur->cm_is_degamma_srgb;
state->regamma_tf = cur->regamma_tf;
state->crc_skip_count = cur->crc_skip_count;
state->mpo_requested = cur->mpo_requested;
state->cursor_mode = cur->cursor_mode;
/* TODO Duplicate dc_stream after objects are stream object is flattened */
return &state->base;
}
static void amdgpu_dm_crtc_destroy(struct drm_crtc *crtc)
{
/*
* amdgpu_dm_ism_fini() is intentionally called in amdgpu_dm_fini().
* It must be called before dc_destroy() in amdgpu_dm_fini()
* to avoid ISM accessing an invalid dc handle once dc is released.
*/
drm_crtc_cleanup(crtc);
kfree(crtc);
}
static void amdgpu_dm_crtc_reset_state(struct drm_crtc *crtc)
{
struct dm_crtc_state *state;
if (crtc->state)
amdgpu_dm_crtc_destroy_state(crtc, crtc->state);
state = kzalloc_obj(*state);
if (WARN_ON(!state))
return;
__drm_atomic_helper_crtc_reset(crtc, &state->base);
}
#ifdef CONFIG_DEBUG_FS
static int amdgpu_dm_crtc_late_register(struct drm_crtc *crtc)
{
crtc_debugfs_init(crtc);
return 0;
}
#endif
#ifdef AMD_PRIVATE_COLOR
/**
* dm_crtc_additional_color_mgmt - enable additional color properties
* @crtc: DRM CRTC
*
* This function lets the driver enable post-blending CRTC regamma transfer
* function property in addition to DRM CRTC gamma LUT. Default value means
* linear transfer function, which is the default CRTC gamma LUT behaviour
* without this property.
*/
static void
dm_crtc_additional_color_mgmt(struct drm_crtc *crtc)
{
struct amdgpu_device *adev = drm_to_adev(crtc->dev);
if (adev->dm.dc->caps.color.mpc.ogam_ram)
drm_object_attach_property(&crtc->base,
adev->mode_info.regamma_tf_property,
AMDGPU_TRANSFER_FUNCTION_DEFAULT);
}
static int
amdgpu_dm_atomic_crtc_set_property(struct drm_crtc *crtc,
struct drm_crtc_state *state,
struct drm_property *property,
uint64_t val)
{
struct amdgpu_device *adev = drm_to_adev(crtc->dev);
struct dm_crtc_state *acrtc_state = to_dm_crtc_state(state);
if (property == adev->mode_info.regamma_tf_property) {
if (acrtc_state->regamma_tf != val) {
acrtc_state->regamma_tf = val;
acrtc_state->base.color_mgmt_changed |= 1;
}
} else {
drm_dbg_atomic(crtc->dev,
"[CRTC:%d:%s] unknown property [PROP:%d:%s]]\n",
crtc->base.id, crtc->name,
property->base.id, property->name);
return -EINVAL;
}
return 0;
}
static int
amdgpu_dm_atomic_crtc_get_property(struct drm_crtc *crtc,
const struct drm_crtc_state *state,
struct drm_property *property,
uint64_t *val)
{
struct amdgpu_device *adev = drm_to_adev(crtc->dev);
struct dm_crtc_state *acrtc_state = to_dm_crtc_state(state);
if (property == adev->mode_info.regamma_tf_property)
*val = acrtc_state->regamma_tf;
else
return -EINVAL;
return 0;
}
#endif
/* Implemented only the options currently available for the driver */
static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = {
.reset = amdgpu_dm_crtc_reset_state,
.destroy = amdgpu_dm_crtc_destroy,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.atomic_duplicate_state = amdgpu_dm_crtc_duplicate_state,
.atomic_destroy_state = amdgpu_dm_crtc_destroy_state,
.set_crc_source = amdgpu_dm_crtc_set_crc_source,
.verify_crc_source = amdgpu_dm_crtc_verify_crc_source,
.get_crc_sources = amdgpu_dm_crtc_get_crc_sources,
.get_vblank_counter = amdgpu_get_vblank_counter_kms,
.enable_vblank = amdgpu_dm_crtc_enable_vblank,
.disable_vblank = amdgpu_dm_crtc_disable_vblank,
.get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
#if defined(CONFIG_DEBUG_FS)
.late_register = amdgpu_dm_crtc_late_register,
#endif
#ifdef AMD_PRIVATE_COLOR
.atomic_set_property = amdgpu_dm_atomic_crtc_set_property,
.atomic_get_property = amdgpu_dm_atomic_crtc_get_property,
#endif
};
static void amdgpu_dm_crtc_helper_disable(struct drm_crtc *crtc)
{
}
static int amdgpu_dm_crtc_count_crtc_active_planes(struct drm_crtc_state *new_crtc_state)
{
struct drm_atomic_commit *state = new_crtc_state->state;
struct drm_plane *plane;
int num_active = 0;
drm_for_each_plane_mask(plane, state->dev, new_crtc_state->plane_mask) {
struct drm_plane_state *new_plane_state;
/* Cursor planes are "fake". */
if (plane->type == DRM_PLANE_TYPE_CURSOR)
continue;
new_plane_state = drm_atomic_get_new_plane_state(state, plane);
if (!new_plane_state) {
/*
* The plane is enable on the CRTC and hasn't changed
* state. This means that it previously passed
* validation and is therefore enabled.
*/
num_active += 1;
continue;
}
/* We need a framebuffer to be considered enabled. */
num_active += (new_plane_state->fb != NULL);
}
return num_active;
}
static void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc,
struct drm_crtc_state *new_crtc_state)
{
struct dm_crtc_state *dm_new_crtc_state =
to_dm_crtc_state(new_crtc_state);
dm_new_crtc_state->active_planes = 0;
if (!dm_new_crtc_state->stream)
return;
dm_new_crtc_state->active_planes =
amdgpu_dm_crtc_count_crtc_active_planes(new_crtc_state);
}
static bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static int amdgpu_dm_crtc_helper_atomic_check(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
crtc);
struct amdgpu_device *adev = drm_to_adev(crtc->dev);
struct dc *dc = adev->dm.dc;
struct dm_crtc_state *dm_crtc_state = to_dm_crtc_state(crtc_state);
int ret = -EINVAL;
trace_amdgpu_dm_crtc_atomic_check(crtc_state);
amdgpu_dm_crtc_update_crtc_active_planes(crtc, crtc_state);
if (WARN_ON(unlikely(!dm_crtc_state->stream &&
amdgpu_dm_crtc_modeset_required(crtc_state, NULL, dm_crtc_state->stream)))) {
return ret;
}
/*
* We require the primary plane to be enabled whenever the CRTC is, otherwise
* drm_mode_cursor_universal may end up trying to enable the cursor plane while all other
* planes are disabled, which is not supported by the hardware. And there is legacy
* userspace which stops using the HW cursor altogether in response to the resulting EINVAL.
*/
if (crtc_state->enable &&
!(crtc_state->plane_mask & drm_plane_mask(crtc->primary))) {
DRM_DEBUG_ATOMIC("Can't enable a CRTC without enabling the primary plane\n");
return -EINVAL;
}
/*
* Only allow async flips for fast updates that don't change the FB
* pitch, the DCC state, rotation, etc.
*/
if (crtc_state->async_flip &&
dm_crtc_state->update_type != UPDATE_TYPE_FAST) {
drm_dbg_atomic(crtc->dev,
"[CRTC:%d:%s] async flips are only supported for fast updates\n",
crtc->base.id, crtc->name);
return -EINVAL;
}
if (!state->legacy_cursor_update && amdgpu_dm_crtc_vrr_active(dm_crtc_state)) {
struct drm_plane_state *primary_state;
/* Pull in primary plane for correct VRR handling */
primary_state = drm_atomic_get_plane_state(state, crtc->primary);
if (IS_ERR(primary_state))
return PTR_ERR(primary_state);
}
/* In some use cases, like reset, no stream is attached */
if (!dm_crtc_state->stream)
return 0;
if (dc_validate_stream(dc, dm_crtc_state->stream) == DC_OK)
return 0;
DRM_DEBUG_ATOMIC("Failed DC stream validation\n");
return ret;
}
static const struct drm_crtc_helper_funcs amdgpu_dm_crtc_helper_funcs = {
.disable = amdgpu_dm_crtc_helper_disable,
.atomic_check = amdgpu_dm_crtc_helper_atomic_check,
.mode_fixup = amdgpu_dm_crtc_helper_mode_fixup,
.get_scanout_position = amdgpu_crtc_get_scanout_position,
};
/*
* This hysteresis filter as configured will:
*
* * Search through the latest 8[filter_history_size] entries in history,
* skipping entries that are older than [filter_old_history_threshold] frames
* (0 means ignore age)
* * Searches for short-idle-periods that lasted shorter than
* 4[filter_num_frames] frames-times
* * If there is at least 1[filter_entry_count] short-idle-period, then a delay
* of 4[activation_num_delay_frames] will applied before allowing idle
* optimizations again.
* * An additional delay of 11[sso_num_frames] is applied before enabling
* panel-specific optimizations.
*
* The values were determined empirically on another OS, optimizing for Z8
* residency on APUs when running a productivity + web browsing test.
*
* TODO: Run similar tests to determine if these values are also optimal for
* Linux, and if each APU generation benefits differently.
*/
static struct amdgpu_dm_ism_config default_ism_config = {
.filter_num_frames = 4,
.filter_history_size = 8,
.filter_entry_count = 1,
.activation_num_delay_frames = 4,
.filter_old_history_threshold = 0,
.sso_num_frames = 11,
};
int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm,
struct drm_plane *plane,
uint32_t crtc_index)
{
struct amdgpu_crtc *acrtc = NULL;
struct drm_plane *cursor_plane;
bool has_degamma;
int res = -ENOMEM;
cursor_plane = kzalloc_obj(*cursor_plane);
if (!cursor_plane)
goto fail;
cursor_plane->type = DRM_PLANE_TYPE_CURSOR;
res = amdgpu_dm_plane_init(dm, cursor_plane, 0, NULL);
acrtc = kzalloc_obj(struct amdgpu_crtc);
if (!acrtc)
goto fail;
res = drm_crtc_init_with_planes(
dm->ddev,
&acrtc->base,
plane,
cursor_plane,
&amdgpu_dm_crtc_funcs, NULL);
if (res)
goto fail;
amdgpu_dm_ism_init(&acrtc->ism, &default_ism_config);
drm_crtc_helper_add(&acrtc->base, &amdgpu_dm_crtc_helper_funcs);
/* Create (reset) the plane state */
if (acrtc->base.funcs->reset)
acrtc->base.funcs->reset(&acrtc->base);
acrtc->max_cursor_width = dm->adev->dm.dc->caps.max_cursor_size;
acrtc->max_cursor_height = dm->adev->dm.dc->caps.max_cursor_size;
acrtc->crtc_id = crtc_index;
acrtc->base.enabled = false;
acrtc->otg_inst = -1;
dm->adev->mode_info.crtcs[crtc_index] = acrtc;
/* Don't enable DRM CRTC degamma property for
* 1. DCE since it doesn't support programmable degamma anywhere.
* 2. DCN401 since pre-blending degamma LUT doesn't apply to cursor.
* Note: DEGAMMA properties are created even if the primary plane has the
* COLOR_PIPELINE property. User space can use either the DEGAMMA properties
* or the COLOR_PIPELINE property. An atomic commit which attempts to enable
* both is rejected.
*/
has_degamma = dm->adev->dm.dc->caps.color.dpp.dcn_arch &&
dm->adev->dm.dc->ctx->dce_version != DCN_VERSION_4_01;
drm_crtc_enable_color_mgmt(&acrtc->base, has_degamma ? MAX_COLOR_LUT_ENTRIES : 0,
true, MAX_COLOR_LUT_ENTRIES);
drm_mode_crtc_set_gamma_size(&acrtc->base, MAX_COLOR_LEGACY_LUT_ENTRIES);
#ifdef AMD_PRIVATE_COLOR
dm_crtc_additional_color_mgmt(&acrtc->base);
#endif
return 0;
fail:
kfree(acrtc);
kfree(cursor_plane);
return res;
}
@@ -0,0 +1,56 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2022 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
*
*/
#ifndef __AMDGPU_DM_CRTC_H__
#define __AMDGPU_DM_CRTC_H__
void amdgpu_dm_crtc_set_static_screen_optimze(
struct amdgpu_display_manager *dm,
struct dc_stream_state *stream,
bool sso_enable, bool allow_sr_entry);
void amdgpu_dm_crtc_handle_vblank(struct amdgpu_crtc *acrtc);
bool amdgpu_dm_crtc_modeset_required(struct drm_crtc_state *crtc_state,
struct dc_stream_state *new_stream,
struct dc_stream_state *old_stream);
int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable);
bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc);
bool amdgpu_dm_crtc_vrr_active(const struct dm_crtc_state *dm_state);
int amdgpu_dm_crtc_enable_vblank(struct drm_crtc *crtc);
void amdgpu_dm_crtc_disable_vblank(struct drm_crtc *crtc);
int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm,
struct drm_plane *plane,
uint32_t link_index);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2018 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
*
*/
#ifndef __AMDGPU_DM_DEBUGFS_H__
#define __AMDGPU_DM_DEBUGFS_H__
#include "amdgpu.h"
#include "amdgpu_dm.h"
void connector_debugfs_init(struct amdgpu_dm_connector *connector);
void dtn_debugfs_init(struct amdgpu_device *adev);
void crtc_debugfs_init(struct drm_crtc *crtc);
#endif
@@ -0,0 +1,834 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2019 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_dm_hdcp.h"
#include "amdgpu.h"
#include "amdgpu_dm.h"
#include "dc_fused_io.h"
#include "dm_helpers.h"
#include <drm/display/drm_hdcp_helper.h>
#include "hdcp_psp.h"
#include "amdgpu_dm_kunit_helpers.h"
/*
* If the SRM version being loaded is less than or equal to the
* currently loaded SRM, psp will return 0xFFFF as the version
*/
#define PSP_SRM_VERSION_MAX 0xFFFF
static bool
lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size)
{
struct dc_link *link = handle;
struct i2c_payload i2c_payloads[] = {{true, address, size, (void *)data} };
struct i2c_command cmd = {i2c_payloads, 1, I2C_COMMAND_ENGINE_HW,
link->dc->caps.i2c_speed_in_khz};
return dm_helpers_submit_i2c(link->ctx, link, &cmd);
}
static bool
lp_read_i2c(void *handle, uint32_t address, uint8_t offset, uint8_t *data, uint32_t size)
{
struct dc_link *link = handle;
struct i2c_payload i2c_payloads[] = {{true, address, 1, &offset},
{false, address, size, data} };
struct i2c_command cmd = {i2c_payloads, 2, I2C_COMMAND_ENGINE_HW,
link->dc->caps.i2c_speed_in_khz};
return dm_helpers_submit_i2c(link->ctx, link, &cmd);
}
static bool
lp_write_dpcd(void *handle, uint32_t address, const uint8_t *data, uint32_t size)
{
struct dc_link *link = handle;
return dm_helpers_dp_write_dpcd(link->ctx, link, address, data, size);
}
static bool
lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size)
{
struct dc_link *link = handle;
return dm_helpers_dp_read_dpcd(link->ctx, link, address, data, size);
}
static bool lp_atomic_write_poll_read_i2c(
void *handle,
const struct mod_hdcp_atomic_op_i2c *write,
const struct mod_hdcp_atomic_op_i2c *poll,
struct mod_hdcp_atomic_op_i2c *read,
uint32_t poll_timeout_us,
uint8_t poll_mask_msb
)
{
struct dc_link *link = handle;
return dm_atomic_write_poll_read_i2c(link, write, poll, read, poll_timeout_us, poll_mask_msb);
}
static bool lp_atomic_write_poll_read_aux(
void *handle,
const struct mod_hdcp_atomic_op_aux *write,
const struct mod_hdcp_atomic_op_aux *poll,
struct mod_hdcp_atomic_op_aux *read,
uint32_t poll_timeout_us,
uint8_t poll_mask_msb
)
{
struct dc_link *link = handle;
return dm_atomic_write_poll_read_aux(link, write, poll, read, poll_timeout_us, poll_mask_msb);
}
static uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size)
{
struct ta_hdcp_shared_memory *hdcp_cmd;
if (!psp->hdcp_context.context.initialized) {
DRM_WARN("Failed to get hdcp srm. HDCP TA is not initialized.");
return NULL;
}
hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf;
memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory));
hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP_GET_SRM;
psp_hdcp_invoke(psp, hdcp_cmd->cmd_id);
if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS)
return NULL;
*srm_version = hdcp_cmd->out_msg.hdcp_get_srm.srm_version;
*srm_size = hdcp_cmd->out_msg.hdcp_get_srm.srm_buf_size;
return hdcp_cmd->out_msg.hdcp_get_srm.srm_buf;
}
static int psp_set_srm(struct psp_context *psp,
u8 *srm, uint32_t srm_size, uint32_t *srm_version)
{
struct ta_hdcp_shared_memory *hdcp_cmd;
if (!psp->hdcp_context.context.initialized) {
DRM_WARN("Failed to get hdcp srm. HDCP TA is not initialized.");
return -EINVAL;
}
hdcp_cmd = (struct ta_hdcp_shared_memory *)psp->hdcp_context.context.mem_context.shared_buf;
memset(hdcp_cmd, 0, sizeof(struct ta_hdcp_shared_memory));
memcpy(hdcp_cmd->in_msg.hdcp_set_srm.srm_buf, srm, srm_size);
hdcp_cmd->in_msg.hdcp_set_srm.srm_buf_size = srm_size;
hdcp_cmd->cmd_id = TA_HDCP_COMMAND__HDCP_SET_SRM;
psp_hdcp_invoke(psp, hdcp_cmd->cmd_id);
if (hdcp_cmd->hdcp_status != TA_HDCP_STATUS__SUCCESS ||
hdcp_cmd->out_msg.hdcp_set_srm.valid_signature != 1 ||
hdcp_cmd->out_msg.hdcp_set_srm.srm_version == PSP_SRM_VERSION_MAX)
return -EINVAL;
*srm_version = hdcp_cmd->out_msg.hdcp_set_srm.srm_version;
return 0;
}
STATIC_IFN_KUNIT
void process_output(struct hdcp_workqueue *hdcp_work)
{
struct mod_hdcp_output output = hdcp_work->output;
if (output.callback_stop)
cancel_delayed_work(&hdcp_work->callback_dwork);
if (output.callback_needed)
schedule_delayed_work(&hdcp_work->callback_dwork,
msecs_to_jiffies(output.callback_delay));
if (output.watchdog_timer_stop)
cancel_delayed_work(&hdcp_work->watchdog_timer_dwork);
if (output.watchdog_timer_needed)
schedule_delayed_work(&hdcp_work->watchdog_timer_dwork,
msecs_to_jiffies(output.watchdog_timer_delay));
schedule_delayed_work(&hdcp_work->property_validate_dwork, msecs_to_jiffies(0));
}
EXPORT_IF_KUNIT(process_output);
static void link_lock(struct hdcp_workqueue *work, bool lock)
{
int i = 0;
for (i = 0; i < work->max_link; i++) {
if (lock)
mutex_lock(&work[i].mutex);
else
mutex_unlock(&work[i].mutex);
}
}
void hdcp_update_display(struct hdcp_workqueue *hdcp_work,
unsigned int link_index,
struct amdgpu_dm_connector *aconnector,
u8 content_type,
bool enable_encryption)
{
struct hdcp_workqueue *hdcp_w = &hdcp_work[link_index];
struct mod_hdcp_link_adjustment link_adjust;
struct mod_hdcp_display_adjustment display_adjust;
unsigned int conn_index = aconnector->base.index;
const struct dc *dc = aconnector->dc_link->dc;
guard(mutex)(&hdcp_w->mutex);
drm_connector_get(&aconnector->base);
if (hdcp_w->aconnector[conn_index])
drm_connector_put(&hdcp_w->aconnector[conn_index]->base);
hdcp_w->aconnector[conn_index] = aconnector;
memset(&link_adjust, 0, sizeof(link_adjust));
memset(&display_adjust, 0, sizeof(display_adjust));
if (enable_encryption) {
/* Explicitly set the saved SRM as sysfs call will be after we already enabled hdcp
* (s3 resume case)
*/
if (hdcp_work->srm_size > 0)
psp_set_srm(hdcp_work->hdcp.config.psp.handle, hdcp_work->srm,
hdcp_work->srm_size,
&hdcp_work->srm_version);
display_adjust.disable = MOD_HDCP_DISPLAY_NOT_DISABLE;
link_adjust.auth_delay = 2;
link_adjust.retry_limit = MAX_NUM_OF_ATTEMPTS;
if (content_type == DRM_MODE_HDCP_CONTENT_TYPE0) {
link_adjust.hdcp2.force_type = MOD_HDCP_FORCE_TYPE_0;
} else if (content_type == DRM_MODE_HDCP_CONTENT_TYPE1) {
link_adjust.hdcp1.disable = 1;
link_adjust.hdcp2.force_type = MOD_HDCP_FORCE_TYPE_1;
}
link_adjust.hdcp2.use_fw_locality_check =
(dc->caps.fused_io_supported || dc->debug.hdcp_lc_force_fw_enable);
link_adjust.hdcp2.use_sw_locality_fallback = dc->debug.hdcp_lc_enable_sw_fallback;
schedule_delayed_work(&hdcp_w->property_validate_dwork,
msecs_to_jiffies(DRM_HDCP_CHECK_PERIOD_MS));
} else {
display_adjust.disable = MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION;
hdcp_w->encryption_status[conn_index] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF;
cancel_delayed_work(&hdcp_w->property_validate_dwork);
}
mod_hdcp_update_display(&hdcp_w->hdcp, conn_index, &link_adjust, &display_adjust, &hdcp_w->output);
process_output(hdcp_w);
}
static void hdcp_remove_display(struct hdcp_workqueue *hdcp_work,
unsigned int link_index,
struct amdgpu_dm_connector *aconnector)
{
struct hdcp_workqueue *hdcp_w = &hdcp_work[link_index];
struct drm_connector_state *conn_state = aconnector->base.state;
unsigned int conn_index = aconnector->base.index;
guard(mutex)(&hdcp_w->mutex);
/* the removal of display will invoke auth reset -> hdcp destroy and
* we'd expect the Content Protection (CP) property changed back to
* DESIRED if at the time ENABLED. CP property change should occur
* before the element removed from linked list.
*/
if (conn_state && conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) {
conn_state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
DRM_DEBUG_DRIVER("[HDCP_DM] display %d, CP 2 -> 1, type %u, DPMS %u\n",
aconnector->base.index, conn_state->hdcp_content_type,
aconnector->base.dpms);
}
mod_hdcp_remove_display(&hdcp_w->hdcp, aconnector->base.index, &hdcp_w->output);
if (hdcp_w->aconnector[conn_index]) {
drm_connector_put(&hdcp_w->aconnector[conn_index]->base);
hdcp_w->aconnector[conn_index] = NULL;
}
process_output(hdcp_w);
}
void hdcp_reset_display(struct hdcp_workqueue *hdcp_work, unsigned int link_index)
{
struct hdcp_workqueue *hdcp_w = &hdcp_work[link_index];
unsigned int conn_index;
guard(mutex)(&hdcp_w->mutex);
mod_hdcp_reset_connection(&hdcp_w->hdcp, &hdcp_w->output);
cancel_delayed_work(&hdcp_w->property_validate_dwork);
for (conn_index = 0; conn_index < AMDGPU_DM_MAX_DISPLAY_INDEX; conn_index++) {
hdcp_w->encryption_status[conn_index] =
MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF;
if (hdcp_w->aconnector[conn_index]) {
drm_connector_put(&hdcp_w->aconnector[conn_index]->base);
hdcp_w->aconnector[conn_index] = NULL;
}
}
process_output(hdcp_w);
}
void hdcp_handle_cpirq(struct hdcp_workqueue *hdcp_work, unsigned int link_index)
{
struct hdcp_workqueue *hdcp_w = &hdcp_work[link_index];
schedule_work(&hdcp_w->cpirq_work);
}
static void event_callback(struct work_struct *work)
{
struct hdcp_workqueue *hdcp_work;
hdcp_work = container_of(to_delayed_work(work), struct hdcp_workqueue,
callback_dwork);
guard(mutex)(&hdcp_work->mutex);
cancel_delayed_work(&hdcp_work->callback_dwork);
mod_hdcp_process_event(&hdcp_work->hdcp, MOD_HDCP_EVENT_CALLBACK,
&hdcp_work->output);
process_output(hdcp_work);
}
static void event_property_update(struct work_struct *work)
{
struct hdcp_workqueue *hdcp_work = container_of(work, struct hdcp_workqueue,
property_update_work);
struct amdgpu_dm_connector *aconnector = NULL;
struct drm_device *dev;
long ret;
unsigned int conn_index;
struct drm_connector *connector;
struct drm_connector_state *conn_state;
for (conn_index = 0; conn_index < AMDGPU_DM_MAX_DISPLAY_INDEX; conn_index++) {
aconnector = hdcp_work->aconnector[conn_index];
if (!aconnector)
continue;
connector = &aconnector->base;
/* check if display connected */
if (connector->status != connector_status_connected)
continue;
conn_state = aconnector->base.state;
if (!conn_state)
continue;
dev = connector->dev;
if (!dev)
continue;
drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
guard(mutex)(&hdcp_work->mutex);
if (conn_state->commit) {
ret = wait_for_completion_interruptible_timeout(&conn_state->commit->hw_done,
10 * HZ);
if (ret == 0) {
DRM_ERROR("HDCP state unknown! Setting it to DESIRED\n");
hdcp_work->encryption_status[conn_index] =
MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF;
}
}
if (hdcp_work->encryption_status[conn_index] !=
MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF) {
if (conn_state->hdcp_content_type ==
DRM_MODE_HDCP_CONTENT_TYPE0 &&
hdcp_work->encryption_status[conn_index] <=
MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON) {
DRM_DEBUG_DRIVER("[HDCP_DM] DRM_MODE_CONTENT_PROTECTION_ENABLED\n");
drm_hdcp_update_content_protection(connector,
DRM_MODE_CONTENT_PROTECTION_ENABLED);
} else if (conn_state->hdcp_content_type ==
DRM_MODE_HDCP_CONTENT_TYPE1 &&
hdcp_work->encryption_status[conn_index] ==
MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON) {
drm_hdcp_update_content_protection(connector,
DRM_MODE_CONTENT_PROTECTION_ENABLED);
}
} else {
DRM_DEBUG_DRIVER("[HDCP_DM] DRM_MODE_CONTENT_PROTECTION_DESIRED\n");
drm_hdcp_update_content_protection(connector,
DRM_MODE_CONTENT_PROTECTION_DESIRED);
}
drm_modeset_unlock(&dev->mode_config.connection_mutex);
}
}
static void event_property_validate(struct work_struct *work)
{
struct hdcp_workqueue *hdcp_work =
container_of(to_delayed_work(work), struct hdcp_workqueue, property_validate_dwork);
struct mod_hdcp_display_query query;
struct amdgpu_dm_connector *aconnector;
unsigned int conn_index;
guard(mutex)(&hdcp_work->mutex);
for (conn_index = 0; conn_index < AMDGPU_DM_MAX_DISPLAY_INDEX;
conn_index++) {
aconnector = hdcp_work->aconnector[conn_index];
if (!aconnector)
continue;
/* check if display connected */
if (aconnector->base.status != connector_status_connected)
continue;
if (!aconnector->base.state)
continue;
query.encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF;
mod_hdcp_query_display(&hdcp_work->hdcp, aconnector->base.index,
&query);
DRM_DEBUG_DRIVER("[HDCP_DM] disp %d, connector->CP %u, (query, work): (%d, %d)\n",
aconnector->base.index,
aconnector->base.state->content_protection,
query.encryption_status,
hdcp_work->encryption_status[conn_index]);
if (query.encryption_status !=
hdcp_work->encryption_status[conn_index]) {
DRM_DEBUG_DRIVER("[HDCP_DM] encryption_status change from %x to %x\n",
hdcp_work->encryption_status[conn_index],
query.encryption_status);
hdcp_work->encryption_status[conn_index] =
query.encryption_status;
DRM_DEBUG_DRIVER("[HDCP_DM] trigger property_update_work\n");
schedule_work(&hdcp_work->property_update_work);
}
}
}
static void event_watchdog_timer(struct work_struct *work)
{
struct hdcp_workqueue *hdcp_work;
hdcp_work = container_of(to_delayed_work(work),
struct hdcp_workqueue,
watchdog_timer_dwork);
guard(mutex)(&hdcp_work->mutex);
cancel_delayed_work(&hdcp_work->watchdog_timer_dwork);
mod_hdcp_process_event(&hdcp_work->hdcp,
MOD_HDCP_EVENT_WATCHDOG_TIMEOUT,
&hdcp_work->output);
process_output(hdcp_work);
}
static void event_cpirq(struct work_struct *work)
{
struct hdcp_workqueue *hdcp_work;
hdcp_work = container_of(work, struct hdcp_workqueue, cpirq_work);
guard(mutex)(&hdcp_work->mutex);
mod_hdcp_process_event(&hdcp_work->hdcp, MOD_HDCP_EVENT_CPIRQ, &hdcp_work->output);
process_output(hdcp_work);
}
void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *hdcp_work)
{
int i = 0;
for (i = 0; i < hdcp_work->max_link; i++) {
cancel_delayed_work_sync(&hdcp_work[i].callback_dwork);
cancel_delayed_work_sync(&hdcp_work[i].watchdog_timer_dwork);
cancel_delayed_work_sync(&hdcp_work[i].property_validate_dwork);
}
sysfs_remove_bin_file(kobj, &hdcp_work[0].attr);
kfree(hdcp_work->srm);
kfree(hdcp_work->srm_temp);
kfree(hdcp_work);
}
static bool enable_assr(void *handle, struct dc_link *link)
{
struct hdcp_workqueue *hdcp_work = handle;
struct mod_hdcp hdcp = hdcp_work->hdcp;
struct psp_context *psp = hdcp.config.psp.handle;
struct ta_dtm_shared_memory *dtm_cmd;
if (!psp->dtm_context.context.initialized) {
drm_info(adev_to_drm(psp->adev),
"Failed to enable ASSR, DTM TA is not initialized.");
return false;
}
dtm_cmd = (struct ta_dtm_shared_memory *)psp->dtm_context.context.mem_context.shared_buf;
guard(mutex)(&psp->dtm_context.mutex);
memset(dtm_cmd, 0, sizeof(struct ta_dtm_shared_memory));
dtm_cmd->cmd_id = TA_DTM_COMMAND__TOPOLOGY_ASSR_ENABLE;
dtm_cmd->dtm_in_message.topology_assr_enable.display_topology_dig_be_index =
link->link_enc_hw_inst;
dtm_cmd->dtm_status = TA_DTM_STATUS__GENERIC_FAILURE;
psp_dtm_invoke(psp, dtm_cmd->cmd_id);
if (dtm_cmd->dtm_status != TA_DTM_STATUS__SUCCESS) {
drm_info(adev_to_drm(psp->adev),
"Failed to enable ASSR");
return false;
}
return true;
}
static void update_config(void *handle, struct cp_psp_stream_config *config)
{
struct hdcp_workqueue *hdcp_work = handle;
struct amdgpu_dm_connector *aconnector = config->dm_stream_ctx;
int link_index = aconnector->dc_link->link_index;
unsigned int conn_index = aconnector->base.index;
struct mod_hdcp_display *display = &hdcp_work[link_index].display;
struct mod_hdcp_link *link = &hdcp_work[link_index].link;
struct hdcp_workqueue *hdcp_w = &hdcp_work[link_index];
struct dc_sink *sink = NULL;
bool link_is_hdcp14 = false;
const struct dc *dc = aconnector->dc_link->dc;
if (config->dpms_off) {
hdcp_remove_display(hdcp_work, link_index, aconnector);
return;
}
memset(display, 0, sizeof(*display));
memset(link, 0, sizeof(*link));
display->index = aconnector->base.index;
display->state = MOD_HDCP_DISPLAY_ACTIVE;
if (aconnector->dc_sink)
sink = aconnector->dc_sink;
else if (aconnector->dc_em_sink)
sink = aconnector->dc_em_sink;
if (sink)
link->mode = mod_hdcp_signal_type_to_operation_mode(sink->sink_signal);
display->controller = CONTROLLER_ID_D0 + config->otg_inst;
display->dig_fe = config->dig_fe;
link->dig_be = config->dig_be;
link->ddc_line = aconnector->dc_link->ddc_hw_inst + 1;
display->stream_enc_idx = config->stream_enc_idx;
link->link_enc_idx = config->link_enc_idx;
link->dio_output_id = config->dio_output_idx;
link->phy_idx = config->phy_idx;
if (sink)
link_is_hdcp14 = dc_link_is_hdcp14(aconnector->dc_link, sink->sink_signal);
link->hdcp_supported_informational = link_is_hdcp14;
link->dp.rev = aconnector->dc_link->dpcd_caps.dpcd_rev.raw;
link->dp.assr_enabled = config->assr_enabled;
link->dp.mst_enabled = config->mst_enabled;
link->dp.dp2_enabled = config->dp2_enabled;
link->dp.usb4_enabled = config->usb4_enabled;
if (aconnector->dc_sink->sink_signal == SIGNAL_TYPE_HDMI_FRL)
link->hdmi.frl_enabled = config->frl_enabled;
display->adjust.disable = MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION;
link->adjust.auth_delay = 2;
link->adjust.retry_limit = MAX_NUM_OF_ATTEMPTS;
link->adjust.hdcp1.disable = 0;
link->adjust.hdcp2.use_fw_locality_check = (dc->caps.fused_io_supported || dc->debug.hdcp_lc_force_fw_enable);
link->adjust.hdcp2.use_sw_locality_fallback = dc->debug.hdcp_lc_enable_sw_fallback;
hdcp_w->encryption_status[display->index] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF;
DRM_DEBUG_DRIVER("[HDCP_DM] display %d, CP %d, type %d\n", aconnector->base.index,
(!!aconnector->base.state) ?
aconnector->base.state->content_protection : -1,
(!!aconnector->base.state) ?
aconnector->base.state->hdcp_content_type : -1);
guard(mutex)(&hdcp_w->mutex);
mod_hdcp_add_display(&hdcp_w->hdcp, link, display, &hdcp_w->output);
drm_connector_get(&aconnector->base);
if (hdcp_w->aconnector[conn_index])
drm_connector_put(&hdcp_w->aconnector[conn_index]->base);
hdcp_w->aconnector[conn_index] = aconnector;
process_output(hdcp_w);
}
/**
* DOC: Add sysfs interface for set/get srm
*
* NOTE: From the usermodes prospective you only need to call write *ONCE*, the kernel
* will automatically call once or twice depending on the size
*
* call: "cat file > /sys/class/drm/card0/device/hdcp_srm" from usermode no matter what the size is
*
* The kernel can only send PAGE_SIZE at once and since MAX_SRM_FILE(5120) > PAGE_SIZE(4096),
* srm_data_write can be called multiple times.
*
* sysfs interface doesn't tell us the size we will get so we are sending partial SRMs to psp and on
* the last call we will send the full SRM. PSP will fail on every call before the last.
*
* This means we don't know if the SRM is good until the last call. And because of this
* limitation we cannot throw errors early as it will stop the kernel from writing to sysfs
*
* Example 1:
* Good SRM size = 5096
* first call to write 4096 -> PSP fails
* Second call to write 1000 -> PSP Pass -> SRM is set
*
* Example 2:
* Bad SRM size = 4096
* first call to write 4096 -> PSP fails (This is the same as above, but we don't know if this
* is the last call)
*
* Solution?:
* 1: Parse the SRM? -> It is signed so we don't know the EOF
* 2: We can have another sysfs that passes the size before calling set. -> simpler solution
* below
*
* Easy Solution:
* Always call get after Set to verify if set was successful.
* +----------------------+
* | Why it works: |
* +----------------------+
* PSP will only update its srm if its older than the one we are trying to load.
* Always do set first than get.
* -if we try to "1. SET" a older version PSP will reject it and we can "2. GET" the newer
* version and save it
*
* -if we try to "1. SET" a newer version PSP will accept it and we can "2. GET" the
* same(newer) version back and save it
*
* -if we try to "1. SET" a newer version and PSP rejects it. That means the format is
* incorrect/corrupted and we should correct our SRM by getting it from PSP
*/
static ssize_t srm_data_write(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buffer,
loff_t pos, size_t count)
{
struct hdcp_workqueue *work;
u32 srm_version = 0;
work = container_of(bin_attr, struct hdcp_workqueue, attr);
link_lock(work, true);
memcpy(work->srm_temp + pos, buffer, count);
if (!psp_set_srm(work->hdcp.config.psp.handle, work->srm_temp, pos + count, &srm_version)) {
DRM_DEBUG_DRIVER("HDCP SRM SET version 0x%X", srm_version);
memcpy(work->srm, work->srm_temp, pos + count);
work->srm_size = pos + count;
work->srm_version = srm_version;
}
link_lock(work, false);
return count;
}
static ssize_t srm_data_read(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buffer,
loff_t pos, size_t count)
{
struct hdcp_workqueue *work;
u8 *srm = NULL;
u32 srm_version;
u32 srm_size;
size_t ret = count;
work = container_of(bin_attr, struct hdcp_workqueue, attr);
link_lock(work, true);
srm = psp_get_srm(work->hdcp.config.psp.handle, &srm_version, &srm_size);
if (!srm) {
ret = -EINVAL;
goto ret;
}
if (pos >= srm_size)
ret = 0;
if (srm_size - pos < count) {
memcpy(buffer, srm + pos, srm_size - pos);
ret = srm_size - pos;
goto ret;
}
memcpy(buffer, srm + pos, count);
ret:
link_lock(work, false);
return ret;
}
/* From the hdcp spec (5.Renewability) SRM needs to be stored in a non-volatile memory.
*
* For example,
* if Application "A" sets the SRM (ver 2) and we reboot/suspend and later when Application "B"
* needs to use HDCP, the version in PSP should be SRM(ver 2). So SRM should be persistent
* across boot/reboots/suspend/resume/shutdown
*
* Currently when the system goes down (suspend/shutdown) the SRM is cleared from PSP. For HDCP
* we need to make the SRM persistent.
*
* -PSP owns the checking of SRM but doesn't have the ability to store it in a non-volatile memory.
* -The kernel cannot write to the file systems.
* -So we need usermode to do this for us, which is why an interface for usermode is needed
*
*
*
* Usermode can read/write to/from PSP using the sysfs interface
* For example:
* to save SRM from PSP to storage : cat /sys/class/drm/card0/device/hdcp_srm > srmfile
* to load from storage to PSP: cat srmfile > /sys/class/drm/card0/device/hdcp_srm
*/
static const struct bin_attribute data_attr = {
.attr = {.name = "hdcp_srm", .mode = 0664},
.size = PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE, /* Limit SRM size */
.write = srm_data_write,
.read = srm_data_read,
};
struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev,
struct cp_psp *cp_psp, struct dc *dc)
{
int max_caps = dc->caps.max_links;
struct hdcp_workqueue *hdcp_work;
int i = 0;
hdcp_work = kzalloc_objs(*hdcp_work, max_caps);
if (ZERO_OR_NULL_PTR(hdcp_work))
return NULL;
hdcp_work->srm = kcalloc(PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE,
sizeof(*hdcp_work->srm), GFP_KERNEL);
if (!hdcp_work->srm)
goto fail_alloc_context;
hdcp_work->srm_temp = kcalloc(PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE,
sizeof(*hdcp_work->srm_temp), GFP_KERNEL);
if (!hdcp_work->srm_temp)
goto fail_alloc_context;
hdcp_work->max_link = max_caps;
for (i = 0; i < max_caps; i++) {
mutex_init(&hdcp_work[i].mutex);
INIT_WORK(&hdcp_work[i].cpirq_work, event_cpirq);
INIT_WORK(&hdcp_work[i].property_update_work, event_property_update);
INIT_DELAYED_WORK(&hdcp_work[i].callback_dwork, event_callback);
INIT_DELAYED_WORK(&hdcp_work[i].watchdog_timer_dwork, event_watchdog_timer);
INIT_DELAYED_WORK(&hdcp_work[i].property_validate_dwork, event_property_validate);
struct mod_hdcp_config *config = &hdcp_work[i].hdcp.config;
struct mod_hdcp_ddc_funcs *ddc_funcs = &config->ddc.funcs;
config->psp.handle = &adev->psp;
if (dc->ctx->dce_version == DCN_VERSION_3_1 ||
dc->ctx->dce_version == DCN_VERSION_3_14 ||
dc->ctx->dce_version == DCN_VERSION_3_15 ||
dc->ctx->dce_version == DCN_VERSION_3_16 ||
dc->ctx->dce_version == DCN_VERSION_3_2 ||
dc->ctx->dce_version == DCN_VERSION_3_21 ||
dc->ctx->dce_version == DCN_VERSION_3_5 ||
dc->ctx->dce_version == DCN_VERSION_3_51 ||
dc->ctx->dce_version == DCN_VERSION_3_6 ||
dc->ctx->dce_version == DCN_VERSION_4_01)
config->psp.caps.dtm_v3_supported = 1;
config->ddc.handle = dc_get_link_at_index(dc, i);
ddc_funcs->write_i2c = lp_write_i2c;
ddc_funcs->read_i2c = lp_read_i2c;
ddc_funcs->write_dpcd = lp_write_dpcd;
ddc_funcs->read_dpcd = lp_read_dpcd;
ddc_funcs->atomic_write_poll_read_i2c = lp_atomic_write_poll_read_i2c;
ddc_funcs->atomic_write_poll_read_aux = lp_atomic_write_poll_read_aux;
memset(hdcp_work[i].aconnector, 0,
sizeof(struct amdgpu_dm_connector *) *
AMDGPU_DM_MAX_DISPLAY_INDEX);
memset(hdcp_work[i].encryption_status, 0,
sizeof(enum mod_hdcp_encryption_status) *
AMDGPU_DM_MAX_DISPLAY_INDEX);
}
cp_psp->funcs.update_stream_config = update_config;
cp_psp->funcs.enable_assr = enable_assr;
cp_psp->handle = hdcp_work;
/* File created at /sys/class/drm/card0/device/hdcp_srm*/
hdcp_work[0].attr = data_attr;
sysfs_bin_attr_init(&hdcp_work[0].attr);
if (sysfs_create_bin_file(&adev->dev->kobj, &hdcp_work[0].attr))
drm_warn(adev_to_drm(adev), "Failed to create device file hdcp_srm\n");
return hdcp_work;
fail_alloc_context:
kfree(hdcp_work->srm);
kfree(hdcp_work->srm_temp);
kfree(hdcp_work);
return NULL;
}
@@ -0,0 +1,101 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2019 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
*
*/
#ifndef AMDGPU_DM_AMDGPU_DM_HDCP_H_
#define AMDGPU_DM_AMDGPU_DM_HDCP_H_
#include "mod_hdcp.h"
#include "hdcp.h"
#include "dc.h"
#include "dm_cp_psp.h"
/*
* Minimal declarations needed by this header.
* Full amdgpu/DM definitions come from amdgpu_dm.h included by each .c file.
*/
#define AMDGPU_DM_MAX_DISPLAY_INDEX 31
struct amdgpu_dm_connector;
struct mod_hdcp;
struct mod_hdcp_link;
struct mod_hdcp_display;
struct cp_psp;
struct amdgpu_device;
struct hdcp_workqueue {
struct work_struct cpirq_work;
struct work_struct property_update_work;
struct delayed_work callback_dwork;
struct delayed_work watchdog_timer_dwork;
struct delayed_work property_validate_dwork;
struct amdgpu_dm_connector *aconnector[AMDGPU_DM_MAX_DISPLAY_INDEX];
struct mutex mutex;
struct mod_hdcp hdcp;
struct mod_hdcp_output output;
struct mod_hdcp_display display;
struct mod_hdcp_link link;
enum mod_hdcp_encryption_status encryption_status[AMDGPU_DM_MAX_DISPLAY_INDEX];
/* when display is unplugged from mst hub, connctor will be
* destroyed within dm_dp_mst_connector_destroy. connector
* hdcp perperties, like type, undesired, desired, enabled,
* will be lost. So, save hdcp properties into hdcp_work within
* amdgpu_dm_atomic_commit_tail. if the same display is
* plugged back with same display index, its hdcp properties
* will be retrieved from hdcp_work within dm_dp_mst_get_modes
*/
/* un-desired, desired, enabled */
unsigned int content_protection[AMDGPU_DM_MAX_DISPLAY_INDEX];
/* hdcp1.x, hdcp2.x */
unsigned int hdcp_content_type[AMDGPU_DM_MAX_DISPLAY_INDEX];
uint8_t max_link;
uint8_t *srm;
uint8_t *srm_temp;
uint32_t srm_version;
uint32_t srm_size;
struct bin_attribute attr;
};
void hdcp_update_display(struct hdcp_workqueue *hdcp_work,
unsigned int link_index,
struct amdgpu_dm_connector *aconnector,
uint8_t content_type,
bool enable_encryption);
void hdcp_reset_display(struct hdcp_workqueue *work, unsigned int link_index);
void hdcp_handle_cpirq(struct hdcp_workqueue *work, unsigned int link_index);
void hdcp_destroy(struct kobject *kobj, struct hdcp_workqueue *work);
struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, struct cp_psp *cp_psp, struct dc *dc);
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
void process_output(struct hdcp_workqueue *hdcp_work);
#endif
#endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,104 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2015 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.
*
*/
#ifndef __AMDGPU_DM_IRQ_H__
#define __AMDGPU_DM_IRQ_H__
#include "irq_types.h" /* DAL irq definitions */
/*
* Display Manager IRQ-related interfaces (for use by DAL).
*/
/**
* amdgpu_dm_irq_init - Initialize internal structures of 'amdgpu_dm_irq'.
*
* This function should be called exactly once - during DM initialization.
*
* Returns:
* 0 - success
* non-zero - error
*/
int amdgpu_dm_irq_init(struct amdgpu_device *adev);
/**
* amdgpu_dm_irq_fini - deallocate internal structures of 'amdgpu_dm_irq'.
*
* This function should be called exactly once - during DM destruction.
*
*/
void amdgpu_dm_irq_fini(struct amdgpu_device *adev);
/**
* amdgpu_dm_irq_register_interrupt - register irq handler for Display block.
*
* @adev: AMD DRM device
* @int_params: parameters for the irq
* @ih: pointer to the irq hander function
* @handler_args: arguments which will be passed to ih
*
* Returns:
* IRQ Handler Index on success.
* NULL on failure.
*
* Cannot be called from an interrupt handler.
*/
void *amdgpu_dm_irq_register_interrupt(struct amdgpu_device *adev,
struct dc_interrupt_params *int_params,
void (*ih)(void *),
void *handler_args);
/**
* amdgpu_dm_irq_unregister_interrupt - unregister handler which was registered
* by amdgpu_dm_irq_register_interrupt().
*
* @adev: AMD DRM device.
* @ih_index: irq handler index which was returned by
* amdgpu_dm_irq_register_interrupt
*/
void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev,
enum dc_irq_source irq_source,
void *ih_index);
void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev);
void amdgpu_dm_outbox_init(struct amdgpu_device *adev);
void amdgpu_dm_hpd_init(struct amdgpu_device *adev);
void amdgpu_dm_hpd_fini(struct amdgpu_device *adev);
/**
* amdgpu_dm_irq_suspend - disable ASIC interrupt during suspend.
*
*/
void amdgpu_dm_irq_suspend(struct amdgpu_device *adev);
/**
* amdgpu_dm_irq_resume_early - enable HPDRX ASIC interrupts during resume.
* amdgpu_dm_irq_resume - enable ASIC interrupt during resume.
*
*/
void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev);
void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev);
#endif /* __AMDGPU_DM_IRQ_H__ */
@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2020 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
*
*/
#ifndef __AMDGPU_DM_IRQ_PARAMS_H__
#define __AMDGPU_DM_IRQ_PARAMS_H__
#include "amdgpu_dm_crc.h"
struct dm_irq_params {
u32 last_flip_vblank;
struct mod_vrr_params vrr_params;
struct dc_stream_state *stream;
int active_planes;
bool allow_sr_entry;
struct mod_freesync_config freesync_config;
#ifdef CONFIG_DEBUG_FS
enum amdgpu_dm_pipe_crc_source crc_src;
int crc_poly_mode; /* enum crc_poly_mode from timing_generator.h */
#ifdef CONFIG_DRM_AMD_SECURE_DISPLAY
struct crc_window_param window_param[MAX_CRC_WINDOW_NUM];
/* At least one CRC window is activated or not*/
bool crc_window_activated;
#endif
#endif
};
#endif /* __AMDGPU_DM_IRQ_PARAMS_H__ */
@@ -0,0 +1,651 @@
// 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 <linux/types.h>
#include <drm/drm_vblank.h>
#include "dc.h"
#include "amdgpu.h"
#include "amdgpu_dm_ism.h"
#include "amdgpu_dm_crtc.h"
#include "amdgpu_dm_trace.h"
#include "amdgpu_dm_kunit_helpers.h"
/**
* dm_ism_next_state - Get next state based on current state and event
* @current_state: current ISM state
* @event: event being processed
* @next_state: place to store the next state
*
* This function defines the idle state management FSM. Invalid transitions
* are ignored and will not progress the FSM.
*/
STATIC_IFN_KUNIT
bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state,
enum amdgpu_dm_ism_event event,
enum amdgpu_dm_ism_state *next_state)
{
switch (STATE_EVENT(current_state, event)) {
case STATE_EVENT(DM_ISM_STATE_FULL_POWER_RUNNING,
DM_ISM_EVENT_ENTER_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_HYSTERESIS_WAITING;
break;
case STATE_EVENT(DM_ISM_STATE_FULL_POWER_RUNNING,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_FULL_POWER_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_FULL_POWER_BUSY,
DM_ISM_EVENT_ENTER_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_HYSTERESIS_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_FULL_POWER_BUSY,
DM_ISM_EVENT_END_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_FULL_POWER_RUNNING;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_TIMER_ABORTED;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_HYSTERESIS_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_TIMER_ELAPSED):
*next_state = DM_ISM_STATE_OPTIMIZED_IDLE;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_IMMEDIATE):
*next_state = DM_ISM_STATE_OPTIMIZED_IDLE;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_BUSY,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_FULL_POWER_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_BUSY,
DM_ISM_EVENT_END_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_HYSTERESIS_WAITING;
break;
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_FULL_POWER_RUNNING;
break;
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_HYSTERESIS_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_SSO_TIMER_ELAPSED):
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_IMMEDIATE):
*next_state = DM_ISM_STATE_OPTIMIZED_IDLE_SSO;
break;
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE_SSO,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_FULL_POWER_RUNNING;
break;
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE_SSO,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_HYSTERESIS_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_TIMER_ABORTED,
DM_ISM_EVENT_IMMEDIATE):
*next_state = DM_ISM_STATE_FULL_POWER_RUNNING;
break;
default:
return false;
}
return true;
}
EXPORT_IF_KUNIT(dm_ism_next_state);
STATIC_IFN_KUNIT
uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism,
const struct dc_stream_state *stream)
{
const struct amdgpu_dm_ism_config *config = &ism->config;
uint32_t v_total, h_total;
uint64_t one_frame_ns, sso_delay_ns;
if (!stream)
return 0;
if (!config->sso_num_frames)
return 0;
v_total = stream->timing.v_total;
h_total = stream->timing.h_total;
one_frame_ns = div64_u64(v_total * h_total * 10000000ull,
stream->timing.pix_clk_100hz);
sso_delay_ns = config->sso_num_frames * one_frame_ns;
return sso_delay_ns;
}
EXPORT_IF_KUNIT(dm_ism_get_sso_delay);
/**
* dm_ism_get_idle_allow_delay - Calculate hysteresis-based idle allow delay
* @ism: ISM instance containing configuration, history, and current state
* @stream: display stream used to derive frame timing values for delay
*
* Calculates the delay before allowing idle optimizations based on recent
* idle history and the current stream timing.
*/
STATIC_IFN_KUNIT
uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism,
const struct dc_stream_state *stream)
{
const struct amdgpu_dm_ism_config *config = &ism->config;
uint32_t v_total, h_total;
uint64_t one_frame_ns, short_idle_ns, old_hist_ns;
uint32_t history_size;
int pos;
uint32_t short_idle_count = 0;
uint64_t ret_ns = 0;
if (!stream)
return 0;
if (!config->filter_num_frames)
return 0;
if (!config->filter_entry_count)
return 0;
if (!config->activation_num_delay_frames)
return 0;
v_total = stream->timing.v_total;
h_total = stream->timing.h_total;
one_frame_ns = div64_u64(v_total * h_total * 10000000ull,
stream->timing.pix_clk_100hz);
short_idle_ns = config->filter_num_frames * one_frame_ns;
old_hist_ns = config->filter_old_history_threshold * one_frame_ns;
/*
* Look back into the recent history and count how many times we entered
* idle power state for a short duration of time
*/
history_size = min(
max(config->filter_history_size, config->filter_entry_count),
AMDGPU_DM_IDLE_HIST_LEN);
pos = ism->next_record_idx;
for (int k = 0; k < history_size; k++) {
if (pos <= 0 || pos > AMDGPU_DM_IDLE_HIST_LEN)
pos = AMDGPU_DM_IDLE_HIST_LEN;
pos -= 1;
if (ism->records[pos].duration_ns <= short_idle_ns)
short_idle_count += 1;
if (short_idle_count >= config->filter_entry_count)
break;
if (old_hist_ns > 0 &&
ism->last_idle_timestamp_ns - ism->records[pos].timestamp_ns > old_hist_ns)
break;
}
if (short_idle_count >= config->filter_entry_count)
ret_ns = config->activation_num_delay_frames * one_frame_ns;
return ret_ns;
}
EXPORT_IF_KUNIT(dm_ism_get_idle_allow_delay);
/**
* dm_ism_insert_record - Insert a record into the circular history buffer
* @ism: ISM instance
*/
STATIC_IFN_KUNIT
void dm_ism_insert_record(struct amdgpu_dm_ism *ism)
{
struct amdgpu_dm_ism_record *record;
if (ism->next_record_idx < 0 ||
ism->next_record_idx >= AMDGPU_DM_IDLE_HIST_LEN)
ism->next_record_idx = 0;
record = &ism->records[ism->next_record_idx];
ism->next_record_idx += 1;
record->timestamp_ns = ktime_get_ns();
record->duration_ns =
record->timestamp_ns - ism->last_idle_timestamp_ns;
}
EXPORT_IF_KUNIT(dm_ism_insert_record);
STATIC_IFN_KUNIT
void dm_ism_set_last_idle_ts(struct amdgpu_dm_ism *ism)
{
ism->last_idle_timestamp_ns = ktime_get_ns();
}
EXPORT_IF_KUNIT(dm_ism_set_last_idle_ts);
STATIC_IFN_KUNIT
bool dm_ism_trigger_event(struct amdgpu_dm_ism *ism,
enum amdgpu_dm_ism_event event)
{
enum amdgpu_dm_ism_state next_state;
bool gotNextState = dm_ism_next_state(ism->current_state, event,
&next_state);
if (gotNextState) {
ism->previous_state = ism->current_state;
ism->current_state = next_state;
}
return gotNextState;
}
EXPORT_IF_KUNIT(dm_ism_trigger_event);
static void dm_ism_commit_idle_optimization_state(struct amdgpu_dm_ism *ism,
struct dc_stream_state *stream,
bool vblank_enabled,
bool allow_panel_sso)
{
struct amdgpu_crtc *acrtc = ism_to_amdgpu_crtc(ism);
struct amdgpu_device *adev = drm_to_adev(acrtc->base.dev);
struct amdgpu_display_manager *dm = &adev->dm;
trace_amdgpu_dm_ism_commit(dm->active_vblank_irq_count,
vblank_enabled,
allow_panel_sso);
/*
* If there is an active vblank requestor, or if SSO is being engaged,
* then disallow idle optimizations.
*/
if (vblank_enabled || allow_panel_sso)
dc_allow_idle_optimizations(dm->dc, false);
/*
* Control PSR based on vblank requirements from OS
*
* If panel supports PSR SU/Replay, there's no need to exit self-refresh
* when OS is submitting fast atomic commits, as they can allow
* self-refresh during vblank periods.
*/
if (stream && stream->link) {
/*
* If the OS requires vblank events (or vblank is otherwise enabled),
* do not allow static screen optimizations.
*
* Keep ism->allow_static_screen_optimizations unchanged so the
* hysteresis-based decision can be reused once vblank is disabled.
*/
allow_panel_sso = allow_panel_sso && !vblank_enabled;
amdgpu_dm_crtc_set_static_screen_optimze(
dm, stream, allow_panel_sso,
acrtc->dm_irq_params.allow_sr_entry);
}
/*
* Check for any active drm vblank requestors on other CRTCs
* (dm->active_vblank_irq_count) before allowing HW-wide idle
* optimizations.
*
* There's no need to have a "balanced" check when disallowing idle
* optimizations at the start of this func -- we should disallow
* whenever there's *an* active CRTC.
*/
if (!vblank_enabled && dm->active_vblank_irq_count == 0) {
dc_post_update_surfaces_to_stream(dm->dc);
dc_allow_idle_optimizations(dm->dc, true);
}
}
STATIC_IFN_KUNIT
enum amdgpu_dm_ism_event dm_ism_dispatch_next_event(
enum amdgpu_dm_ism_state current_state,
uint64_t delay_ns,
uint64_t sso_delay_ns)
{
switch (current_state) {
case DM_ISM_STATE_HYSTERESIS_WAITING:
if (delay_ns == 0)
return DM_ISM_EVENT_IMMEDIATE;
break;
case DM_ISM_STATE_OPTIMIZED_IDLE:
if (sso_delay_ns == 0)
return DM_ISM_EVENT_IMMEDIATE;
break;
case DM_ISM_STATE_TIMER_ABORTED:
return DM_ISM_EVENT_IMMEDIATE;
default:
break;
}
return DM_ISM_NUM_EVENTS;
}
EXPORT_IF_KUNIT(dm_ism_dispatch_next_event);
static enum amdgpu_dm_ism_event dm_ism_dispatch_power_state(
struct amdgpu_dm_ism *ism,
struct dm_crtc_state *acrtc_state)
{
const struct amdgpu_dm_ism_config *config = &ism->config;
uint64_t delay_ns = 0, sso_delay_ns = 0;
switch (ism->previous_state) {
case DM_ISM_STATE_HYSTERESIS_WAITING:
/*
* Stop the timer if it was set, and we're not running from the
* idle allow worker.
*/
if (ism->current_state != DM_ISM_STATE_OPTIMIZED_IDLE &&
ism->current_state != DM_ISM_STATE_OPTIMIZED_IDLE_SSO)
cancel_delayed_work(&ism->delayed_work);
break;
case DM_ISM_STATE_OPTIMIZED_IDLE:
if (ism->current_state == DM_ISM_STATE_OPTIMIZED_IDLE_SSO)
break;
/* If idle disallow, cancel SSO work and insert record */
cancel_delayed_work(&ism->sso_delayed_work);
dm_ism_insert_record(ism);
dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream,
true, false);
break;
case DM_ISM_STATE_OPTIMIZED_IDLE_SSO:
/* Disable idle optimization */
dm_ism_insert_record(ism);
dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream,
true, false);
break;
default:
break;
}
switch (ism->current_state) {
case DM_ISM_STATE_HYSTERESIS_WAITING:
dm_ism_set_last_idle_ts(ism);
delay_ns = dm_ism_get_idle_allow_delay(ism, acrtc_state->stream);
/* Schedule worker */
if (delay_ns > 0)
mod_delayed_work(system_dfl_wq, &ism->delayed_work,
nsecs_to_jiffies(delay_ns));
break;
case DM_ISM_STATE_OPTIMIZED_IDLE:
sso_delay_ns = dm_ism_get_sso_delay(ism, acrtc_state->stream);
if (sso_delay_ns > 0) {
/*
* If sso_num_frames is less than hysteresis frames, it
* indicates that allowing idle here, then disallowing
* idle after sso_num_frames has expired, will likely
* have a negative power impact. Skip idle allow here,
* and let the sso_delayed_work handle it.
*/
if (config->sso_num_frames >= config->filter_num_frames)
dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream,
false, false);
mod_delayed_work(system_dfl_wq,
&ism->sso_delayed_work,
nsecs_to_jiffies(sso_delay_ns));
}
break;
case DM_ISM_STATE_OPTIMIZED_IDLE_SSO:
/* Enable static screen optimizations. */
dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream,
false, true);
break;
case DM_ISM_STATE_TIMER_ABORTED:
dm_ism_insert_record(ism);
dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream,
true, false);
break;
default:
break;
}
return dm_ism_dispatch_next_event(ism->current_state, delay_ns, sso_delay_ns);
}
static char *dm_ism_events_str[DM_ISM_NUM_EVENTS] = {
[DM_ISM_EVENT_IMMEDIATE] = "IMMEDIATE",
[DM_ISM_EVENT_ENTER_IDLE_REQUESTED] = "ENTER_IDLE_REQUESTED",
[DM_ISM_EVENT_EXIT_IDLE_REQUESTED] = "EXIT_IDLE_REQUESTED",
[DM_ISM_EVENT_BEGIN_CURSOR_UPDATE] = "BEGIN_CURSOR_UPDATE",
[DM_ISM_EVENT_END_CURSOR_UPDATE] = "END_CURSOR_UPDATE",
[DM_ISM_EVENT_TIMER_ELAPSED] = "TIMER_ELAPSED",
[DM_ISM_EVENT_SSO_TIMER_ELAPSED] = "SSO_TIMER_ELAPSED",
};
static char *dm_ism_states_str[DM_ISM_NUM_STATES] = {
[DM_ISM_STATE_FULL_POWER_RUNNING] = "FULL_POWER_RUNNING",
[DM_ISM_STATE_FULL_POWER_BUSY] = "FULL_POWER_BUSY",
[DM_ISM_STATE_HYSTERESIS_WAITING] = "HYSTERESIS_WAITING",
[DM_ISM_STATE_HYSTERESIS_BUSY] = "HYSTERESIS_BUSY",
[DM_ISM_STATE_OPTIMIZED_IDLE] = "OPTIMIZED_IDLE",
[DM_ISM_STATE_OPTIMIZED_IDLE_SSO] = "OPTIMIZED_IDLE_SSO",
[DM_ISM_STATE_TIMER_ABORTED] = "TIMER_ABORTED",
};
void amdgpu_dm_ism_commit_event(struct amdgpu_dm_ism *ism,
enum amdgpu_dm_ism_event event)
{
enum amdgpu_dm_ism_event next_event = event;
struct amdgpu_crtc *acrtc = ism_to_amdgpu_crtc(ism);
struct amdgpu_device *adev = drm_to_adev(acrtc->base.dev);
struct amdgpu_display_manager *dm = &adev->dm;
struct dm_crtc_state *acrtc_state = to_dm_crtc_state(acrtc->base.state);
/* ISM transitions must be called with dc_lock held */
lockdep_assert_held(&dm->dc_lock);
/* ISM should not run after dc is destroyed */
ASSERT(dm->dc);
if (!acrtc_state) {
trace_amdgpu_dm_ism_event(acrtc->crtc_id, "NO_STATE",
"NO_STATE", "N/A");
return;
}
do {
bool transition = dm_ism_trigger_event(ism, event);
next_event = DM_ISM_NUM_EVENTS;
if (transition) {
trace_amdgpu_dm_ism_event(
acrtc->crtc_id,
dm_ism_states_str[ism->previous_state],
dm_ism_states_str[ism->current_state],
dm_ism_events_str[event]);
next_event = dm_ism_dispatch_power_state(ism, acrtc_state);
} else {
trace_amdgpu_dm_ism_event(
acrtc->crtc_id,
dm_ism_states_str[ism->current_state],
dm_ism_states_str[ism->current_state],
dm_ism_events_str[event]);
}
event = next_event;
} while (next_event < DM_ISM_NUM_EVENTS);
}
static void dm_ism_delayed_work_func(struct work_struct *work)
{
struct amdgpu_dm_ism *ism =
container_of(work, struct amdgpu_dm_ism, delayed_work.work);
struct amdgpu_crtc *acrtc = ism_to_amdgpu_crtc(ism);
struct amdgpu_device *adev = drm_to_adev(acrtc->base.dev);
struct amdgpu_display_manager *dm = &adev->dm;
guard(mutex)(&dm->dc_lock);
amdgpu_dm_ism_commit_event(ism, DM_ISM_EVENT_TIMER_ELAPSED);
}
static void dm_ism_sso_delayed_work_func(struct work_struct *work)
{
struct amdgpu_dm_ism *ism =
container_of(work, struct amdgpu_dm_ism, sso_delayed_work.work);
struct amdgpu_crtc *acrtc = ism_to_amdgpu_crtc(ism);
struct amdgpu_device *adev = drm_to_adev(acrtc->base.dev);
struct amdgpu_display_manager *dm = &adev->dm;
guard(mutex)(&dm->dc_lock);
amdgpu_dm_ism_commit_event(ism, DM_ISM_EVENT_SSO_TIMER_ELAPSED);
}
/**
* amdgpu_dm_ism_disable - Quiesce ISM workers
*
* @dm: The amdgpu display manager
*
* Cancels and disables any pending or in-flight ISM delayed work and waits
* for in-progress work to finish. After this returns, no ISM worker can run
* and subsequent mod_delayed_work() calls become no-ops via
* clear_pending_if_disabled().
*
* Must NOT be called with dc_lock held: the workers themselves take dc_lock,
* so a synchronous wait under dc_lock would deadlock.
*
* The caller is responsible for driving the FSM back to FULL_POWER_RUNNING
* (under dc_lock) by calling amdgpu_dm_ism_force_full_power().
*/
void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm)
{
struct drm_crtc *crtc;
struct amdgpu_crtc *acrtc;
struct amdgpu_dm_ism *ism;
/*
* Caller must NOT hold dc_lock: the ISM delayed work handlers
* acquire dc_lock themselves, so waiting for them via
* disable_delayed_work_sync() while holding dc_lock would
* self-deadlock against an in-flight worker.
*/
lockdep_assert_not_held(&dm->dc_lock);
drm_for_each_crtc(crtc, dm->ddev) {
acrtc = to_amdgpu_crtc(crtc);
ism = &acrtc->ism;
disable_delayed_work_sync(&ism->delayed_work);
disable_delayed_work_sync(&ism->sso_delayed_work);
}
}
/**
* amdgpu_dm_ism_force_full_power - Force every CRTC's ISM FSM to FULL_POWER
*
* @dm: The amdgpu display manager
*
* Sends DM_ISM_EVENT_EXIT_IDLE_REQUESTED to every CRTC's ISM, leaving each
* FSM in FULL_POWER_RUNNING. Intended to be paired with
* amdgpu_dm_ism_disable(): callers should first quiesce workers (without
* dc_lock), then take dc_lock and call this helper.
*
* Must be called with dc_lock held.
*/
void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm)
{
struct drm_crtc *crtc;
struct amdgpu_crtc *acrtc;
/*
* Caller must hold dc_lock: commit_event() drives the FSM and
* may touch dc state via dc_allow_idle_optimizations() etc.
*/
lockdep_assert_held(&dm->dc_lock);
drm_for_each_crtc(crtc, dm->ddev) {
acrtc = to_amdgpu_crtc(crtc);
/*
* When disabled, leave in FULL_POWER_RUNNING state.
* EXIT_IDLE will not queue any work.
*/
amdgpu_dm_ism_commit_event(&acrtc->ism,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED);
}
}
/**
* amdgpu_dm_ism_enable - enable the ISM
*
* @dm: The amdgpu display manager
*
* Re-enable the idle state manager by enabling work that was disabled by
* amdgpu_dm_ism_disable.
*/
void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm)
{
struct drm_crtc *crtc;
struct amdgpu_crtc *acrtc;
struct amdgpu_dm_ism *ism;
drm_for_each_crtc(crtc, dm->ddev) {
acrtc = to_amdgpu_crtc(crtc);
ism = &acrtc->ism;
enable_delayed_work(&ism->delayed_work);
enable_delayed_work(&ism->sso_delayed_work);
}
}
void amdgpu_dm_ism_init(struct amdgpu_dm_ism *ism,
struct amdgpu_dm_ism_config *config)
{
ism->config = *config;
ism->current_state = DM_ISM_STATE_FULL_POWER_RUNNING;
ism->previous_state = DM_ISM_STATE_FULL_POWER_RUNNING;
ism->next_record_idx = 0;
ism->last_idle_timestamp_ns = 0;
INIT_DELAYED_WORK(&ism->delayed_work, dm_ism_delayed_work_func);
INIT_DELAYED_WORK(&ism->sso_delayed_work, dm_ism_sso_delayed_work_func);
}
EXPORT_IF_KUNIT(amdgpu_dm_ism_init);
void amdgpu_dm_ism_fini(struct amdgpu_dm_ism *ism)
{
cancel_delayed_work_sync(&ism->sso_delayed_work);
cancel_delayed_work_sync(&ism->delayed_work);
}
EXPORT_IF_KUNIT(amdgpu_dm_ism_fini);
@@ -0,0 +1,169 @@
/* 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
*
*/
#ifndef __AMDGPU_DM_ISM_H__
#define __AMDGPU_DM_ISM_H__
#include <linux/workqueue.h>
struct amdgpu_crtc;
struct amdgpu_display_manager;
#define AMDGPU_DM_IDLE_HIST_LEN 16
enum amdgpu_dm_ism_state {
DM_ISM_STATE_FULL_POWER_RUNNING,
DM_ISM_STATE_FULL_POWER_BUSY,
DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_STATE_HYSTERESIS_BUSY,
DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_STATE_OPTIMIZED_IDLE_SSO,
DM_ISM_STATE_TIMER_ABORTED,
DM_ISM_NUM_STATES,
};
enum amdgpu_dm_ism_event {
DM_ISM_EVENT_IMMEDIATE,
DM_ISM_EVENT_ENTER_IDLE_REQUESTED,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE,
DM_ISM_EVENT_END_CURSOR_UPDATE,
DM_ISM_EVENT_TIMER_ELAPSED,
DM_ISM_EVENT_SSO_TIMER_ELAPSED,
DM_ISM_NUM_EVENTS,
};
#define STATE_EVENT(state, event) (((state) << 8) | (event))
struct amdgpu_dm_ism_config {
/**
* @filter_num_frames: Idle periods shorter than this number of frames
* will be considered a "short idle period" for filtering.
*
* 0 indicates no filtering (i.e. no idle allow delay will be applied)
*/
unsigned int filter_num_frames;
/**
* @filter_history_size: Number of recent idle periods to consider when
* counting the number of short idle periods.
*/
unsigned int filter_history_size;
/**
* @filter_entry_count: When the number of short idle periods within
* recent &filter_history_size reaches this count, the idle allow delay
* will be applied.
*
* 0 indicates no filtering (i.e. no idle allow delay will be applied)
*/
unsigned int filter_entry_count;
/**
* @activation_num_delay_frames: Defines the number of frames to wait
* for the idle allow delay.
*
* 0 indicates no filtering (i.e. no idle allow delay will be applied)
*/
unsigned int activation_num_delay_frames;
/**
* @filter_old_history_threshold: A time-based restriction on top of
* &filter_history_size. Idle periods older than this threshold (in
* number of frames) will be ignored when counting the number of short
* idle periods.
*
* 0 indicates no time-based restriction, i.e. history is limited only
* by &filter_history_size.
*/
unsigned int filter_old_history_threshold;
/**
* @sso_num_frames: Number of frames to delay before enabling static
* screen optimizations, such as PSR1 and Replay low HZ idle mode.
*
* 0 indicates immediate SSO enable upon allowing idle.
*/
unsigned int sso_num_frames;
};
struct amdgpu_dm_ism_record {
/**
* @timestamp_ns: When idle was allowed
*/
unsigned long long timestamp_ns;
/**
* @duration_ns: How long idle was allowed
*/
unsigned long long duration_ns;
};
struct amdgpu_dm_ism {
struct amdgpu_dm_ism_config config;
unsigned long long last_idle_timestamp_ns;
enum amdgpu_dm_ism_state current_state;
enum amdgpu_dm_ism_state previous_state;
struct amdgpu_dm_ism_record records[AMDGPU_DM_IDLE_HIST_LEN];
int next_record_idx;
struct delayed_work delayed_work;
struct delayed_work sso_delayed_work;
};
#define ism_to_amdgpu_crtc(ism_ptr) \
container_of(ism_ptr, struct amdgpu_crtc, ism)
void amdgpu_dm_ism_init(struct amdgpu_dm_ism *ism,
struct amdgpu_dm_ism_config *config);
void amdgpu_dm_ism_fini(struct amdgpu_dm_ism *ism);
void amdgpu_dm_ism_commit_event(struct amdgpu_dm_ism *ism,
enum amdgpu_dm_ism_event event);
void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm);
void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm);
void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm);
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state,
enum amdgpu_dm_ism_event event,
enum amdgpu_dm_ism_state *next_state);
uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism,
const struct dc_stream_state *stream);
uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism,
const struct dc_stream_state *stream);
void dm_ism_insert_record(struct amdgpu_dm_ism *ism);
void dm_ism_set_last_idle_ts(struct amdgpu_dm_ism *ism);
bool dm_ism_trigger_event(struct amdgpu_dm_ism *ism,
enum amdgpu_dm_ism_event event);
enum amdgpu_dm_ism_event dm_ism_dispatch_next_event(enum amdgpu_dm_ism_state current_state,
uint64_t delay_ns,
uint64_t sso_delay_ns);
#endif
#endif
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2026 Advanced Micro Devices, Inc.
*/
#ifndef AMDGPU_DM_KUNIT_HELPERS_H
#define AMDGPU_DM_KUNIT_HELPERS_H
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
#define STATIC_IFN_KUNIT
#define INLINE_IFN_KUNIT inline
#define EXPORT_IF_KUNIT(symbol) EXPORT_SYMBOL(symbol)
#else
#define STATIC_IFN_KUNIT static
#define INLINE_IFN_KUNIT
#define EXPORT_IF_KUNIT(symbol)
#endif
#endif /* AMDGPU_DM_KUNIT_HELPERS_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,97 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2012-15 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
*
*/
#ifndef __DAL_AMDGPU_DM_MST_TYPES_H__
#define __DAL_AMDGPU_DM_MST_TYPES_H__
#define DP_BRANCH_DEVICE_ID_90CC24 0x90CC24
#define SYNAPTICS_RC_COMMAND 0x4B2
#define SYNAPTICS_RC_RESULT 0x4B3
#define SYNAPTICS_RC_LENGTH 0x4B8
#define SYNAPTICS_RC_OFFSET 0x4BC
#define SYNAPTICS_RC_DATA 0x4C0
#define DP_BRANCH_VENDOR_SPECIFIC_START 0x50C
/**
* Panamera MST Hub detection
* Offset DPCD 050Eh == 0x5A indicates cascaded MST hub case
* Check from beginning of branch device vendor specific field (050Ch)
*/
#define IS_SYNAPTICS_PANAMERA(branchDevName) (((int)branchDevName[4] & 0xF0) == 0x50 ? 1 : 0)
#define BRANCH_HW_REVISION_PANAMERA_A2 0x10
#define SYNAPTICS_CASCADED_HUB_ID 0x5A
#define IS_SYNAPTICS_CASCADED_PANAMERA(devName, data) ((IS_SYNAPTICS_PANAMERA(devName) && ((int)data[2] == SYNAPTICS_CASCADED_HUB_ID)) ? 1 : 0)
#define PBN_FEC_OVERHEAD_MULTIPLIER_8B_10B 1031
#define PBN_FEC_OVERHEAD_MULTIPLIER_128B_132B 1000
enum mst_msg_ready_type {
NONE_MSG_RDY_EVENT = 0,
DOWN_REP_MSG_RDY_EVENT = 1,
UP_REQ_MSG_RDY_EVENT = 2,
DOWN_OR_UP_MSG_RDY_EVENT = 3
};
struct amdgpu_display_manager;
struct amdgpu_dm_connector;
uint32_t dm_mst_get_pbn_divider(struct dc_link *link);
void amdgpu_dm_initialize_dp_connector(struct amdgpu_display_manager *dm,
struct amdgpu_dm_connector *aconnector,
int link_index);
void
dm_dp_create_fake_mst_encoders(struct amdgpu_device *adev);
void dm_handle_mst_sideband_msg_ready_event(
struct drm_dp_mst_topology_mgr *mgr,
enum mst_msg_ready_type msg_rdy_type);
struct dsc_mst_fairness_vars {
int pbn;
bool dsc_enabled;
int bpp_x16;
struct amdgpu_dm_connector *aconnector;
};
int compute_mst_dsc_configs_for_state(struct drm_atomic_commit *state,
struct dc_state *dc_state,
struct dsc_mst_fairness_vars *vars);
bool needs_dsc_aux_workaround(struct dc_link *link);
int pre_validate_dsc(struct drm_atomic_commit *state,
struct dm_atomic_state **dm_state_ptr,
struct dsc_mst_fairness_vars *vars);
enum dc_status dm_dp_mst_is_port_support_mode(
struct amdgpu_dm_connector *aconnector,
struct dc_stream_state *stream);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2022 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
*
*/
#ifndef __AMDGPU_DM_PLANE_H__
#define __AMDGPU_DM_PLANE_H__
#include "dc.h"
int amdgpu_dm_plane_get_cursor_position(struct drm_plane *plane, struct drm_crtc *crtc,
struct dc_cursor_position *position);
void amdgpu_dm_plane_handle_cursor_update(struct drm_plane *plane,
struct drm_plane_state *old_plane_state);
int amdgpu_dm_plane_fill_dc_scaling_info(struct amdgpu_device *adev,
const struct drm_plane_state *state,
struct dc_scaling_info *scaling_info);
int amdgpu_dm_plane_helper_check_state(struct drm_plane_state *state,
struct drm_crtc_state *new_crtc_state);
int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev,
const struct amdgpu_framebuffer *afb,
const enum surface_pixel_format format,
const enum dc_rotation_angle rotation,
const uint64_t tiling_flags,
struct dc_tiling_info *tiling_info,
struct plane_size *plane_size,
struct dc_plane_dcc_param *dcc,
struct dc_plane_address *address,
bool tmz_surface);
int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm,
struct drm_plane *plane,
unsigned long possible_crtcs,
const struct dc_plane_cap *plane_cap);
const struct drm_format_info *amdgpu_dm_plane_get_format_info(u32 pixel_format, u64 modifier);
void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state *plane_state,
bool *per_pixel_alpha, bool *pre_multiplied_alpha,
bool *global_alpha, int *global_alpha_value);
bool amdgpu_dm_plane_is_video_format(uint32_t format);
#endif
@@ -0,0 +1,753 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2018 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 <linux/string.h>
#include <linux/acpi.h>
#include <drm/drm_probe_helper.h>
#include <drm/amdgpu_drm.h>
#include "dm_services.h"
#include "amdgpu.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_irq.h"
#include "amdgpu_pm.h"
#include "dm_pp_smu.h"
bool dm_pp_apply_display_requirements(
const struct dc_context *ctx,
const struct dm_pp_display_configuration *pp_display_cfg)
{
struct amdgpu_device *adev = ctx->driver_context;
int i;
if (adev->pm.dpm_enabled) {
memset(&adev->pm.pm_display_cfg, 0,
sizeof(adev->pm.pm_display_cfg));
adev->pm.pm_display_cfg.cpu_cc6_disable =
pp_display_cfg->cpu_cc6_disable;
adev->pm.pm_display_cfg.cpu_pstate_disable =
pp_display_cfg->cpu_pstate_disable;
adev->pm.pm_display_cfg.cpu_pstate_separation_time =
pp_display_cfg->cpu_pstate_separation_time;
adev->pm.pm_display_cfg.nb_pstate_switch_disable =
pp_display_cfg->nb_pstate_switch_disable;
adev->pm.pm_display_cfg.num_display =
pp_display_cfg->display_count;
adev->pm.pm_display_cfg.num_path_including_non_display =
pp_display_cfg->display_count;
adev->pm.pm_display_cfg.min_core_set_clock =
pp_display_cfg->min_engine_clock_khz/10;
adev->pm.pm_display_cfg.min_core_set_clock_in_sr =
pp_display_cfg->min_engine_clock_deep_sleep_khz/10;
adev->pm.pm_display_cfg.min_mem_set_clock =
pp_display_cfg->min_memory_clock_khz/10;
adev->pm.pm_display_cfg.min_dcef_deep_sleep_set_clk =
pp_display_cfg->min_engine_clock_deep_sleep_khz/10;
adev->pm.pm_display_cfg.min_dcef_set_clk =
pp_display_cfg->min_dcfclock_khz/10;
adev->pm.pm_display_cfg.multi_monitor_in_sync =
pp_display_cfg->all_displays_in_sync;
adev->pm.pm_display_cfg.min_vblank_time =
pp_display_cfg->avail_mclk_switch_time_us;
adev->pm.pm_display_cfg.display_clk =
pp_display_cfg->disp_clk_khz/10;
adev->pm.pm_display_cfg.dce_tolerable_mclk_in_active_latency =
pp_display_cfg->avail_mclk_switch_time_in_disp_active_us;
adev->pm.pm_display_cfg.crtc_index = pp_display_cfg->crtc_index;
adev->pm.pm_display_cfg.line_time_in_us =
pp_display_cfg->line_time_in_us;
adev->pm.pm_display_cfg.vrefresh = pp_display_cfg->disp_configs[0].v_refresh;
adev->pm.pm_display_cfg.crossfire_display_index = -1;
adev->pm.pm_display_cfg.min_bus_bandwidth = 0;
for (i = 0; i < pp_display_cfg->display_count; i++) {
const struct dm_pp_single_disp_config *dc_cfg =
&pp_display_cfg->disp_configs[i];
adev->pm.pm_display_cfg.displays[i].controller_id = dc_cfg->pipe_idx + 1;
adev->pm.pm_display_cfg.displays[i].pixel_clock = dc_cfg->pixel_clock;
}
amdgpu_dpm_display_configuration_change(adev, &adev->pm.pm_display_cfg);
amdgpu_dpm_compute_clocks(adev);
}
return true;
}
static void get_default_clock_levels(
enum dm_pp_clock_type clk_type,
struct dm_pp_clock_levels *clks)
{
uint32_t disp_clks_in_khz[6] = {
300000, 400000, 496560, 626090, 685720, 757900 };
uint32_t sclks_in_khz[6] = {
300000, 360000, 423530, 514290, 626090, 720000 };
uint32_t mclks_in_khz[2] = { 333000, 800000 };
switch (clk_type) {
case DM_PP_CLOCK_TYPE_DISPLAY_CLK:
clks->num_levels = 6;
memmove(clks->clocks_in_khz, disp_clks_in_khz,
sizeof(disp_clks_in_khz));
break;
case DM_PP_CLOCK_TYPE_ENGINE_CLK:
clks->num_levels = 6;
memmove(clks->clocks_in_khz, sclks_in_khz,
sizeof(sclks_in_khz));
break;
case DM_PP_CLOCK_TYPE_MEMORY_CLK:
clks->num_levels = 2;
memmove(clks->clocks_in_khz, mclks_in_khz,
sizeof(mclks_in_khz));
break;
default:
clks->num_levels = 0;
break;
}
}
static enum amd_pp_clock_type dc_to_pp_clock_type(
enum dm_pp_clock_type dm_pp_clk_type)
{
enum amd_pp_clock_type amd_pp_clk_type = 0;
switch (dm_pp_clk_type) {
case DM_PP_CLOCK_TYPE_DISPLAY_CLK:
amd_pp_clk_type = amd_pp_disp_clock;
break;
case DM_PP_CLOCK_TYPE_ENGINE_CLK:
amd_pp_clk_type = amd_pp_sys_clock;
break;
case DM_PP_CLOCK_TYPE_MEMORY_CLK:
amd_pp_clk_type = amd_pp_mem_clock;
break;
case DM_PP_CLOCK_TYPE_DCEFCLK:
amd_pp_clk_type = amd_pp_dcef_clock;
break;
case DM_PP_CLOCK_TYPE_DCFCLK:
amd_pp_clk_type = amd_pp_dcf_clock;
break;
case DM_PP_CLOCK_TYPE_PIXELCLK:
amd_pp_clk_type = amd_pp_pixel_clock;
break;
case DM_PP_CLOCK_TYPE_FCLK:
amd_pp_clk_type = amd_pp_f_clock;
break;
case DM_PP_CLOCK_TYPE_DISPLAYPHYCLK:
amd_pp_clk_type = amd_pp_phy_clock;
break;
case DM_PP_CLOCK_TYPE_DPPCLK:
amd_pp_clk_type = amd_pp_dpp_clock;
break;
default:
DRM_ERROR("DM_PPLIB: invalid clock type: %d!\n",
dm_pp_clk_type);
break;
}
return amd_pp_clk_type;
}
static void pp_to_dc_clock_levels(
const struct amd_pp_clocks *pp_clks,
struct dm_pp_clock_levels *dc_clks,
enum dm_pp_clock_type dc_clk_type)
{
uint32_t i;
if (pp_clks->count > DM_PP_MAX_CLOCK_LEVELS) {
DRM_INFO("DM_PPLIB: Warning: %s clock: number of levels %d exceeds maximum of %d!\n",
DC_DECODE_PP_CLOCK_TYPE(dc_clk_type),
pp_clks->count,
DM_PP_MAX_CLOCK_LEVELS);
dc_clks->num_levels = DM_PP_MAX_CLOCK_LEVELS;
} else
dc_clks->num_levels = pp_clks->count;
DRM_INFO("DM_PPLIB: values for %s clock\n",
DC_DECODE_PP_CLOCK_TYPE(dc_clk_type));
for (i = 0; i < dc_clks->num_levels; i++) {
DRM_INFO("DM_PPLIB:\t %d\n", pp_clks->clock[i]);
dc_clks->clocks_in_khz[i] = pp_clks->clock[i];
}
}
static void pp_to_dc_clock_levels_with_latency(
const struct pp_clock_levels_with_latency *pp_clks,
struct dm_pp_clock_levels_with_latency *clk_level_info,
enum dm_pp_clock_type dc_clk_type)
{
uint32_t i;
if (pp_clks->num_levels > DM_PP_MAX_CLOCK_LEVELS) {
DRM_INFO("DM_PPLIB: Warning: %s clock: number of levels %d exceeds maximum of %d!\n",
DC_DECODE_PP_CLOCK_TYPE(dc_clk_type),
pp_clks->num_levels,
DM_PP_MAX_CLOCK_LEVELS);
clk_level_info->num_levels = DM_PP_MAX_CLOCK_LEVELS;
} else
clk_level_info->num_levels = pp_clks->num_levels;
DRM_DEBUG("DM_PPLIB: values for %s clock\n",
DC_DECODE_PP_CLOCK_TYPE(dc_clk_type));
for (i = 0; i < clk_level_info->num_levels; i++) {
DRM_DEBUG("DM_PPLIB:\t %d in kHz\n", pp_clks->data[i].clocks_in_khz);
clk_level_info->data[i].clocks_in_khz = pp_clks->data[i].clocks_in_khz;
clk_level_info->data[i].latency_in_us = pp_clks->data[i].latency_in_us;
}
}
static void pp_to_dc_clock_levels_with_voltage(
const struct pp_clock_levels_with_voltage *pp_clks,
struct dm_pp_clock_levels_with_voltage *clk_level_info,
enum dm_pp_clock_type dc_clk_type)
{
uint32_t i;
if (pp_clks->num_levels > DM_PP_MAX_CLOCK_LEVELS) {
DRM_INFO("DM_PPLIB: Warning: %s clock: number of levels %d exceeds maximum of %d!\n",
DC_DECODE_PP_CLOCK_TYPE(dc_clk_type),
pp_clks->num_levels,
DM_PP_MAX_CLOCK_LEVELS);
clk_level_info->num_levels = DM_PP_MAX_CLOCK_LEVELS;
} else
clk_level_info->num_levels = pp_clks->num_levels;
DRM_INFO("DM_PPLIB: values for %s clock\n",
DC_DECODE_PP_CLOCK_TYPE(dc_clk_type));
for (i = 0; i < clk_level_info->num_levels; i++) {
DRM_INFO("DM_PPLIB:\t %d in kHz, %d in mV\n", pp_clks->data[i].clocks_in_khz,
pp_clks->data[i].voltage_in_mv);
clk_level_info->data[i].clocks_in_khz = pp_clks->data[i].clocks_in_khz;
clk_level_info->data[i].voltage_in_mv = pp_clks->data[i].voltage_in_mv;
}
}
bool dm_pp_get_clock_levels_by_type(
const struct dc_context *ctx,
enum dm_pp_clock_type clk_type,
struct dm_pp_clock_levels *dc_clks)
{
struct amdgpu_device *adev = ctx->driver_context;
struct amd_pp_clocks pp_clks = { 0 };
struct amd_pp_simple_clock_info validation_clks = { 0 };
uint32_t i;
if (amdgpu_dpm_get_clock_by_type(adev,
dc_to_pp_clock_type(clk_type), &pp_clks)) {
/* Error in pplib. Provide default values. */
get_default_clock_levels(clk_type, dc_clks);
return true;
}
pp_to_dc_clock_levels(&pp_clks, dc_clks, clk_type);
if (amdgpu_dpm_get_display_mode_validation_clks(adev, &validation_clks)) {
/* Error in pplib. Provide default values. */
DRM_INFO("DM_PPLIB: Warning: using default validation clocks!\n");
validation_clks.engine_max_clock = 72000;
validation_clks.memory_max_clock = 80000;
}
DRM_INFO("DM_PPLIB: Validation clocks:\n");
DRM_INFO("DM_PPLIB: engine_max_clock: %d\n",
validation_clks.engine_max_clock);
DRM_INFO("DM_PPLIB: memory_max_clock: %d\n",
validation_clks.memory_max_clock);
/* Translate 10 kHz to kHz. */
validation_clks.engine_max_clock *= 10;
validation_clks.memory_max_clock *= 10;
/* Determine the highest non-boosted level from the Validation Clocks */
if (clk_type == DM_PP_CLOCK_TYPE_ENGINE_CLK) {
for (i = 0; i < dc_clks->num_levels; i++) {
if (dc_clks->clocks_in_khz[i] > validation_clks.engine_max_clock) {
/* This clock is higher the validation clock.
* Than means the previous one is the highest
* non-boosted one.
*/
DRM_INFO("DM_PPLIB: reducing engine clock level from %d to %d\n",
dc_clks->num_levels, i);
dc_clks->num_levels = i > 0 ? i : 1;
break;
}
}
} else if (clk_type == DM_PP_CLOCK_TYPE_MEMORY_CLK) {
for (i = 0; i < dc_clks->num_levels; i++) {
if (dc_clks->clocks_in_khz[i] > validation_clks.memory_max_clock) {
DRM_INFO("DM_PPLIB: reducing memory clock level from %d to %d\n",
dc_clks->num_levels, i);
dc_clks->num_levels = i > 0 ? i : 1;
break;
}
}
}
return true;
}
bool dm_pp_get_clock_levels_by_type_with_latency(
const struct dc_context *ctx,
enum dm_pp_clock_type clk_type,
struct dm_pp_clock_levels_with_latency *clk_level_info)
{
struct amdgpu_device *adev = ctx->driver_context;
struct pp_clock_levels_with_latency pp_clks = { 0 };
int ret;
ret = amdgpu_dpm_get_clock_by_type_with_latency(adev,
dc_to_pp_clock_type(clk_type),
&pp_clks);
if (ret)
return false;
pp_to_dc_clock_levels_with_latency(&pp_clks, clk_level_info, clk_type);
return true;
}
bool dm_pp_get_clock_levels_by_type_with_voltage(
const struct dc_context *ctx,
enum dm_pp_clock_type clk_type,
struct dm_pp_clock_levels_with_voltage *clk_level_info)
{
struct amdgpu_device *adev = ctx->driver_context;
struct pp_clock_levels_with_voltage pp_clk_info = {0};
int ret;
ret = amdgpu_dpm_get_clock_by_type_with_voltage(adev,
dc_to_pp_clock_type(clk_type),
&pp_clk_info);
if (ret)
return false;
pp_to_dc_clock_levels_with_voltage(&pp_clk_info, clk_level_info, clk_type);
return true;
}
bool dm_pp_notify_wm_clock_changes(
const struct dc_context *ctx,
struct dm_pp_wm_sets_with_clock_ranges *wm_with_clock_ranges)
{
struct amdgpu_device *adev = ctx->driver_context;
/*
* Limit this watermark setting for Polaris for now
* TODO: expand this to other ASICs
*/
if ((adev->asic_type >= CHIP_POLARIS10) &&
(adev->asic_type <= CHIP_VEGAM) &&
!amdgpu_dpm_set_watermarks_for_clocks_ranges(adev,
(void *)wm_with_clock_ranges))
return true;
return false;
}
bool dm_pp_apply_clock_for_voltage_request(
const struct dc_context *ctx,
struct dm_pp_clock_for_voltage_req *clock_for_voltage_req)
{
struct amdgpu_device *adev = ctx->driver_context;
struct pp_display_clock_request pp_clock_request = {0};
int ret = 0;
pp_clock_request.clock_type = dc_to_pp_clock_type(clock_for_voltage_req->clk_type);
pp_clock_request.clock_freq_in_khz = clock_for_voltage_req->clocks_in_khz;
if (!pp_clock_request.clock_type)
return false;
ret = amdgpu_dpm_display_clock_voltage_request(adev, &pp_clock_request);
if (ret && (ret != -EOPNOTSUPP))
return false;
return true;
}
static void pp_rv_set_wm_ranges(struct pp_smu *pp,
struct pp_smu_wm_range_sets *ranges)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
struct dm_pp_wm_sets_with_clock_ranges_soc15 wm_with_clock_ranges;
struct dm_pp_clock_range_for_dmif_wm_set_soc15 *wm_dce_clocks = wm_with_clock_ranges.wm_dmif_clocks_ranges;
struct dm_pp_clock_range_for_mcif_wm_set_soc15 *wm_soc_clocks = wm_with_clock_ranges.wm_mcif_clocks_ranges;
int32_t i;
wm_with_clock_ranges.num_wm_dmif_sets = ranges->num_reader_wm_sets;
wm_with_clock_ranges.num_wm_mcif_sets = ranges->num_writer_wm_sets;
for (i = 0; i < wm_with_clock_ranges.num_wm_dmif_sets; i++) {
if (ranges->reader_wm_sets[i].wm_inst > 3)
wm_dce_clocks[i].wm_set_id = WM_SET_A;
else
wm_dce_clocks[i].wm_set_id =
ranges->reader_wm_sets[i].wm_inst;
wm_dce_clocks[i].wm_max_dcfclk_clk_in_khz =
ranges->reader_wm_sets[i].max_drain_clk_mhz * 1000;
wm_dce_clocks[i].wm_min_dcfclk_clk_in_khz =
ranges->reader_wm_sets[i].min_drain_clk_mhz * 1000;
wm_dce_clocks[i].wm_max_mem_clk_in_khz =
ranges->reader_wm_sets[i].max_fill_clk_mhz * 1000;
wm_dce_clocks[i].wm_min_mem_clk_in_khz =
ranges->reader_wm_sets[i].min_fill_clk_mhz * 1000;
}
for (i = 0; i < wm_with_clock_ranges.num_wm_mcif_sets; i++) {
if (ranges->writer_wm_sets[i].wm_inst > 3)
wm_soc_clocks[i].wm_set_id = WM_SET_A;
else
wm_soc_clocks[i].wm_set_id =
ranges->writer_wm_sets[i].wm_inst;
wm_soc_clocks[i].wm_max_socclk_clk_in_khz =
ranges->writer_wm_sets[i].max_fill_clk_mhz * 1000;
wm_soc_clocks[i].wm_min_socclk_clk_in_khz =
ranges->writer_wm_sets[i].min_fill_clk_mhz * 1000;
wm_soc_clocks[i].wm_max_mem_clk_in_khz =
ranges->writer_wm_sets[i].max_drain_clk_mhz * 1000;
wm_soc_clocks[i].wm_min_mem_clk_in_khz =
ranges->writer_wm_sets[i].min_drain_clk_mhz * 1000;
}
amdgpu_dpm_set_watermarks_for_clocks_ranges(adev,
&wm_with_clock_ranges);
}
static void pp_rv_set_pme_wa_enable(struct pp_smu *pp)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
amdgpu_dpm_notify_smu_enable_pwe(adev);
}
static void pp_rv_set_active_display_count(struct pp_smu *pp, int count)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
amdgpu_dpm_set_active_display_count(adev, count);
}
static void pp_rv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int clock)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
amdgpu_dpm_set_min_deep_sleep_dcefclk(adev, clock);
}
static void pp_rv_set_hard_min_dcefclk_by_freq(struct pp_smu *pp, int clock)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
amdgpu_dpm_set_hard_min_dcefclk_by_freq(adev, clock);
}
static void pp_rv_set_hard_min_fclk_by_freq(struct pp_smu *pp, int mhz)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
amdgpu_dpm_set_hard_min_fclk_by_freq(adev, mhz);
}
static enum pp_smu_status pp_nv_set_wm_ranges(struct pp_smu *pp,
struct pp_smu_wm_range_sets *ranges)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
amdgpu_dpm_set_watermarks_for_clocks_ranges(adev, ranges);
return PP_SMU_RESULT_OK;
}
static enum pp_smu_status pp_nv_set_display_count(struct pp_smu *pp, int count)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
int ret = 0;
ret = amdgpu_dpm_set_active_display_count(adev, count);
if (ret == -EOPNOTSUPP)
return PP_SMU_RESULT_UNSUPPORTED;
else if (ret)
/* 0: successful or smu.ppt_funcs->set_display_count = NULL; 1: fail */
return PP_SMU_RESULT_FAIL;
return PP_SMU_RESULT_OK;
}
static enum pp_smu_status
pp_nv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int mhz)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
int ret = 0;
/* 0: successful or smu.ppt_funcs->set_deep_sleep_dcefclk = NULL;1: fail */
ret = amdgpu_dpm_set_min_deep_sleep_dcefclk(adev, mhz);
if (ret == -EOPNOTSUPP)
return PP_SMU_RESULT_UNSUPPORTED;
else if (ret)
return PP_SMU_RESULT_FAIL;
return PP_SMU_RESULT_OK;
}
static enum pp_smu_status pp_nv_set_hard_min_dcefclk_by_freq(
struct pp_smu *pp, int mhz)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
struct pp_display_clock_request clock_req;
int ret = 0;
clock_req.clock_type = amd_pp_dcef_clock;
clock_req.clock_freq_in_khz = mhz * 1000;
/* 0: successful or smu.ppt_funcs->display_clock_voltage_request = NULL
* 1: fail
*/
ret = amdgpu_dpm_display_clock_voltage_request(adev, &clock_req);
if (ret == -EOPNOTSUPP)
return PP_SMU_RESULT_UNSUPPORTED;
else if (ret)
return PP_SMU_RESULT_FAIL;
return PP_SMU_RESULT_OK;
}
static enum pp_smu_status
pp_nv_set_hard_min_uclk_by_freq(struct pp_smu *pp, int mhz)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
struct pp_display_clock_request clock_req;
int ret = 0;
clock_req.clock_type = amd_pp_mem_clock;
clock_req.clock_freq_in_khz = mhz * 1000;
/* 0: successful or smu.ppt_funcs->display_clock_voltage_request = NULL
* 1: fail
*/
ret = amdgpu_dpm_display_clock_voltage_request(adev, &clock_req);
if (ret == -EOPNOTSUPP)
return PP_SMU_RESULT_UNSUPPORTED;
else if (ret)
return PP_SMU_RESULT_FAIL;
return PP_SMU_RESULT_OK;
}
static enum pp_smu_status pp_nv_set_pstate_handshake_support(
struct pp_smu *pp, bool pstate_handshake_supported)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
if (amdgpu_dpm_display_disable_memory_clock_switch(adev,
!pstate_handshake_supported))
return PP_SMU_RESULT_FAIL;
return PP_SMU_RESULT_OK;
}
static enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp,
enum pp_smu_nv_clock_id clock_id, int mhz)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
struct pp_display_clock_request clock_req;
int ret = 0;
switch (clock_id) {
case PP_SMU_NV_DISPCLK:
clock_req.clock_type = amd_pp_disp_clock;
break;
case PP_SMU_NV_PHYCLK:
clock_req.clock_type = amd_pp_phy_clock;
break;
case PP_SMU_NV_PIXELCLK:
clock_req.clock_type = amd_pp_pixel_clock;
break;
default:
break;
}
clock_req.clock_freq_in_khz = mhz * 1000;
/* 0: successful or smu.ppt_funcs->display_clock_voltage_request = NULL
* 1: fail
*/
ret = amdgpu_dpm_display_clock_voltage_request(adev, &clock_req);
if (ret == -EOPNOTSUPP)
return PP_SMU_RESULT_UNSUPPORTED;
else if (ret)
return PP_SMU_RESULT_FAIL;
return PP_SMU_RESULT_OK;
}
static enum pp_smu_status pp_nv_get_maximum_sustainable_clocks(
struct pp_smu *pp, struct pp_smu_nv_clock_table *max_clocks)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
int ret = 0;
ret = amdgpu_dpm_get_max_sustainable_clocks_by_dc(adev,
max_clocks);
if (ret == -EOPNOTSUPP)
return PP_SMU_RESULT_UNSUPPORTED;
else if (ret)
return PP_SMU_RESULT_FAIL;
return PP_SMU_RESULT_OK;
}
static enum pp_smu_status pp_nv_get_uclk_dpm_states(struct pp_smu *pp,
unsigned int *clock_values_in_khz, unsigned int *num_states)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
int ret = 0;
ret = amdgpu_dpm_get_uclk_dpm_states(adev,
clock_values_in_khz,
num_states);
if (ret == -EOPNOTSUPP)
return PP_SMU_RESULT_UNSUPPORTED;
else if (ret)
return PP_SMU_RESULT_FAIL;
return PP_SMU_RESULT_OK;
}
static enum pp_smu_status pp_rn_get_dpm_clock_table(
struct pp_smu *pp, struct dpm_clocks *clock_table)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
int ret = 0;
ret = amdgpu_dpm_get_dpm_clock_table(adev, clock_table);
if (ret == -EOPNOTSUPP)
return PP_SMU_RESULT_UNSUPPORTED;
else if (ret)
return PP_SMU_RESULT_FAIL;
return PP_SMU_RESULT_OK;
}
static enum pp_smu_status pp_rn_set_wm_ranges(struct pp_smu *pp,
struct pp_smu_wm_range_sets *ranges)
{
const struct dc_context *ctx = pp->dm;
struct amdgpu_device *adev = ctx->driver_context;
amdgpu_dpm_set_watermarks_for_clocks_ranges(adev, ranges);
return PP_SMU_RESULT_OK;
}
void dm_pp_get_funcs(
struct dc_context *ctx,
struct pp_smu_funcs *funcs)
{
switch (ctx->dce_version) {
case DCN_VERSION_1_0:
case DCN_VERSION_1_01:
funcs->ctx.ver = PP_SMU_VER_RV;
funcs->rv_funcs.pp_smu.dm = ctx;
funcs->rv_funcs.set_wm_ranges = pp_rv_set_wm_ranges;
funcs->rv_funcs.set_pme_wa_enable = pp_rv_set_pme_wa_enable;
funcs->rv_funcs.set_display_count =
pp_rv_set_active_display_count;
funcs->rv_funcs.set_min_deep_sleep_dcfclk =
pp_rv_set_min_deep_sleep_dcfclk;
funcs->rv_funcs.set_hard_min_dcfclk_by_freq =
pp_rv_set_hard_min_dcefclk_by_freq;
funcs->rv_funcs.set_hard_min_fclk_by_freq =
pp_rv_set_hard_min_fclk_by_freq;
break;
case DCN_VERSION_2_0:
funcs->ctx.ver = PP_SMU_VER_NV;
funcs->nv_funcs.pp_smu.dm = ctx;
funcs->nv_funcs.set_display_count = pp_nv_set_display_count;
funcs->nv_funcs.set_hard_min_dcfclk_by_freq =
pp_nv_set_hard_min_dcefclk_by_freq;
funcs->nv_funcs.set_min_deep_sleep_dcfclk =
pp_nv_set_min_deep_sleep_dcfclk;
funcs->nv_funcs.set_voltage_by_freq =
pp_nv_set_voltage_by_freq;
funcs->nv_funcs.set_wm_ranges = pp_nv_set_wm_ranges;
/* todo set_pme_wa_enable cause 4k@6ohz display not light up */
funcs->nv_funcs.set_pme_wa_enable = NULL;
/* todo debug waring message */
funcs->nv_funcs.set_hard_min_uclk_by_freq = pp_nv_set_hard_min_uclk_by_freq;
/* todo compare data with window driver*/
funcs->nv_funcs.get_maximum_sustainable_clocks = pp_nv_get_maximum_sustainable_clocks;
/*todo compare data with window driver */
funcs->nv_funcs.get_uclk_dpm_states = pp_nv_get_uclk_dpm_states;
funcs->nv_funcs.set_pstate_handshake_support = pp_nv_set_pstate_handshake_support;
break;
case DCN_VERSION_2_1:
funcs->ctx.ver = PP_SMU_VER_RN;
funcs->rn_funcs.pp_smu.dm = ctx;
funcs->rn_funcs.set_wm_ranges = pp_rn_set_wm_ranges;
funcs->rn_funcs.get_dpm_clock_table = pp_rn_get_dpm_clock_table;
break;
default:
DRM_ERROR("smu version is not supported !\n");
break;
}
}
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2021 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_dm_psr.h"
#include "amdgpu.h"
#include "dc_dmub_srv.h"
#include "dc.h"
#include "amdgpu_dm.h"
#include "modules/power/power_helpers.h"
#include "amdgpu_dm_kunit_helpers.h"
static bool link_supports_psrsu(struct dc_link *link)
{
struct dc *dc = link->ctx->dc;
if (!dc->caps.dmcub_support)
return false;
if (dc->ctx->dce_version < DCN_VERSION_3_1)
return false;
if (!is_psr_su_specific_panel(link))
return false;
if (!link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP ||
!link->dpcd_caps.psr_info.psr_dpcd_caps.bits.Y_COORDINATE_REQUIRED)
return false;
if (link->dpcd_caps.psr_info.psr_dpcd_caps.bits.SU_GRANULARITY_REQUIRED &&
!link->dpcd_caps.psr_info.psr2_su_y_granularity_cap)
return false;
if (amdgpu_dc_debug_mask & DC_DISABLE_PSR_SU)
return false;
/* Temporarily disable PSR-SU to avoid glitches */
return false;
}
STATIC_IFN_KUNIT
void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps)
{
struct dpcd_caps *dpcd_caps = &link->dpcd_caps;
unsigned int power_opts = 0;
if (amdgpu_dc_feature_mask & DC_PSR_ALLOW_SMU_OPT)
power_opts |= psr_power_opt_smu_opt_static_screen;
power_opts |= psr_power_opt_z10_static_screen;
if (link->psr_settings.psr_version == DC_PSR_VERSION_1)
caps->psr_version = 1;
else if (link->psr_settings.psr_version == DC_PSR_VERSION_SU_1)
caps->psr_version = 2;
caps->psr_rfb_setup_time = (6 - dpcd_caps->psr_info.psr_dpcd_caps.bits.PSR_SETUP_TIME) * 55;
caps->psr_exit_link_training_required =
!dpcd_caps->psr_info.psr_dpcd_caps.bits.LINK_TRAINING_ON_EXIT_NOT_REQUIRED;
caps->edp_revision = dpcd_caps->edp_rev;
caps->support_ver = dpcd_caps->psr_info.psr_version;
caps->su_granularity_required =
dpcd_caps->psr_info.psr_dpcd_caps.bits.SU_GRANULARITY_REQUIRED;
caps->y_coordinate_required = dpcd_caps->psr_info.psr_dpcd_caps.bits.Y_COORDINATE_REQUIRED;
caps->su_y_granularity = dpcd_caps->psr_info.psr2_su_y_granularity_cap;
caps->alpm_cap = dpcd_caps->alpm_caps.bits.AUX_WAKE_ALPM_CAP;
caps->standby_support = dpcd_caps->alpm_caps.bits.PM_STATE_2A_SUPPORT;
caps->rate_control_caps = 0; /* TODO: read in rc caps from aux */
caps->psr_power_opt_flag = power_opts;
}
EXPORT_IF_KUNIT(amdgpu_dm_psr_fill_caps);
/*
* amdgpu_dm_set_psr_caps() - set link psr capabilities
* @link: link
* @aconnector: amdgpu_dm_connector
*/
bool amdgpu_dm_set_psr_caps(struct dc_link *link, struct amdgpu_dm_connector *aconnector)
{
struct dc *dc;
unsigned int panel_inst = 0;
if (!link || !aconnector)
return false;
dc = link->ctx->dc;
/* Reset psr version first */
link->psr_settings.psr_version = DC_PSR_VERSION_UNSUPPORTED;
if (!dc->caps.dmub_caps.psr)
return false;
if (!(link->connector_signal & SIGNAL_TYPE_EDP))
return false;
if (link->type == dc_connection_none)
return false;
if (link->dpcd_caps.psr_info.psr_version == 0)
return false;
/*disable allow psr/psrsu/replay on eDP1*/
if (dc_get_edp_link_panel_inst(dc, link, &panel_inst) && panel_inst == 1)
return false;
if (link_supports_psrsu(link))
link->psr_settings.psr_version = DC_PSR_VERSION_SU_1;
else
link->psr_settings.psr_version = DC_PSR_VERSION_1;
amdgpu_dm_psr_fill_caps(link, &aconnector->psr_caps);
return true;
}
/*
* amdgpu_dm_psr_is_active_allowed() - check if psr is allowed on any stream
* @dm: pointer to amdgpu_display_manager
*
* Return: true if allowed
*/
bool amdgpu_dm_psr_is_active_allowed(struct amdgpu_display_manager *dm)
{
unsigned int i;
for (i = 0; i < dm->dc->current_state->stream_count; i++) {
const struct dc_link *link = dm->dc->current_state->streams[i]->link;
if (!link)
continue;
if (link->psr_settings.psr_feature_enabled && link->psr_settings.psr_allow_active)
return true;
}
return false;
}
/*
* amdgpu_dm_psr_set_event() - set or clear PSR event for stream
* @dm: pointer to amdgpu_display_manager
* @stream: pointer to dc_stream_state
* @set_event: true to set event, false to clear event
* @event: PSR event type
* @wait_for_disable: whether to wait for PSR to be disabled
*
* Return: true if successful, false otherwise
*/
bool amdgpu_dm_psr_set_event(struct amdgpu_display_manager *dm, struct dc_stream_state *stream,
bool set_event, enum psr_event event, bool wait_for_disable)
{
unsigned int psr_events;
/* Validate all required parameters */
if (!stream || !stream->link ||
!stream->link->psr_settings.psr_feature_enabled)
return false;
/* Get current psr events */
if (!mod_power_get_psr_event(dm->power_module, stream, &psr_events))
return false;
/* If all events already in desired state, return true. */
if ((psr_events & event) == (set_event ? event : 0))
return true;
return mod_power_set_psr_event(dm->power_module, stream,
set_event, event, wait_for_disable);
}
EXPORT_IF_KUNIT(amdgpu_dm_psr_set_event);
@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2021 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
*
*/
#ifndef AMDGPU_DM_AMDGPU_DM_PSR_H_
#define AMDGPU_DM_AMDGPU_DM_PSR_H_
#include "dc.h"
#include "modules/inc/mod_power.h"
struct amdgpu_display_manager;
struct amdgpu_dm_connector;
/* the number of pageflips before enabling psr */
#define AMDGPU_DM_PSR_ENTRY_DELAY 5
bool amdgpu_dm_set_psr_caps(struct dc_link *link, struct amdgpu_dm_connector *aconnector);
bool amdgpu_dm_psr_is_active_allowed(struct amdgpu_display_manager *dm);
bool amdgpu_dm_psr_set_event(struct amdgpu_display_manager *dm,
struct dc_stream_state *stream, bool set_event, enum psr_event event,
bool wait_for_disable);
#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps);
#endif
#endif /* AMDGPU_DM_AMDGPU_DM_PSR_H_ */
@@ -0,0 +1,178 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2025 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 <linux/dmi.h>
#include "amdgpu.h"
#include "amdgpu_dm.h"
struct amdgpu_dm_quirks {
bool aux_hpd_discon;
bool support_edp0_on_dp1;
};
static struct amdgpu_dm_quirks quirk_entries = {
.aux_hpd_discon = false,
.support_edp0_on_dp1 = false
};
static int edp0_on_dp1_callback(const struct dmi_system_id *id)
{
quirk_entries.support_edp0_on_dp1 = true;
return 0;
}
static int aux_hpd_discon_callback(const struct dmi_system_id *id)
{
quirk_entries.aux_hpd_discon = true;
return 0;
}
static const struct dmi_system_id dmi_quirk_table[] = {
{
.callback = aux_hpd_discon_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Precision 3660"),
},
},
{
.callback = aux_hpd_discon_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Precision 3260"),
},
},
{
.callback = aux_hpd_discon_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Precision 3460"),
},
},
{
.callback = aux_hpd_discon_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex Tower Plus 7010"),
},
},
{
.callback = aux_hpd_discon_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex Tower 7010"),
},
},
{
.callback = aux_hpd_discon_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex SFF Plus 7010"),
},
},
{
.callback = aux_hpd_discon_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex SFF 7010"),
},
},
{
.callback = aux_hpd_discon_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex Micro Plus 7010"),
},
},
{
.callback = aux_hpd_discon_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex Micro 7010"),
},
},
{
.callback = edp0_on_dp1_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP Elite mt645 G8 Mobile Thin Client"),
},
},
{
.callback = edp0_on_dp1_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP EliteBook 645 14 inch G11 Notebook PC"),
},
},
{
.callback = edp0_on_dp1_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP EliteBook 665 16 inch G11 Notebook PC"),
},
},
{
.callback = edp0_on_dp1_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook 445 14 inch G11 Notebook PC"),
},
},
{
.callback = edp0_on_dp1_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook 465 16 inch G11 Notebook PC"),
},
},
{}
/* TODO: refactor this from a fixed table to a dynamic option */
};
void retrieve_dmi_info(struct amdgpu_display_manager *dm)
{
struct drm_device *dev = dm->ddev;
int dmi_id;
dm->aux_hpd_discon_quirk = false;
dm->edp0_on_dp1_quirk = false;
dmi_id = dmi_check_system(dmi_quirk_table);
if (!dmi_id)
return;
if (quirk_entries.aux_hpd_discon) {
dm->aux_hpd_discon_quirk = true;
drm_info(dev, "aux_hpd_discon_quirk attached\n");
}
if (quirk_entries.support_edp0_on_dp1) {
dm->edp0_on_dp1_quirk = true;
drm_info(dev, "support_edp0_on_dp1 attached\n");
}
}
@@ -0,0 +1,210 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2023 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_dm_replay.h"
#include "dc_dmub_srv.h"
#include "dc.h"
#include "amdgpu_dm.h"
#include "modules/power/power_helpers.h"
#include "dmub/inc/dmub_cmd.h"
#include "dc/inc/link_service.h"
#include "amdgpu_dm_kunit_helpers.h"
/*
* amdgpu_dm_link_supports_replay() - check if the link supports replay
* @link: link
* @aconnector: aconnector
*
*/
bool amdgpu_dm_link_supports_replay(struct dc_link *link, struct amdgpu_dm_connector *aconnector)
{
struct dm_connector_state *state = to_dm_connector_state(aconnector->base.state);
struct dpcd_caps *dpcd_caps = &link->dpcd_caps;
struct adaptive_sync_caps *as_caps = &link->dpcd_caps.adaptive_sync_caps;
if (!state->freesync_capable)
return false;
if (!aconnector->vsdb_info.replay_mode)
return false;
// Check the eDP version
if (dpcd_caps->edp_rev < EDP_REVISION_13)
return false;
if (!dpcd_caps->alpm_caps.bits.AUX_WAKE_ALPM_CAP)
return false;
// Check adaptive sync support cap
if (!as_caps->dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT)
return false;
// Sink shall populate line deviation information
if (dpcd_caps->pr_info.pixel_deviation_per_line == 0 ||
dpcd_caps->pr_info.max_deviation_line == 0)
return false;
return true;
}
EXPORT_IF_KUNIT(amdgpu_dm_link_supports_replay);
/*
* amdgpu_dm_set_replay_caps() - setup Replay capabilities
* @link: link
* @aconnector: aconnector
*
*/
bool amdgpu_dm_set_replay_caps(struct dc_link *link, struct amdgpu_dm_connector *aconnector)
{
struct replay_config pr_config = { 0 };
union replay_debug_flags *debug_flags = NULL;
struct dc *dc = link->ctx->dc;
// If Replay is already set to support, return true to skip checks
if (link->replay_settings.config.replay_supported)
return true;
if (!dc_is_embedded_signal(link->connector_signal))
return false;
if (link->panel_config.psr.disallow_replay)
return false;
if (!amdgpu_dm_link_supports_replay(link, aconnector))
return false;
if (!dc->ctx->dmub_srv || !dc->ctx->dmub_srv->dmub ||
!dc->ctx->dmub_srv->dmub->feature_caps.replay_supported)
return false;
/* Mark Replay is supported in link and update related attributes
* This flag presents DPCD caps & amd_vsdb caps satisfy replay requirement.
*/
pr_config.replay_cap_support = true;
// Mark Replay is supported in pr_config
pr_config.replay_supported = true;
pr_config.replay_enable_option = pr_enable_option_general_ui |
pr_enable_option_static_screen |
pr_enable_option_static_screen_coasting;
pr_config.replay_power_opt_supported = replay_power_opt_smu_opt_static_screen |
replay_power_opt_z10_static_screen;
pr_config.replay_smu_opt_supported = false;
pr_config.replay_support_fast_resync_in_ultra_sleep_mode =
aconnector->max_vfreq >= 2 * aconnector->min_vfreq;
pr_config.force_disable_desync_error_check = false;
debug_flags = (union replay_debug_flags *)&pr_config.debug_flags;
debug_flags->u32All = 0;
debug_flags->bitfields.visual_confirm =
link->ctx->dc->debug.visual_confirm == VISUAL_CONFIRM_REPLAY;
debug_flags->bitfields.skip_crtc_disabled = dc->debug.replay_skip_crtc_disabled;
init_replay_config(link, &pr_config);
return true;
}
/*
* amdgpu_dm_link_setup_replay() - config replay settings
* @stream: pointer to dc_stream_state structure
* @vrr_params: pointer to mod_vrr_params structure containing VRR parameters
*
* config replay link settings including coasting vtotal calculations.
*
* Return: true if successful, false if any parameter is invalid or replay not supported
*/
bool amdgpu_dm_link_setup_replay(struct dc_stream_state *stream,
struct mod_vrr_params *vrr_params)
{
struct dc_link *link;
unsigned int static_coasting_vtotal;
unsigned int nom_coasting_vtotal;
if (!stream || !stream->link || !vrr_params)
return false;
link = stream->link;
if (!link->replay_settings.config.replay_supported)
return false;
if (link->replay_settings.replay_feature_enabled)
return true;
calculate_replay_link_off_frame_count(link, stream->timing.v_total,
stream->timing.h_total);
nom_coasting_vtotal = stream->timing.v_total;
static_coasting_vtotal = mod_freesync_calc_v_total_from_refresh(stream,
vrr_params->min_refresh_in_uhz);
set_replay_coasting_vtotal(link, PR_COASTING_TYPE_NOM,
nom_coasting_vtotal);
set_replay_coasting_vtotal(link, PR_COASTING_TYPE_STATIC,
static_coasting_vtotal);
return true;
}
/*
* amdgpu_dm_replay_set_event() - set or clear replay event for a stream
* @dm: pointer to amdgpu_display_manager
* @stream: pointer to dc_stream_state
* @set_event: true to set event, false to clear event
* @event: replay event type to set or clear
* @wait_for_disable: whether to wait for replay to be disabled before returning
*
* This function sets or clears a specific replay event for the given stream.
* It temporarily disables idle optimizations during the operation to ensure
* hardware access is available.
*
* Return: true if successful, false if any parameter is invalid or operation fails
*/
bool amdgpu_dm_replay_set_event(struct amdgpu_display_manager *dm,
struct dc_stream_state *stream,
bool set_event,
enum replay_event event,
bool wait_for_disable)
{
unsigned int replay_events;
/* Validate all required parameters */
if (!stream || !stream->link ||
!stream->link->replay_settings.replay_feature_enabled)
return false;
/* Get current replay events */
if (!mod_power_get_replay_event(dm->power_module, stream, &replay_events))
return false;
/* If all events already in desired state, return true. */
if ((replay_events & event) == (set_event ? event : 0))
return true;
return mod_power_set_replay_event(dm->power_module, stream,
set_event, event, wait_for_disable);
}
@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2021 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
*
*/
#ifndef AMDGPU_DM_AMDGPU_DM_REPLAY_H_
#define AMDGPU_DM_AMDGPU_DM_REPLAY_H_
#include "amdgpu.h"
#include "dc.h"
#include "modules/inc/mod_power.h"
bool amdgpu_dm_link_supports_replay(struct dc_link *link,
struct amdgpu_dm_connector *aconnector);
bool amdgpu_dm_set_replay_caps(struct dc_link *link,
struct amdgpu_dm_connector *aconnector);
bool amdgpu_dm_link_setup_replay(struct dc_stream_state *stream,
struct mod_vrr_params *vrr_params);
bool amdgpu_dm_replay_set_event(struct amdgpu_display_manager *dm,
struct dc_stream_state *stream, bool set_event,
enum replay_event event, bool wait_for_disable);
#endif /* AMDGPU_DM_AMDGPU_DM_REPLAY_H_ */
@@ -0,0 +1,91 @@
//SPDX-License-Identifier: MIT
/*
* Copyright 2015 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 <linux/string.h>
#include <linux/acpi.h>
#include <drm/drm_probe_helper.h>
#include <drm/amdgpu_drm.h>
#include "dm_services.h"
#include "amdgpu.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_irq.h"
#include "amdgpu_pm.h"
#include "amdgpu_dm_trace.h"
unsigned long long
dm_get_elapse_time_in_ns(struct dc_context *ctx,
unsigned long long current_time_stamp,
unsigned long long last_time_stamp)
{
return current_time_stamp - last_time_stamp;
}
void dm_perf_trace_timestamp(const char *func_name, unsigned int line, struct dc_context *ctx)
{
trace_amdgpu_dc_performance(ctx->perf_trace->read_count,
ctx->perf_trace->write_count,
&ctx->perf_trace->last_entry_read,
&ctx->perf_trace->last_entry_write,
func_name, line);
}
void dm_trace_smu_enter(uint32_t msg_id, uint32_t param_in, unsigned int delay, struct dc_context *ctx)
{
}
void dm_trace_smu_exit(bool success, uint32_t response, struct dc_context *ctx)
{
}
/**** power component interfaces ****/
bool dm_query_extended_brightness_caps(struct dc_context *ctx,
enum dm_acpi_display_type display, struct dm_acpi_atif_backlight_caps *pCaps)
{
struct amdgpu_device *adev;
struct amdgpu_display_manager *dm;
int bl_index = (display == AcpiDisplayType_LCD1) ? 0 : 1;
if (!ctx || !pCaps || !ctx->driver_context)
return false;
adev = (struct amdgpu_device *)ctx->driver_context;
dm = &adev->dm;
amdgpu_dm_update_backlight_caps(dm, bl_index);
pCaps->num_data_points = dm->backlight_caps[bl_index].data_points;
pCaps->max_input_signal = dm->backlight_caps[bl_index].max_input_signal;
pCaps->min_input_signal = dm->backlight_caps[bl_index].min_input_signal;
pCaps->ac_level_percentage = dm->backlight_caps[bl_index].ac_level;
pCaps->dc_level_percentage = dm->backlight_caps[bl_index].dc_level;
if (pCaps->num_data_points > 0)
memcpy(pCaps->data_points, dm->backlight_caps[bl_index].luminance_data,
sizeof(struct dm_bl_data_point) * pCaps->num_data_points);
return true;
}
@@ -0,0 +1,824 @@
//SPDX-License-Identifier: MIT
/*
* Copyright 2018 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
*
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM amdgpu_dm
#if !defined(_AMDGPU_DM_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
#define _AMDGPU_DM_TRACE_H_
#include <linux/tracepoint.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_plane.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_encoder.h>
#include <drm/drm_atomic.h>
#include "dc/inc/hw/optc.h"
#include "dc/inc/core_types.h"
DECLARE_EVENT_CLASS(amdgpu_dc_reg_template,
TP_PROTO(unsigned long *count, uint32_t reg, uint32_t value),
TP_ARGS(count, reg, value),
TP_STRUCT__entry(
__field(uint32_t, reg)
__field(uint32_t, value)
),
TP_fast_assign(
__entry->reg = reg;
__entry->value = value;
*count = *count + 1;
),
TP_printk("reg=0x%08lx, value=0x%08lx",
(unsigned long)__entry->reg,
(unsigned long)__entry->value)
);
DEFINE_EVENT(amdgpu_dc_reg_template, amdgpu_dc_rreg,
TP_PROTO(unsigned long *count, uint32_t reg, uint32_t value),
TP_ARGS(count, reg, value));
DEFINE_EVENT(amdgpu_dc_reg_template, amdgpu_dc_wreg,
TP_PROTO(unsigned long *count, uint32_t reg, uint32_t value),
TP_ARGS(count, reg, value));
TRACE_EVENT(amdgpu_dc_performance,
TP_PROTO(unsigned long read_count, unsigned long write_count,
unsigned long *last_read, unsigned long *last_write,
const char *func, unsigned int line),
TP_ARGS(read_count, write_count, last_read, last_write, func, line),
TP_STRUCT__entry(
__field(uint32_t, reads)
__field(uint32_t, writes)
__field(uint32_t, read_delta)
__field(uint32_t, write_delta)
__string(func, func)
__field(uint32_t, line)
),
TP_fast_assign(
__entry->reads = read_count;
__entry->writes = write_count;
__entry->read_delta = read_count - *last_read;
__entry->write_delta = write_count - *last_write;
__assign_str(func);
__entry->line = line;
*last_read = read_count;
*last_write = write_count;
),
TP_printk("%s:%d reads=%08ld (%08ld total), writes=%08ld (%08ld total)",
__get_str(func), __entry->line,
(unsigned long)__entry->read_delta,
(unsigned long)__entry->reads,
(unsigned long)__entry->write_delta,
(unsigned long)__entry->writes)
);
TRACE_EVENT(amdgpu_dm_connector_atomic_check,
TP_PROTO(const struct drm_connector_state *state),
TP_ARGS(state),
TP_STRUCT__entry(
__field(uint32_t, conn_id)
__field(const struct drm_connector_state *, conn_state)
__field(const struct drm_atomic_commit *, state)
__field(const struct drm_crtc_commit *, commit)
__field(uint32_t, crtc_id)
__field(uint32_t, best_encoder_id)
__field(enum drm_link_status, link_status)
__field(bool, self_refresh_aware)
__field(enum hdmi_picture_aspect, picture_aspect_ratio)
__field(unsigned int, content_type)
__field(unsigned int, hdcp_content_type)
__field(unsigned int, content_protection)
__field(unsigned int, scaling_mode)
__field(u32, colorspace)
__field(u8, max_requested_bpc)
__field(u8, max_bpc)
),
TP_fast_assign(
__entry->conn_id = state->connector->base.id;
__entry->conn_state = state;
__entry->state = state->state;
__entry->commit = state->commit;
__entry->crtc_id = state->crtc ? state->crtc->base.id : 0;
__entry->best_encoder_id = state->best_encoder ?
state->best_encoder->base.id : 0;
__entry->link_status = state->link_status;
__entry->self_refresh_aware = state->self_refresh_aware;
__entry->picture_aspect_ratio = state->picture_aspect_ratio;
__entry->content_type = state->content_type;
__entry->hdcp_content_type = state->hdcp_content_type;
__entry->content_protection = state->content_protection;
__entry->scaling_mode = state->scaling_mode;
__entry->colorspace = state->colorspace;
__entry->max_requested_bpc = state->max_requested_bpc;
__entry->max_bpc = state->max_bpc;
),
TP_printk("conn_id=%u conn_state=%p state=%p commit=%p crtc_id=%u "
"best_encoder_id=%u link_status=%d self_refresh_aware=%d "
"picture_aspect_ratio=%d content_type=%u "
"hdcp_content_type=%u content_protection=%u scaling_mode=%u "
"colorspace=%u max_requested_bpc=%u max_bpc=%u",
__entry->conn_id, __entry->conn_state, __entry->state,
__entry->commit, __entry->crtc_id, __entry->best_encoder_id,
__entry->link_status, __entry->self_refresh_aware,
__entry->picture_aspect_ratio, __entry->content_type,
__entry->hdcp_content_type, __entry->content_protection,
__entry->scaling_mode, __entry->colorspace,
__entry->max_requested_bpc, __entry->max_bpc)
);
TRACE_EVENT(amdgpu_dm_crtc_atomic_check,
TP_PROTO(const struct drm_crtc_state *state),
TP_ARGS(state),
TP_STRUCT__entry(
__field(const struct drm_atomic_commit *, state)
__field(const struct drm_crtc_state *, crtc_state)
__field(const struct drm_crtc_commit *, commit)
__field(uint32_t, crtc_id)
__field(bool, enable)
__field(bool, active)
__field(bool, planes_changed)
__field(bool, mode_changed)
__field(bool, active_changed)
__field(bool, connectors_changed)
__field(bool, zpos_changed)
__field(bool, color_mgmt_changed)
__field(bool, no_vblank)
__field(bool, async_flip)
__field(bool, vrr_enabled)
__field(bool, self_refresh_active)
__field(u32, plane_mask)
__field(u32, connector_mask)
__field(u32, encoder_mask)
),
TP_fast_assign(
__entry->state = state->state;
__entry->crtc_state = state;
__entry->crtc_id = state->crtc->base.id;
__entry->commit = state->commit;
__entry->enable = state->enable;
__entry->active = state->active;
__entry->planes_changed = state->planes_changed;
__entry->mode_changed = state->mode_changed;
__entry->active_changed = state->active_changed;
__entry->connectors_changed = state->connectors_changed;
__entry->zpos_changed = state->zpos_changed;
__entry->color_mgmt_changed = state->color_mgmt_changed;
__entry->no_vblank = state->no_vblank;
__entry->async_flip = state->async_flip;
__entry->vrr_enabled = state->vrr_enabled;
__entry->self_refresh_active = state->self_refresh_active;
__entry->plane_mask = state->plane_mask;
__entry->connector_mask = state->connector_mask;
__entry->encoder_mask = state->encoder_mask;
),
TP_printk("crtc_id=%u crtc_state=%p state=%p commit=%p changed("
"planes=%d mode=%d active=%d conn=%d zpos=%d color_mgmt=%d) "
"state(enable=%d active=%d async_flip=%d vrr_enabled=%d "
"self_refresh_active=%d no_vblank=%d) mask(plane=%x conn=%x "
"enc=%x)",
__entry->crtc_id, __entry->crtc_state, __entry->state,
__entry->commit, __entry->planes_changed,
__entry->mode_changed, __entry->active_changed,
__entry->connectors_changed, __entry->zpos_changed,
__entry->color_mgmt_changed, __entry->enable, __entry->active,
__entry->async_flip, __entry->vrr_enabled,
__entry->self_refresh_active, __entry->no_vblank,
__entry->plane_mask, __entry->connector_mask,
__entry->encoder_mask)
);
DECLARE_EVENT_CLASS(amdgpu_dm_plane_state_template,
TP_PROTO(const struct drm_plane_state *state),
TP_ARGS(state),
TP_STRUCT__entry(
__field(uint32_t, plane_id)
__field(enum drm_plane_type, plane_type)
__field(const struct drm_plane_state *, plane_state)
__field(const struct drm_atomic_commit *, state)
__field(uint32_t, crtc_id)
__field(uint32_t, fb_id)
__field(uint32_t, fb_format)
__field(uint8_t, fb_planes)
__field(uint64_t, fb_modifier)
__field(const struct dma_fence *, fence)
__field(int32_t, crtc_x)
__field(int32_t, crtc_y)
__field(uint32_t, crtc_w)
__field(uint32_t, crtc_h)
__field(uint32_t, src_x)
__field(uint32_t, src_y)
__field(uint32_t, src_w)
__field(uint32_t, src_h)
__field(u32, alpha)
__field(uint32_t, pixel_blend_mode)
__field(unsigned int, rotation)
__field(unsigned int, zpos)
__field(unsigned int, normalized_zpos)
__field(enum drm_color_encoding, color_encoding)
__field(enum drm_color_range, color_range)
__field(bool, visible)
),
TP_fast_assign(
__entry->plane_id = state->plane->base.id;
__entry->plane_type = state->plane->type;
__entry->plane_state = state;
__entry->state = state->state;
__entry->crtc_id = state->crtc ? state->crtc->base.id : 0;
__entry->fb_id = state->fb ? state->fb->base.id : 0;
__entry->fb_format = state->fb ? state->fb->format->format : 0;
__entry->fb_planes = state->fb ? state->fb->format->num_planes : 0;
__entry->fb_modifier = state->fb ? state->fb->modifier : 0;
__entry->fence = state->fence;
__entry->crtc_x = state->crtc_x;
__entry->crtc_y = state->crtc_y;
__entry->crtc_w = state->crtc_w;
__entry->crtc_h = state->crtc_h;
__entry->src_x = state->src_x >> 16;
__entry->src_y = state->src_y >> 16;
__entry->src_w = state->src_w >> 16;
__entry->src_h = state->src_h >> 16;
__entry->alpha = state->alpha;
__entry->pixel_blend_mode = state->pixel_blend_mode;
__entry->rotation = state->rotation;
__entry->zpos = state->zpos;
__entry->normalized_zpos = state->normalized_zpos;
__entry->color_encoding = state->color_encoding;
__entry->color_range = state->color_range;
__entry->visible = state->visible;
),
TP_printk("plane_id=%u plane_type=%d plane_state=%p state=%p "
"crtc_id=%u fb(id=%u fmt=%c%c%c%c planes=%u mod=%llu) "
"fence=%p crtc_x=%d crtc_y=%d crtc_w=%u crtc_h=%u "
"src_x=%u src_y=%u src_w=%u src_h=%u alpha=%u "
"pixel_blend_mode=%u rotation=%u zpos=%u "
"normalized_zpos=%u color_encoding=%d color_range=%d "
"visible=%d",
__entry->plane_id, __entry->plane_type, __entry->plane_state,
__entry->state, __entry->crtc_id, __entry->fb_id,
(__entry->fb_format & 0xff) ? (__entry->fb_format & 0xff) : 'N',
((__entry->fb_format >> 8) & 0xff) ? ((__entry->fb_format >> 8) & 0xff) : 'O',
((__entry->fb_format >> 16) & 0xff) ? ((__entry->fb_format >> 16) & 0xff) : 'N',
((__entry->fb_format >> 24) & 0x7f) ? ((__entry->fb_format >> 24) & 0x7f) : 'E',
__entry->fb_planes,
__entry->fb_modifier, __entry->fence, __entry->crtc_x,
__entry->crtc_y, __entry->crtc_w, __entry->crtc_h,
__entry->src_x, __entry->src_y, __entry->src_w, __entry->src_h,
__entry->alpha, __entry->pixel_blend_mode, __entry->rotation,
__entry->zpos, __entry->normalized_zpos,
__entry->color_encoding, __entry->color_range,
__entry->visible)
);
DEFINE_EVENT(amdgpu_dm_plane_state_template, amdgpu_dm_plane_atomic_check,
TP_PROTO(const struct drm_plane_state *state),
TP_ARGS(state));
DEFINE_EVENT(amdgpu_dm_plane_state_template, amdgpu_dm_atomic_update_cursor,
TP_PROTO(const struct drm_plane_state *state),
TP_ARGS(state));
TRACE_EVENT(amdgpu_dm_atomic_state_template,
TP_PROTO(const struct drm_atomic_commit *state),
TP_ARGS(state),
TP_STRUCT__entry(
__field(const struct drm_atomic_commit *, state)
__field(bool, allow_modeset)
__field(bool, legacy_cursor_update)
__field(bool, async_update)
__field(bool, duplicated)
__field(int, num_connector)
__field(int, num_private_objs)
),
TP_fast_assign(
__entry->state = state;
__entry->allow_modeset = state->allow_modeset;
__entry->legacy_cursor_update = state->legacy_cursor_update;
__entry->async_update = state->async_update;
__entry->duplicated = state->duplicated;
__entry->num_connector = state->num_connector;
__entry->num_private_objs = state->num_private_objs;
),
TP_printk("state=%p allow_modeset=%d legacy_cursor_update=%d "
"async_update=%d duplicated=%d num_connector=%d "
"num_private_objs=%d",
__entry->state, __entry->allow_modeset, __entry->legacy_cursor_update,
__entry->async_update, __entry->duplicated, __entry->num_connector,
__entry->num_private_objs)
);
DEFINE_EVENT(amdgpu_dm_atomic_state_template, amdgpu_dm_atomic_commit_tail_begin,
TP_PROTO(const struct drm_atomic_commit *state),
TP_ARGS(state));
DEFINE_EVENT(amdgpu_dm_atomic_state_template, amdgpu_dm_atomic_commit_tail_finish,
TP_PROTO(const struct drm_atomic_commit *state),
TP_ARGS(state));
DEFINE_EVENT(amdgpu_dm_atomic_state_template, amdgpu_dm_atomic_check_begin,
TP_PROTO(const struct drm_atomic_commit *state),
TP_ARGS(state));
TRACE_EVENT(amdgpu_dm_atomic_check_finish,
TP_PROTO(const struct drm_atomic_commit *state, int res),
TP_ARGS(state, res),
TP_STRUCT__entry(
__field(const struct drm_atomic_commit *, state)
__field(int, res)
__field(bool, async_update)
__field(bool, allow_modeset)
),
TP_fast_assign(
__entry->state = state;
__entry->res = res;
__entry->async_update = state->async_update;
__entry->allow_modeset = state->allow_modeset;
),
TP_printk("state=%p res=%d async_update=%d allow_modeset=%d",
__entry->state, __entry->res,
__entry->async_update, __entry->allow_modeset)
);
TRACE_EVENT(amdgpu_dm_dc_pipe_state,
TP_PROTO(int pipe_idx, const struct dc_plane_state *plane_state,
const struct dc_stream_state *stream,
const struct plane_resource *plane_res,
int update_flags),
TP_ARGS(pipe_idx, plane_state, stream, plane_res, update_flags),
TP_STRUCT__entry(
__field(int, pipe_idx)
__field(const void *, stream)
__field(int, stream_w)
__field(int, stream_h)
__field(int, dst_x)
__field(int, dst_y)
__field(int, dst_w)
__field(int, dst_h)
__field(int, src_x)
__field(int, src_y)
__field(int, src_w)
__field(int, src_h)
__field(int, clip_x)
__field(int, clip_y)
__field(int, clip_w)
__field(int, clip_h)
__field(int, recout_x)
__field(int, recout_y)
__field(int, recout_w)
__field(int, recout_h)
__field(int, viewport_x)
__field(int, viewport_y)
__field(int, viewport_w)
__field(int, viewport_h)
__field(int, flip_immediate)
__field(int, surface_pitch)
__field(int, format)
__field(int, swizzle)
__field(unsigned int, update_flags)
),
TP_fast_assign(
__entry->pipe_idx = pipe_idx;
__entry->stream = stream;
__entry->stream_w = stream->timing.h_addressable;
__entry->stream_h = stream->timing.v_addressable;
__entry->dst_x = plane_state->dst_rect.x;
__entry->dst_y = plane_state->dst_rect.y;
__entry->dst_w = plane_state->dst_rect.width;
__entry->dst_h = plane_state->dst_rect.height;
__entry->src_x = plane_state->src_rect.x;
__entry->src_y = plane_state->src_rect.y;
__entry->src_w = plane_state->src_rect.width;
__entry->src_h = plane_state->src_rect.height;
__entry->clip_x = plane_state->clip_rect.x;
__entry->clip_y = plane_state->clip_rect.y;
__entry->clip_w = plane_state->clip_rect.width;
__entry->clip_h = plane_state->clip_rect.height;
__entry->recout_x = plane_res->scl_data.recout.x;
__entry->recout_y = plane_res->scl_data.recout.y;
__entry->recout_w = plane_res->scl_data.recout.width;
__entry->recout_h = plane_res->scl_data.recout.height;
__entry->viewport_x = plane_res->scl_data.viewport.x;
__entry->viewport_y = plane_res->scl_data.viewport.y;
__entry->viewport_w = plane_res->scl_data.viewport.width;
__entry->viewport_h = plane_res->scl_data.viewport.height;
__entry->flip_immediate = plane_state->flip_immediate;
__entry->surface_pitch = plane_state->plane_size.surface_pitch;
__entry->format = plane_state->format;
__entry->swizzle = plane_state->tiling_info.gfx9.swizzle;
__entry->update_flags = update_flags;
),
TP_printk("pipe_idx=%d stream=%p rct(%d,%d) dst=(%d,%d,%d,%d) "
"src=(%d,%d,%d,%d) clip=(%d,%d,%d,%d) recout=(%d,%d,%d,%d) "
"viewport=(%d,%d,%d,%d) flip_immediate=%d pitch=%d "
"format=%d swizzle=%d update_flags=%x",
__entry->pipe_idx,
__entry->stream,
__entry->stream_w,
__entry->stream_h,
__entry->dst_x,
__entry->dst_y,
__entry->dst_w,
__entry->dst_h,
__entry->src_x,
__entry->src_y,
__entry->src_w,
__entry->src_h,
__entry->clip_x,
__entry->clip_y,
__entry->clip_w,
__entry->clip_h,
__entry->recout_x,
__entry->recout_y,
__entry->recout_w,
__entry->recout_h,
__entry->viewport_x,
__entry->viewport_y,
__entry->viewport_w,
__entry->viewport_h,
__entry->flip_immediate,
__entry->surface_pitch,
__entry->format,
__entry->swizzle,
__entry->update_flags
)
);
TRACE_EVENT(amdgpu_dm_dc_clocks_state,
TP_PROTO(const struct dc_clocks *clk),
TP_ARGS(clk),
TP_STRUCT__entry(
__field(int, dispclk_khz)
__field(int, dppclk_khz)
__field(int, disp_dpp_voltage_level_khz)
__field(int, dcfclk_khz)
__field(int, socclk_khz)
__field(int, dcfclk_deep_sleep_khz)
__field(int, fclk_khz)
__field(int, phyclk_khz)
__field(int, dramclk_khz)
__field(int, p_state_change_support)
__field(int, prev_p_state_change_support)
__field(int, pwr_state)
__field(int, dtm_level)
__field(int, max_supported_dppclk_khz)
__field(int, max_supported_dispclk_khz)
__field(int, bw_dppclk_khz)
__field(int, bw_dispclk_khz)
),
TP_fast_assign(
__entry->dispclk_khz = clk->dispclk_khz;
__entry->dppclk_khz = clk->dppclk_khz;
__entry->dcfclk_khz = clk->dcfclk_khz;
__entry->socclk_khz = clk->socclk_khz;
__entry->dcfclk_deep_sleep_khz = clk->dcfclk_deep_sleep_khz;
__entry->fclk_khz = clk->fclk_khz;
__entry->phyclk_khz = clk->phyclk_khz;
__entry->dramclk_khz = clk->dramclk_khz;
__entry->p_state_change_support = clk->p_state_change_support;
__entry->prev_p_state_change_support = clk->prev_p_state_change_support;
__entry->pwr_state = clk->pwr_state;
__entry->prev_p_state_change_support = clk->prev_p_state_change_support;
__entry->dtm_level = clk->dtm_level;
__entry->max_supported_dppclk_khz = clk->max_supported_dppclk_khz;
__entry->max_supported_dispclk_khz = clk->max_supported_dispclk_khz;
__entry->bw_dppclk_khz = clk->bw_dppclk_khz;
__entry->bw_dispclk_khz = clk->bw_dispclk_khz;
),
TP_printk("dispclk_khz=%d dppclk_khz=%d disp_dpp_voltage_level_khz=%d dcfclk_khz=%d socclk_khz=%d "
"dcfclk_deep_sleep_khz=%d fclk_khz=%d phyclk_khz=%d "
"dramclk_khz=%d p_state_change_support=%d "
"prev_p_state_change_support=%d pwr_state=%d prev_p_state_change_support=%d "
"dtm_level=%d max_supported_dppclk_khz=%d max_supported_dispclk_khz=%d "
"bw_dppclk_khz=%d bw_dispclk_khz=%d ",
__entry->dispclk_khz,
__entry->dppclk_khz,
__entry->disp_dpp_voltage_level_khz,
__entry->dcfclk_khz,
__entry->socclk_khz,
__entry->dcfclk_deep_sleep_khz,
__entry->fclk_khz,
__entry->phyclk_khz,
__entry->dramclk_khz,
__entry->p_state_change_support,
__entry->prev_p_state_change_support,
__entry->pwr_state,
__entry->prev_p_state_change_support,
__entry->dtm_level,
__entry->max_supported_dppclk_khz,
__entry->max_supported_dispclk_khz,
__entry->bw_dppclk_khz,
__entry->bw_dispclk_khz
)
);
TRACE_EVENT(amdgpu_dm_dce_clocks_state,
TP_PROTO(const struct dce_bw_output *clk),
TP_ARGS(clk),
TP_STRUCT__entry(
__field(bool, cpuc_state_change_enable)
__field(bool, cpup_state_change_enable)
__field(bool, stutter_mode_enable)
__field(bool, nbp_state_change_enable)
__field(bool, all_displays_in_sync)
__field(int, sclk_khz)
__field(int, sclk_deep_sleep_khz)
__field(int, yclk_khz)
__field(int, dispclk_khz)
__field(int, blackout_recovery_time_us)
),
TP_fast_assign(
__entry->cpuc_state_change_enable = clk->cpuc_state_change_enable;
__entry->cpup_state_change_enable = clk->cpup_state_change_enable;
__entry->stutter_mode_enable = clk->stutter_mode_enable;
__entry->nbp_state_change_enable = clk->nbp_state_change_enable;
__entry->all_displays_in_sync = clk->all_displays_in_sync;
__entry->sclk_khz = clk->sclk_khz;
__entry->sclk_deep_sleep_khz = clk->sclk_deep_sleep_khz;
__entry->yclk_khz = clk->yclk_khz;
__entry->dispclk_khz = clk->dispclk_khz;
__entry->blackout_recovery_time_us = clk->blackout_recovery_time_us;
),
TP_printk("cpuc_state_change_enable=%d cpup_state_change_enable=%d stutter_mode_enable=%d "
"nbp_state_change_enable=%d all_displays_in_sync=%d sclk_khz=%d sclk_deep_sleep_khz=%d "
"yclk_khz=%d dispclk_khz=%d blackout_recovery_time_us=%d",
__entry->cpuc_state_change_enable,
__entry->cpup_state_change_enable,
__entry->stutter_mode_enable,
__entry->nbp_state_change_enable,
__entry->all_displays_in_sync,
__entry->sclk_khz,
__entry->sclk_deep_sleep_khz,
__entry->yclk_khz,
__entry->dispclk_khz,
__entry->blackout_recovery_time_us
)
);
TRACE_EVENT(amdgpu_dmub_trace_high_irq,
TP_PROTO(uint32_t trace_code, uint32_t tick_count, uint32_t param0,
uint32_t param1),
TP_ARGS(trace_code, tick_count, param0, param1),
TP_STRUCT__entry(
__field(uint32_t, trace_code)
__field(uint32_t, tick_count)
__field(uint32_t, param0)
__field(uint32_t, param1)
),
TP_fast_assign(
__entry->trace_code = trace_code;
__entry->tick_count = tick_count;
__entry->param0 = param0;
__entry->param1 = param1;
),
TP_printk("trace_code=%u tick_count=%u param0=%u param1=%u",
__entry->trace_code, __entry->tick_count,
__entry->param0, __entry->param1)
);
TRACE_EVENT(amdgpu_refresh_rate_track,
TP_PROTO(int crtc_index, ktime_t refresh_rate_ns, uint32_t refresh_rate_hz),
TP_ARGS(crtc_index, refresh_rate_ns, refresh_rate_hz),
TP_STRUCT__entry(
__field(int, crtc_index)
__field(ktime_t, refresh_rate_ns)
__field(uint32_t, refresh_rate_hz)
),
TP_fast_assign(
__entry->crtc_index = crtc_index;
__entry->refresh_rate_ns = refresh_rate_ns;
__entry->refresh_rate_hz = refresh_rate_hz;
),
TP_printk("crtc_index=%d refresh_rate=%dHz (%lld)",
__entry->crtc_index,
__entry->refresh_rate_hz,
__entry->refresh_rate_ns)
);
TRACE_EVENT(dcn_fpu,
TP_PROTO(bool begin, const char *function, const int line, const int recursion_depth),
TP_ARGS(begin, function, line, recursion_depth),
TP_STRUCT__entry(
__field(bool, begin)
__field(const char *, function)
__field(int, line)
__field(int, recursion_depth)
),
TP_fast_assign(
__entry->begin = begin;
__entry->function = function;
__entry->line = line;
__entry->recursion_depth = recursion_depth;
),
TP_printk("%s: recursion_depth: %d: %s()+%d:",
__entry->begin ? "begin" : "end",
__entry->recursion_depth,
__entry->function,
__entry->line
)
);
TRACE_EVENT(dcn_optc_lock_unlock_state,
TP_PROTO(const struct optc *optc_state, int instance, bool lock, const char *function, const int line),
TP_ARGS(optc_state, instance, lock, function, line),
TP_STRUCT__entry(
__field(const char *, function)
__field(int, instance)
__field(bool, lock)
__field(int, line)
__field(int, opp_count)
__field(int, max_h_total)
__field(int, max_v_total)
__field(int, min_h_blank)
__field(int, min_h_sync_width)
__field(int, min_v_sync_width)
__field(int, min_v_blank)
__field(int, min_v_blank_interlace)
__field(int, vstartup_start)
__field(int, vupdate_offset)
__field(int, vupdate_width)
__field(int, vready_offset)
),
TP_fast_assign(
__entry->function = function;
__entry->instance = instance;
__entry->lock = lock;
__entry->line = line;
__entry->opp_count = optc_state->opp_count;
__entry->max_h_total = optc_state->max_h_total;
__entry->max_v_total = optc_state->max_v_total;
__entry->min_h_blank = optc_state->min_h_blank;
__entry->min_h_sync_width = optc_state->min_h_sync_width;
__entry->min_v_sync_width = optc_state->min_v_sync_width;
__entry->min_v_blank = optc_state->min_v_blank;
__entry->min_v_blank_interlace = optc_state->min_v_blank_interlace;
__entry->vstartup_start = optc_state->vstartup_start;
__entry->vupdate_offset = optc_state->vupdate_offset;
__entry->vupdate_width = optc_state->vupdate_width;
__entry->vready_offset = optc_state->vupdate_offset;
),
TP_printk("%s: %s()+%d: optc_instance=%d opp_count=%d max_h_total=%d max_v_total=%d "
"min_h_blank=%d min_h_sync_width=%d min_v_sync_width=%d min_v_blank=%d "
"min_v_blank_interlace=%d vstartup_start=%d vupdate_offset=%d vupdate_width=%d "
"vready_offset=%d",
__entry->lock ? "Lock" : "Unlock",
__entry->function,
__entry->line,
__entry->instance,
__entry->opp_count,
__entry->max_h_total,
__entry->max_v_total,
__entry->min_h_blank,
__entry->min_h_sync_width,
__entry->min_v_sync_width,
__entry->min_v_blank,
__entry->min_v_blank_interlace,
__entry->vstartup_start,
__entry->vupdate_offset,
__entry->vupdate_width,
__entry->vready_offset
)
);
TRACE_EVENT(amdgpu_dm_brightness,
TP_PROTO(void *function, u32 user_brightness, u32 converted_brightness, bool aux, bool ac),
TP_ARGS(function, user_brightness, converted_brightness, aux, ac),
TP_STRUCT__entry(
__field(void *, function)
__field(u32, user_brightness)
__field(u32, converted_brightness)
__field(bool, aux)
__field(bool, ac)
),
TP_fast_assign(
__entry->function = function;
__entry->user_brightness = user_brightness;
__entry->converted_brightness = converted_brightness;
__entry->aux = aux;
__entry->ac = ac;
),
TP_printk("%ps: brightness requested=%u converted=%u aux=%s power=%s",
(void *)__entry->function,
(u32)__entry->user_brightness,
(u32)__entry->converted_brightness,
(__entry->aux) ? "true" : "false",
(__entry->ac) ? "AC" : "DC"
)
);
TRACE_EVENT(amdgpu_dm_ism_commit,
TP_PROTO(
int active_vblank_irq_count,
bool vblank_enabled,
bool allow_panel_sso
),
TP_ARGS(
active_vblank_irq_count,
vblank_enabled,
allow_panel_sso
),
TP_STRUCT__entry(
__field(int, active_vblank_irq_count)
__field(bool, vblank_enabled)
__field(bool, allow_panel_sso)
),
TP_fast_assign(
__entry->active_vblank_irq_count = active_vblank_irq_count;
__entry->vblank_enabled = vblank_enabled;
__entry->allow_panel_sso = allow_panel_sso;
),
TP_printk(
"active_vblank_irq_count=%d vblank_enabled=%d allow_panel_sso=%d",
__entry->active_vblank_irq_count,
__entry->vblank_enabled,
__entry->allow_panel_sso
)
);
TRACE_EVENT(amdgpu_dm_ism_event,
TP_PROTO(
int crtc_id,
const char *prev_state,
const char *curr_state,
const char *event
),
TP_ARGS(
crtc_id,
prev_state,
curr_state,
event
),
TP_STRUCT__entry(
__field(int, crtc_id)
__string(prev_state, prev_state)
__string(curr_state, curr_state)
__string(event, event)
),
TP_fast_assign(
__entry->crtc_id = crtc_id;
__assign_str(prev_state);
__assign_str(curr_state);
__assign_str(event);
),
TP_printk(
"[CRTC %d] %s -> %s on event %s",
__entry->crtc_id,
__get_str(prev_state),
__get_str(curr_state),
__get_str(event))
);
#endif /* _AMDGPU_DM_TRACE_H_ */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE amdgpu_dm_trace
#include <trace/define_trace.h>
@@ -0,0 +1,213 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2022 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 "dm_services_types.h"
#include "amdgpu.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_wb.h"
#include "amdgpu_display.h"
#include "dc.h"
#include <drm/drm_edid.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_modeset_helper_vtables.h>
static const u32 amdgpu_dm_wb_formats[] = {
DRM_FORMAT_XRGB2101010,
};
static int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct drm_framebuffer *fb;
const struct drm_display_mode *mode = &crtc_state->mode;
bool found = false;
uint8_t i;
if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
return 0;
fb = conn_state->writeback_job->fb;
if (fb->width != mode->hdisplay || fb->height != mode->vdisplay) {
DRM_DEBUG_KMS("Invalid framebuffer size %ux%u\n",
fb->width, fb->height);
return -EINVAL;
}
for (i = 0; i < sizeof(amdgpu_dm_wb_formats) / sizeof(u32); i++) {
if (fb->format->format == amdgpu_dm_wb_formats[i])
found = true;
}
if (!found) {
DRM_DEBUG_KMS("Invalid pixel format %p4cc\n",
&fb->format->format);
return -EINVAL;
}
return 0;
}
static int amdgpu_dm_wb_connector_get_modes(struct drm_connector *connector)
{
/* Maximum resolution supported by DWB */
return drm_add_modes_noedid(connector, 3840, 2160);
}
static int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector,
struct drm_writeback_job *job)
{
struct amdgpu_framebuffer *afb;
struct drm_gem_object *obj;
struct amdgpu_device *adev;
struct amdgpu_bo *rbo;
uint32_t domain;
int r;
if (!job->fb) {
DRM_DEBUG_KMS("No FB bound\n");
return 0;
}
afb = to_amdgpu_framebuffer(job->fb);
obj = job->fb->obj[0];
rbo = gem_to_amdgpu_bo(obj);
adev = amdgpu_ttm_adev(rbo->tbo.bdev);
r = amdgpu_bo_reserve(rbo, true);
if (r) {
drm_err(adev_to_drm(adev), "fail to reserve bo (%d)\n", r);
return r;
}
r = dma_resv_reserve_fences(rbo->tbo.base.resv, TTM_NUM_MOVE_FENCES);
if (r)
goto error_unlock;
domain = amdgpu_display_supported_domains(adev, rbo->flags);
rbo->flags |= AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS;
r = amdgpu_bo_pin(rbo, domain);
if (unlikely(r != 0)) {
if (r != -ERESTARTSYS)
DRM_ERROR("Failed to pin framebuffer with error %d\n", r);
goto error_unlock;
}
r = amdgpu_ttm_alloc_gart(&rbo->tbo);
if (unlikely(r != 0)) {
DRM_ERROR("%p bind failed\n", rbo);
goto error_unpin;
}
amdgpu_bo_unreserve(rbo);
afb->address = amdgpu_bo_gpu_offset(rbo);
amdgpu_bo_ref(rbo);
return 0;
error_unpin:
amdgpu_bo_unpin(rbo);
error_unlock:
amdgpu_bo_unreserve(rbo);
return r;
}
static void amdgpu_dm_wb_cleanup_job(struct drm_writeback_connector *connector,
struct drm_writeback_job *job)
{
struct amdgpu_bo *rbo;
int r;
if (!job->fb)
return;
rbo = gem_to_amdgpu_bo(job->fb->obj[0]);
r = amdgpu_bo_reserve(rbo, false);
if (unlikely(r)) {
DRM_ERROR("failed to reserve rbo before unpin\n");
return;
}
amdgpu_bo_unpin(rbo);
amdgpu_bo_unreserve(rbo);
amdgpu_bo_unref(&rbo);
}
static const struct drm_encoder_helper_funcs amdgpu_dm_wb_encoder_helper_funcs = {
.atomic_check = amdgpu_dm_wb_encoder_atomic_check,
};
static const struct drm_connector_funcs amdgpu_dm_wb_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = amdgpu_dm_connector_funcs_reset,
.atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static const struct drm_connector_helper_funcs amdgpu_dm_wb_conn_helper_funcs = {
.get_modes = amdgpu_dm_wb_connector_get_modes,
.prepare_writeback_job = amdgpu_dm_wb_prepare_job,
.cleanup_writeback_job = amdgpu_dm_wb_cleanup_job,
};
int amdgpu_dm_wb_connector_init(struct amdgpu_display_manager *dm,
struct amdgpu_dm_wb_connector *wbcon,
uint32_t link_index)
{
struct dc *dc = dm->dc;
struct dc_link *link = dc_get_link_at_index(dc, link_index);
int res = 0;
wbcon->link = link;
drm_connector_helper_add(&wbcon->base.base, &amdgpu_dm_wb_conn_helper_funcs);
res = drm_writeback_connector_init(&dm->adev->ddev, &wbcon->base,
&amdgpu_dm_wb_connector_funcs,
&amdgpu_dm_wb_encoder_helper_funcs,
amdgpu_dm_wb_formats,
ARRAY_SIZE(amdgpu_dm_wb_formats),
amdgpu_dm_get_encoder_crtc_mask(dm->adev));
if (res)
return res;
/*
* Some of the properties below require access to state, like bpc.
* Allocate some default initial connector state with our reset helper.
*/
if (wbcon->base.base.funcs->reset)
wbcon->base.base.funcs->reset(&wbcon->base.base);
return 0;
}
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2022 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
*
*/
#ifndef __AMDGPU_DM_WB_H__
#define __AMDGPU_DM_WB_H__
#include <drm/drm_writeback.h>
int amdgpu_dm_wb_connector_init(struct amdgpu_display_manager *dm,
struct amdgpu_dm_wb_connector *dm_wbcon,
uint32_t link_index);
#endif
@@ -0,0 +1,131 @@
// SPDX-License-Identifier: MIT
/*
* Copyright 2021 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 "dc_trace.h"
#include <linux/fpu.h>
/**
* DOC: DC FPU manipulation overview
*
* DC core uses FPU operations in multiple parts of the code, which requires a
* more specialized way to manage these areas' entrance. To fulfill this
* requirement, we created some wrapper functions that encapsulate
* kernel_fpu_begin/end to better fit our need in the display component. In
* summary, in this file, you can find functions related to FPU operation
* management.
*/
static DEFINE_PER_CPU(int, fpu_recursion_depth);
/**
* dc_assert_fp_enabled - Check if FPU protection is enabled
*
* This function tells if the code is already under FPU protection or not. A
* function that works as an API for a set of FPU operations can use this
* function for checking if the caller invoked it after DC_FP_START(). For
* example, take a look at dcn20_fpu.c file.
*/
inline void dc_assert_fp_enabled(void)
{
int depth;
depth = this_cpu_read(fpu_recursion_depth);
ASSERT(depth >= 1);
}
/**
* dc_is_fp_enabled - Check if FPU protection is enabled
*
* This function tells if the code is already under FPU protection or not. A
* function that works as an API for a set of FPU operations can use this
* function for checking if the caller invoked it after DC_FP_START(). For
* example, take a look at dcn20_fpu.c file.
*
* Similar to dc_assert_fp_enabled, but does not assert, returns status instead.
*/
inline bool dc_is_fp_enabled(void)
{
int depth;
depth = this_cpu_read(fpu_recursion_depth);
return (depth >= 1);
}
/**
* dc_fpu_begin - Enables FPU protection
* @function_name: A string containing the function name for debug purposes
* (usually __func__)
*
* @line: A line number where DC_FP_START was invoked for debug purpose
* (usually __LINE__)
*
* This function is responsible for managing the use of kernel_fpu_begin() with
* the advantage of providing an event trace for debugging.
*
* Note: Do not call this function directly; always use DC_FP_START().
*/
void dc_fpu_begin(const char *function_name, const int line)
{
int depth;
WARN_ON_ONCE(!in_task());
preempt_disable();
depth = this_cpu_inc_return(fpu_recursion_depth);
if (depth == 1) {
BUG_ON(!kernel_fpu_available());
kernel_fpu_begin();
}
TRACE_DCN_FPU(true, function_name, line, depth);
}
/**
* dc_fpu_end - Disable FPU protection
* @function_name: A string containing the function name for debug purposes
* @line: A-line number where DC_FP_END was invoked for debug purpose
*
* This function is responsible for managing the use of kernel_fpu_end() with
* the advantage of providing an event trace for debugging.
*
* Note: Do not call this function directly; always use DC_FP_END().
*/
void dc_fpu_end(const char *function_name, const int line)
{
int depth;
depth = this_cpu_dec_return(fpu_recursion_depth);
if (depth == 0) {
kernel_fpu_end();
} else {
WARN_ON_ONCE(depth < 0);
}
TRACE_DCN_FPU(false, function_name, line, depth);
preempt_enable();
}
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright 2021 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
*
*/
#ifndef __DC_FPU_H__
#define __DC_FPU_H__
void dc_assert_fp_enabled(void);
bool dc_is_fp_enabled(void);
void dc_fpu_begin(const char *function_name, const int line);
void dc_fpu_end(const char *function_name, const int line);
#ifndef _LINUX_FPU_COMPILATION_UNIT
#define DC_FP_START() dc_fpu_begin(__func__, __LINE__)
#define DC_FP_END() dc_fpu_end(__func__, __LINE__)
#ifdef CONFIG_DRM_AMD_DC_FP
#define DC_RUN_WITH_PREEMPTION_ENABLED(code) \
do { \
bool dc_fp_enabled = dc_is_fp_enabled(); \
if (dc_fp_enabled) \
DC_FP_END(); \
code; \
if (dc_fp_enabled) \
DC_FP_START(); \
} while (0)
#else
#define DC_RUN_WITH_PREEMPTION_ENABLED(code) code
#endif // !CONFIG_DRM_AMD_DC_FP
#else
#define DC_FP_START() BUILD_BUG()
#define DC_FP_END() BUILD_BUG()
#define DC_RUN_WITH_PREEMPTION_ENABLED(code) code
#endif // !_LINUX_FPU_COMPILATION_UNIT
#endif /* __DC_FPU_H__ */
@@ -0,0 +1,22 @@
CONFIG_KUNIT=y
CONFIG_PCI=y
CONFIG_DRM=y
CONFIG_DRM_AMDGPU=y
CONFIG_DRM_AMD_DC=y
CONFIG_DEBUG_FS=y
CONFIG_DRM_AMD_DC_KUNIT_TEST=y
CONFIG_FW_LOADER=y
CONFIG_DRM_KUNIT_TEST=y
CONFIG_DRM_KUNIT_TEST_HELPERS=y
CONFIG_DRM_KMS_HELPER=y
CONFIG_DRM_TTM=y
CONFIG_HWMON=y
CONFIG_I2C=y
CONFIG_POWER_SUPPLY=y
CONFIG_CRC16=y
# GCOV Coverage - see tools/testing/kunit/configs/coverage_uml.config
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
CONFIG_GCOV=y
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for amdgpu_dm KUnit tests.
ccflags-y += -I$(src)/..
ccflags-y += -I$(src)/../..
ccflags-y += -I$(src)/../../include
ccflags-y += -I$(src)/../../modules/inc
ccflags-y += -I$(src)/../../dc
ccflags-y += -I$(src)/../../../amdgpu
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o
obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,239 @@
// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* KUnit tests for amdgpu_dm_colorop.c
*
* Copyright 2026 Advanced Micro Devices, Inc.
*/
#include <kunit/test.h>
#include <drm/drm_colorop.h>
#include <drm/drm_kunit_helpers.h>
#include "amdgpu_dm_colorop.h"
/* Tests for amdgpu_dm_supported_degam_tfs */
static void dm_test_supported_degam_tfs_has_srgb_eotf(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs &
BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF));
}
static void dm_test_supported_degam_tfs_has_pq125_eotf(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs &
BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF));
}
static void dm_test_supported_degam_tfs_has_bt2020_inv_oetf(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs &
BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF));
}
static void dm_test_supported_degam_tfs_has_gamma22(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_degam_tfs &
BIT(DRM_COLOROP_1D_CURVE_GAMMA22));
}
static void dm_test_supported_degam_tfs_no_extra_bits(struct kunit *test)
{
u64 expected = BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) |
BIT(DRM_COLOROP_1D_CURVE_GAMMA22);
KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_degam_tfs, expected);
}
/* Tests for amdgpu_dm_supported_shaper_tfs */
static void dm_test_supported_shaper_tfs_has_srgb_inv_eotf(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs &
BIT(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF));
}
static void dm_test_supported_shaper_tfs_has_pq125_inv_eotf(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs &
BIT(DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF));
}
static void dm_test_supported_shaper_tfs_has_bt2020_oetf(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs &
BIT(DRM_COLOROP_1D_CURVE_BT2020_OETF));
}
static void dm_test_supported_shaper_tfs_has_gamma22_inv(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_shaper_tfs &
BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV));
}
static void dm_test_supported_shaper_tfs_no_extra_bits(struct kunit *test)
{
u64 expected = BIT(DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_PQ_125_INV_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_BT2020_OETF) |
BIT(DRM_COLOROP_1D_CURVE_GAMMA22_INV);
KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_shaper_tfs, expected);
}
/* Tests for amdgpu_dm_supported_blnd_tfs */
static void dm_test_supported_blnd_tfs_has_srgb_eotf(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs &
BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF));
}
static void dm_test_supported_blnd_tfs_has_pq125_eotf(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs &
BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF));
}
static void dm_test_supported_blnd_tfs_has_bt2020_inv_oetf(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs &
BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF));
}
static void dm_test_supported_blnd_tfs_has_gamma22(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_supported_blnd_tfs &
BIT(DRM_COLOROP_1D_CURVE_GAMMA22));
}
static void dm_test_supported_blnd_tfs_no_extra_bits(struct kunit *test)
{
u64 expected = BIT(DRM_COLOROP_1D_CURVE_SRGB_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_PQ_125_EOTF) |
BIT(DRM_COLOROP_1D_CURVE_BT2020_INV_OETF) |
BIT(DRM_COLOROP_1D_CURVE_GAMMA22);
KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_blnd_tfs, expected);
}
/* degam and blnd should support the same set of EOTF curves */
static void dm_test_degam_and_blnd_tfs_match(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, amdgpu_dm_supported_degam_tfs,
amdgpu_dm_supported_blnd_tfs);
}
/* Tests for amdgpu_dm_initialize_default_pipeline */
static void kunit_colorop_pipeline_destroy(void *drm)
{
drm_colorop_pipeline_destroy((struct drm_device *)drm);
}
/**
* dm_test_initialize_default_pipeline() - Verify amdgpu_dm_build_default_pipeline()
* produces the expected colorop chain with all ops bypassable.
* @test: KUnit test context.
*/
static void dm_test_initialize_default_pipeline(struct kunit *test)
{
static const enum drm_colorop_type expected[] = {
DRM_COLOROP_1D_CURVE, /* degam TF */
DRM_COLOROP_MULTIPLIER,
DRM_COLOROP_CTM_3X4,
DRM_COLOROP_1D_CURVE, /* shaper TF */
DRM_COLOROP_1D_LUT, /* shaper LUT */
DRM_COLOROP_3D_LUT,
DRM_COLOROP_1D_CURVE, /* blnd TF */
DRM_COLOROP_1D_LUT, /* blnd LUT */
};
struct device *dev;
struct drm_device *drm;
struct drm_plane *plane;
struct drm_prop_enum_list list = {};
struct drm_colorop *op, *first = NULL;
int i = 0;
int ret;
dev = drm_kunit_helper_alloc_device(test);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
/*
* Allocate a plain drm_device (not an amdgpu_device) — sufficient
* because amdgpu_dm_build_default_pipeline() only needs the DRM
* mode-config infrastructure, not the amdgpu device wrapper.
*/
drm = __drm_kunit_helper_alloc_drm_device(test, dev,
sizeof(*drm), 0, 0);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
plane = drm_kunit_helper_create_primary_plane(test, drm,
NULL, NULL, NULL, 0, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane);
/*
* Destroy the pipeline before the DRM device is cleaned up so
* that the colorop objects (kzalloc'd inside the function) are
* freed while the device is still valid.
*/
kunit_add_action(test, kunit_colorop_pipeline_destroy, drm);
ret = amdgpu_dm_build_default_pipeline(drm, plane, true, &list);
KUNIT_ASSERT_EQ(test, ret, 0);
kfree(list.name);
drm_for_each_colorop(op, drm) {
if (op->base.id == (uint32_t)list.type) {
first = op;
break;
}
}
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, first);
for (op = first; op; op = op->next, i++) {
KUNIT_ASSERT_LT(test, i, (int)ARRAY_SIZE(expected));
KUNIT_EXPECT_EQ(test, op->type, expected[i]);
KUNIT_EXPECT_NOT_NULL(test, op->bypass_property);
}
KUNIT_EXPECT_EQ(test, i, (int)ARRAY_SIZE(expected));
}
static struct kunit_case dm_colorop_test_cases[] = {
/* degam TFs */
KUNIT_CASE(dm_test_supported_degam_tfs_has_srgb_eotf),
KUNIT_CASE(dm_test_supported_degam_tfs_has_pq125_eotf),
KUNIT_CASE(dm_test_supported_degam_tfs_has_bt2020_inv_oetf),
KUNIT_CASE(dm_test_supported_degam_tfs_has_gamma22),
KUNIT_CASE(dm_test_supported_degam_tfs_no_extra_bits),
/* shaper TFs */
KUNIT_CASE(dm_test_supported_shaper_tfs_has_srgb_inv_eotf),
KUNIT_CASE(dm_test_supported_shaper_tfs_has_pq125_inv_eotf),
KUNIT_CASE(dm_test_supported_shaper_tfs_has_bt2020_oetf),
KUNIT_CASE(dm_test_supported_shaper_tfs_has_gamma22_inv),
KUNIT_CASE(dm_test_supported_shaper_tfs_no_extra_bits),
/* blnd TFs */
KUNIT_CASE(dm_test_supported_blnd_tfs_has_srgb_eotf),
KUNIT_CASE(dm_test_supported_blnd_tfs_has_pq125_eotf),
KUNIT_CASE(dm_test_supported_blnd_tfs_has_bt2020_inv_oetf),
KUNIT_CASE(dm_test_supported_blnd_tfs_has_gamma22),
KUNIT_CASE(dm_test_supported_blnd_tfs_no_extra_bits),
/* cross-check */
KUNIT_CASE(dm_test_degam_and_blnd_tfs_match),
/* amdgpu_dm_initialize_default_pipeline */
KUNIT_CASE(dm_test_initialize_default_pipeline),
{}
};
static struct kunit_suite dm_colorop_test_suite = {
.name = "amdgpu_dm_colorop",
.test_cases = dm_colorop_test_cases,
};
kunit_test_suite(dm_colorop_test_suite);
MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_colorop");
MODULE_AUTHOR("AMD");
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* KUnit tests for amdgpu_dm_crc.c
*
* Copyright 2026 Advanced Micro Devices, Inc.
*/
#include <kunit/test.h>
#include "amdgpu_dm_crc.h"
static void dm_test_parse_crc_source_none(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_NONE, dm_parse_crc_source("none"));
KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_NONE, dm_parse_crc_source(NULL));
}
static void dm_test_parse_crc_source_crtc(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_CRTC, dm_parse_crc_source("crtc"));
KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_CRTC, dm_parse_crc_source("auto"));
}
static void dm_test_parse_crc_source_dprx(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, dm_parse_crc_source("dprx"));
}
static void dm_test_parse_crc_source_crtc_dither(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER,
dm_parse_crc_source("crtc dither"));
}
static void dm_test_parse_crc_source_dprx_dither(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER,
dm_parse_crc_source("dprx dither"));
}
static void dm_test_parse_crc_source_invalid(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_INVALID,
dm_parse_crc_source("invalid"));
KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_INVALID,
dm_parse_crc_source("unknown"));
KUNIT_EXPECT_EQ(test, AMDGPU_DM_PIPE_CRC_SOURCE_INVALID,
dm_parse_crc_source(""));
}
static void dm_test_is_crc_source_crtc(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC));
KUNIT_EXPECT_TRUE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER));
KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_NONE));
KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX));
KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER));
KUNIT_EXPECT_FALSE(test, dm_is_crc_source_crtc(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID));
}
static void dm_test_is_crc_source_dprx(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX));
KUNIT_EXPECT_TRUE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER));
KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE));
KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC));
KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER));
KUNIT_EXPECT_FALSE(test, dm_is_crc_source_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID));
}
static void dm_test_need_crc_dither(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_NONE));
KUNIT_EXPECT_TRUE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER));
KUNIT_EXPECT_TRUE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER));
KUNIT_EXPECT_FALSE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC));
KUNIT_EXPECT_FALSE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX));
KUNIT_EXPECT_FALSE(test, dm_need_crc_dither(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID));
}
static void dm_test_is_valid_crc_source(struct kunit *test)
{
KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC));
KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX));
KUNIT_EXPECT_TRUE(test,
amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC_DITHER));
KUNIT_EXPECT_TRUE(test,
amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER));
KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_NONE));
KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_MAX));
KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID));
}
static struct kunit_case dm_crc_test_cases[] = {
KUNIT_CASE(dm_test_parse_crc_source_none),
KUNIT_CASE(dm_test_parse_crc_source_crtc),
KUNIT_CASE(dm_test_parse_crc_source_dprx),
KUNIT_CASE(dm_test_parse_crc_source_crtc_dither),
KUNIT_CASE(dm_test_parse_crc_source_dprx_dither),
KUNIT_CASE(dm_test_parse_crc_source_invalid),
KUNIT_CASE(dm_test_is_crc_source_crtc),
KUNIT_CASE(dm_test_is_crc_source_dprx),
KUNIT_CASE(dm_test_need_crc_dither),
KUNIT_CASE(dm_test_is_valid_crc_source),
{}
};
static struct kunit_suite dm_crc_test_suite = {
.name = "amdgpu_dm_crc",
.test_cases = dm_crc_test_cases,
};
kunit_test_suite(dm_crc_test_suite);
MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_crc");
MODULE_AUTHOR("AMD");
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* KUnit tests for amdgpu_dm_hdcp.c
*
* Copyright 2026 Advanced Micro Devices, Inc.
*/
#include <kunit/test.h>
#include <linux/workqueue.h>
#include "amdgpu_dm_hdcp.h"
static void dummy_work_fn(struct work_struct *work) {}
/* Tests for process_output() */
/*
* Helper: allocate and initialise a minimal hdcp_workqueue sufficient for
* process_output() testing. Only the three delayed works accessed by
* process_output() are initialised; everything else is zeroed.
*/
static struct hdcp_workqueue *alloc_test_workqueue(struct kunit *test)
{
struct hdcp_workqueue *work;
work = kunit_kzalloc(test, sizeof(*work), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, work);
INIT_DELAYED_WORK(&work->callback_dwork, dummy_work_fn);
INIT_DELAYED_WORK(&work->watchdog_timer_dwork, dummy_work_fn);
INIT_DELAYED_WORK(&work->property_validate_dwork, dummy_work_fn);
return work;
}
/*
* process_output() always schedules property_validate_dwork with delay=0,
* which queues the work item directly (bypassing the timer). Use
* work_pending() rather than delayed_work_pending() to detect this.
*/
static void dm_test_process_output_property_validate_always_scheduled(struct kunit *test)
{
struct hdcp_workqueue *work = alloc_test_workqueue(test);
/* No flags set: only property_validate_dwork should be enqueued */
process_output(work);
KUNIT_EXPECT_TRUE(test, work_pending(&work->property_validate_dwork.work));
KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork));
KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork));
cancel_delayed_work_sync(&work->property_validate_dwork);
}
/*
* output.callback_needed=true must schedule callback_dwork.
*/
static void dm_test_process_output_callback_needed(struct kunit *test)
{
struct hdcp_workqueue *work = alloc_test_workqueue(test);
work->output.callback_needed = true;
work->output.callback_delay = 500;
process_output(work);
KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->callback_dwork));
cancel_delayed_work_sync(&work->callback_dwork);
cancel_delayed_work_sync(&work->property_validate_dwork);
}
/*
* output.callback_stop=true must cancel a previously scheduled callback_dwork.
*/
static void dm_test_process_output_callback_stop(struct kunit *test)
{
struct hdcp_workqueue *work = alloc_test_workqueue(test);
/* Pre-schedule callback_dwork with a long delay so it won't fire. */
schedule_delayed_work(&work->callback_dwork, msecs_to_jiffies(10000));
KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->callback_dwork));
work->output.callback_stop = true;
process_output(work);
KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->callback_dwork));
cancel_delayed_work_sync(&work->property_validate_dwork);
}
/*
* output.watchdog_timer_needed=true must schedule watchdog_timer_dwork.
*/
static void dm_test_process_output_watchdog_needed(struct kunit *test)
{
struct hdcp_workqueue *work = alloc_test_workqueue(test);
work->output.watchdog_timer_needed = true;
work->output.watchdog_timer_delay = 1000;
process_output(work);
KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork));
cancel_delayed_work_sync(&work->watchdog_timer_dwork);
cancel_delayed_work_sync(&work->property_validate_dwork);
}
/*
* output.watchdog_timer_stop=true must cancel a previously scheduled
* watchdog_timer_dwork.
*/
static void dm_test_process_output_watchdog_stop(struct kunit *test)
{
struct hdcp_workqueue *work = alloc_test_workqueue(test);
/* Pre-schedule watchdog_timer_dwork with a long delay. */
schedule_delayed_work(&work->watchdog_timer_dwork, msecs_to_jiffies(10000));
KUNIT_ASSERT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork));
work->output.watchdog_timer_stop = true;
process_output(work);
KUNIT_EXPECT_FALSE(test, delayed_work_pending(&work->watchdog_timer_dwork));
cancel_delayed_work_sync(&work->property_validate_dwork);
}
/*
* Both callback_needed and watchdog_timer_needed set: both dworks are
* scheduled independently.
*/
static void dm_test_process_output_callback_and_watchdog_needed(struct kunit *test)
{
struct hdcp_workqueue *work = alloc_test_workqueue(test);
work->output.callback_needed = true;
work->output.callback_delay = 200;
work->output.watchdog_timer_needed = true;
work->output.watchdog_timer_delay = 800;
process_output(work);
KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->callback_dwork));
KUNIT_EXPECT_TRUE(test, delayed_work_pending(&work->watchdog_timer_dwork));
cancel_delayed_work_sync(&work->callback_dwork);
cancel_delayed_work_sync(&work->watchdog_timer_dwork);
cancel_delayed_work_sync(&work->property_validate_dwork);
}
/* End of tests for process_output() */
static struct kunit_case dm_hdcp_test_cases[] = {
KUNIT_CASE(dm_test_process_output_property_validate_always_scheduled),
KUNIT_CASE(dm_test_process_output_callback_needed),
KUNIT_CASE(dm_test_process_output_callback_stop),
KUNIT_CASE(dm_test_process_output_watchdog_needed),
KUNIT_CASE(dm_test_process_output_watchdog_stop),
KUNIT_CASE(dm_test_process_output_callback_and_watchdog_needed),
{}
};
static struct kunit_suite dm_hdcp_test_suite = {
.name = "amdgpu_dm_hdcp",
.test_cases = dm_hdcp_test_cases,
};
kunit_test_suite(dm_hdcp_test_suite);
MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_hdcp");
MODULE_AUTHOR("AMD");
@@ -0,0 +1,938 @@
// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* KUnit tests for amdgpu_dm_ism.c
*
* Copyright 2026 Advanced Micro Devices, Inc.
*/
#include <kunit/test.h>
#include "dc.h"
#include "amdgpu_dm_ism.h"
/*
* Helper: allocate and zero-initialise a dc_stream_state for timing tests.
* Only the timing sub-struct is accessed by the functions under test.
*/
static struct dc_stream_state *alloc_test_stream(struct kunit *test)
{
struct dc_stream_state *stream;
stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, stream);
return stream;
}
/*
* Helper: allocate and zero-initialise an ISM instance.
*/
static struct amdgpu_dm_ism *alloc_test_ism(struct kunit *test)
{
struct amdgpu_dm_ism *ism;
ism = kunit_kzalloc(test, sizeof(*ism), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ism);
return ism;
}
/* ===== Tests for dm_ism_next_state — FULL_POWER_RUNNING transitions ===== */
static void dm_test_ism_next_state_running_enter_idle(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING,
DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_WAITING);
}
static void dm_test_ism_next_state_running_begin_cursor(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_BUSY);
}
static void dm_test_ism_next_state_running_invalid(struct kunit *test)
{
enum amdgpu_dm_ism_state next = DM_ISM_NUM_STATES;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_RUNNING,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next);
KUNIT_EXPECT_FALSE(test, ok);
/* next should remain untouched on invalid transition */
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_NUM_STATES);
}
/* ===== Tests for dm_ism_next_state — FULL_POWER_BUSY transitions ===== */
static void dm_test_ism_next_state_busy_enter_idle(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_BUSY,
DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY);
}
static void dm_test_ism_next_state_busy_end_cursor(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_FULL_POWER_BUSY,
DM_ISM_EVENT_END_CURSOR_UPDATE, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING);
}
/* ===== Tests for dm_ism_next_state — HYSTERESIS_WAITING transitions ===== */
static void dm_test_ism_next_state_hyst_wait_exit_idle(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_TIMER_ABORTED);
}
static void dm_test_ism_next_state_hyst_wait_begin_cursor(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY);
}
static void dm_test_ism_next_state_hyst_wait_timer(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_TIMER_ELAPSED, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE);
}
static void dm_test_ism_next_state_hyst_wait_immediate(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_IMMEDIATE, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE);
}
/* ===== Tests for dm_ism_next_state — HYSTERESIS_BUSY transitions ===== */
static void dm_test_ism_next_state_hyst_busy_exit_idle(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_BUSY,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_BUSY);
}
static void dm_test_ism_next_state_hyst_busy_end_cursor(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_HYSTERESIS_BUSY,
DM_ISM_EVENT_END_CURSOR_UPDATE, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_WAITING);
}
/* ===== Tests for dm_ism_next_state — OPTIMIZED_IDLE transitions ===== */
static void dm_test_ism_next_state_opt_idle_exit(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING);
}
static void dm_test_ism_next_state_opt_idle_begin_cursor(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY);
}
static void dm_test_ism_next_state_opt_idle_sso_timer(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_SSO_TIMER_ELAPSED, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE_SSO);
}
static void dm_test_ism_next_state_opt_idle_immediate(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_IMMEDIATE, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_OPTIMIZED_IDLE_SSO);
}
/* ===== Tests for dm_ism_next_state — OPTIMIZED_IDLE_SSO transitions ===== */
static void dm_test_ism_next_state_opt_idle_sso_exit(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE_SSO,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING);
}
static void dm_test_ism_next_state_opt_idle_sso_cursor(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_OPTIMIZED_IDLE_SSO,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_HYSTERESIS_BUSY);
}
/* ===== Tests for dm_ism_next_state — TIMER_ABORTED transitions ===== */
static void dm_test_ism_next_state_aborted_immediate(struct kunit *test)
{
enum amdgpu_dm_ism_state next;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_TIMER_ABORTED,
DM_ISM_EVENT_IMMEDIATE, &next);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_STATE_FULL_POWER_RUNNING);
}
static void dm_test_ism_next_state_aborted_invalid(struct kunit *test)
{
enum amdgpu_dm_ism_state next = DM_ISM_NUM_STATES;
bool ok;
ok = dm_ism_next_state(DM_ISM_STATE_TIMER_ABORTED,
DM_ISM_EVENT_ENTER_IDLE_REQUESTED, &next);
KUNIT_EXPECT_FALSE(test, ok);
KUNIT_EXPECT_EQ(test, (int)next, (int)DM_ISM_NUM_STATES);
}
/* ===== Tests for dm_ism_get_sso_delay ===== */
static void dm_test_ism_sso_delay_null_stream(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
ism->config.sso_num_frames = 5;
KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, NULL), (uint64_t)0);
}
static void dm_test_ism_sso_delay_zero_frames(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
stream->timing.v_total = 1125;
stream->timing.h_total = 2200;
stream->timing.pix_clk_100hz = 1485000;
ism->config.sso_num_frames = 0;
KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream), (uint64_t)0);
}
static void dm_test_ism_sso_delay_1080p60_3frames(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
uint64_t expected_one_frame_ns, expected;
/*
* 1080p@60Hz: v_total=1125, h_total=2200, pix_clk=148.5MHz
* pix_clk_100hz = 1485000
* one_frame_ns = (1125 * 2200 * 10000000) / 1485000 = 16666666 ns
*/
stream->timing.v_total = 1125;
stream->timing.h_total = 2200;
stream->timing.pix_clk_100hz = 1485000;
ism->config.sso_num_frames = 3;
expected_one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
1485000);
expected = 3 * expected_one_frame_ns;
KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream), expected);
}
static void dm_test_ism_sso_delay_4k60_1frame(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
uint64_t expected_one_frame_ns;
/*
* 4K@60Hz: v_total=2250, h_total=4400, pix_clk=594MHz
* pix_clk_100hz = 5940000
*/
stream->timing.v_total = 2250;
stream->timing.h_total = 4400;
stream->timing.pix_clk_100hz = 5940000;
ism->config.sso_num_frames = 1;
expected_one_frame_ns = div64_u64((uint64_t)2250 * 4400 * 10000000ULL,
5940000);
KUNIT_EXPECT_EQ(test, dm_ism_get_sso_delay(ism, stream),
expected_one_frame_ns);
}
/* ===== Tests for dm_ism_get_idle_allow_delay ===== */
static void dm_test_ism_idle_delay_null_stream(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
ism->config.filter_num_frames = 5;
ism->config.filter_entry_count = 3;
ism->config.activation_num_delay_frames = 10;
KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, NULL),
(uint64_t)0);
}
static void dm_test_ism_idle_delay_zero_filter_frames(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
stream->timing.v_total = 1125;
stream->timing.h_total = 2200;
stream->timing.pix_clk_100hz = 1485000;
ism->config.filter_num_frames = 0;
KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
(uint64_t)0);
}
static void dm_test_ism_idle_delay_zero_entry_count(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
stream->timing.v_total = 1125;
stream->timing.h_total = 2200;
stream->timing.pix_clk_100hz = 1485000;
ism->config.filter_num_frames = 5;
ism->config.filter_entry_count = 0;
KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
(uint64_t)0);
}
static void dm_test_ism_idle_delay_zero_delay_frames(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
stream->timing.v_total = 1125;
stream->timing.h_total = 2200;
stream->timing.pix_clk_100hz = 1485000;
ism->config.filter_num_frames = 5;
ism->config.filter_entry_count = 3;
ism->config.activation_num_delay_frames = 0;
KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
(uint64_t)0);
}
static void dm_test_ism_idle_delay_no_short_idles(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
uint64_t one_frame_ns;
/*
* All history records have long durations (well above the
* short_idle_ns threshold), so no delay should be applied.
*/
stream->timing.v_total = 1125;
stream->timing.h_total = 2200;
stream->timing.pix_clk_100hz = 1485000;
one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
1485000);
ism->config.filter_num_frames = 5;
ism->config.filter_entry_count = 3;
ism->config.activation_num_delay_frames = 10;
ism->config.filter_history_size = 8;
ism->config.filter_old_history_threshold = 0;
/* Fill history with long idle durations */
for (int i = 0; i < 8; i++) {
ism->records[i].duration_ns = one_frame_ns * 100;
ism->records[i].timestamp_ns = 0;
}
ism->next_record_idx = 8;
KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
(uint64_t)0);
}
static void dm_test_ism_idle_delay_enough_short_idles(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
uint64_t one_frame_ns, expected;
/*
* Fill history with short idle durations that meet the threshold.
* filter_entry_count=3, so 3 short idles should trigger the delay.
*/
stream->timing.v_total = 1125;
stream->timing.h_total = 2200;
stream->timing.pix_clk_100hz = 1485000;
one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
1485000);
ism->config.filter_num_frames = 5;
ism->config.filter_entry_count = 3;
ism->config.activation_num_delay_frames = 10;
ism->config.filter_history_size = 8;
ism->config.filter_old_history_threshold = 0;
/* Fill history with short idle durations (1 frame each) */
for (int i = 0; i < 8; i++) {
ism->records[i].duration_ns = one_frame_ns;
ism->records[i].timestamp_ns = 0;
}
ism->next_record_idx = 8;
expected = 10 * one_frame_ns;
KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
expected);
}
static void dm_test_ism_idle_delay_wraps_around_buffer(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
uint64_t one_frame_ns, expected;
/*
* Test the circular buffer wraparound: next_record_idx at 2 means
* the most recent records are at indices 1, 0, 15, 14, ...
*/
stream->timing.v_total = 1125;
stream->timing.h_total = 2200;
stream->timing.pix_clk_100hz = 1485000;
one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
1485000);
ism->config.filter_num_frames = 5;
ism->config.filter_entry_count = 3;
ism->config.activation_num_delay_frames = 10;
ism->config.filter_history_size = 8;
ism->config.filter_old_history_threshold = 0;
/* Fill entire buffer with short idles */
for (int i = 0; i < AMDGPU_DM_IDLE_HIST_LEN; i++) {
ism->records[i].duration_ns = one_frame_ns;
ism->records[i].timestamp_ns = 0;
}
/* Position next_record_idx at 2 to test wraparound */
ism->next_record_idx = 2;
expected = 10 * one_frame_ns;
KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
expected);
}
static void dm_test_ism_idle_delay_old_history_cutoff(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
uint64_t one_frame_ns;
/*
* Test old_history_threshold: only recent entries within the
* threshold should be counted. Set up 2 recent short idles but
* require 3 — older entries are outside the threshold.
*/
stream->timing.v_total = 1125;
stream->timing.h_total = 2200;
stream->timing.pix_clk_100hz = 1485000;
one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
1485000);
ism->config.filter_num_frames = 5;
ism->config.filter_entry_count = 3;
ism->config.activation_num_delay_frames = 10;
ism->config.filter_history_size = 8;
/* Threshold: entries older than 20 frames are ignored */
ism->config.filter_old_history_threshold = 20;
ism->last_idle_timestamp_ns = one_frame_ns * 100;
/* 2 recent short idles (within threshold) */
ism->records[6].duration_ns = one_frame_ns;
ism->records[6].timestamp_ns = one_frame_ns * 95;
ism->records[7].duration_ns = one_frame_ns;
ism->records[7].timestamp_ns = one_frame_ns * 98;
/* Older entries outside the threshold with long durations */
for (int i = 0; i < 6; i++) {
ism->records[i].duration_ns = one_frame_ns * 100;
ism->records[i].timestamp_ns = one_frame_ns * 10;
}
ism->next_record_idx = 8;
/*
* Only 2 short idles within threshold, but 3 required —
* should return 0 (no delay).
*/
KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
(uint64_t)0);
}
static void dm_test_ism_idle_delay_mixed_durations(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
uint64_t one_frame_ns;
/*
* Mix of short and long idle durations. Only 2 short idles
* in 8 entries, but filter_entry_count=3, so no delay.
*/
stream->timing.v_total = 1125;
stream->timing.h_total = 2200;
stream->timing.pix_clk_100hz = 1485000;
one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
1485000);
ism->config.filter_num_frames = 5;
ism->config.filter_entry_count = 3;
ism->config.activation_num_delay_frames = 10;
ism->config.filter_history_size = 8;
ism->config.filter_old_history_threshold = 0;
/* 2 short idles, 6 long idles */
for (int i = 0; i < 8; i++) {
if (i == 6 || i == 7)
ism->records[i].duration_ns = one_frame_ns;
else
ism->records[i].duration_ns = one_frame_ns * 100;
ism->records[i].timestamp_ns = 0;
}
ism->next_record_idx = 8;
KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream),
(uint64_t)0);
}
/**
* dm_test_ism_idle_delay_entry_count_exceeds_history_size - entry_count > history_size sets delay
* @test: KUnit test context
*/
static void dm_test_ism_idle_delay_entry_count_exceeds_history_size(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct dc_stream_state *stream = alloc_test_stream(test);
uint64_t one_frame_ns, expected;
/*
* filter_entry_count (5) > filter_history_size (3), so history_size
* is determined by filter_entry_count via max(). All 5 records are
* short idles, triggering the delay.
*/
stream->timing.v_total = 1125;
stream->timing.h_total = 2200;
stream->timing.pix_clk_100hz = 1485000;
one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL,
1485000);
ism->config.filter_num_frames = 5;
ism->config.filter_entry_count = 5;
ism->config.filter_history_size = 3;
ism->config.activation_num_delay_frames = 10;
ism->config.filter_old_history_threshold = 0;
for (int i = 0; i < 5; i++) {
ism->records[i].duration_ns = one_frame_ns;
ism->records[i].timestamp_ns = 0;
}
ism->next_record_idx = 5;
expected = 10 * one_frame_ns;
KUNIT_EXPECT_EQ(test, dm_ism_get_idle_allow_delay(ism, stream), expected);
}
/* ===== Tests for amdgpu_dm_ism_init ===== */
/**
* dm_test_ism_init_sets_initial_state - all ISM fields initialized to expected values
* @test: KUnit test context
*/
static void dm_test_ism_init_sets_initial_state(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct amdgpu_dm_ism_config config = {
.filter_num_frames = 5,
.filter_entry_count = 3,
.activation_num_delay_frames = 10,
.filter_history_size = 8,
.filter_old_history_threshold = 20,
.sso_num_frames = 2,
};
amdgpu_dm_ism_init(ism, &config);
KUNIT_EXPECT_EQ(test, (int)ism->current_state,
(int)DM_ISM_STATE_FULL_POWER_RUNNING);
KUNIT_EXPECT_EQ(test, (int)ism->previous_state,
(int)DM_ISM_STATE_FULL_POWER_RUNNING);
KUNIT_EXPECT_EQ(test, ism->next_record_idx, 0);
KUNIT_EXPECT_EQ(test, ism->last_idle_timestamp_ns, (uint64_t)0);
KUNIT_EXPECT_EQ(test, ism->config.filter_num_frames,
config.filter_num_frames);
KUNIT_EXPECT_EQ(test, ism->config.filter_entry_count,
config.filter_entry_count);
KUNIT_EXPECT_EQ(test, ism->config.activation_num_delay_frames,
config.activation_num_delay_frames);
KUNIT_EXPECT_EQ(test, ism->config.sso_num_frames, config.sso_num_frames);
}
/* ===== Tests for amdgpu_dm_ism_fini ===== */
/**
* dm_test_ism_fini_after_init - fini cancels never-scheduled work without error
* @test: KUnit test context
*/
static void dm_test_ism_fini_after_init(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
struct amdgpu_dm_ism_config config = {
.filter_num_frames = 5,
.filter_entry_count = 3,
.activation_num_delay_frames = 10,
.sso_num_frames = 2,
};
amdgpu_dm_ism_init(ism, &config);
/* Work was never scheduled; cancel_delayed_work_sync is a no-op. */
amdgpu_dm_ism_fini(ism);
/* FSM state is untouched by fini */
KUNIT_EXPECT_EQ(test, (int)ism->current_state,
(int)DM_ISM_STATE_FULL_POWER_RUNNING);
}
/* ===== Tests for dm_ism_set_last_idle_ts ===== */
/**
* dm_test_ism_set_last_idle_ts_updates_timestamp - last_idle_timestamp_ns updated to current time
* @test: KUnit test context
*/
static void dm_test_ism_set_last_idle_ts_updates_timestamp(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
uint64_t before;
ism->last_idle_timestamp_ns = 0;
before = ktime_get_ns();
dm_ism_set_last_idle_ts(ism);
KUNIT_EXPECT_GE(test, ism->last_idle_timestamp_ns, before);
}
/* ===== Tests for dm_ism_insert_record ===== */
/**
* dm_test_ism_insert_record_basic - record inserted with correct index and duration
* @test: KUnit test context
*/
static void dm_test_ism_insert_record_basic(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
ism->last_idle_timestamp_ns = 0;
ism->next_record_idx = 0;
dm_ism_insert_record(ism);
KUNIT_EXPECT_EQ(test, ism->next_record_idx, 1);
KUNIT_EXPECT_GT(test, ism->records[0].timestamp_ns, (uint64_t)0);
/* duration = timestamp - last_idle_timestamp_ns (0) */
KUNIT_EXPECT_EQ(test, ism->records[0].duration_ns,
ism->records[0].timestamp_ns);
}
/**
* dm_test_ism_insert_record_wraps_around - out-of-bounds index wraps to slot 0
* @test: KUnit test context
*/
static void dm_test_ism_insert_record_wraps_around(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
ism->last_idle_timestamp_ns = 0;
/* Out-of-bounds index triggers reset to 0 */
ism->next_record_idx = AMDGPU_DM_IDLE_HIST_LEN;
dm_ism_insert_record(ism);
KUNIT_EXPECT_EQ(test, ism->next_record_idx, 1);
KUNIT_EXPECT_GT(test, ism->records[0].timestamp_ns, (uint64_t)0);
}
/* ===== Tests for dm_ism_trigger_event ===== */
/**
* dm_test_ism_trigger_event_valid_transition - valid event advances current and previous state
* @test: KUnit test context
*/
static void dm_test_ism_trigger_event_valid_transition(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
bool ok;
ism->current_state = DM_ISM_STATE_FULL_POWER_RUNNING;
ism->previous_state = DM_ISM_STATE_FULL_POWER_RUNNING;
ok = dm_ism_trigger_event(ism, DM_ISM_EVENT_ENTER_IDLE_REQUESTED);
KUNIT_EXPECT_TRUE(test, ok);
KUNIT_EXPECT_EQ(test, (int)ism->current_state,
(int)DM_ISM_STATE_HYSTERESIS_WAITING);
KUNIT_EXPECT_EQ(test, (int)ism->previous_state,
(int)DM_ISM_STATE_FULL_POWER_RUNNING);
}
/**
* dm_test_ism_trigger_event_invalid_transition - invalid event leaves state unchanged
* @test: KUnit test context
*/
static void dm_test_ism_trigger_event_invalid_transition(struct kunit *test)
{
struct amdgpu_dm_ism *ism = alloc_test_ism(test);
bool ok;
ism->current_state = DM_ISM_STATE_FULL_POWER_RUNNING;
ism->previous_state = DM_ISM_STATE_FULL_POWER_RUNNING;
/* EXIT_IDLE_REQUESTED is not valid from FULL_POWER_RUNNING */
ok = dm_ism_trigger_event(ism, DM_ISM_EVENT_EXIT_IDLE_REQUESTED);
KUNIT_EXPECT_FALSE(test, ok);
/* State must remain unchanged on invalid transition */
KUNIT_EXPECT_EQ(test, (int)ism->current_state,
(int)DM_ISM_STATE_FULL_POWER_RUNNING);
KUNIT_EXPECT_EQ(test, (int)ism->previous_state,
(int)DM_ISM_STATE_FULL_POWER_RUNNING);
}
/* ===== Tests for dm_ism_dispatch_next_event ===== */
/**
* dm_test_dispatch_next_event_hyst_wait_no_delay - zero delay_ns: IMMEDIATE in HYSTERESIS_WAITING
* @test: KUnit test context
*/
static void dm_test_dispatch_next_event_hyst_wait_no_delay(struct kunit *test)
{
enum amdgpu_dm_ism_event result;
result = dm_ism_dispatch_next_event(DM_ISM_STATE_HYSTERESIS_WAITING,
0, 0);
KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_EVENT_IMMEDIATE);
}
/**
* dm_test_dispatch_next_event_hyst_wait_with_delay - delay_ns > 0, no IMMEDIATE event returned
* @test: KUnit test context
*/
static void dm_test_dispatch_next_event_hyst_wait_with_delay(struct kunit *test)
{
enum amdgpu_dm_ism_event result;
result = dm_ism_dispatch_next_event(DM_ISM_STATE_HYSTERESIS_WAITING,
1000000, 0);
KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS);
}
/**
* dm_test_dispatch_next_event_opt_idle_no_sso_delay - sso_delay_ns == 0 triggers IMMEDIATE event
* @test: KUnit test context
*/
static void dm_test_dispatch_next_event_opt_idle_no_sso_delay(struct kunit *test)
{
enum amdgpu_dm_ism_event result;
result = dm_ism_dispatch_next_event(DM_ISM_STATE_OPTIMIZED_IDLE,
0, 0);
KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_EVENT_IMMEDIATE);
}
/**
* dm_test_dispatch_next_event_opt_idle_with_sso_delay - sso_delay_ns > 0, SSO timer, no IMMEDIATE
* @test: KUnit test context
*/
static void dm_test_dispatch_next_event_opt_idle_with_sso_delay(struct kunit *test)
{
enum amdgpu_dm_ism_event result;
result = dm_ism_dispatch_next_event(DM_ISM_STATE_OPTIMIZED_IDLE,
0, 1000000);
KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS);
}
/**
* dm_test_dispatch_next_event_timer_aborted - TIMER_ABORTED always returns IMMEDIATE
* @test: KUnit test context
*/
static void dm_test_dispatch_next_event_timer_aborted(struct kunit *test)
{
enum amdgpu_dm_ism_event result;
result = dm_ism_dispatch_next_event(DM_ISM_STATE_TIMER_ABORTED,
0, 0);
KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_EVENT_IMMEDIATE);
}
/**
* dm_test_dispatch_next_event_no_action_state - other states return DM_ISM_NUM_EVENTS
* @test: KUnit test context
*/
static void dm_test_dispatch_next_event_no_action_state(struct kunit *test)
{
enum amdgpu_dm_ism_event result;
result = dm_ism_dispatch_next_event(DM_ISM_STATE_FULL_POWER_RUNNING,
0, 0);
KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS);
}
static struct kunit_case dm_ism_test_cases[] = {
/* dm_ism_next_state — FULL_POWER_RUNNING */
KUNIT_CASE(dm_test_ism_next_state_running_enter_idle),
KUNIT_CASE(dm_test_ism_next_state_running_begin_cursor),
KUNIT_CASE(dm_test_ism_next_state_running_invalid),
/* dm_ism_next_state — FULL_POWER_BUSY */
KUNIT_CASE(dm_test_ism_next_state_busy_enter_idle),
KUNIT_CASE(dm_test_ism_next_state_busy_end_cursor),
/* dm_ism_next_state — HYSTERESIS_WAITING */
KUNIT_CASE(dm_test_ism_next_state_hyst_wait_exit_idle),
KUNIT_CASE(dm_test_ism_next_state_hyst_wait_begin_cursor),
KUNIT_CASE(dm_test_ism_next_state_hyst_wait_timer),
KUNIT_CASE(dm_test_ism_next_state_hyst_wait_immediate),
/* dm_ism_next_state — HYSTERESIS_BUSY */
KUNIT_CASE(dm_test_ism_next_state_hyst_busy_exit_idle),
KUNIT_CASE(dm_test_ism_next_state_hyst_busy_end_cursor),
/* dm_ism_next_state — OPTIMIZED_IDLE */
KUNIT_CASE(dm_test_ism_next_state_opt_idle_exit),
KUNIT_CASE(dm_test_ism_next_state_opt_idle_begin_cursor),
KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_timer),
KUNIT_CASE(dm_test_ism_next_state_opt_idle_immediate),
/* dm_ism_next_state — OPTIMIZED_IDLE_SSO */
KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_exit),
KUNIT_CASE(dm_test_ism_next_state_opt_idle_sso_cursor),
/* dm_ism_next_state — TIMER_ABORTED */
KUNIT_CASE(dm_test_ism_next_state_aborted_immediate),
KUNIT_CASE(dm_test_ism_next_state_aborted_invalid),
/* dm_ism_get_sso_delay */
KUNIT_CASE(dm_test_ism_sso_delay_null_stream),
KUNIT_CASE(dm_test_ism_sso_delay_zero_frames),
KUNIT_CASE(dm_test_ism_sso_delay_1080p60_3frames),
KUNIT_CASE(dm_test_ism_sso_delay_4k60_1frame),
/* dm_ism_get_idle_allow_delay */
KUNIT_CASE(dm_test_ism_idle_delay_null_stream),
KUNIT_CASE(dm_test_ism_idle_delay_zero_filter_frames),
KUNIT_CASE(dm_test_ism_idle_delay_zero_entry_count),
KUNIT_CASE(dm_test_ism_idle_delay_zero_delay_frames),
KUNIT_CASE(dm_test_ism_idle_delay_no_short_idles),
KUNIT_CASE(dm_test_ism_idle_delay_enough_short_idles),
KUNIT_CASE(dm_test_ism_idle_delay_wraps_around_buffer),
KUNIT_CASE(dm_test_ism_idle_delay_old_history_cutoff),
KUNIT_CASE(dm_test_ism_idle_delay_mixed_durations),
KUNIT_CASE(dm_test_ism_idle_delay_entry_count_exceeds_history_size),
/* amdgpu_dm_ism_init */
KUNIT_CASE(dm_test_ism_init_sets_initial_state),
/* amdgpu_dm_ism_fini */
KUNIT_CASE(dm_test_ism_fini_after_init),
/* dm_ism_set_last_idle_ts */
KUNIT_CASE(dm_test_ism_set_last_idle_ts_updates_timestamp),
/* dm_ism_insert_record */
KUNIT_CASE(dm_test_ism_insert_record_basic),
KUNIT_CASE(dm_test_ism_insert_record_wraps_around),
/* dm_ism_trigger_event */
KUNIT_CASE(dm_test_ism_trigger_event_valid_transition),
KUNIT_CASE(dm_test_ism_trigger_event_invalid_transition),
/* dm_ism_dispatch_next_event */
KUNIT_CASE(dm_test_dispatch_next_event_hyst_wait_no_delay),
KUNIT_CASE(dm_test_dispatch_next_event_hyst_wait_with_delay),
KUNIT_CASE(dm_test_dispatch_next_event_opt_idle_no_sso_delay),
KUNIT_CASE(dm_test_dispatch_next_event_opt_idle_with_sso_delay),
KUNIT_CASE(dm_test_dispatch_next_event_timer_aborted),
KUNIT_CASE(dm_test_dispatch_next_event_no_action_state),
{}
};
static struct kunit_suite dm_ism_test_suite = {
.name = "amdgpu_dm_ism",
.test_cases = dm_ism_test_cases,
};
kunit_test_suite(dm_ism_test_suite);
MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_ism");
MODULE_AUTHOR("AMD");
@@ -0,0 +1,291 @@
// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* KUnit tests for amdgpu_dm_psr.c
*
* Copyright 2026 Advanced Micro Devices, Inc.
*/
#include <kunit/test.h>
#include "amdgpu_dm_psr.h"
/*
* Helper: allocate and zero-initialise a dc_link sufficient for
* amdgpu_dm_psr_fill_caps() testing. The function only accesses
* embedded members (dpcd_caps, psr_settings) so no pointer fields
* need to be wired up.
*/
static struct dc_link *alloc_test_link(struct kunit *test)
{
struct dc_link *link;
link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, link);
return link;
}
/* Tests for amdgpu_dm_psr_fill_caps() — PSR version mapping */
static void dm_test_psr_fill_caps_version_1(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
memset(&caps, 0, sizeof(caps));
link->psr_settings.psr_version = DC_PSR_VERSION_1;
amdgpu_dm_psr_fill_caps(link, &caps);
KUNIT_EXPECT_EQ(test, (int)caps.psr_version, 1);
}
static void dm_test_psr_fill_caps_version_su1(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
memset(&caps, 0, sizeof(caps));
link->psr_settings.psr_version = DC_PSR_VERSION_SU_1;
amdgpu_dm_psr_fill_caps(link, &caps);
KUNIT_EXPECT_EQ(test, (int)caps.psr_version, 2);
}
static void dm_test_psr_fill_caps_version_unsupported(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
memset(&caps, 0, sizeof(caps));
link->psr_settings.psr_version = DC_PSR_VERSION_UNSUPPORTED;
amdgpu_dm_psr_fill_caps(link, &caps);
/*
* Neither DC_PSR_VERSION_1 nor DC_PSR_VERSION_SU_1,
* so psr_version stays at its zero-initialised value.
*/
KUNIT_EXPECT_EQ(test, (int)caps.psr_version, 0);
}
/* Tests for amdgpu_dm_psr_fill_caps() — RFB setup time */
static void dm_test_psr_fill_caps_setup_time_zero(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
memset(&caps, 0, sizeof(caps));
/* PSR_SETUP_TIME = 0 → (6 - 0) * 55 = 330 */
link->dpcd_caps.psr_info.psr_dpcd_caps.bits.PSR_SETUP_TIME = 0;
amdgpu_dm_psr_fill_caps(link, &caps);
KUNIT_EXPECT_EQ(test, caps.psr_rfb_setup_time, 330U);
}
static void dm_test_psr_fill_caps_setup_time_mid(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
memset(&caps, 0, sizeof(caps));
/* PSR_SETUP_TIME = 3 → (6 - 3) * 55 = 165 */
link->dpcd_caps.psr_info.psr_dpcd_caps.bits.PSR_SETUP_TIME = 3;
amdgpu_dm_psr_fill_caps(link, &caps);
KUNIT_EXPECT_EQ(test, caps.psr_rfb_setup_time, 165U);
}
static void dm_test_psr_fill_caps_setup_time_max(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
memset(&caps, 0, sizeof(caps));
/* PSR_SETUP_TIME = 6 → (6 - 6) * 55 = 0 */
link->dpcd_caps.psr_info.psr_dpcd_caps.bits.PSR_SETUP_TIME = 6;
amdgpu_dm_psr_fill_caps(link, &caps);
KUNIT_EXPECT_EQ(test, caps.psr_rfb_setup_time, 0U);
}
/* Tests for amdgpu_dm_psr_fill_caps() — link training flag */
static void dm_test_psr_fill_caps_link_training_required(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
memset(&caps, 0, sizeof(caps));
link->dpcd_caps.psr_info.psr_dpcd_caps.bits.LINK_TRAINING_ON_EXIT_NOT_REQUIRED = 0;
amdgpu_dm_psr_fill_caps(link, &caps);
KUNIT_EXPECT_TRUE(test, caps.psr_exit_link_training_required);
}
static void dm_test_psr_fill_caps_link_training_not_required(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
memset(&caps, 0, sizeof(caps));
link->dpcd_caps.psr_info.psr_dpcd_caps.bits.LINK_TRAINING_ON_EXIT_NOT_REQUIRED = 1;
amdgpu_dm_psr_fill_caps(link, &caps);
KUNIT_EXPECT_FALSE(test, caps.psr_exit_link_training_required);
}
/* Tests for amdgpu_dm_psr_fill_caps() — DPCD field passthrough */
static void dm_test_psr_fill_caps_dpcd_fields(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
memset(&caps, 0, sizeof(caps));
link->dpcd_caps.edp_rev = 0x14;
link->dpcd_caps.psr_info.psr_version = 2;
link->dpcd_caps.psr_info.psr_dpcd_caps.bits.SU_GRANULARITY_REQUIRED = 1;
link->dpcd_caps.psr_info.psr_dpcd_caps.bits.Y_COORDINATE_REQUIRED = 1;
link->dpcd_caps.psr_info.psr2_su_y_granularity_cap = 4;
link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 1;
link->dpcd_caps.alpm_caps.bits.PM_STATE_2A_SUPPORT = 1;
amdgpu_dm_psr_fill_caps(link, &caps);
KUNIT_EXPECT_EQ(test, (int)caps.edp_revision, 0x14);
KUNIT_EXPECT_EQ(test, (int)caps.support_ver, 2);
KUNIT_EXPECT_TRUE(test, caps.su_granularity_required);
KUNIT_EXPECT_TRUE(test, caps.y_coordinate_required);
KUNIT_EXPECT_EQ(test, (int)caps.su_y_granularity, 4);
KUNIT_EXPECT_TRUE(test, caps.alpm_cap);
KUNIT_EXPECT_TRUE(test, caps.standby_support);
}
static void dm_test_psr_fill_caps_dpcd_fields_unset(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
memset(&caps, 0xFF, sizeof(caps));
/* All dpcd_caps fields are zero from kzalloc */
amdgpu_dm_psr_fill_caps(link, &caps);
KUNIT_EXPECT_EQ(test, (int)caps.edp_revision, 0);
KUNIT_EXPECT_EQ(test, (int)caps.support_ver, 0);
KUNIT_EXPECT_FALSE(test, caps.su_granularity_required);
KUNIT_EXPECT_FALSE(test, caps.y_coordinate_required);
KUNIT_EXPECT_EQ(test, (int)caps.su_y_granularity, 0);
KUNIT_EXPECT_FALSE(test, caps.alpm_cap);
KUNIT_EXPECT_FALSE(test, caps.standby_support);
}
/* Tests for amdgpu_dm_psr_fill_caps() — rate control and power opts */
static void dm_test_psr_fill_caps_rate_control_always_zero(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
/* Pre-fill caps with non-zero to verify overwrite */
memset(&caps, 0xFF, sizeof(caps));
amdgpu_dm_psr_fill_caps(link, &caps);
KUNIT_EXPECT_EQ(test, (int)caps.rate_control_caps, 0);
}
static void dm_test_psr_fill_caps_power_opts_z10_always_set(struct kunit *test)
{
struct dc_link *link = alloc_test_link(test);
struct psr_caps caps;
memset(&caps, 0, sizeof(caps));
amdgpu_dm_psr_fill_caps(link, &caps);
/*
* psr_power_opt_z10_static_screen is always added to power_opts
* regardless of amdgpu_dc_feature_mask.
*/
KUNIT_EXPECT_TRUE(test,
(caps.psr_power_opt_flag &
psr_power_opt_z10_static_screen) != 0);
}
/* End of tests for amdgpu_dm_psr_fill_caps() */
/* Tests for amdgpu_dm_psr_set_event() — early-exit validation guards */
static void dm_test_psr_set_event_null_stream(struct kunit *test)
{
/* NULL stream → immediate false, dm is not accessed */
KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_set_event(NULL, NULL, true, psr_event_vsync, false));
}
static void dm_test_psr_set_event_null_link(struct kunit *test)
{
struct dc_stream_state *stream;
stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, stream);
/* stream->link remains NULL from kzalloc */
KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_set_event(NULL, stream, true, psr_event_vsync, false));
}
static void dm_test_psr_set_event_psr_not_enabled(struct kunit *test)
{
struct dc_stream_state *stream;
struct dc_link *link;
stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, stream);
link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, link);
stream->link = link;
/* link->psr_settings.psr_feature_enabled remains false from kzalloc */
KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_set_event(NULL, stream, true, psr_event_vsync, false));
}
/* End of tests for amdgpu_dm_psr_set_event() */
static struct kunit_case dm_psr_test_cases[] = {
KUNIT_CASE(dm_test_psr_fill_caps_version_1),
KUNIT_CASE(dm_test_psr_fill_caps_version_su1),
KUNIT_CASE(dm_test_psr_fill_caps_version_unsupported),
KUNIT_CASE(dm_test_psr_fill_caps_setup_time_zero),
KUNIT_CASE(dm_test_psr_fill_caps_setup_time_mid),
KUNIT_CASE(dm_test_psr_fill_caps_setup_time_max),
KUNIT_CASE(dm_test_psr_fill_caps_link_training_required),
KUNIT_CASE(dm_test_psr_fill_caps_link_training_not_required),
KUNIT_CASE(dm_test_psr_fill_caps_dpcd_fields),
KUNIT_CASE(dm_test_psr_fill_caps_dpcd_fields_unset),
KUNIT_CASE(dm_test_psr_fill_caps_rate_control_always_zero),
KUNIT_CASE(dm_test_psr_fill_caps_power_opts_z10_always_set),
KUNIT_CASE(dm_test_psr_set_event_null_stream),
KUNIT_CASE(dm_test_psr_set_event_null_link),
KUNIT_CASE(dm_test_psr_set_event_psr_not_enabled),
{}
};
static struct kunit_suite dm_psr_test_suite = {
.name = "amdgpu_dm_psr",
.test_cases = dm_psr_test_cases,
};
kunit_test_suite(dm_psr_test_suite);
MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_psr");
MODULE_AUTHOR("AMD");
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* KUnit tests for amdgpu_dm_replay.c
*
* Copyright 2026 Advanced Micro Devices, Inc.
*/
#include <kunit/test.h>
#include "dc.h"
#include "amdgpu_mode.h"
#include "amdgpu_dm.h"
/* Extern declaration for the function under test */
extern bool amdgpu_dm_link_supports_replay(struct dc_link *link,
struct amdgpu_dm_connector *aconnector);
/*
* Helper: allocate a dc_link, amdgpu_dm_connector, and dm_connector_state
* wired up so that to_dm_connector_state(aconnector->base.state) works.
*/
struct replay_test_ctx {
struct dc_link *link;
struct amdgpu_dm_connector *aconnector;
struct dm_connector_state *dm_state;
};
static struct replay_test_ctx *alloc_replay_ctx(struct kunit *test)
{
struct replay_test_ctx *ctx;
ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ctx);
ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ctx->link);
ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector);
ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state);
/* Wire connector state so to_dm_connector_state() works */
ctx->aconnector->base.state = &ctx->dm_state->base;
return ctx;
}
/*
* Helper: set all conditions for replay support to pass so individual
* tests can disable one condition at a time.
*/
static void set_all_replay_caps(struct replay_test_ctx *ctx)
{
ctx->dm_state->freesync_capable = true;
ctx->aconnector->vsdb_info.replay_mode = true;
ctx->link->dpcd_caps.edp_rev = EDP_REVISION_13;
ctx->link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 1;
ctx->link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1;
ctx->link->dpcd_caps.pr_info.pixel_deviation_per_line = 1;
ctx->link->dpcd_caps.pr_info.max_deviation_line = 1;
}
/* Tests for amdgpu_dm_link_supports_replay() — all caps met */
static void dm_test_replay_supports_all_caps(struct kunit *test)
{
struct replay_test_ctx *ctx = alloc_replay_ctx(test);
set_all_replay_caps(ctx);
KUNIT_EXPECT_TRUE(test,
amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector));
}
/* Tests for amdgpu_dm_link_supports_replay() — freesync not capable */
static void dm_test_replay_no_freesync(struct kunit *test)
{
struct replay_test_ctx *ctx = alloc_replay_ctx(test);
set_all_replay_caps(ctx);
ctx->dm_state->freesync_capable = false;
KUNIT_EXPECT_FALSE(test,
amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector));
}
/* Tests for amdgpu_dm_link_supports_replay() — no replay mode in VSDB */
static void dm_test_replay_no_vsdb_replay_mode(struct kunit *test)
{
struct replay_test_ctx *ctx = alloc_replay_ctx(test);
set_all_replay_caps(ctx);
ctx->aconnector->vsdb_info.replay_mode = false;
KUNIT_EXPECT_FALSE(test,
amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector));
}
/* Tests for amdgpu_dm_link_supports_replay() — eDP revision too low */
static void dm_test_replay_edp_rev_too_low(struct kunit *test)
{
struct replay_test_ctx *ctx = alloc_replay_ctx(test);
set_all_replay_caps(ctx);
ctx->link->dpcd_caps.edp_rev = EDP_REVISION_12;
KUNIT_EXPECT_FALSE(test,
amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector));
}
/* Tests for amdgpu_dm_link_supports_replay() — no ALPM AUX wake cap */
static void dm_test_replay_no_alpm_aux_wake(struct kunit *test)
{
struct replay_test_ctx *ctx = alloc_replay_ctx(test);
set_all_replay_caps(ctx);
ctx->link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 0;
KUNIT_EXPECT_FALSE(test,
amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector));
}
/* Tests for amdgpu_dm_link_supports_replay() — no adaptive sync SDP */
static void dm_test_replay_no_adaptive_sync_sdp(struct kunit *test)
{
struct replay_test_ctx *ctx = alloc_replay_ctx(test);
set_all_replay_caps(ctx);
ctx->link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 0;
KUNIT_EXPECT_FALSE(test,
amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector));
}
/* Tests for amdgpu_dm_link_supports_replay() — zero pixel deviation */
static void dm_test_replay_zero_pixel_deviation(struct kunit *test)
{
struct replay_test_ctx *ctx = alloc_replay_ctx(test);
set_all_replay_caps(ctx);
ctx->link->dpcd_caps.pr_info.pixel_deviation_per_line = 0;
KUNIT_EXPECT_FALSE(test,
amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector));
}
/* Tests for amdgpu_dm_link_supports_replay() — zero max deviation line */
static void dm_test_replay_zero_max_deviation_line(struct kunit *test)
{
struct replay_test_ctx *ctx = alloc_replay_ctx(test);
set_all_replay_caps(ctx);
ctx->link->dpcd_caps.pr_info.max_deviation_line = 0;
KUNIT_EXPECT_FALSE(test,
amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector));
}
/* Tests for amdgpu_dm_link_supports_replay() — both deviation fields zero */
static void dm_test_replay_both_deviations_zero(struct kunit *test)
{
struct replay_test_ctx *ctx = alloc_replay_ctx(test);
set_all_replay_caps(ctx);
ctx->link->dpcd_caps.pr_info.pixel_deviation_per_line = 0;
ctx->link->dpcd_caps.pr_info.max_deviation_line = 0;
KUNIT_EXPECT_FALSE(test,
amdgpu_dm_link_supports_replay(ctx->link, ctx->aconnector));
}
/* End of tests for amdgpu_dm_link_supports_replay() */
static struct kunit_case dm_replay_test_cases[] = {
KUNIT_CASE(dm_test_replay_supports_all_caps),
KUNIT_CASE(dm_test_replay_no_freesync),
KUNIT_CASE(dm_test_replay_no_vsdb_replay_mode),
KUNIT_CASE(dm_test_replay_edp_rev_too_low),
KUNIT_CASE(dm_test_replay_no_alpm_aux_wake),
KUNIT_CASE(dm_test_replay_no_adaptive_sync_sdp),
KUNIT_CASE(dm_test_replay_zero_pixel_deviation),
KUNIT_CASE(dm_test_replay_zero_max_deviation_line),
KUNIT_CASE(dm_test_replay_both_deviations_zero),
{}
};
static struct kunit_suite dm_replay_test_suite = {
.name = "amdgpu_dm_replay",
.test_cases = dm_replay_test_cases,
};
kunit_test_suite(dm_replay_test_suite);
MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_replay");
MODULE_AUTHOR("AMD");