* [PATCH 1/5] drm/amd/display: Render the panel power savings kernel-doc
2026-11-02 10:17 [PATCH 0/5] drm/amd/display: Commit panel_power_savings writes from the kernel Hugo Sales
@ 2026-11-02 10:17 ` Hugo Sales
2026-11-02 10:17 ` [PATCH 2/5] drm/amd/display: Don't force a modeset when the ABM level changes Hugo Sales
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Hugo Sales @ 2026-11-02 10:17 UTC (permalink / raw)
To: sunpeng.li, siqueira, amd-gfx, alexander.deucher, harry.wentland
Cc: Hugo Sales, airlied, linux-kernel, mario.limonciello,
christian.koenig, simona, linux-doc, bhawanpreet.lakha,
ivan.lipski, dri-devel, ray.wu, alex.hung
The DOC section for the panel_power_savings sysfs file is not included
in any rst file, so it never shows up in the built documentation. Add it
to the Display Properties section next to the 'adaptive backlight
modulation' property it interacts with
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
Documentation/gpu/amdgpu/display/display-manager.rst | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Documentation/gpu/amdgpu/display/display-manager.rst b/Documentation/gpu/amdgpu/display/display-manager.rst
index f6de9e7779e29..ca4a956d2aa17 100644
--- a/Documentation/gpu/amdgpu/display/display-manager.rst
+++ b/Documentation/gpu/amdgpu/display/display-manager.rst
@@ -187,3 +187,6 @@ Display Properties
.. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_display.c
:doc: property for adaptive backlight modulation
+
+.. kernel-doc:: drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
+ :doc: panel power savings
--
2.56.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 2/5] drm/amd/display: Don't force a modeset when the ABM level changes
2026-11-02 10:17 [PATCH 0/5] drm/amd/display: Commit panel_power_savings writes from the kernel Hugo Sales
2026-11-02 10:17 ` [PATCH 1/5] drm/amd/display: Render the panel power savings kernel-doc Hugo Sales
@ 2026-11-02 10:17 ` Hugo Sales
2026-11-02 10:17 ` [PATCH 3/5] drm/amd/display: Give the panel_power_savings store tests a full pipe Hugo Sales
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Hugo Sales @ 2026-11-02 10:17 UTC (permalink / raw)
To: sunpeng.li, siqueira, amd-gfx, alexander.deucher, harry.wentland
Cc: Hugo Sales, airlied, linux-kernel, mario.limonciello,
christian.koenig, simona, linux-doc, bhawanpreet.lakha,
ivan.lipski, dri-devel, ray.wu, alex.hung
An upcoming patch commits panel_power_savings writes from the kernel
without a modeset. amdgpu_dm_atomic_check() sets connectors_changed on the
CRTC when the connector's ABM level changes, so that commit would fail
with -EINVAL
The flag came in with commit c5892a102182 ("drm/amd/display: Fix dmesg
warning from setting abm level"), because setting the level did not add
the CRTC to the commit and no stream update reached DC. With other changes
since then, that fix is no longer needed. On the non-modeset path,
dm_update_crtc_state() copies the level from the connector to the CRTC
state and the commit tail sends a changed level as stream_update.abm_level
Keep the flag for scaling changes, which move the stream's source and
destination rectangles
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
Upstream, a level already reaches DC this way when a compositor commits
the connector after a panel_power_savings write. The store has already
written the level into the current connector state, so the old and new
connector states match and atomic check doesn't ask for a modeset. Only
the CRTC state, which still holds the old level, sees the change. With
this series, the hardware tests in the cover letter apply every level this
way
If a level change through the KMS property should still force a modeset,
the store could flag its own commits instead. It would set a flag in
dm_connector_state that atomic check reads to skip connectors_changed.
duplicate_state wouldn't copy the flag, so it only lasts for that one
commit. I think it's simpler to treat the change the same way whoever made
it, but I can send that version
Scaling has been in the same check since commit 3c6d1aeb4342.
From my reading the non-modeset path handles it too, as a src/dst stream
update, but it changes the scaler ratio and I can't tell whether DC needs
the full validation for that. Could it go as well?
---
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index dfbc384fbbdb4..256699cdd0b5c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -6304,8 +6304,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_check(struct drm_device *dev,
goto fail;
}
- if (dm_old_con_state->abm_level != dm_new_con_state->abm_level ||
- dm_old_con_state->scaling != dm_new_con_state->scaling)
+ if (dm_old_con_state->scaling != dm_new_con_state->scaling)
new_crtc_state->connectors_changed = true;
}
--
2.56.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 3/5] drm/amd/display: Give the panel_power_savings store tests a full pipe
2026-11-02 10:17 [PATCH 0/5] drm/amd/display: Commit panel_power_savings writes from the kernel Hugo Sales
2026-11-02 10:17 ` [PATCH 1/5] drm/amd/display: Render the panel power savings kernel-doc Hugo Sales
2026-11-02 10:17 ` [PATCH 2/5] drm/amd/display: Don't force a modeset when the ABM level changes Hugo Sales
@ 2026-11-02 10:17 ` Hugo Sales
2026-11-02 10:17 ` [PATCH 4/5] drm/amd/display: Commit panel_power_savings writes from the kernel Hugo Sales
2026-11-02 10:17 ` [PATCH 5/5] drm/amd/display: Test panel_power_savings commits Hugo Sales
4 siblings, 0 replies; 6+ messages in thread
From: Hugo Sales @ 2026-11-02 10:17 UTC (permalink / raw)
To: sunpeng.li, siqueira, amd-gfx, alexander.deucher, harry.wentland
Cc: Hugo Sales, airlied, linux-kernel, mario.limonciello,
christian.koenig, simona, linux-doc, bhawanpreet.lakha,
ivan.lipski, dri-devel, ray.wu, alex.hung
An upcoming patch makes the store apply the level with an atomic commit.
The store tests allocate their connector with kunit_kzalloc() instead of
initialising it on the DRM device, and have no CRTC, plane or encoder, so
a commit can't run on it
Build a primary plane, a CRTC, an encoder and an eDP connector on the
KUnit device instead, with runtime PM enabled as on a probed device, and
move the two store tests onto it. They check the same results as before
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
.../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 88 ++++++++++++++++++++--
1 file changed, 82 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
index c6d15fb8ad989..35e245b078f7a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
@@ -7,10 +7,14 @@
#include <kunit/test.h>
#include <linux/backlight.h>
+#include <linux/pm_runtime.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_connector.h>
+#include <drm/drm_kunit_helpers.h>
+#include <drm/drm_managed.h>
#include <drm/drm_mode_config.h>
+#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_property.h>
#include "dc.h"
@@ -544,6 +548,75 @@ static struct drm_connector *setup_panel_power_savings_connector(struct kunit *t
return connector;
}
+static const struct drm_connector_funcs dm_test_pps_connector_funcs = {
+ .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 int dm_test_pps_get_modes(struct drm_connector *connector)
+{
+ return 0;
+}
+
+/* update_connector_routing() dereferences the helper funcs unconditionally */
+static const struct drm_connector_helper_funcs dm_test_pps_connector_helper_funcs = {
+ .get_modes = dm_test_pps_get_modes,
+};
+
+/*
+ * Build a primary plane, a CRTC, an encoder and an eDP connector that the
+ * store can commit to. The KUnit device uses the generic atomic helpers, so a
+ * commit runs drm_atomic_helper_check() and drm_atomic_helper_commit() rather
+ * than the DM implementations
+ */
+static struct drm_connector *
+setup_panel_power_savings_pipe(struct kunit *test, struct device **device_out)
+{
+ struct amdgpu_dm_connector *aconnector;
+ struct drm_connector *connector;
+ struct drm_encoder *encoder;
+ struct amdgpu_device *adev;
+ struct drm_plane *primary;
+ struct drm_crtc *crtc;
+ struct device *device;
+
+ adev = dm_kunit_alloc_adev(test);
+ KUNIT_ASSERT_EQ(test, pm_runtime_set_active(adev->ddev.dev), 0);
+ KUNIT_ASSERT_EQ(test, devm_pm_runtime_enable(adev->ddev.dev), 0);
+
+ primary = drm_kunit_helper_create_primary_plane(test, &adev->ddev, NULL,
+ NULL, NULL, 0, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary);
+ crtc = drm_kunit_helper_create_crtc(test, &adev->ddev, primary, NULL,
+ NULL, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
+
+ encoder = drmm_kzalloc(&adev->ddev, sizeof(*encoder), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, encoder);
+ KUNIT_ASSERT_EQ(test, drmm_encoder_init(&adev->ddev, encoder, NULL,
+ DRM_MODE_ENCODER_TMDS, NULL), 0);
+ encoder->possible_crtcs = drm_crtc_mask(crtc);
+
+ aconnector = drmm_kzalloc(&adev->ddev, sizeof(*aconnector), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, aconnector);
+ connector = &aconnector->base;
+ KUNIT_ASSERT_EQ(test, drmm_connector_init(&adev->ddev, connector,
+ &dm_test_pps_connector_funcs,
+ DRM_MODE_CONNECTOR_eDP, NULL), 0);
+ drm_connector_helper_add(connector, &dm_test_pps_connector_helper_funcs);
+ KUNIT_ASSERT_EQ(test, drm_connector_attach_encoder(connector, encoder), 0);
+
+ drm_mode_config_reset(&adev->ddev);
+
+ device = kunit_kzalloc(test, sizeof(*device), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, device);
+ dev_set_drvdata(device, connector);
+ *device_out = device;
+
+ return connector;
+}
+
static void dm_test_free_sysfs_buf(void *data)
{
free_page((unsigned long)data);
@@ -607,15 +680,16 @@ static void dm_test_panel_power_savings_show_reports_level(struct kunit *test)
*/
static void dm_test_panel_power_savings_store_sets_disable(struct kunit *test)
{
- struct dm_connector_state *state;
+ struct drm_connector *connector;
struct device *device;
size_t count = strlen("0");
- setup_panel_power_savings_connector(test, &device, &state);
+ connector = setup_panel_power_savings_pipe(test, &device);
KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "0", count),
(ssize_t)count);
- KUNIT_EXPECT_EQ(test, state->abm_level, ABM_LEVEL_IMMEDIATE_DISABLE);
+ KUNIT_EXPECT_EQ(test, to_dm_connector_state(connector->state)->abm_level,
+ ABM_LEVEL_IMMEDIATE_DISABLE);
}
/**
@@ -624,11 +698,13 @@ static void dm_test_panel_power_savings_store_sets_disable(struct kunit *test)
*/
static void dm_test_panel_power_savings_store_forbidden(struct kunit *test)
{
- struct dm_connector_state *state;
+ struct dm_connector_state *dm_state;
+ struct drm_connector *connector;
struct device *device;
- setup_panel_power_savings_connector(test, &device, &state);
- state->abm_sysfs_forbidden = true;
+ connector = setup_panel_power_savings_pipe(test, &device);
+ dm_state = to_dm_connector_state(connector->state);
+ dm_state->abm_sysfs_forbidden = true;
KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "1", 1), -EBUSY);
}
--
2.56.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 4/5] drm/amd/display: Commit panel_power_savings writes from the kernel
2026-11-02 10:17 [PATCH 0/5] drm/amd/display: Commit panel_power_savings writes from the kernel Hugo Sales
` (2 preceding siblings ...)
2026-11-02 10:17 ` [PATCH 3/5] drm/amd/display: Give the panel_power_savings store tests a full pipe Hugo Sales
@ 2026-11-02 10:17 ` Hugo Sales
2026-11-02 10:17 ` [PATCH 5/5] drm/amd/display: Test panel_power_savings commits Hugo Sales
4 siblings, 0 replies; 6+ messages in thread
From: Hugo Sales @ 2026-11-02 10:17 UTC (permalink / raw)
To: sunpeng.li, siqueira, amd-gfx, alexander.deucher, harry.wentland
Cc: Hugo Sales, airlied, linux-kernel, mario.limonciello,
christian.koenig, simona, linux-doc, bhawanpreet.lakha,
ivan.lipski, dri-devel, ray.wu, alex.hung
panel_power_savings_store() currently writes the level into
connector->state and sends a hotplug uevent, and the hardware only gets it
on a later atomic commit that includes the connector. Hyprland adds the
connector to a commit only on a modeset or when a connector property it
sets changes, so it never applies the level
Programs that write the sysfs file, such as power-profiles-daemon, aren't
the DRM master and can't commit themselves, so they depend on the master
taking the uevent as a cue to commit the connector, but nothing documents
they should. A master that wants to control ABM sets the 'adaptive
backlight modulation' property, and from then on writes return -EBUSY. The
file has to work without the master's help, like the backlight device,
whose writes reach DC without a commit
Commit the level from the store instead, with a duplicated connector state
and no modeset. The write returns once the level is applied, or with the
commit's error, so no uevent is needed. A connector that is off gets the
level when it is next enabled
Document the new behaviour in the kernel-doc
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
This needs "drm/amd/display: Don't force a modeset when the ABM level
changes". Without it, atomic check sees the changed level, asks for a
modeset and the commit fails with -EINVAL
A failed write used to leave the level stored and send the uevent anyway.
Now the level only changes through the commit, so the store reports the
error. power-profiles-daemon logs a failed write and tries again on the
next profile, AC or battery change, because it compares against the value
it reads back. It ignores the hotplug uevent. Compositors used it only as
a cue to commit the connector, which the store now does itself
The commit is blocking, so a write can hold the CRTC locks for up to a
frame while a compositor commit waits. The other way round, while the
store's commit is pending, a nonblocking commit from the compositor on the
same CRTC fails with -EBUSY, as it would behind any pending commit
The store takes a runtime PM reference around the commit, like the
atomic ioctl path, and its locks are interruptible
---
.../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 90 +++++++++++++++++++---
1 file changed, 79 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
index 27546febf90d3..a661fd558686f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
@@ -40,7 +40,10 @@
#include <linux/backlight.h>
#include <linux/minmax.h>
+#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_drv.h>
#include <drm/drm_edid.h>
#include <drm/drm_utils.h>
@@ -691,6 +694,18 @@ EXPORT_IF_KUNIT(amdgpu_dm_setup_backlight_device);
*
* Modifying this value can have implications on color accuracy, so tread
* carefully.
+ *
+ * A write commits the new level before it returns, without a modeset. The
+ * commit waits for any commit already pending on the connector's CRTC, and
+ * while it is pending, a nonblocking commit from the DRM master on that CRTC
+ * fails with -EBUSY. If the connector or its CRTC is off, the driver stores
+ * the level and applies it when the connector is next enabled
+ *
+ * If the commit fails, the write returns its error and the level stays as it
+ * was. While the CRTC needs a modeset, as after a DSC override in debugfs,
+ * writes fail with -EINVAL until a commit that allows a modeset clears that
+ * state. Writes fail with -EBUSY once the DRM master has set the connector's
+ * 'adaptive backlight modulation' property to a value other than 'sysfs'
*/
STATIC_IFN_KUNIT
@@ -712,13 +727,75 @@ ssize_t panel_power_savings_show(struct device *device,
}
EXPORT_IF_KUNIT(panel_power_savings_show);
+static int panel_power_savings_apply(struct drm_connector *connector,
+ unsigned int level)
+{
+ struct drm_connector_state *conn_state;
+ struct dm_connector_state *dm_state;
+ struct drm_device *dev = connector->dev;
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_atomic_commit *state;
+ int ret, idx;
+
+ if (!drm_dev_enter(dev, &idx))
+ return -ENODEV;
+
+ ret = pm_runtime_resume_and_get(dev->dev);
+ if (ret < 0)
+ goto dev_exit;
+
+ state = drm_atomic_commit_alloc(dev);
+ if (!state) {
+ ret = -ENOMEM;
+ goto pm_put;
+ }
+
+ /* Kernel commits allow a modeset by default, which a level never needs */
+ state->allow_modeset = false;
+
+ drm_modeset_acquire_init(&ctx, DRM_MODESET_ACQUIRE_INTERRUPTIBLE);
+ state->acquire_ctx = &ctx;
+retry:
+ conn_state = drm_atomic_get_connector_state(state, connector);
+ if (IS_ERR(conn_state)) {
+ ret = PTR_ERR(conn_state);
+ goto out;
+ }
+
+ dm_state = to_dm_connector_state(conn_state);
+ if (dm_state->abm_sysfs_forbidden) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ dm_state->abm_level = level ?: ABM_LEVEL_IMMEDIATE_DISABLE;
+
+ ret = drm_atomic_commit(state);
+out:
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ ret = drm_modeset_backoff(&ctx);
+ if (!ret)
+ goto retry;
+ }
+
+ drm_atomic_commit_put(state);
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+pm_put:
+ pm_runtime_put_autosuspend(dev->dev);
+dev_exit:
+ drm_dev_exit(idx);
+
+ return ret;
+}
+
STATIC_IFN_KUNIT
ssize_t panel_power_savings_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct drm_connector *connector = dev_get_drvdata(device);
- struct drm_device *dev = connector->dev;
long val;
int ret;
@@ -730,19 +807,10 @@ ssize_t panel_power_savings_store(struct device *device,
if (val < 0 || val > 4)
return -EINVAL;
- drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
- if (to_dm_connector_state(connector->state)->abm_sysfs_forbidden)
- ret = -EBUSY;
- else
- to_dm_connector_state(connector->state)->abm_level = val ?:
- ABM_LEVEL_IMMEDIATE_DISABLE;
- drm_modeset_unlock(&dev->mode_config.connection_mutex);
-
+ ret = panel_power_savings_apply(connector, val);
if (ret)
return ret;
- drm_kms_helper_hotplug_event(dev);
-
return count;
}
EXPORT_IF_KUNIT(panel_power_savings_store);
--
2.56.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 5/5] drm/amd/display: Test panel_power_savings commits
2026-11-02 10:17 [PATCH 0/5] drm/amd/display: Commit panel_power_savings writes from the kernel Hugo Sales
` (3 preceding siblings ...)
2026-11-02 10:17 ` [PATCH 4/5] drm/amd/display: Commit panel_power_savings writes from the kernel Hugo Sales
@ 2026-11-02 10:17 ` Hugo Sales
4 siblings, 0 replies; 6+ messages in thread
From: Hugo Sales @ 2026-11-02 10:17 UTC (permalink / raw)
To: sunpeng.li, siqueira, amd-gfx, alexander.deucher, harry.wentland
Cc: Hugo Sales, airlied, linux-kernel, mario.limonciello,
christian.koenig, simona, linux-doc, bhawanpreet.lakha,
ivan.lipski, dri-devel, ray.wu, alex.hung
Add KUnit cases for a panel_power_savings write to a connector bound to a
CRTC. With the CRTC on or off, the store commits a connector state that
carries the level. When atomic check rejects the commit, the write returns
the error and the connector keeps its old level. The existing store tests
also check that a write replaces the connector state, and that a forbidden
write keeps the state and level and drops its runtime PM reference. For
the new cases, the pipe helper can route the connector to the CRTC, set
whether the CRTC is active and take CRTC helper funcs
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
.../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 157 ++++++++++++++++++++-
1 file changed, 150 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
index 35e245b078f7a..d58418c88592c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
@@ -10,6 +10,7 @@
#include <linux/pm_runtime.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_uapi.h>
#include <drm/drm_connector.h>
#include <drm/drm_kunit_helpers.h>
#include <drm/drm_managed.h>
@@ -564,16 +565,43 @@ static const struct drm_connector_helper_funcs dm_test_pps_connector_helper_func
.get_modes = dm_test_pps_get_modes,
};
+static int dm_test_pps_crtc_atomic_check_fail(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ return -ENOSPC;
+}
+
+static const struct drm_crtc_helper_funcs dm_test_pps_crtc_helper_funcs_fail = {
+ .atomic_check = dm_test_pps_crtc_atomic_check_fail,
+};
+
+/* The generic helpers accept a commit that allows a modeset it doesn't need */
+static int dm_test_pps_crtc_atomic_check_no_modeset(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ return state->allow_modeset ? -EINVAL : 0;
+}
+
+static const struct drm_crtc_helper_funcs dm_test_pps_crtc_helper_funcs_no_modeset = {
+ .atomic_check = dm_test_pps_crtc_atomic_check_no_modeset,
+};
+
/*
* Build a primary plane, a CRTC, an encoder and an eDP connector that the
- * store can commit to. The KUnit device uses the generic atomic helpers, so a
- * commit runs drm_atomic_helper_check() and drm_atomic_helper_commit() rather
- * than the DM implementations
+ * store can commit to. With @bound, the connector's current state is routed
+ * to the CRTC, which is enabled and has @active as its active state. The KUnit
+ * device uses the generic atomic helpers, so a commit runs
+ * drm_atomic_helper_check() and drm_atomic_helper_commit() rather than the DM
+ * implementations. A NULL @crtc_helper_funcs gives the KUnit helper's defaults
*/
static struct drm_connector *
-setup_panel_power_savings_pipe(struct kunit *test, struct device **device_out)
+setup_panel_power_savings_pipe(struct kunit *test, struct device **device_out,
+ bool bound, bool active,
+ const struct drm_crtc_helper_funcs *crtc_helper_funcs)
{
struct amdgpu_dm_connector *aconnector;
+ struct drm_display_mode *mode;
+ struct drm_crtc_state *crtc_state;
struct drm_connector *connector;
struct drm_encoder *encoder;
struct amdgpu_device *adev;
@@ -589,7 +617,7 @@ setup_panel_power_savings_pipe(struct kunit *test, struct device **device_out)
NULL, NULL, 0, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary);
crtc = drm_kunit_helper_create_crtc(test, &adev->ddev, primary, NULL,
- NULL, NULL);
+ NULL, crtc_helper_funcs);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
encoder = drmm_kzalloc(&adev->ddev, sizeof(*encoder), GFP_KERNEL);
@@ -609,6 +637,21 @@ setup_panel_power_savings_pipe(struct kunit *test, struct device **device_out)
drm_mode_config_reset(&adev->ddev);
+ if (bound) {
+ /* A connector state bound to a CRTC holds a connector reference */
+ connector->state->crtc = crtc;
+ drm_connector_get(connector);
+ connector->state->best_encoder = encoder;
+ crtc_state = crtc->state;
+ crtc_state->connector_mask = drm_connector_mask(connector);
+ crtc_state->encoder_mask = drm_encoder_mask(encoder);
+ crtc_state->enable = true;
+ crtc_state->active = active;
+ mode = drm_kunit_display_mode_from_cea_vic(test, &adev->ddev, 1);
+ KUNIT_ASSERT_NOT_NULL(test, mode);
+ KUNIT_ASSERT_EQ(test, drm_atomic_set_mode_for_crtc(crtc_state, mode), 0);
+ }
+
device = kunit_kzalloc(test, sizeof(*device), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, device);
dev_set_drvdata(device, connector);
@@ -617,6 +660,14 @@ setup_panel_power_savings_pipe(struct kunit *test, struct device **device_out)
return connector;
}
+static void dm_test_pps_expect_no_pm_reference(struct kunit *test,
+ struct drm_connector *connector)
+{
+#ifdef CONFIG_PM
+ KUNIT_EXPECT_EQ(test, atomic_read(&connector->dev->dev->power.usage_count), 0);
+#endif
+}
+
static void dm_test_free_sysfs_buf(void *data)
{
free_page((unsigned long)data);
@@ -677,17 +728,24 @@ static void dm_test_panel_power_savings_show_reports_level(struct kunit *test)
/**
* dm_test_panel_power_savings_store_sets_disable - Test zero maps to disable
* @test: The KUnit test context
+ *
+ * With no CRTC on the connector, the store commits a connector state that
+ * carries the disable sentinel
*/
static void dm_test_panel_power_savings_store_sets_disable(struct kunit *test)
{
+ struct drm_connector_state *old_state;
struct drm_connector *connector;
struct device *device;
size_t count = strlen("0");
- connector = setup_panel_power_savings_pipe(test, &device);
+ connector = setup_panel_power_savings_pipe(test, &device, false, false, NULL);
+ to_dm_connector_state(connector->state)->abm_level = 2;
+ old_state = connector->state;
KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "0", count),
(ssize_t)count);
+ KUNIT_EXPECT_PTR_NE(test, connector->state, old_state);
KUNIT_EXPECT_EQ(test, to_dm_connector_state(connector->state)->abm_level,
ABM_LEVEL_IMMEDIATE_DISABLE);
}
@@ -695,6 +753,9 @@ static void dm_test_panel_power_savings_store_sets_disable(struct kunit *test)
/**
* dm_test_panel_power_savings_store_forbidden - Test forbidden update
* @test: The KUnit test context
+ *
+ * Once the DRM master owns the level, the store fails without committing and
+ * drops its runtime PM reference
*/
static void dm_test_panel_power_savings_store_forbidden(struct kunit *test)
{
@@ -702,11 +763,15 @@ static void dm_test_panel_power_savings_store_forbidden(struct kunit *test)
struct drm_connector *connector;
struct device *device;
- connector = setup_panel_power_savings_pipe(test, &device);
+ connector = setup_panel_power_savings_pipe(test, &device, false, false, NULL);
dm_state = to_dm_connector_state(connector->state);
dm_state->abm_sysfs_forbidden = true;
+ dm_state->abm_level = 2;
KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "1", 1), -EBUSY);
+ KUNIT_EXPECT_PTR_EQ(test, connector->state, &dm_state->base);
+ KUNIT_EXPECT_EQ(test, dm_state->abm_level, 2);
+ dm_test_pps_expect_no_pm_reference(test, connector);
}
/**
@@ -755,6 +820,81 @@ static void dm_test_panel_power_savings_store_rejects_out_of_range(struct kunit
KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "5", 1), -EINVAL);
}
+/**
+ * dm_test_panel_power_savings_store_inactive_crtc - Test store with the CRTC off
+ * @test: The KUnit test context
+ *
+ * With the connector's CRTC enabled but inactive, the store still commits a
+ * connector state that carries the level, without allowing a modeset, and
+ * drops its runtime PM reference
+ */
+static void dm_test_panel_power_savings_store_inactive_crtc(struct kunit *test)
+{
+ struct drm_connector_state *old_state;
+ struct drm_connector *connector;
+ struct device *device;
+
+ connector = setup_panel_power_savings_pipe(test, &device, true, false,
+ &dm_test_pps_crtc_helper_funcs_no_modeset);
+ old_state = connector->state;
+
+ KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "3", 1), 1);
+ KUNIT_EXPECT_PTR_NE(test, connector->state, old_state);
+ KUNIT_EXPECT_EQ(test, to_dm_connector_state(connector->state)->abm_level, 3);
+ KUNIT_EXPECT_FALSE(test, drm_atomic_crtc_needs_modeset(connector->state->crtc->state));
+ dm_test_pps_expect_no_pm_reference(test, connector);
+}
+
+/**
+ * dm_test_panel_power_savings_store_active_crtc - Test store with the CRTC on
+ * @test: The KUnit test context
+ *
+ * With the connector's CRTC active, the store commits a connector state that
+ * carries the level, without allowing a modeset, and drops its runtime PM
+ * reference
+ */
+static void dm_test_panel_power_savings_store_active_crtc(struct kunit *test)
+{
+ struct drm_connector_state *old_state;
+ struct drm_connector *connector;
+ struct device *device;
+
+ connector = setup_panel_power_savings_pipe(test, &device, true, true,
+ &dm_test_pps_crtc_helper_funcs_no_modeset);
+ old_state = connector->state;
+
+ KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "2", 1), 1);
+ KUNIT_EXPECT_PTR_NE(test, connector->state, old_state);
+ KUNIT_EXPECT_EQ(test, to_dm_connector_state(connector->state)->abm_level, 2);
+ KUNIT_EXPECT_FALSE(test, drm_atomic_crtc_needs_modeset(connector->state->crtc->state));
+ dm_test_pps_expect_no_pm_reference(test, connector);
+}
+
+/**
+ * dm_test_panel_power_savings_store_commit_fails - Test store when the commit fails
+ * @test: The KUnit test context
+ *
+ * When atomic check rejects the commit, the write returns the error, the
+ * current connector state keeps the old level and the runtime PM reference is
+ * dropped
+ */
+static void dm_test_panel_power_savings_store_commit_fails(struct kunit *test)
+{
+ struct drm_connector_state *old_state;
+ struct drm_connector *connector;
+ struct device *device;
+
+ connector = setup_panel_power_savings_pipe(test, &device, true, true,
+ &dm_test_pps_crtc_helper_funcs_fail);
+ to_dm_connector_state(connector->state)->abm_level = 1;
+ old_state = connector->state;
+
+ KUNIT_EXPECT_EQ(test, panel_power_savings_store(device, NULL, "4", 1), -ENOSPC);
+ KUNIT_EXPECT_PTR_EQ(test, connector->state, old_state);
+ KUNIT_EXPECT_EQ(test, to_dm_connector_state(connector->state)->abm_level, 1);
+ dm_test_pps_expect_no_pm_reference(test, connector);
+}
+
/* Tests for amdgpu_dm_backlight_get_device_index() */
/**
@@ -2017,6 +2157,9 @@ static struct kunit_case dm_backlight_test_cases[] = {
KUNIT_CASE(dm_test_panel_power_savings_store_forbidden),
KUNIT_CASE(dm_test_panel_power_savings_store_rejects_invalid_text),
KUNIT_CASE(dm_test_panel_power_savings_store_rejects_out_of_range),
+ KUNIT_CASE(dm_test_panel_power_savings_store_inactive_crtc),
+ KUNIT_CASE(dm_test_panel_power_savings_store_active_crtc),
+ KUNIT_CASE(dm_test_panel_power_savings_store_commit_fails),
/* amdgpu_dm_backlight_get_device_index */
KUNIT_CASE(dm_test_backlight_device_index_matches_second),
KUNIT_CASE(dm_test_backlight_device_index_missing_fallback),
--
2.56.0
^ permalink raw reply related [flat|nested] 6+ messages in thread