On Mon, 07 Sep 2026, Mario Limonciello <[email protected]> wrote:
> Backlight brightness is a property of a display, and thus of a DRM
> connector, yet it has historically only been controllable through the
> separate backlight sysfs interface. Add a generic, backend-agnostic
> per-connector LUMINANCE range property so brightness can be driven
> through the atomic modeset path like any other connector state.

This might be a good opportunity to call it "drm brightness" and hide
the "backlight" implementation detail.

BR,
Jani.

>
> A struct drm_backlight is embedded in every connector and initialized by
> the core; drivers do not allocate it. A driver links a backend (today a
> backlight_device, in the future DDC/CI, MIPI-DCS, ...) with
> drm_backlight_link(), which creates the connector's LUMINANCE property
> with the backend's range. The property value is staged into the atomic
> connector state and only pushed to the hardware from the commit/enable
> path, via a workqueue so that slow backends never stall a commit. DPMS
> off drives the backlight to 0 and DPMS on restores the committed value.
>
> The property range is per-connector (created from the backend's
> max_brightness), so multiple panels no longer share and corrupt a single
> device-wide range. drm_backlight_link() also carries the legacy-sysfs
> takeover accounting used by the client capability added in a later patch.
>
> The whole feature is guarded by CONFIG_DRM_BACKLIGHT (which depends on,
> rather than selects, BACKLIGHT_CLASS_DEVICE) so DRM does not pull the
> backlight subsystem into the kernel when it is not wanted.
>
> Co-developed-by: David Herrmann <[email protected]>
> Signed-off-by: David Herrmann <[email protected]>
> Signed-off-by: Mario Limonciello (AMD) <[email protected]>
> ---
>  drivers/gpu/drm/Kconfig             |  18 +
>  drivers/gpu/drm/Makefile            |   2 +
>  drivers/gpu/drm/drm_atomic_helper.c |  35 ++
>  drivers/gpu/drm/drm_atomic_uapi.c   |  49 ++-
>  drivers/gpu/drm/drm_backlight.c     | 550 ++++++++++++++++++++++++++++
>  drivers/gpu/drm/drm_connector.c     |  56 +++
>  drivers/gpu/drm/drm_drv.c           |   8 +
>  drivers/gpu/drm/drm_mode_config.c   |   1 +
>  drivers/gpu/drm/drm_sysfs.c         |  32 +-
>  include/drm/drm_atomic_helper.h     |   2 +
>  include/drm/drm_backlight.h         | 166 +++++++++
>  include/drm/drm_connector.h         |  20 +
>  include/drm/drm_mode_config.h       |  11 +
>  13 files changed, 945 insertions(+), 5 deletions(-)
>  create mode 100644 drivers/gpu/drm/drm_backlight.c
>  create mode 100644 include/drm/drm_backlight.h
>
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 323422861e8f6..ddb6827d613f8 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -33,6 +33,24 @@ endmenu
>  
>  if DRM
>  
> +config DRM_BACKLIGHT
> +     bool "DRM connector backlight (luminance) support"
> +     depends on DRM
> +     depends on BACKLIGHT_CLASS_DEVICE
> +     default DRM
> +     help
> +       Expose per-connector backlight brightness control through the DRM
> +       connector LUMINANCE property, backed by the backlight subsystem (and,
> +       in the future, other backends such as DDC/CI). This lets luminance
> +       changes go through the same atomic modeset path as the rest of the
> +       display state.
> +
> +       This depends on BACKLIGHT_CLASS_DEVICE. When that is disabled, DRM is
> +       built without this support and does not pull the backlight subsystem
> +       into the kernel.
> +
> +       If in doubt, say Y.
> +
>  config DRM_MIPI_DBI
>       tristate
>       depends on DRM
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index e635fcffd3790..bcb06dbb71343 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -78,6 +78,8 @@ drm-$(CONFIG_DRM_CLIENT) += \
>       drm_client_event.o \
>       drm_client_modeset.o \
>       drm_client_sysrq.o
> +drm-$(CONFIG_DRM_BACKLIGHT) += drm_backlight.o
> +drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
>  drm-$(CONFIG_COMPAT) += drm_ioc32.o
>  drm-$(CONFIG_DRM_PANEL) += drm_panel.o
>  drm-$(CONFIG_OF) += drm_of.o
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c 
> b/drivers/gpu/drm/drm_atomic_helper.c
> index 285aac3554dfd..110cd81e5d6db 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -33,6 +33,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>
> @@ -1230,6 +1231,10 @@ 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);
> +
> +             /* Turn the backlight off before disabling the pipeline. */
> +             drm_backlight_set_luminance(connector, 0);
> +
>               drm_atomic_bridge_chain_disable(bridge, state);
>               drm_bridge_put(bridge);
>  
> @@ -1775,6 +1780,24 @@ void drm_atomic_helper_commit_modeset_enables(struct 
> drm_device *dev,
>  }
>  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
>  
> +/**
> + * drm_atomic_helper_connector_apply_luminance - apply connector luminance 
> from atomic state
> + * @conn_state: atomic connector state to apply luminance for
> + *
> + * Updates the backlight luminance from the atomic connector state. If the
> + * connector has a linked backlight and is associated with an active CRTC,
> + * push the luminance value to hardware.
> + */
> +void drm_atomic_helper_connector_apply_luminance(const struct 
> drm_connector_state *conn_state)
> +{
> +     struct drm_connector *connector = conn_state->connector;
> +
> +     if (conn_state->crtc && conn_state->crtc->state &&
> +         conn_state->crtc->state->active)
> +             drm_backlight_set_luminance(connector, conn_state->luminance);
> +}
> +EXPORT_SYMBOL(drm_atomic_helper_connector_apply_luminance);
> +
>  /*
>   * For atomic updates which touch just a single CRTC, calculate the time of 
> the
>   * next vblank, and inform all the fences of the deadline.
> @@ -1989,6 +2012,9 @@ EXPORT_SYMBOL(drm_atomic_helper_wait_for_flip_done);
>  void drm_atomic_helper_commit_tail(struct drm_atomic_commit *state)
>  {
>       struct drm_device *dev = state->dev;
> +     struct drm_connector *connector;
> +     struct drm_connector_state *new_conn_state;
> +     int i;
>  
>       drm_atomic_helper_commit_modeset_disables(dev, state);
>  
> @@ -1996,6 +2022,9 @@ void drm_atomic_helper_commit_tail(struct 
> drm_atomic_commit *state)
>  
>       drm_atomic_helper_commit_modeset_enables(dev, state);
>  
> +     for_each_new_connector_in_state(state, connector, new_conn_state, i)
> +             drm_atomic_helper_connector_apply_luminance(new_conn_state);
> +
>       drm_atomic_helper_fake_vblank(state);
>  
>       drm_atomic_helper_commit_hw_done(state);
> @@ -2019,11 +2048,17 @@ EXPORT_SYMBOL(drm_atomic_helper_commit_tail);
>  void drm_atomic_helper_commit_tail_rpm(struct drm_atomic_commit *state)
>  {
>       struct drm_device *dev = state->dev;
> +     struct drm_connector *connector;
> +     struct drm_connector_state *new_conn_state;
> +     int i;
>  
>       drm_atomic_helper_commit_modeset_disables(dev, state);
>  
>       drm_atomic_helper_commit_modeset_enables(dev, state);
>  
> +     for_each_new_connector_in_state(state, connector, new_conn_state, i)
> +             drm_atomic_helper_connector_apply_luminance(new_conn_state);
> +
>       drm_atomic_helper_commit_planes(dev, state,
>                                       DRM_PLANE_COMMIT_ACTIVE_ONLY);
>  
> diff --git a/drivers/gpu/drm/drm_atomic_uapi.c 
> b/drivers/gpu/drm/drm_atomic_uapi.c
> index 5ea593b3a98ec..07d4bcb16cd23 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>
> @@ -962,6 +964,13 @@ static int drm_atomic_connector_set_property(struct 
> drm_connector *connector,
>               state->hdmi.broadcast_rgb = val;
>       } else if (property == connector->color_format_property) {
>               state->color_format = val;
> +     } else if (property == connector->luminance_property) {
> +             /*
> +              * Only stage the value into the atomic state; the hardware is
> +              * updated from the commit path (see
> +              * drm_atomic_helper_connector_apply_luminance()).
> +              */
> +             state->luminance = val;
>       } else if (connector->funcs->atomic_set_property) {
>               return connector->funcs->atomic_set_property(connector,
>                               state, property, val);
> @@ -1049,6 +1058,8 @@ drm_atomic_connector_get_property(struct drm_connector 
> *connector,
>               *val = state->hdmi.broadcast_rgb;
>       } else if (property == connector->color_format_property) {
>               *val = state->color_format;
> +     } else if (property == connector->luminance_property) {
> +             *val = state->luminance;
>       } else if (connector->funcs->atomic_get_property) {
>               return connector->funcs->atomic_get_property(connector,
>                               state, property, val);
> @@ -1133,6 +1144,22 @@ static struct drm_pending_vblank_event 
> *create_vblank_event(
>       return e;
>  }
>  
> +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_backlight_set_luminance(connector,
> +                                         active ? 
> connector->state->luminance : 0);
> +     }
> +     drm_connector_list_iter_end(&conn_iter);
> +}
> +
>  int drm_atomic_connector_commit_dpms(struct drm_atomic_commit *state,
>                                    struct drm_connector *connector,
>                                    int mode)
> @@ -1155,9 +1182,29 @@ int drm_atomic_connector_commit_dpms(struct 
> drm_atomic_commit *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_backlight_set_luminance(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_backlight_set_luminance(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
> new file mode 100644
> index 0000000000000..dd02d1878727e
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_backlight.c
> @@ -0,0 +1,550 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * DRM Backlight Helpers
> + * Copyright (c) 2014 David Herrmann
> + * Copyright (c) 2026 Advanced Micro Devices, Inc.
> + */
> +
> +#include <linux/backlight.h>
> +#include <linux/list.h>
> +#include <linux/notifier.h>
> +#include <linux/spinlock.h>
> +#include <linux/workqueue.h>
> +
> +#include <drm/drm_backlight.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_device.h>
> +#include <drm/drm_property.h>
> +
> +/**
> + * DOC: Backlight Devices
> + *
> + * Backlight devices have always been managed as a separate subsystem,
> + * independent of DRM. They are usually controlled via separate hardware
> + * interfaces than the display controller, so the split works out fine.
> + * However, backlight brightness is a property of a display, and thus a
> + * property of a DRM connector. We already manage DPMS states via connector
> + * properties, so it is natural to keep brightness control at the same place.
> + *
> + * This DRM backlight interface implements a generic per-connector LUMINANCE
> + * property. The core is backend-agnostic: it does not talk to any hardware
> + * itself, it only forwards luminance requests to a backend that a driver has
> + * linked. The backend is described by a &struct drm_backlight_funcs. Today 
> the
> + * only backend is the backlight subsystem (&struct backlight_device), linked
> + * with drm_backlight_link(), but other backends (DDC/CI, MIPI-DCS, ...) can 
> be
> + * added by providing a different set of operations without changing the 
> core.
> + *
> + * A &struct drm_backlight is embedded in every &struct drm_connector and
> + * initialized by the DRM core (drm_backlight_connector_init()); drivers do 
> not
> + * allocate it. Drivers link a backend once it is available by calling
> + * drm_backlight_link(); this creates the connector's LUMINANCE property with
> + * the backend's range. Passing NULL unlinks the backend. Hardware is only 
> ever
> + * touched from a workqueue, so slow backends never stall an atomic commit.
> + */
> +
> +static LIST_HEAD(drm_backlight_list);
> +static DEFINE_SPINLOCK(drm_backlight_lock);
> +
> +/* caller must hold @drm_backlight_lock */
> +static bool __drm_backlight_is_linked(struct drm_backlight *b)
> +{
> +     lockdep_assert_held(&drm_backlight_lock);
> +     /* a backlight is live while it is on @drm_backlight_list */
> +     return !list_empty(&b->list);
> +}
> +
> +/*
> + * Return the linked &backlight_device if the current backend is the 
> backlight
> + * subsystem, or NULL otherwise. The legacy-sysfs takeover accounting only
> + * applies to that backend.
> + */
> +static const struct drm_backlight_funcs drm_backlight_bd_funcs;
> +
> +static struct backlight_device *drm_backlight_bd(struct drm_backlight *b)
> +{
> +     if (b->funcs != &drm_backlight_bd_funcs)
> +             return NULL;
> +     return b->backend;
> +}
> +
> +/* caller must hold @drm_backlight_lock */
> +static void __drm_backlight_schedule(struct drm_backlight *b)
> +{
> +     lockdep_assert_held(&drm_backlight_lock);
> +     if (__drm_backlight_is_linked(b))
> +             schedule_work(&b->work);
> +}
> +
> +static void __drm_backlight_worker(struct work_struct *w)
> +{
> +     struct drm_backlight *b = container_of(w, struct drm_backlight, work);
> +     static char *ep[] = { "BACKLIGHT=1", NULL };
> +     const struct drm_backlight_funcs *funcs;
> +     bool send_uevent;
> +     unsigned int v;
> +
> +     scoped_guard(spinlock, &drm_backlight_lock) {
> +             send_uevent = b->changed;
> +             b->changed = false;
> +             v = b->set_value;
> +             funcs = b->funcs;
> +     }
> +
> +     /*
> +      * The backend stays valid here: drm_backlight_do_unlink() clears
> +      * @backend and drops its reference only after cancel_work_sync(), so an
> +      * in-flight worker always sees a live backend.
> +      */
> +     if (funcs && funcs->set_luminance)
> +             WARN_ON(funcs->set_luminance(b, v));
> +
> +     if (send_uevent && b->connector->kdev)
> +             kobject_uevent_env(&b->connector->kdev->kobj, KOBJ_CHANGE, ep);
> +}
> +
> +/* caller must hold @drm_backlight_lock */
> +static void __drm_backlight_set(struct drm_backlight *b, unsigned int v)
> +{
> +     unsigned int max = 0;
> +     bool can_disable = false;
> +
> +     lockdep_assert_held(&drm_backlight_lock);
> +
> +     if (!b->funcs || !b->funcs->get_range)
> +             return;
> +
> +     b->funcs->get_range(b, &max, &can_disable);
> +     if (!max)
> +             return;
> +
> +     /* clamp to the backend maximum */
> +     b->set_value = min(v, max);
> +     __drm_backlight_schedule(b);
> +}
> +
> +/* caller must hold @drm_backlight_lock */
> +static void __drm_backlight_readback(struct drm_backlight *b, unsigned int v)
> +{
> +     struct drm_connector *connector = b->connector;
> +     unsigned int max = 0;
> +     bool can_disable = false;
> +
> +     lockdep_assert_held(&drm_backlight_lock);
> +
> +     if (!b->funcs || !b->funcs->get_range)
> +             return;
> +
> +     b->funcs->get_range(b, &max, &can_disable);
> +     if (!max)
> +             return;
> +
> +     /*
> +      * Reflect a hardware-side brightness change (firmware hotkeys, or a
> +      * legacy sysfs write while not inhibited) back into the connector's
> +      * committed luminance so a read-back returns the real value.
> +      */
> +     if (connector->state)
> +             connector->state->luminance = min(v, max);
> +}
> +
> +/**
> + * drm_backlight_create_property - create and attach a connector's LUMINANCE 
> property
> + * @connector: connector to modify
> + * @max: maximum luminance value
> + * @can_disable: true if luminance 0 disables the backlight
> + *
> + * Create and attach the per-connector LUMINANCE property. Must be called 
> during
> + * connector initialization before the DRM device is registered.
> + *
> + * Returns: 0 on success, or a negative error code.
> + */
> +int drm_backlight_create_property(struct drm_connector *connector,
> +                               unsigned int max, bool can_disable)
> +{
> +     struct drm_device *dev = connector->dev;
> +     struct drm_property *prop;
> +     unsigned int min = can_disable ? 0 : 1;
> +
> +     if (!dev || !connector->base.properties || 
> connector->luminance_property)
> +             return 0;
> +
> +     prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
> +                                      "LUMINANCE", min, max);
> +     if (!prop)
> +             return -ENOMEM;
> +
> +     prop->is_luminance = true;
> +     connector->luminance_property = prop;
> +     drm_object_attach_property(&connector->base, prop, min);
> +     if (connector->state)
> +             connector->state->luminance = min;
> +
> +     return 0;
> +}
> +EXPORT_SYMBOL(drm_backlight_create_property);
> +
> +static void drm_backlight_do_unlink(struct drm_backlight *b)
> +{
> +     struct backlight_device *bd;
> +     unsigned int clients;
> +
> +     /*
> +      * Stop new work first, but leave @backend in place so an in-flight
> +      * worker keeps a valid backend to operate on. Capture the linked
> +      * backlight_device (if any) while @funcs is still set.
> +      */
> +     scoped_guard(spinlock, &drm_backlight_lock) {
> +             if (!b->funcs)
> +                     return;
> +             bd = drm_backlight_bd(b);
> +             clients = b->luminance_clients;
> +             b->funcs = NULL;
> +             list_del_init(&b->list);
> +     }
> +
> +     cancel_work_sync(&b->work);
> +
> +     scoped_guard(spinlock, &drm_backlight_lock) {
> +             if (clients && bd)
> +                     atomic_sub(clients, &bd->drm_takeover);
> +             /*
> +              * Only clear @backend if no concurrent drm_backlight_link()
> +              * re-linked a new backend after we dropped the lock for
> +              * cancel_work_sync(); otherwise we would clobber it.
> +              */
> +             if (!b->funcs)
> +                     b->backend = NULL;
> +     }
> +
> +     backlight_device_unref(bd);
> +}
> +
> +/**
> + * drm_backlight_connector_init - initialize a connector's embedded backlight
> + * @connector: connector to initialize
> + *
> + * Called by the DRM core from drm_connector_init(). Drivers never call this.
> + */
> +void drm_backlight_connector_init(struct drm_connector *connector)
> +{
> +     struct drm_backlight *b = &connector->backlight;
> +
> +     b->connector = connector;
> +     INIT_LIST_HEAD(&b->list);
> +     INIT_WORK(&b->work, __drm_backlight_worker);
> +}
> +
> +/**
> + * drm_backlight_connector_cleanup - tear down a connector's embedded 
> backlight
> + * @connector: connector being cleaned up
> + *
> + * Called by the DRM core from drm_connector_cleanup(). Any still-linked 
> backend
> + * is unlinked here as a safety net for connectors that are torn down on a 
> probe
> + * error path before drm_connector_unregister() ever runs, so no stale entry 
> is
> + * left on the global list. The LUMINANCE property itself is freed by
> + * drm_mode_config_cleanup().
> + */
> +void drm_backlight_connector_cleanup(struct drm_connector *connector)
> +{
> +     drm_backlight_do_unlink(&connector->backlight);
> +}
> +
> +/**
> + * drm_backlight_unregister - unlink a connector's backlight on unregister
> + * @connector: connector being unregistered
> + *
> + * Called by the DRM core from drm_connector_unregister() as a safety net in
> + * case a driver did not unlink its backend itself.
> + */
> +void drm_backlight_unregister(struct drm_connector *connector)
> +{
> +     drm_backlight_do_unlink(&connector->backlight);
> +}
> +
> +/**
> + * drm_backlight_link - link a backlight device to a connector
> + * @connector: connector to modify
> + * @bd: backlight device to link, or NULL to unlink
> + *
> + * Establish the link between a connector's LUMINANCE property and a 
> registered
> + * backlight_device. On the first link the connector's LUMINANCE property is
> + * created with the backend's range. Passing NULL unlinks any linked device.
> + *
> + * User-space cannot create or modify this link.
> + *
> + * Returns: 0 on success, or a negative error code if the property could not 
> be
> + * created (in which case no backend is linked).
> + */
> +int drm_backlight_link(struct drm_connector *connector,
> +                    struct backlight_device *bd)
> +{
> +     struct drm_backlight *b = &connector->backlight;
> +     unsigned int max;
> +     int ret;
> +
> +     if (!bd) {
> +             drm_backlight_do_unlink(b);
> +             return 0;
> +     }
> +
> +     /* Retarget: drop any previously linked backend first. */
> +     if (b->funcs)
> +             drm_backlight_do_unlink(b);
> +
> +     max = bd->props.max_brightness;
> +     if (max && !connector->luminance_property) {
> +             ret = drm_backlight_create_property(connector, max, false);
> +             if (ret)
> +                     return ret;
> +     }
> +
> +     scoped_guard(spinlock, &drm_backlight_lock) {
> +             b->funcs = &drm_backlight_bd_funcs;
> +             b->backend = bd;
> +             backlight_device_ref(bd);
> +             list_add(&b->list, &drm_backlight_list);
> +             /*
> +              * Inherit the device-wide inhibit level so a connector linked
> +              * (or hotplugged) while luminance-aware clients are active also
> +              * has its legacy sysfs control inhibited.
> +              */
> +             b->luminance_clients = 
> connector->dev->mode_config.luminance_clients;
> +             if (b->luminance_clients)
> +                     atomic_add(b->luminance_clients, &bd->drm_takeover);
> +             __drm_backlight_readback(b, bd->props.brightness);
> +             b->changed = true;
> +             __drm_backlight_set(b, bd->props.brightness);
> +     }
> +
> +     return 0;
> +}
> +EXPORT_SYMBOL(drm_backlight_link);
> +
> +/**
> + * drm_backlight_get_device - get the backlight_device linked to a connector
> + * @connector: connector to query
> + *
> + * Returns the &backlight_device linked to @connector with an additional
> + * reference taken, or NULL if no backlight subsystem device is linked. The
> + * caller must drop the reference with backlight_device_unref() when done.
> + */
> +struct backlight_device *drm_backlight_get_device(struct drm_connector 
> *connector)
> +{
> +     struct backlight_device *bd;
> +
> +     guard(spinlock)(&drm_backlight_lock);
> +     bd = drm_backlight_bd(&connector->backlight);
> +     backlight_device_ref(bd);
> +
> +     return bd;
> +}
> +EXPORT_SYMBOL(drm_backlight_get_device);
> +
> +/**
> + * drm_backlight_inhibit_legacy - disable legacy sysfs control of the backend
> + * @connector: connector whose backlight should be inhibited
> + *
> + * Record that one more luminance-aware DRM client has taken over this
> + * connector's backlight. While any clients are recorded, writes to the 
> linked
> + * backlight_device's legacy ``brightness`` sysfs attribute return 
> ``-EBUSY``.
> + * The takeover follows the linked device if the link changes.
> + *
> + * Calls must be balanced with drm_backlight_uninhibit_legacy().
> + */
> +void drm_backlight_inhibit_legacy(struct drm_connector *connector)
> +{
> +     struct drm_backlight *b = &connector->backlight;
> +     struct backlight_device *bd;
> +
> +     guard(spinlock)(&drm_backlight_lock);
> +     b->luminance_clients++;
> +     bd = drm_backlight_bd(b);
> +     if (bd)
> +             atomic_inc(&bd->drm_takeover);
> +}
> +EXPORT_SYMBOL(drm_backlight_inhibit_legacy);
> +
> +/**
> + * drm_backlight_uninhibit_legacy - re-enable legacy sysfs control
> + * @connector: connector to uninhibit
> + *
> + * Balances a previous drm_backlight_inhibit_legacy() call.
> + */
> +void drm_backlight_uninhibit_legacy(struct drm_connector *connector)
> +{
> +     struct drm_backlight *b = &connector->backlight;
> +     struct backlight_device *bd;
> +
> +     guard(spinlock)(&drm_backlight_lock);
> +     if (WARN_ON(b->luminance_clients == 0))
> +             return;
> +     b->luminance_clients--;
> +     bd = drm_backlight_bd(b);
> +     if (bd)
> +             atomic_dec(&bd->drm_takeover);
> +}
> +EXPORT_SYMBOL(drm_backlight_uninhibit_legacy);
> +
> +/**
> + * drm_backlight_inhibit_legacy_all - inhibit legacy sysfs on every connector
> + * @dev: DRM device whose connectors should be inhibited
> + *
> + * Used when a client declares it is luminance-aware via
> + * DRM_CLIENT_CAP_LUMINANCE. The device-wide count is bumped so connectors
> + * hotplugged later inherit the inhibit in drm_backlight_link().
> + */
> +void drm_backlight_inhibit_legacy_all(struct drm_device *dev)
> +{
> +     struct drm_connector_list_iter iter;
> +     struct drm_connector *connector;
> +
> +     scoped_guard(spinlock, &drm_backlight_lock)
> +             dev->mode_config.luminance_clients++;
> +
> +     drm_connector_list_iter_begin(dev, &iter);
> +     drm_for_each_connector_iter(connector, &iter)
> +             drm_backlight_inhibit_legacy(connector);
> +     drm_connector_list_iter_end(&iter);
> +}
> +EXPORT_SYMBOL(drm_backlight_inhibit_legacy_all);
> +
> +/**
> + * drm_backlight_uninhibit_legacy_all - reverse 
> drm_backlight_inhibit_legacy_all()
> + * @dev: DRM device whose connectors should be uninhibited
> + */
> +void drm_backlight_uninhibit_legacy_all(struct drm_device *dev)
> +{
> +     struct drm_connector_list_iter iter;
> +     struct drm_connector *connector;
> +
> +     scoped_guard(spinlock, &drm_backlight_lock) {
> +             if (WARN_ON(dev->mode_config.luminance_clients == 0))
> +                     return;
> +             dev->mode_config.luminance_clients--;
> +     }
> +
> +     drm_connector_list_iter_begin(dev, &iter);
> +     drm_for_each_connector_iter(connector, &iter)
> +             drm_backlight_uninhibit_legacy(connector);
> +     drm_connector_list_iter_end(&iter);
> +}
> +EXPORT_SYMBOL(drm_backlight_uninhibit_legacy_all);
> +
> +/**
> + * drm_backlight_set_luminance - request a luminance change on a connector
> + * @connector: connector to update
> + * @value: luminance value to apply
> + *
> + * Clamp @value to the backend range and schedule the hardware update. Safe 
> to
> + * call from an atomic commit tail: the actual hardware access happens later
> + * from a workqueue.
> + */
> +void drm_backlight_set_luminance(struct drm_connector *connector,
> +                              unsigned int value)
> +{
> +     guard(spinlock)(&drm_backlight_lock);
> +     __drm_backlight_set(&connector->backlight, value);
> +}
> +EXPORT_SYMBOL(drm_backlight_set_luminance);
> +
> +/* backlight_device backend 
> ------------------------------------------------- */
> +
> +static int drm_backlight_bd_set_luminance(struct drm_backlight *b,
> +                                       unsigned int value)
> +{
> +     struct backlight_device *bd = b->backend;
> +     int rc;
> +
> +     rc = backlight_set_brightness(bd, value, BACKLIGHT_UPDATE_DRM);
> +     if (rc)
> +             backlight_set_brightness(bd, U16_MAX, BACKLIGHT_UPDATE_DRM);
> +
> +     return rc;
> +}
> +
> +static int drm_backlight_bd_get_luminance(struct drm_backlight *b,
> +                                       unsigned int *value)
> +{
> +     struct backlight_device *bd = b->backend;
> +
> +     if (!bd)
> +             return -ENODEV;
> +     *value = bd->props.brightness;
> +
> +     return 0;
> +}
> +
> +static void drm_backlight_bd_get_range(struct drm_backlight *b,
> +                                    unsigned int *max, bool *can_disable)
> +{
> +     struct backlight_device *bd = b->backend;
> +
> +     *max = bd ? bd->props.max_brightness : 0;
> +     /*
> +      * A generic backlight_device gives no guarantee that a value of 0 turns
> +      * the panel fully off, so keep 0 reserved for the DPMS-off sentinel and
> +      * expose a 1..max range.
> +      */
> +     *can_disable = false;
> +}
> +
> +static const struct drm_backlight_funcs drm_backlight_bd_funcs = {
> +     .set_luminance = drm_backlight_bd_set_luminance,
> +     .get_luminance = drm_backlight_bd_get_luminance,
> +     .get_range = drm_backlight_bd_get_range,
> +};
> +
> +static int drm_backlight_notify(struct notifier_block *self,
> +                             unsigned long event, void *data)
> +{
> +     struct backlight_device *bd = data;
> +     struct drm_backlight *b;
> +
> +     switch (event) {
> +     case BACKLIGHT_UNREGISTERED:
> +             /*
> +              * Unlink every connector using @bd. drm_backlight_do_unlink()
> +              * sleeps (cancel_work_sync()), so it cannot run under the list
> +              * spinlock; re-scan for the next match after each unlink.
> +              */
> +             for (;;) {
> +                     struct drm_backlight *found = NULL;
> +
> +                     scoped_guard(spinlock, &drm_backlight_lock) {
> +                             list_for_each_entry(b, &drm_backlight_list, 
> list) {
> +                                     if (drm_backlight_bd(b) == bd) {
> +                                             found = b;
> +                                             break;
> +                                     }
> +                             }
> +                     }
> +                     if (!found)
> +                             break;
> +                     drm_backlight_do_unlink(found);
> +             }
> +             break;
> +     case BACKLIGHT_BRIGHTNESS_CHANGED:
> +             scoped_guard(spinlock, &drm_backlight_lock) {
> +                     list_for_each_entry(b, &drm_backlight_list, list)
> +                             if (drm_backlight_bd(b) == bd)
> +                                     __drm_backlight_readback(b, 
> bd->props.brightness);
> +             }
> +             break;
> +     }
> +
> +     return 0;
> +}
> +
> +static struct notifier_block drm_backlight_notifier = {
> +     .notifier_call = drm_backlight_notify,
> +};
> +
> +int drm_backlight_init(void)
> +{
> +     return backlight_register_notifier(&drm_backlight_notifier);
> +}
> +
> +void drm_backlight_exit(void)
> +{
> +     backlight_unregister_notifier(&drm_backlight_notifier);
> +}
> diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> index 8b4baed060f3a..92ed0c1879d53 100644
> --- a/drivers/gpu/drm/drm_connector.c
> +++ b/drivers/gpu/drm/drm_connector.c
> @@ -21,6 +21,7 @@
>   */
>  
>  #include <drm/drm_auth.h>
> +#include <drm/drm_backlight.h>
>  #include <drm/drm_connector.h>
>  #include <drm/drm_drv.h>
>  #include <drm/drm_edid.h>
> @@ -316,6 +317,8 @@ static int drm_connector_init_only(struct drm_device *dev,
>               drm_object_attach_property(&connector->base, 
> config->prop_crtc_id, 0);
>       }
>  
> +     drm_backlight_connector_init(connector);
> +
>       connector->debugfs_entry = NULL;
>  out_put_type_id:
>       if (ret)
> @@ -773,6 +776,7 @@ void drm_connector_cleanup(struct drm_connector 
> *connector)
>       struct drm_device *dev = connector->dev;
>       struct drm_display_mode *mode, *t;
>  
> +     drm_backlight_connector_cleanup(connector);
>       /* The connector should have been removed from userspace long before
>        * it is finally destroyed.
>        */
> @@ -944,6 +948,8 @@ EXPORT_SYMBOL(drm_connector_dynamic_register);
>  void drm_connector_unregister(struct drm_connector *connector)
>  {
>       mutex_lock(&connector->mutex);
> +     drm_backlight_unregister(connector);
> +
>       if (connector->registration_state != DRM_CONNECTOR_REGISTERED) {
>               mutex_unlock(&connector->mutex);
>               return;
> @@ -1532,6 +1538,56 @@ 
> 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, per-connector range property for controlling the backlight
> + *   brightness level of the connector's display. It provides unified access
> + *   to the display backlight through the atomic modeset path, replacing the
> + *   legacy sysfs interface for brightness control.
> + *
> + *   The property value is an unsigned integer. Its valid range is baked in
> + *   when a backlight backend is linked to the connector and reflects the
> + *   backend's capabilities:
> + *
> + *   - Range 1-N: Normal operation for a backend that cannot guarantee a full
> + *     off state. 1 is the minimum *visible* brightness and N is the backend
> + *     maximum. Drivers are expected never to program a duty cycle of 0 for a
> + *     value of 1.
> + *   - Range 0-N: Used when the backend can also fully turn the panel off, so
> + *     0 is a normal in-range value.
> + *
> + *   Value 0 is always accepted, even when the advertised range starts at 1:
> + *   it is the sentinel used to turn the backlight off when the connector is
> + *   powered down (DPMS off). Turning the backlight off this way may power 
> the
> + *   panel down entirely; unlike programming a legacy sysfs duty cycle of 0,
> + *   this can stop vblank/pageflip events until the connector is enabled
> + *   again, so luminance-aware clients must not rely on such events while the
> + *   connector is off.
> + *
> + *   Connectors without a linked backend do not expose this property at all.
> + *
> + *   For atomic drivers the value is stored in &drm_connector_state.luminance
> + *   and applied to the hardware from the atomic commit path once the
> + *   connector is enabled. When DPMS transitions to OFF the backlight is set
> + *   to 0; when it transitions back to ON the committed luminance is 
> restored.
> + *   Reading the property returns the last committed value (or the hardware's
> + *   current state for backends that support reading brightness back).
> + *
> + *   The property is created by the DRM core when a driver links a backlight
> + *   backend with drm_backlight_link(); drivers do not create it directly.
> + *
> + *   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 should continue
> + *           using the sysfs interface (if available).
> + *
> + *   Note: This property can be set through the 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), "OLED", and "LCD".
> diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
> index c808958a2188e..c8aa6834c1f4e 100644
> --- a/drivers/gpu/drm/drm_drv.c
> +++ b/drivers/gpu/drm/drm_drv.c
> @@ -42,6 +42,7 @@
>  #include <linux/xarray.h>
>  
>  #include <drm/drm_accel.h>
> +#include <drm/drm_backlight.h>
>  #include <drm/drm_bridge.h>
>  #include <drm/drm_cache.h>
>  #include <drm/drm_client_event.h>
> @@ -1250,6 +1251,7 @@ static void drm_core_exit(void)
>       drm_privacy_screen_lookup_exit();
>       drm_panic_exit();
>       accel_core_exit();
> +     drm_backlight_exit();
>       unregister_chrdev(DRM_MAJOR, "drm");
>       drm_debugfs_remove_root();
>       drm_sysfs_destroy();
> @@ -1273,6 +1275,12 @@ static int __init drm_core_init(void)
>       drm_debugfs_init_root();
>       drm_debugfs_bridge_params();
>  
> +     ret = drm_backlight_init();
> +     if (ret < 0) {
> +             DRM_ERROR("Cannot initialize backlight interface\n");
> +             goto error;
> +     }
> +
>       ret = register_chrdev(DRM_MAJOR, "drm", &drm_stub_fops);
>       if (ret < 0)
>               goto error;
> diff --git a/drivers/gpu/drm/drm_mode_config.c 
> b/drivers/gpu/drm/drm_mode_config.c
> index 366f6d8212425..36579e8a7c0e5 100644
> --- a/drivers/gpu/drm/drm_mode_config.c
> +++ b/drivers/gpu/drm/drm_mode_config.c
> @@ -33,6 +33,7 @@
>  #include <drm/drm_print.h>
>  #include <drm/drm_colorop.h>
>  #include <linux/dma-resv.h>
> +#include <drm/drm_backlight.h>
>  
>  #include "drm_crtc_internal.h"
>  #include "drm_internal.h"
> diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c
> index ef4e923a87284..2f4ce7d040f9d 100644
> --- a/drivers/gpu/drm/drm_sysfs.c
> +++ b/drivers/gpu/drm/drm_sysfs.c
> @@ -11,6 +11,7 @@
>   */
>  
>  #include <linux/acpi.h>
> +#include <linux/backlight.h>
>  #include <linux/component.h>
>  #include <linux/device.h>
>  #include <linux/err.h>
> @@ -27,6 +28,7 @@
>  #include <drm/drm_device.h>
>  #include <drm/drm_file.h>
>  #include <drm/drm_modes.h>
> +#include <drm/drm_backlight.h>
>  #include <drm/drm_print.h>
>  #include <drm/drm_property.h>
>  #include <drm/drm_sysfs.h>
> @@ -391,15 +393,37 @@ int drm_sysfs_connector_add(struct drm_connector 
> *connector)
>  
>  int drm_sysfs_connector_add_late(struct drm_connector *connector)
>  {
> -     if (connector->ddc)
> -             return sysfs_create_link(&connector->kdev->kobj,
> -                                      &connector->ddc->dev.kobj, "ddc");
> +     struct backlight_device *bd = drm_backlight_get_device(connector);
> +     int ret = 0;
> +
> +     if (connector->ddc) {
> +             ret = sysfs_create_link(&connector->kdev->kobj,
> +                                     &connector->ddc->dev.kobj, "ddc");
> +             if (ret)
> +                     goto out;
> +     }
>  
> -     return 0;
> +     if (bd) {
> +             ret = sysfs_create_link(&connector->kdev->kobj,
> +                                     &bd->dev.kobj, "backlight");
> +             if (ret && connector->ddc)
> +                     sysfs_remove_link(&connector->kdev->kobj, "ddc");
> +     }
> +
> +out:
> +     backlight_device_unref(bd);
> +     return ret;
>  }
>  
>  void drm_sysfs_connector_remove_early(struct drm_connector *connector)
>  {
> +     struct backlight_device *bd = drm_backlight_get_device(connector);
> +
> +     if (bd) {
> +             sysfs_remove_link(&connector->kdev->kobj, "backlight");
> +             backlight_device_unref(bd);
> +     }
> +
>       if (connector->ddc)
>               sysfs_remove_link(&connector->kdev->kobj, "ddc");
>  }
> diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h
> index 4cfeec70d648a..8a166ead13c55 100644
> --- a/include/drm/drm_atomic_helper.h
> +++ b/include/drm/drm_atomic_helper.h
> @@ -116,6 +116,8 @@ void 
> drm_atomic_helper_commit_encoder_bridge_enable(struct drm_device *dev,
>  void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
>                                         struct drm_atomic_commit *old_state);
>  
> +void drm_atomic_helper_connector_apply_luminance(const struct 
> drm_connector_state *conn_state);
> +
>  int drm_atomic_helper_prepare_planes(struct drm_device *dev,
>                                    struct drm_atomic_commit *state);
>  void drm_atomic_helper_unprepare_planes(struct drm_device *dev,
> diff --git a/include/drm/drm_backlight.h b/include/drm/drm_backlight.h
> new file mode 100644
> index 0000000000000..417ca4ecbf15b
> --- /dev/null
> +++ b/include/drm/drm_backlight.h
> @@ -0,0 +1,166 @@
> +/* SPDX-License-Identifier: MIT */
> +#ifndef __DRM_BACKLIGHT_H__
> +#define __DRM_BACKLIGHT_H__
> +
> +/*
> + * Copyright (c) 2014 David Herrmann <dh.herrmann at gmail.com>
> + *
> + * 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.
> + */
> +
> +#include <linux/list.h>
> +#include <linux/types.h>
> +#include <linux/workqueue.h>
> +
> +struct backlight_device;
> +struct drm_backlight;
> +struct drm_connector;
> +struct drm_device;
> +
> +/**
> + * struct drm_backlight_funcs - backend operations for a DRM backlight
> + *
> + * A DRM backlight is backend-agnostic: the core forwards luminance requests 
> to
> + * whatever backend a driver has linked. Today the only backend is the 
> backlight
> + * subsystem (&struct backlight_device), but the same core can drive other
> + * backends (DDC/CI, MIPI-DCS, ...) by providing a different set of these
> + * operations without any change to the core.
> + *
> + * All callbacks are invoked from process context (a workqueue), never from 
> an
> + * atomic commit tail or while holding a spinlock, so they are allowed to 
> sleep.
> + */
> +struct drm_backlight_funcs {
> +     /**
> +      * @set_luminance:
> +      *
> +      * Push @value (already clamped to the backend range) to the hardware.
> +      * Returns 0 on success or a negative error code.
> +      */
> +     int (*set_luminance)(struct drm_backlight *b, unsigned int value);
> +
> +     /**
> +      * @get_luminance:
> +      *
> +      * Read the current hardware luminance into @value. Returns 0 on success
> +      * or a negative error code. May be NULL if the backend cannot be read
> +      * back.
> +      */
> +     int (*get_luminance)(struct drm_backlight *b, unsigned int *value);
> +
> +     /**
> +      * @get_range:
> +      *
> +      * Report the backend's maximum luminance in @max and whether the
> +      * backend can turn the panel off at luminance 0 in @can_disable.
> +      */
> +     void (*get_range)(struct drm_backlight *b, unsigned int *max,
> +                       bool *can_disable);
> +};
> +
> +/**
> + * struct drm_backlight - per-connector backlight state
> + *
> + * This structure is embedded in &struct drm_connector and initialized by the
> + * DRM core; drivers never allocate it. It becomes active once a driver 
> links a
> + * backend with drm_backlight_link().
> + */
> +struct drm_backlight {
> +     /** @connector: connector this backlight belongs to */
> +     struct drm_connector *connector;
> +     /** @funcs: backend operations, or NULL while no backend is linked */
> +     const struct drm_backlight_funcs *funcs;
> +     /** @backend: backend private pointer (e.g. the &backlight_device) */
> +     void *backend;
> +     /** @list: entry on the global list of linked DRM backlights */
> +     struct list_head list;
> +     /** @work: deferred hardware update and uevent */
> +     struct work_struct work;
> +     /** @set_value: luminance value pending application by @work */
> +     unsigned int set_value;
> +     /**
> +      * @luminance_clients: number of luminance-aware DRM clients that have
> +      * taken this backlight over. While > 0, legacy sysfs writes to the
> +      * linked backend return -EBUSY.
> +      */
> +     unsigned int luminance_clients;
> +     /** @changed: a uevent is pending for @work to emit */
> +     bool changed : 1;
> +};
> +
> +#if IS_ENABLED(CONFIG_DRM_BACKLIGHT)
> +
> +int drm_backlight_init(void);
> +void drm_backlight_exit(void);
> +
> +void drm_backlight_connector_init(struct drm_connector *connector);
> +void drm_backlight_connector_cleanup(struct drm_connector *connector);
> +void drm_backlight_unregister(struct drm_connector *connector);
> +
> +int drm_backlight_create_property(struct drm_connector *connector,
> +                               unsigned int max, bool can_disable);
> +int drm_backlight_link(struct drm_connector *connector,
> +                    struct backlight_device *bd);
> +struct backlight_device *drm_backlight_get_device(struct drm_connector 
> *connector);
> +
> +void drm_backlight_inhibit_legacy(struct drm_connector *connector);
> +void drm_backlight_uninhibit_legacy(struct drm_connector *connector);
> +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_connector *connector,
> +                              unsigned int value);
> +
> +#else /* CONFIG_DRM_BACKLIGHT */
> +
> +static inline int drm_backlight_init(void) { return 0; }
> +static inline void drm_backlight_exit(void) {}
> +
> +static inline void drm_backlight_connector_init(struct drm_connector 
> *connector) {}
> +static inline void drm_backlight_connector_cleanup(struct drm_connector 
> *connector) {}
> +static inline void drm_backlight_unregister(struct drm_connector *connector) 
> {}
> +
> +static inline int drm_backlight_create_property(struct drm_connector 
> *connector,
> +                                             unsigned int max, bool 
> can_disable)
> +{
> +     return 0;
> +}
> +
> +static inline int drm_backlight_link(struct drm_connector *connector,
> +                                  struct backlight_device *bd)
> +{
> +     return 0;
> +}
> +
> +static inline struct backlight_device *
> +drm_backlight_get_device(struct drm_connector *connector)
> +{
> +     return NULL;
> +}
> +
> +static inline void drm_backlight_inhibit_legacy(struct drm_connector 
> *connector) {}
> +static inline void drm_backlight_uninhibit_legacy(struct drm_connector 
> *connector) {}
> +static inline void drm_backlight_inhibit_legacy_all(struct drm_device *dev) 
> {}
> +static inline void drm_backlight_uninhibit_legacy_all(struct drm_device 
> *dev) {}
> +
> +static inline void drm_backlight_set_luminance(struct drm_connector 
> *connector,
> +                                            unsigned int value) {}
> +
> +#endif /* CONFIG_DRM_BACKLIGHT */
> +
> +#endif /* __DRM_BACKLIGHT_H__ */
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index a0cf0268de483..0535f20e7dba5 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -28,6 +28,7 @@
>  #include <linux/ctype.h>
>  #include <linux/hdmi.h>
>  #include <linux/notifier.h>
> +#include <drm/drm_backlight.h>
>  #include <drm/drm_mode_object.h>
>  #include <drm/drm_util.h>
>  #include <drm/drm_property.h>
> @@ -1290,6 +1291,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 {
> @@ -2526,6 +2532,20 @@ struct drm_connector {
>        * @cec: CEC-related data.
>        */
>       struct drm_connector_cec cec;
> +
> +     /**
> +      * @backlight: DRM backlight state, embedded and initialized by the DRM
> +      * core. Becomes active once a driver links a backend with
> +      * drm_backlight_link().
> +      */
> +     struct drm_backlight backlight;
> +
> +     /**
> +      * @luminance_property: Per-connector range property controlling the
> +      * connector's backlight luminance. Created with the backend's range
> +      * when a backlight is linked; NULL while no backlight is linked.
> +      */
> +     struct drm_property *luminance_property;
>  };
>  
>  #define obj_to_connector(x) container_of(x, struct drm_connector, base)
> diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> index d8f5b7e9673ee..4ab45bd4edf7e 100644
> --- a/include/drm/drm_mode_config.h
> +++ b/include/drm/drm_mode_config.h
> @@ -390,6 +390,17 @@ struct drm_mode_config {
>        */
>       struct drm_modeset_acquire_ctx *acquire_ctx;
>  
> +     /**
> +      * @luminance_clients:
> +      *
> +      * Number of open DRM clients that have enabled
> +      * &DRM_CLIENT_CAP_LUMINANCE and thereby inhibited legacy sysfs
> +      * backlight control device-wide. Connectors linked while this is
> +      * non-zero inherit the inhibit so hotplugged outputs stay consistent.
> +      * Managed and serialized by the DRM backlight helpers.
> +      */
> +     unsigned int luminance_clients;
> +
>       /**
>        * @idr_mutex:
>        *

-- 
Jani Nikula, Intel

Reply via email to