]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
drm/amd: Re-introduce property to control adaptive backlight modulation
authorMario Limonciello <mario.limonciello@amd.com>
Fri, 18 Jul 2025 19:20:45 +0000 (14:20 -0500)
committerAlex Deucher <alexander.deucher@amd.com>
Tue, 28 Oct 2025 13:56:14 +0000 (09:56 -0400)
commit 0887054d14ae ("drm/amd: Drop abm_level property") dropped the
abm level property in favor of sysfs control. Since then there have
been discussions that compositors showed an interest in modifying
a vendor specific property instead.

So re-introduce the abm level property, but with different semantics.
Rather than being an integer it's now an enum. One of the enum options
is 'sysfs', and that is because there is still a sysfs file for use by
userspace when the compositor doesn't support this property.

If usespace has not modified this property, the default value will
be for sysfs to control it. Once userspace has set the property stop
allowing sysfs control.

The property is only attached to non-OLED eDP panels.

Cc: Xaver Hugl <xaver.hugl@kde.org>
Reviewed-by: Harry Wentland <harry.wentland@amd.com>
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
drivers/gpu/drm/amd/amdgpu/amdgpu_display.c
drivers/gpu/drm/amd/amdgpu/amdgpu_display.h
drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h

index 51bab32fd8c6fcd41378744668be0bc578b5714e..c74b95bd41b510b444ae9992c24d8b53e884b215 100644 (file)
@@ -1365,6 +1365,64 @@ static const struct drm_prop_enum_list amdgpu_dither_enum_list[] = {
        { AMDGPU_FMT_DITHER_ENABLE, "on" },
 };
 
+/**
+ * DOC: property for adaptive backlight modulation
+ *
+ * The 'adaptive backlight modulation' property is used for the compositor to
+ * directly control the adaptive backlight modulation power savings feature
+ * that is part of DCN hardware.
+ *
+ * The property will be attached specifically to eDP panels that support it.
+ *
+ * The property is by default set to 'sysfs' to allow the sysfs file 'panel_power_savings'
+ * to be able to control it.
+ * If set to 'off' the compositor will ensure it stays off.
+ * The other values 'min', 'bias min', 'bias max', and 'max' will control the
+ * intensity of the power savings.
+ *
+ * Modifying this value can have implications on color accuracy, so tread
+ * carefully.
+ */
+static int amdgpu_display_setup_abm_prop(struct amdgpu_device *adev)
+{
+       const struct drm_prop_enum_list props[] = {
+               { ABM_SYSFS_CONTROL, "sysfs" },
+               { ABM_LEVEL_OFF, "off" },
+               { ABM_LEVEL_MIN, "min" },
+               { ABM_LEVEL_BIAS_MIN, "bias min" },
+               { ABM_LEVEL_BIAS_MAX, "bias max" },
+               { ABM_LEVEL_MAX, "max" },
+       };
+       struct drm_property *prop;
+       int i;
+
+       if (!adev->dc_enabled)
+               return 0;
+
+       prop = drm_property_create(adev_to_drm(adev), DRM_MODE_PROP_ENUM,
+                               "adaptive backlight modulation",
+                               6);
+       if (!prop)
+               return -ENOMEM;
+
+       for (i = 0; i < ARRAY_SIZE(props); i++) {
+               int ret;
+
+               ret = drm_property_add_enum(prop, props[i].type,
+                                               props[i].name);
+
+               if (ret) {
+                       drm_property_destroy(adev_to_drm(adev), prop);
+
+                       return ret;
+               }
+       }
+
+       adev->mode_info.abm_level_property = prop;
+
+       return 0;
+}
+
 int amdgpu_display_modeset_create_props(struct amdgpu_device *adev)
 {
        int sz;
@@ -1411,7 +1469,7 @@ int amdgpu_display_modeset_create_props(struct amdgpu_device *adev)
                                         "dither",
                                         amdgpu_dither_enum_list, sz);
 
-       return 0;
+       return amdgpu_display_setup_abm_prop(adev);
 }
 
 void amdgpu_display_update_priority(struct amdgpu_device *adev)
index 930c171473b4d69cac81f43278e20532fb87c3ef..49a29bf47a3793e396a4119452a24c9f98e0a0ad 100644 (file)
@@ -55,4 +55,11 @@ int amdgpu_display_resume_helper(struct amdgpu_device *adev);
 int amdgpu_display_get_scanout_buffer(struct drm_plane *plane,
                                      struct drm_scanout_buffer *sb);
 
+#define ABM_SYSFS_CONTROL      -1
+#define ABM_LEVEL_OFF          0
+#define ABM_LEVEL_MIN          1
+#define ABM_LEVEL_BIAS_MIN     2
+#define ABM_LEVEL_BIAS_MAX     3
+#define ABM_LEVEL_MAX          4
+
 #endif
index 20460cfd09bc211b185af7467cf9c2e6ddb07cb0..dc8d2f52c7d615a94a294929ae680560fb04bf60 100644 (file)
@@ -326,6 +326,8 @@ struct amdgpu_mode_info {
        struct drm_property *audio_property;
        /* FMT dithering */
        struct drm_property *dither_property;
+       /* Adaptive Backlight Modulation (power feature) */
+       struct drm_property *abm_level_property;
        /* hardcoded DFP edid from BIOS */
        const struct drm_edid *bios_hardcoded_edid;
 
index e3be2620f959c9180f420bf888f9a939b9c18175..5b446e4000efeb249351df1a7a74da0909832924 100644 (file)
@@ -5200,6 +5200,7 @@ static int initialize_plane(struct amdgpu_display_manager *dm,
 static void setup_backlight_device(struct amdgpu_display_manager *dm,
                                   struct amdgpu_dm_connector *aconnector)
 {
+       struct amdgpu_dm_backlight_caps *caps;
        struct dc_link *link = aconnector->dc_link;
        int bl_idx = dm->num_of_edps;
 
@@ -5219,6 +5220,13 @@ static void setup_backlight_device(struct amdgpu_display_manager *dm,
        dm->num_of_edps++;
 
        update_connector_ext_caps(aconnector);
+       caps = &dm->backlight_caps[aconnector->bl_idx];
+
+       /* Only offer ABM property when non-OLED and user didn't turn off by module parameter */
+       if (!caps->ext_caps->bits.oled && amdgpu_dm_abm_level < 0)
+               drm_object_attach_property(&aconnector->base.base,
+                                          dm->adev->mode_info.abm_level_property,
+                                          ABM_SYSFS_CONTROL);
 }
 
 static void amdgpu_set_panel_orientation(struct drm_connector *connector);
@@ -7291,6 +7299,20 @@ int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector,
        } else if (property == adev->mode_info.underscan_property) {
                dm_new_state->underscan_enable = val;
                ret = 0;
+       } else if (property == adev->mode_info.abm_level_property) {
+               switch (val) {
+               case ABM_SYSFS_CONTROL:
+                       dm_new_state->abm_sysfs_forbidden = false;
+                       break;
+               case ABM_LEVEL_OFF:
+                       dm_new_state->abm_sysfs_forbidden = true;
+                       dm_new_state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE;
+                       break;
+               default:
+                       dm_new_state->abm_sysfs_forbidden = true;
+                       dm_new_state->abm_level = val;
+               };
+               ret = 0;
        }
 
        return ret;
@@ -7333,6 +7355,13 @@ int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector,
        } else if (property == adev->mode_info.underscan_property) {
                *val = dm_state->underscan_enable;
                ret = 0;
+       } else if (property == adev->mode_info.abm_level_property) {
+               if (!dm_state->abm_sysfs_forbidden)
+                       *val = ABM_SYSFS_CONTROL;
+               else
+                       *val = (dm_state->abm_level != ABM_LEVEL_IMMEDIATE_DISABLE) ?
+                               dm_state->abm_level : 0;
+               ret = 0;
        }
 
        return ret;
@@ -7385,10 +7414,16 @@ static ssize_t panel_power_savings_store(struct device *device,
                return -EINVAL;
 
        drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
-       to_dm_connector_state(connector->state)->abm_level = val ?:
-               ABM_LEVEL_IMMEDIATE_DISABLE;
+       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);
 
+       if (ret)
+               return ret;
+
        drm_kms_helper_hotplug_event(dev);
 
        return count;
index db75e991ac7b07b8cc0019ce3b4ec8d7613ddbdf..5a7aa903bd3ce7ba771c7dbf62945478602f4d78 100644 (file)
@@ -993,6 +993,7 @@ struct dm_connector_state {
        bool underscan_enable;
        bool freesync_capable;
        bool update_hdcp;
+       bool abm_sysfs_forbidden;
        uint8_t abm_level;
        int vcpi_slots;
        uint64_t pbn;