The legacy backlight control interface can only be disabled when both the client and driver has connector support that the luminance can be set during a modeset. Add capability for the client to register.
When a luminance-aware client sets DRM_CLIENT_CAP_LUMINANCE, each DRM-connected backlight on the device is marked as taken over. Writes to the legacy /sys/class/backlight/<dev>/brightness attribute then return -EBUSY until the last luminance-aware client clears the cap or closes its DRM file. The takeover follows the active backlight_device when drm_backlight_link() retargets the link. Tested-by: Dmitry Baryshkov <[email protected]> # SM8150-HDK Signed-off-by: Mario Limonciello (AMD) <[email protected]> --- v5: * Add tag * Added special validation handling for the luminance property * Allows value 0 to be set even when the range starts at 1 * This enables DPMS to set luminance to 0 to turn off the display * Add documentation * Fix a compile error * Drop the driver capability - no longer needed! (yay) v4: * Update unit for luminance * Disable backlight of other connectors on same CRTC * handle CRTC disconnect * Make DRM_CLIENT_CAP_LUMINANCE actually inhibit legacy sysfs writes with -EBUSY f-luminance --- drivers/gpu/drm/drm_atomic_helper.c | 7 +++ drivers/gpu/drm/drm_atomic_uapi.c | 59 +++++++++++++++++++- drivers/gpu/drm/drm_backlight.c | 86 +++++++++++++++++++++++++++-- drivers/gpu/drm/drm_connector.c | 51 +++++++++++++++++ drivers/gpu/drm/drm_file.c | 5 ++ drivers/gpu/drm/drm_ioctl.c | 15 +++++ drivers/gpu/drm/drm_property.c | 6 ++ drivers/video/backlight/backlight.c | 7 +++ include/drm/drm_backlight.h | 2 +- include/drm/drm_connector.h | 5 ++ include/drm/drm_file.h | 8 +++ include/linux/backlight.h | 15 +++++ include/uapi/drm/drm.h | 22 ++++++++ 13 files changed, 280 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 34ed8e5d93d7..bf85e98f1dff 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -32,6 +32,7 @@ #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_uapi.h> +#include <drm/drm_backlight.h> #include <drm/drm_blend.h> #include <drm/drm_bridge.h> #include <drm/drm_colorop.h> @@ -1225,6 +1226,8 @@ drm_atomic_helper_commit_encoder_bridge_disable(struct drm_device *dev, * it away), so we won't call disable hooks twice. */ bridge = drm_bridge_chain_get_first_bridge(encoder); + if (connector->backlight) + drm_backlight_set_luminance(connector->backlight, 0); drm_atomic_bridge_chain_disable(bridge, state); drm_bridge_put(bridge); @@ -1737,6 +1740,10 @@ drm_atomic_helper_commit_encoder_bridge_enable(struct drm_device *dev, struct dr drm_atomic_bridge_chain_enable(bridge, state); drm_bridge_put(bridge); + + if (connector->backlight && connector->state) + drm_backlight_set_luminance(connector->backlight, + connector->state->luminance); } } EXPORT_SYMBOL(drm_atomic_helper_commit_encoder_bridge_enable); diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index 5bd5bf6661df..64cd0830beb7 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -30,6 +30,8 @@ #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_uapi.h> +#include <drm/drm_backlight.h> +#include <drm/drm_connector.h> #include <drm/drm_framebuffer.h> #include <drm/drm_print.h> #include <drm/drm_drv.h> @@ -935,6 +937,14 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector, state->privacy_screen_sw_state = val; } else if (property == connector->broadcast_rgb_property) { state->hdmi.broadcast_rgb = val; + } else if (property == config->luminance_property) { + state->luminance = val; + /* Update hardware backlight only when DPMS is ON. + * Property value is always updated to remember the user's + * desired brightness. + */ + if (connector->dpms == DRM_MODE_DPMS_ON) + drm_backlight_set_luminance(connector->backlight, val); } else if (connector->funcs->atomic_set_property) { return connector->funcs->atomic_set_property(connector, state, property, val); @@ -1020,6 +1030,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector, *val = state->privacy_screen_sw_state; } else if (property == connector->broadcast_rgb_property) { *val = state->hdmi.broadcast_rgb; + } else if (property == config->luminance_property) { + *val = state->luminance; } else if (connector->funcs->atomic_get_property) { return connector->funcs->atomic_get_property(connector, state, property, val); @@ -1104,6 +1116,31 @@ static struct drm_pending_vblank_event *create_vblank_event( return e; } +static void drm_atomic_connector_set_backlight(struct drm_connector *connector, + unsigned int luminance) +{ + if (!connector->backlight) + return; + + drm_backlight_set_luminance(connector->backlight, luminance); +} + +static void drm_atomic_crtc_set_backlight(struct drm_crtc *crtc, bool active) +{ + struct drm_connector_list_iter conn_iter; + struct drm_connector *connector; + + 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; + + drm_atomic_connector_set_backlight(connector, + active ? connector->state->luminance : 0); + } + drm_connector_list_iter_end(&conn_iter); +} + int drm_atomic_connector_commit_dpms(struct drm_atomic_state *state, struct drm_connector *connector, int mode) @@ -1126,9 +1163,29 @@ int drm_atomic_connector_commit_dpms(struct drm_atomic_state *state, if (connector->dpms == mode) goto out; + crtc = connector->state ? connector->state->crtc : NULL; + + /* Handle backlight brightness coordination with DPMS state changes */ + if (old_mode != DRM_MODE_DPMS_OFF && mode == DRM_MODE_DPMS_OFF) { + /* DPMS ON -> OFF: dim all connectors driven by this CRTC. */ + if (crtc) + drm_atomic_crtc_set_backlight(crtc, false); + else + drm_atomic_connector_set_backlight(connector, 0); + } + connector->dpms = mode; - crtc = connector->state->crtc; + /* DPMS OFF -> ON: restore brightness to property value */ + if (old_mode == DRM_MODE_DPMS_OFF && mode == DRM_MODE_DPMS_ON && + connector->state) { + if (crtc) + drm_atomic_crtc_set_backlight(crtc, true); + else + drm_atomic_connector_set_backlight(connector, + connector->state->luminance); + } + if (!crtc) goto out; ret = drm_atomic_add_affected_connectors(state, crtc); diff --git a/drivers/gpu/drm/drm_backlight.c b/drivers/gpu/drm/drm_backlight.c index b1ec470be86c..2cddf209d508 100644 --- a/drivers/gpu/drm/drm_backlight.c +++ b/drivers/gpu/drm/drm_backlight.c @@ -71,6 +71,7 @@ static bool __drm_backlight_is_registered(struct drm_backlight *b) /* caller must hold @drm_backlight_lock */ static void __drm_backlight_real_changed(struct drm_backlight *b, uint64_t v) { + struct drm_connector *connector = b->connector; unsigned int max, set; lockdep_assert_held(&drm_backlight_lock); @@ -85,6 +86,15 @@ static void __drm_backlight_real_changed(struct drm_backlight *b, uint64_t v) set = v; if (set >= max) set = max; + + /* Update the atomic state directly. + * For atomic drivers, the luminance value is stored in + * connector->state->luminance, not in the legacy property array. + * We update it unconditionally to reflect the hardware state, + * regardless of DPMS. + */ + if (connector->state) + connector->state->luminance = set; } /** @@ -100,18 +110,22 @@ static void __drm_backlight_update_prop_range(struct drm_backlight *b) struct drm_device *dev = b->connector->dev; struct drm_property *prop = dev->mode_config.luminance_property; unsigned int max = 0; + bool can_disable = false; lockdep_assert_held(&drm_backlight_lock); - if (b->link && b->link->props.max_brightness > 0) + if (b->link && b->link->props.max_brightness > 0) { max = b->link->props.max_brightness; + can_disable = b->link->props.can_disable; + } /* Update property range to match hardware capabilities. * Range of 0-0 indicates no backing device. - * Range of 1-max for normal operation (0 reserved for display off). + * Range of 1-max for normal operation. + * Range of 0-max means that the display would turn off at 0 */ if (prop->values[1] != max) { - prop->values[0] = max ? 1 : 0; + prop->values[0] = max ? (can_disable ? 0 : 1) : 0; prop->values[1] = max; } } @@ -123,6 +137,16 @@ static bool __drm_backlight_link(struct drm_backlight *b, if (bd == b->link) return false; + /* Transfer any DRM legacy-sysfs takeover from the old link to the + * new one so the inhibit follows the active backlight_device. + */ + if (b->luminance_clients) { + if (b->link) + atomic_sub(b->luminance_clients, &b->link->drm_takeover); + if (bd) + atomic_add(b->luminance_clients, &bd->drm_takeover); + } + backlight_device_unref(b->link); b->link = bd; backlight_device_ref(b->link); @@ -177,6 +201,7 @@ void drm_backlight_free(struct drm_connector *connector) WARN_ON(__drm_backlight_is_registered(b)); WARN_ON(b->link); + WARN_ON(b->luminance_clients); kfree(b); connector->backlight = NULL; @@ -228,11 +253,18 @@ EXPORT_SYMBOL(drm_backlight_unregister); */ void drm_backlight_link(struct drm_backlight *b, struct backlight_device *bd) { + static const char * const ep[] = { "BACKLIGHT=1", NULL }; + bool send_uevent = false; + if (!b) return; guard(spinlock)(&drm_backlight_lock); - __drm_backlight_link(b, bd); + send_uevent = __drm_backlight_link(b, bd); + + if (send_uevent) + kobject_uevent_env(&b->connector->kdev->kobj, KOBJ_CHANGE, + (char **)ep); } EXPORT_SYMBOL(drm_backlight_link); @@ -269,6 +301,11 @@ void drm_backlight_inhibit_legacy(struct drm_backlight *b) { if (!b) return; + + guard(spinlock)(&drm_backlight_lock); + b->luminance_clients++; + if (b->link) + atomic_inc(&b->link->drm_takeover); } EXPORT_SYMBOL(drm_backlight_inhibit_legacy); @@ -283,6 +320,13 @@ void drm_backlight_uninhibit_legacy(struct drm_backlight *b) { if (!b) return; + + guard(spinlock)(&drm_backlight_lock); + if (WARN_ON(b->luminance_clients == 0)) + return; + b->luminance_clients--; + if (b->link) + atomic_dec(&b->link->drm_takeover); } EXPORT_SYMBOL(drm_backlight_uninhibit_legacy); @@ -324,8 +368,38 @@ EXPORT_SYMBOL(drm_backlight_uninhibit_legacy_all); void drm_backlight_set_luminance(struct drm_backlight *b, unsigned int value) { - guard(spinlock)(&drm_backlight_lock); - __drm_backlight_real_changed(b, value); + struct backlight_device *bd = NULL; + unsigned int set = 0; + unsigned long flags; + unsigned int max = 0; + + spin_lock_irqsave(&drm_backlight_lock, flags); + if (b && b->link) { + struct backlight_device *link = b->link; + + max = b->link->props.max_brightness; + + if (max == 0) + goto out; + + set = min(value, max); + if (set == link->props.brightness) + goto out; + + bd = link; + backlight_device_ref(bd); + } +out: + spin_unlock_irqrestore(&drm_backlight_lock, flags); + + if (bd) { + int rc = backlight_set_brightness(bd, set, BACKLIGHT_UPDATE_DRM); + + WARN_ON(rc); + if (rc) + backlight_set_brightness(bd, max, BACKLIGHT_UPDATE_DRM); + backlight_device_unref(bd); + } } EXPORT_SYMBOL(drm_backlight_set_luminance); diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 09bb790782f1..224661587241 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -1512,6 +1512,57 @@ EXPORT_SYMBOL(drm_hdmi_connector_get_output_format_name); * Summarizing: Only set "DPMS" when the connector is known to be enabled, * assume that a successful SETCONFIG call also sets "DPMS" to on, and * never read back the value of "DPMS" because it can be incorrect. + * LUMINANCE: + * Atomic property for controlling the backlight brightness level of the + * connector's display. This property provides unified access to the display + * backlight, replacing the legacy sysfs interface for brightness control. + * + * The property value is an unsigned integer representing the brightness level. + * The valid range is dynamically determined by the capabilities of the + * connected backlight hardware and is exposed through the property's minimum + * and maximum values: + * + * - Range 0-0: No backlight device is available for this connector. + * - Range 1-N: Normal operation. Values from 1 to N (max_brightness) are + * valid brightness levels, where 1 is the minimum visible brightness and + * N is the maximum brightness the hardware supports. + * - Value 0: Special value to turn off the display backlight completely. + * This value is accepted even when the normal range starts at 1. + * + * The range may change during runtime if a new backlight device is linked + * or unlinked. The kernel will send a change uevent when this occurs. + * + * Setting LUMINANCE to 0 turns off the backlight, which may turn off the + * display completely depending on the hardware. Setting it to any value + * from 1 to N adjusts the brightness accordingly. Reading this property + * returns the current brightness level that was last set (or the hardware's + * current state for drivers that support reading actual brightness). + * + * For atomic drivers, the luminance value is stored in + * &drm_connector_state.luminance. The actual hardware update only occurs + * when the connector is active (DPMS is ON). When DPMS transitions to OFF, + * the kernel automatically sets luminance to 0 to turn off the backlight. + * When DPMS transitions back to ON, the kernel restores the previously + * set luminance value. + * + * This property is only available on connectors that have an associated + * backlight device. The property is created by calling drm_backlight_alloc() + * during connector initialization. + * + * Client Capability: + * User-space must set the DRM_CLIENT_CAP_LUMINANCE client capability + * to 1 before using this property. When this capability is enabled, + * the legacy sysfs backlight interface is inhibited to prevent + * conflicts between multiple clients trying to control the same + * backlight. This ensures that only luminance-aware clients control + * the backlight through the DRM atomic interface. + * + * Legacy clients that do not set this capability will not see the + * LUMINANCE property and should continue using the sysfs interface + * (if available). + * + * Note: This property can be set through MODE_ATOMIC ioctl as part of the + * atomic state. * panel_type: * Immutable enum property to indicate the type of connected panel. * Possible values are "unknown" (default) and "OLED". diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c index ec820686b302..4d2520de7614 100644 --- a/drivers/gpu/drm/drm_file.c +++ b/drivers/gpu/drm/drm_file.c @@ -41,6 +41,7 @@ #include <linux/slab.h> #include <linux/vga_switcheroo.h> +#include <drm/drm_backlight.h> #include <drm/drm_client_event.h> #include <drm/drm_drv.h> #include <drm/drm_file.h> @@ -252,6 +253,10 @@ void drm_file_free(struct drm_file *file) if (drm_core_check_feature(dev, DRIVER_MODESET)) { drm_fb_release(file); drm_property_destroy_user_blobs(dev, file); + if (file->supports_luminance_control) { + drm_backlight_uninhibit_legacy_all(dev); + file->supports_luminance_control = false; + } } if (drm_core_check_feature(dev, DRIVER_SYNCOBJ)) diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c index ff193155129e..fdae36b13300 100644 --- a/drivers/gpu/drm/drm_ioctl.c +++ b/drivers/gpu/drm/drm_ioctl.c @@ -28,12 +28,14 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include "drm/drm.h" #include <linux/export.h> #include <linux/nospec.h> #include <linux/pci.h> #include <linux/uaccess.h> #include <drm/drm_auth.h> +#include <drm/drm_backlight.h> #include <drm/drm_crtc.h> #include <drm/drm_drv.h> #include <drm/drm_file.h> @@ -380,6 +382,19 @@ drm_setclientcap(struct drm_device *dev, void *data, struct drm_file *file_priv) return -EINVAL; file_priv->plane_color_pipeline = req->value; break; + case DRM_CLIENT_CAP_LUMINANCE: + if (!file_priv->atomic) + return -EINVAL; + if (req->value > 1) + return -EINVAL; + if (req->value == file_priv->supports_luminance_control) + break; + if (req->value) + drm_backlight_inhibit_legacy_all(dev); + else + drm_backlight_uninhibit_legacy_all(dev); + file_priv->supports_luminance_control = req->value; + break; default: return -EINVAL; } diff --git a/drivers/gpu/drm/drm_property.c b/drivers/gpu/drm/drm_property.c index f38f2c5437e6..4475896c963b 100644 --- a/drivers/gpu/drm/drm_property.c +++ b/drivers/gpu/drm/drm_property.c @@ -952,6 +952,12 @@ bool drm_property_change_valid_get(struct drm_property *property, *ref = NULL; if (drm_property_type_is(property, DRM_MODE_PROP_RANGE)) { + /* Special case for luminance property: allow 0 to turn off display + * even when the normal range starts at 1. + */ + if (property == property->dev->mode_config.luminance_property && + value == 0 && property->values[1] > 0) + return true; if (value < property->values[0] || value > property->values[1]) return false; return true; diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c index 13954c2220b7..40cfc2296445 100644 --- a/drivers/video/backlight/backlight.c +++ b/drivers/video/backlight/backlight.c @@ -217,6 +217,13 @@ static ssize_t brightness_store(struct device *dev, struct backlight_device *bd = to_backlight_device(dev); unsigned long brightness; + /* A luminance-aware DRM client has taken over this backlight; the + * legacy sysfs interface is disabled until the last such client + * goes away. + */ + if (atomic_read(&bd->drm_takeover) > 0) + return -EBUSY; + rc = kstrtoul(buf, 0, &brightness); if (rc) return rc; diff --git a/include/drm/drm_backlight.h b/include/drm/drm_backlight.h index e0e09e38f7c0..2af48be3aa37 100644 --- a/include/drm/drm_backlight.h +++ b/include/drm/drm_backlight.h @@ -43,9 +43,9 @@ void drm_backlight_unregister(struct drm_backlight *b); void drm_backlight_link(struct drm_backlight *b, struct backlight_device *bd); struct backlight_device *drm_backlight_get_device(struct drm_backlight *b); -void drm_backlight_set_luminance(struct drm_backlight *b, unsigned int value); void drm_backlight_inhibit_legacy(struct drm_backlight *b); void drm_backlight_uninhibit_legacy(struct drm_backlight *b); void drm_backlight_inhibit_legacy_all(struct drm_device *dev); void drm_backlight_uninhibit_legacy_all(struct drm_device *dev); +void drm_backlight_set_luminance(struct drm_backlight *b, unsigned int value); #endif /* __DRM_BACKLIGHT_H__ */ diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 10daf088b8f1..dcb7dd0bdf44 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1209,6 +1209,11 @@ struct drm_connector_state { * @drm_atomic_helper_connector_hdmi_check(). */ struct drm_connector_hdmi_state hdmi; + + /** + * @luminance: Luminance for the connector + */ + unsigned int luminance; }; struct drm_connector_hdmi_audio_funcs { diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h index 6ee70ad65e1f..0bb1e53f36be 100644 --- a/include/drm/drm_file.h +++ b/include/drm/drm_file.h @@ -248,6 +248,14 @@ struct drm_file { */ bool supports_virtualized_cursor_plane; + /** + * @supports_luminance_control: + * + * This client is capable of setting the luminance for connectors. + * + */ + bool supports_luminance_control; + /** * @master: * diff --git a/include/linux/backlight.h b/include/linux/backlight.h index 26a7281d179c..b03737ee8dac 100644 --- a/include/linux/backlight.h +++ b/include/linux/backlight.h @@ -197,6 +197,13 @@ struct backlight_properties { */ int brightness; + /** + * @can_disable: Minimum brightness would turn off the panel. + * + * Setting minimum brightness turns off the panel. + */ + bool can_disable; + /** * @max_brightness: The maximum brightness value. * @@ -314,6 +321,14 @@ struct backlight_device { * @use_count: The number of unblanked displays. */ int use_count; + + /** + * @drm_takeover: Number of luminance-aware DRM clients that have + * taken over brightness control of this device. When non-zero, + * writes to the legacy sysfs ``brightness`` attribute return + * ``-EBUSY``. Managed by the DRM backlight helpers. + */ + atomic_t drm_takeover; }; /* Forward declaration for backlight_update_status */ diff --git a/include/uapi/drm/drm.h b/include/uapi/drm/drm.h index 27cc159c1d27..1b17fd2d3b2c 100644 --- a/include/uapi/drm/drm.h +++ b/include/uapi/drm/drm.h @@ -921,6 +921,28 @@ struct drm_get_cap { */ #define DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE 7 +/** + * DRM_CLIENT_CAP_LUMINANCE + * + * If set to 1, the client declares support for the LUMINANCE connector property + * and will control backlight brightness through the DRM atomic interface. This + * enables the kernel to expose the LUMINANCE property on connectors that have + * an associated backlight device. + * + * When this capability is enabled: + * - The LUMINANCE property becomes visible on supported connectors + * - Legacy sysfs writes to /sys/class/backlight/{*}/brightness will return + * -EBUSY to prevent conflicts with DRM-based brightness control + * - The client should include luminance values as part of atomic commits + * - Brightness changes are synchronized with display power state (DPMS) + * + * The LUMINANCE property accepts values from 0 to max_brightness, where 0 turns + * off the backlight, and 1 to max_brightness control the brightness level. + * + * This capability is supported starting in kernel 7.2. + */ +#define DRM_CLIENT_CAP_LUMINANCE 8 + /* DRM_IOCTL_SET_CLIENT_CAP ioctl argument type */ struct drm_set_client_cap { __u64 capability; -- 2.54.0
