mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v11 00/12] Add support for a DRM backlight capability
@ 2026-11-03 22:42 Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 01/12] drm: add connector LUMINANCE property Mario Limonciello (AMD)
                   ` (11 more replies)
  0 siblings, 12 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

At Display Next Hackfest 2026 we reviewed progress moving brightness
control into the DRM connector properties.

There is a range LUMINANCE property that will default to 0->0.
Once a driver attaches a backlight it will be updated to 1->max.
If the panel supports the minimum backlight turning off the display
the range can later be updated to 0->max instead of 1->max.

Version 8 had a lot of review feedback, and the biggest change is that
this no longer has the legacy sysfs interface for brightness control.

There is a Kconfig knob introduced that turns on the new API, and when
the new API is available all drivers will conflict with the old backlight
control API.

This will allow us to land the new brightness control API and the transition
to the new API separately.  *This series only tracks the new API itself.*

The following compositors have implemented matching support, although they
need to be changed again for items modified in v10 (the dropped cap):
 * Kwin: https://invent.kde.org/plasma/kwin/-/merge_requests/9298
 * Mutter: https://gitlab.gnome.org/swick/mutter/-/commits/wip/kms-luminance-prop
 * Wlroots: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5384

I've tested this series using modetest on amdgpu.

The series is also available on this branch for easy testing.

https://git.kernel.org/pub/scm/linux/kernel/git/superm1/linux.git/log/?h=superm1/backlight-property-v11

Signed-off-by: Mario Limonciello <superm1@kernel.org>
---
Changes in v11:
 * Rebased on drm-tip (fixes Intel CI failing to apply the KUnit patch)
 * Squashed the amdgpu KUnit fixture update into the amdgpu luminance
   patch and moved the bios_scratch_reg_offset fix before it, so every
   commit builds and passes KUnit
 * Dropped drm/radeon and drm/gma500 patches: both are non-atomic drivers,
   so userspace can't use an atomic connector property there (Sashiko)
 * Restored the unconditional select of BACKLIGHT_CLASS_DEVICE in all
   migrated drivers, fixing ACPI_VIDEO unmet dependencies and link
   failures (Sashiko)
 * Core: don't call .disable_luminance when entering self refresh, and
   only call .set_luminance when the luminance changes or on a modeset
   that isn't a self refresh exit (Leo Li, Sashiko)
 * Drivers on ACPI systems (amdgpu, i915/xe) only attach LUMINANCE when
   acpi_video_backlight_use_native() allows it, keeping the acpi_video
   fallback otherwise (Sashiko)
 * Non-atomic paths (legacy non-DC amdgpu, nouveau NV04 and
   nouveau.atomic=0) keep the backlight class device (Sashiko)
 * bridge/panel: removed rebase artifacts from drm_bridge.h/drm_panel.h,
   fixed an invalid container_of() in the panel bridge, cleaned up the
   connector on error, and left backlight power to drm_panel_enable()/
   drm_panel_disable() (Sashiko)
 * bridge_connector: dropped .disable_luminance, which clobbered the
   bridge's saved level
 * amdgpu: restore brightness after GPU reset and modesets for LUMINANCE
   panels, and skip programming unchanged values (Leo Li, Sashiko)
 * amdgpu KUnit: fixed connector_type IDA mismatch (Sashiko)
 * amdgpu: seed LUMINANCE from the hardware level; left the unrelated
   privacy screen code in commit_tail unchanged
 * i915: fall back to the backlight class device if attaching LUMINANCE
   fails
 * panel: don't attach LUMINANCE for panel bridges attached after device
   registration, and update brightness under the backlight ops_lock
 * ps8622: program the committed luminance in pre_enable
 * i915: enable the backlight at the committed level, and fix the build
   with BACKLIGHT_CLASS_DEVICE=n (Sashiko)
 * nouveau: call the luminance helpers from the NV50 commit tail, attach
   the property before drm_dev_register(), and fix a stale eDP brightness
   on the class device path (Sashiko)
 * gud: apply LUMINANCE within the atomic commit instead of a worker,
   fixing an endless commit loop, a use-after-free on disconnect and
   brightness clobbering; attach the property at init (Sashiko)
 * ps8622: track internal PWM mode explicitly and don't touch I2C while
   powered down (Sashiko)
 * ssd130x: register the backlight device only without DRM_LUMINANCE,
   use the committed contrast on enable, don't clobber it on disable,
   skip writes while unpowered, and unify contrast dispatch (Sashiko)
 * Known limitation: with DRM_LUMINANCE, amdgpu ATIF firmware brightness
   requests are not forwarded (i915 OpRegion ASLE requests are already
   ignored when native backlight control is in use)
 * Link to v10: https://lore.kernel.org/dri-devel/20261102-superm1-backlight-property-v10-0-b2a6f24131f5@kernel.org/

Changes in v10:

* Split patch 7 (panel/bridge connector luminance forwarding) into three
   separate patches:
  - drm/bridge: add connector luminance operations
  - drm/panel: add connector luminance forwarding
  - drm/bridge_connector: add luminance support
 * Dropped MIPI-DBI tiny driver migrations and the new mipi_dbi connector
   luminance helpers (Javier): backlight is always NULL when
   CONFIG_BACKLIGHT_CLASS_DEVICE is disabled, so these drivers can't
   support LUMINANCE without DT binding changes or a new
   CONFIG_BACKLIGHT_SYSFS option. Will revisit later.
 * Added IS_ENABLED(CONFIG_DRM_LUMINANCE) conditional guards to all
   individual DRM driver patches that call drm_connector_attach_luminance_property()
 * Moved the LUMINANCE property patch after the new "Add support for a
   DRM backlight capability" patch to better separate backlight vs
   luminance functionality
 * Link to v9: https://lore.kernel.org/dri-devel/20261002190030.211356-1-mario.limonciello@amd.com/

To: Maxime Ripard <mripard@kernel.org>
To: Alex Deucher <alexander.deucher@amd.com>
To: Jani Nikula <jani.nikula@linux.intel.com>
To: Javier Martinez Canillas <javierm@redhat.com>
To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
To: Thomas Zimmermann <tzimmermann@suse.de>
To: David Airlie <airlied@gmail.com>
To: Simona Vetter <simona@ffwll.ch>
To: Harry Wentland <harry.wentland@amd.com>
To: Leo Li <sunpeng.li@amd.com>
To: Rodrigo Siqueira <siqueira@igalia.com>
Cc: Xaver Hugl <xaver.hugl@gmail.com>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-kernel@vger.kernel.org
Cc: amd-gfx@lists.freedesktop.org
Cc: intel-gfx@lists.freedesktop.org
Cc: intel-xe@lists.freedesktop.org
Cc: nouveau@lists.freedesktop.org

---
Mario Limonciello (11):
      drm: add connector LUMINANCE property
      drm/amd/display: Pass up errors reading actual brightness
      drm/amd: add luminance property support
      drm/i915/display: add luminance property support
      drm/bridge: add connector luminance operations
      drm/panel: add connector luminance forwarding
      drm/bridge_connector: add luminance support
      drm/nouveau: add connector luminance support
      drm/gud: add connector luminance support
      drm/bridge: add PS8622 connector luminance support
      drm/solomon: add SSD130x connector luminance support

Mario Limonciello (AMD) (1):
      drm/amdgpu: Check bios_scratch_reg_offset in backlight level helper

 drivers/gpu/drm/Kconfig                            |  14 ++
 drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c       |   7 +-
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c  |  16 +--
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h  |   7 +
 .../amd/display/amdgpu_dm/amdgpu_dm_backlight.c    |  91 +++++++++++-
 .../amd/display/amdgpu_dm/amdgpu_dm_backlight.h    |   5 +-
 .../amd/display/amdgpu_dm/amdgpu_dm_connector.c    |   5 +-
 .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c     |  72 ++++++++--
 .../amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c      |   7 +-
 drivers/gpu/drm/bridge/parade-ps8622.c             |  66 +++++++--
 drivers/gpu/drm/display/drm_bridge_connector.c     |  39 ++++++
 drivers/gpu/drm/drm_atomic_helper.c                |  98 ++++++++++++-
 drivers/gpu/drm/drm_atomic_state_helper.c          |   7 +
 drivers/gpu/drm/drm_atomic_uapi.c                  |   5 +
 drivers/gpu/drm/drm_connector.c                    |  57 ++++++++
 drivers/gpu/drm/drm_panel.c                        |  93 +++++++++++++
 drivers/gpu/drm/gud/gud_connector.c                |  20 ++-
 drivers/gpu/drm/i915/display/icl_dsi.c             |   1 +
 drivers/gpu/drm/i915/display/intel_backlight.c     |  74 ++++++++--
 drivers/gpu/drm/i915/display/intel_backlight.h     |   1 +
 drivers/gpu/drm/i915/display/intel_display.c       |   2 +
 drivers/gpu/drm/i915/display/intel_dp.c            |   1 +
 drivers/gpu/drm/i915/display/intel_lvds.c          |   1 +
 drivers/gpu/drm/i915/display/intel_panel.c         |   8 +-
 drivers/gpu/drm/i915/display/vlv_dsi.c             |   1 +
 drivers/gpu/drm/nouveau/dispnv50/disp.c            |  13 +-
 drivers/gpu/drm/nouveau/nouveau_backlight.c        | 152 ++++++++++++++++++---
 drivers/gpu/drm/nouveau/nouveau_connector.c        |   3 +
 drivers/gpu/drm/nouveau/nouveau_connector.h        |  23 ++++
 drivers/gpu/drm/nouveau/nouveau_drm.c              |   6 +
 drivers/gpu/drm/solomon/ssd130x.c                  | 149 +++++++++++++-------
 drivers/gpu/drm/solomon/ssd130x.h                  |   2 +
 drivers/gpu/drm/tests/drm_connector_test.c         |  28 ++++
 include/drm/drm_atomic_helper.h                    |   3 +
 include/drm/drm_bridge.h                           |  20 +++
 include/drm/drm_connector.h                        |  16 +++
 include/drm/drm_modeset_helper_vtables.h           |  28 ++++
 include/drm/drm_panel.h                            |   4 +
 38 files changed, 1008 insertions(+), 137 deletions(-)
---
base-commit: 9f54e1a5b50dc017d371c435b12ddb5300c7d5b2
change-id: 20261101-superm1-backlight-property-1ee9b1a4b24d

Best regards,
--  
Mario Limonciello <superm1@kernel.org>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 01/12] drm: add connector LUMINANCE property
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 02/12] drm/amd/display: Pass up errors reading actual brightness Mario Limonciello (AMD)
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

From: Mario Limonciello <mario.limonciello@amd.com>

Panel brightness is a property of a display, and thus of a DRM
connector. Add an atomic per-connector LUMINANCE range property so
compositors can manage relative panel luminance together with the rest
of the display state.

Keep the core backend-agnostic. Drivers create the property with their
hardware range and implement connector helper callbacks to set luminance
or disable panel emission. The atomic helpers sequence those callbacks
around pipeline enable and disable, including luminance-only commits.
The panel is left lit when the pipeline enters self refresh, and the
luminance is only reprogrammed when it changes or a modeset enables the
pipeline.

The stored property value is independent of display power. A commit made
while the connector is inactive is retained and applied on the next
enable; disabling the pipeline does not overwrite it.

LUMINANCE values are relative, driver-defined levels rather than
absolute luminance in cd/m^2. The interface supports emissive panels as
well as panels with a separate backlight.

Keep the interface behind a default-off DRM_LUMINANCE option. Migrated
drivers retain their legacy backlight class ABI unless the new interface
is explicitly selected, allowing the DRM UAPI and legacy compatibility
policy to remain separate.

Acked-by: Javier Martinez Canillas <javierm@redhat.com>
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
v11:
 * Don't call .disable_luminance when the CRTC enters self refresh (Sashiko)
 * Only call .set_luminance when the luminance changes or on a modeset
   that isn't a self refresh exit (Leo Li, Sashiko)
 * Fix the .set_luminance self refresh documentation
v10:
 * Add tag
---
 drivers/gpu/drm/Kconfig                    | 14 +++++
 drivers/gpu/drm/drm_atomic_helper.c        | 98 +++++++++++++++++++++++++++++-
 drivers/gpu/drm/drm_atomic_state_helper.c  |  7 +++
 drivers/gpu/drm/drm_atomic_uapi.c          |  5 ++
 drivers/gpu/drm/drm_connector.c            | 57 +++++++++++++++++
 drivers/gpu/drm/tests/drm_connector_test.c | 28 +++++++++
 include/drm/drm_atomic_helper.h            |  3 +
 include/drm/drm_connector.h                | 16 +++++
 include/drm/drm_modeset_helper_vtables.h   | 28 +++++++++
 9 files changed, 255 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 61111d50b1c65..1bbe2c2d7c76f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -33,6 +33,20 @@ endmenu
 
 if DRM
 
+config DRM_LUMINANCE
+	bool "DRM connector luminance support"
+	help
+	  Expose relative luminance as an atomic DRM connector property. Values
+	  are driver-defined levels rather than absolute luminance in cd/m^2.
+	  Drivers provide connector helper callbacks to program their hardware;
+	  the DRM core does not depend on a particular backend.
+
+	  Drivers migrated to this interface will use it instead of registering
+	  a legacy backlight class device. Leave this disabled to preserve the
+	  de-facto userspace ABI in /sys/class/backlight.
+
+	  If in doubt, say N.
+
 config DRM_MIPI_DBI
 	tristate
 	depends on DRM
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 9d006f98413a5..856abc91ec53d 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -1375,6 +1375,8 @@ EXPORT_SYMBOL(drm_atomic_helper_commit_encoder_bridge_post_disable);
 static void
 disable_outputs(struct drm_device *dev, struct drm_atomic_commit *state)
 {
+	drm_atomic_helper_commit_luminance_disables(state);
+
 	drm_atomic_helper_commit_encoder_bridge_disable(dev, state);
 
 	drm_atomic_helper_commit_encoder_bridge_post_disable(dev, state);
@@ -1382,6 +1384,54 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_commit *state)
 	drm_atomic_helper_commit_crtc_disable(dev, state);
 }
 
+/**
+ * drm_atomic_helper_commit_luminance_disables - disable panel emission
+ * @state: atomic state object being committed
+ *
+ * Invokes each connector driver's luminance disable callback before a
+ * display pipeline is disabled. The callback is not invoked when the pipeline
+ * enters self refresh, since the panel keeps displaying the last frame. The
+ * connector property's stored value is left untouched so it can be restored
+ * when the pipeline is enabled again.
+ */
+void drm_atomic_helper_commit_luminance_disables(struct drm_atomic_commit *state)
+{
+	struct drm_connector_state *old_conn_state, *new_conn_state;
+	struct drm_connector *connector;
+	int i;
+
+	for_each_oldnew_connector_in_state(state, connector, old_conn_state,
+					   new_conn_state, i) {
+		const struct drm_connector_helper_funcs *funcs;
+		struct drm_crtc_state *old_crtc_state, *new_crtc_state = NULL;
+
+		if (!connector->luminance_property || !old_conn_state->crtc)
+			continue;
+
+		old_crtc_state =
+			drm_atomic_get_old_crtc_state(state, old_conn_state->crtc);
+		if (new_conn_state->crtc)
+			new_crtc_state =
+				drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
+
+		if (!crtc_needs_disable(old_crtc_state, new_crtc_state) ||
+		    !drm_atomic_crtc_needs_modeset(old_conn_state->crtc->state))
+			continue;
+
+		/*
+		 * Keep the panel lit while entering self refresh, matching the
+		 * panel bridge which also leaves the panel enabled.
+		 */
+		if (new_crtc_state && new_crtc_state->self_refresh_active)
+			continue;
+
+		funcs = connector->helper_private;
+		if (funcs && funcs->disable_luminance)
+			funcs->disable_luminance(old_conn_state);
+	}
+}
+EXPORT_SYMBOL(drm_atomic_helper_commit_luminance_disables);
+
 /**
  * drm_atomic_helper_update_legacy_modeset_state - update legacy modeset state
  * @dev: DRM device
@@ -1771,10 +1821,57 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
 
 	drm_atomic_helper_commit_encoder_bridge_enable(dev, state);
 
+	drm_atomic_helper_commit_luminance_enables(state);
+
 	drm_atomic_helper_commit_writebacks(dev, state);
 }
 EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
 
+/**
+ * drm_atomic_helper_commit_luminance_enables - apply luminance
+ * @state: atomic state object being committed
+ *
+ * Invokes each connector driver's luminance callback after the display
+ * pipeline has been enabled by a modeset, and for commits changing the
+ * luminance while the connector remains active. Commits that don't change
+ * the luminance only invoke the callback for a modeset other than a self
+ * refresh exit, since the panel is kept lit during self refresh. Changes
+ * committed while the connector is inactive remain stored in atomic state
+ * until a later enable.
+ */
+void drm_atomic_helper_commit_luminance_enables(struct drm_atomic_commit *state)
+{
+	struct drm_connector_state *old_conn_state, *new_conn_state;
+	struct drm_connector *connector;
+	int i;
+
+	for_each_oldnew_connector_in_state(state, connector, old_conn_state,
+					   new_conn_state, i) {
+		const struct drm_connector_helper_funcs *funcs;
+		struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+
+		if (!connector->luminance_property || !new_conn_state->crtc)
+			continue;
+
+		old_crtc_state =
+			drm_atomic_get_old_crtc_state(state, new_conn_state->crtc);
+		new_crtc_state =
+			drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
+		if (!new_crtc_state->active)
+			continue;
+
+		if (old_conn_state->luminance == new_conn_state->luminance &&
+		    (!drm_atomic_crtc_needs_modeset(new_crtc_state) ||
+		     old_crtc_state->self_refresh_active))
+			continue;
+
+		funcs = connector->helper_private;
+		if (funcs && funcs->set_luminance)
+			funcs->set_luminance(new_conn_state);
+	}
+}
+EXPORT_SYMBOL(drm_atomic_helper_commit_luminance_enables);
+
 /*
  * For atomic updates which touch just a single CRTC, calculate the time of the
  * next vblank, and inform all the fences of the deadline.
@@ -4187,4 +4284,3 @@ drm_atomic_helper_bridge_get_hdmi_output_bus_fmts(struct drm_bridge *bridge,
 	return out_fmts;
 }
 EXPORT_SYMBOL(drm_atomic_helper_bridge_get_hdmi_output_bus_fmts);
-
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index 3534925636d54..bc26683db52c3 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -408,7 +408,14 @@ void
 __drm_atomic_helper_connector_state_init(struct drm_connector_state *conn_state,
 					 struct drm_connector *connector)
 {
+	u64 value;
+
 	conn_state->connector = connector;
+	if (connector->luminance_property &&
+	    !drm_object_property_get_default_value(&connector->base,
+						   connector->luminance_property,
+						   &value))
+		conn_state->luminance = value;
 }
 EXPORT_SYMBOL(__drm_atomic_helper_connector_state_init);
 
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index 1eebcf6f5e05b..dffc51cd8d13c 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -30,6 +30,7 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_atomic_uapi.h>
+#include <drm/drm_connector.h>
 #include <drm/drm_framebuffer.h>
 #include <drm/drm_print.h>
 #include <drm/drm_drv.h>
@@ -969,6 +970,8 @@ 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) {
+		state->luminance = val;
 	} else if (connector->funcs->atomic_set_property) {
 		return connector->funcs->atomic_set_property(connector,
 				state, property, val);
@@ -1056,6 +1059,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);
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 893716a9e4895..ab75e8bd9991a 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -810,6 +810,47 @@ void drm_connector_attach_edid_property(struct drm_connector *connector)
 }
 EXPORT_SYMBOL(drm_connector_attach_edid_property);
 
+/**
+ * drm_connector_attach_luminance_property - attach luminance property
+ * @connector: connector to attach the property to
+ * @min: minimum supported relative brightness
+ * @max: maximum supported relative brightness
+ * @value: initial relative brightness
+ *
+ * Creates the atomic ``LUMINANCE`` range property for @connector.
+ * Drivers should call this during connector initialization. The property's
+ * units are driver-defined; they do not represent absolute luminance.
+ *
+ * Returns:
+ * Zero on success, a negative errno otherwise.
+ */
+int drm_connector_attach_luminance_property(struct drm_connector *connector,
+					    unsigned int min,
+					    unsigned int max,
+					    unsigned int value)
+{
+	struct drm_property *property;
+
+	if (drm_WARN_ON(connector->dev, min > max || value < min || value > max))
+		return -EINVAL;
+
+	if (connector->luminance_property)
+		return 0;
+
+	property = drm_property_create_range(connector->dev, DRM_MODE_PROP_ATOMIC,
+					     "LUMINANCE", min, max);
+	if (!property)
+		return -ENOMEM;
+
+	connector->luminance_property = property;
+	drm_object_attach_property(&connector->base, property, value);
+	if (connector->state)
+		connector->state->luminance = value;
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_connector_attach_luminance_property);
+
 /**
  * drm_connector_attach_encoder - attach a connector to an encoder
  * @connector: connector to attach
@@ -1681,6 +1722,22 @@ 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 range property for controlling a panel's relative luminance.
+ *     The unit and range are driver-defined and do not represent an absolute
+ *     luminance in cd/m^2. This name therefore also applies to emissive panels
+ *     that do not contain a separate backlight.
+ *
+ *     The property is independent of the connector's display-power state.
+ *     Changing it while the connector is inactive stores the new value without
+ *     turning the display on. The atomic helpers apply the stored value after
+ *     the display pipeline is enabled and invoke a separate driver callback
+ *     before disabling the pipeline; neither action changes the property value.
+ *
+ *     Drivers create the property with
+ *     drm_connector_attach_luminance_property() and implement the
+ *     corresponding &drm_connector_helper_funcs callbacks. Connectors that do
+ *     not support luminance control do not expose the property.
  * 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/tests/drm_connector_test.c b/drivers/gpu/drm/tests/drm_connector_test.c
index 3368aedd4e669..e43692d38ebb8 100644
--- a/drivers/gpu/drm/tests/drm_connector_test.c
+++ b/drivers/gpu/drm/tests/drm_connector_test.c
@@ -137,6 +137,33 @@ static void drm_test_drmm_connector_init_null_ddc(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, ret, 0);
 }
 
+static void drm_test_connector_attach_luminance_property(struct kunit *test)
+{
+	struct drm_connector_init_priv *priv = test->priv;
+	struct drm_connector *connector = &priv->connector;
+	struct drm_property *property;
+	u64 value;
+	int ret;
+
+	ret = drmm_connector_init(&priv->drm, connector, &dummy_funcs,
+				  DRM_MODE_CONNECTOR_eDP, NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_ASSERT_NULL(test, connector->state);
+
+	ret = drm_connector_attach_luminance_property(connector, 10, 100, 50);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	property = connector->luminance_property;
+	KUNIT_ASSERT_NOT_NULL(test, property);
+
+	ret = drm_object_property_get_default_value(&connector->base, property, &value);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, value, (u64)50);
+
+	drm_atomic_helper_connector_reset(connector);
+	KUNIT_ASSERT_NOT_NULL(test, connector->state);
+	KUNIT_EXPECT_EQ(test, connector->state->luminance, 50u);
+}
+
 /*
  * Test that the registration of a connector succeeds for all possible
  * connector types.
@@ -190,6 +217,7 @@ KUNIT_ARRAY_PARAM(drm_connector_init_type_valid,
 static struct kunit_case drmm_connector_init_tests[] = {
 	KUNIT_CASE(drm_test_drmm_connector_init),
 	KUNIT_CASE(drm_test_drmm_connector_init_null_ddc),
+	KUNIT_CASE(drm_test_connector_attach_luminance_property),
 	KUNIT_CASE_PARAM(drm_test_drmm_connector_init_type_valid,
 			 drm_connector_init_type_valid_gen_params),
 	{ }
diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h
index 4cfeec70d648a..3084dd5cd6a1f 100644
--- a/include/drm/drm_atomic_helper.h
+++ b/include/drm/drm_atomic_helper.h
@@ -116,6 +116,9 @@ 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_commit_luminance_disables(struct drm_atomic_commit *state);
+void drm_atomic_helper_commit_luminance_enables(struct drm_atomic_commit *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_connector.h b/include/drm/drm_connector.h
index 2ee7c59329aae..7f7d115b65f9f 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1369,6 +1369,11 @@ struct drm_connector_state {
 	 * @drm_atomic_helper_connector_hdmi_check().
 	 */
 	struct drm_connector_hdmi_state hdmi;
+
+	/**
+	 * @luminance: Relative panel luminance requested by userspace.
+	 */
+	unsigned int luminance;
 };
 
 struct drm_connector_hdmi_audio_funcs {
@@ -2714,6 +2719,13 @@ struct drm_connector {
 	 * @cec: CEC-related data.
 	 */
 	struct drm_connector_cec cec;
+
+	/**
+	 * @luminance_property: Optional atomic range property for relative panel
+	 * luminance. Drivers create it with
+	 * drm_connector_attach_luminance_property().
+	 */
+	struct drm_property *luminance_property;
 };
 
 #define obj_to_connector(x) container_of(x, struct drm_connector, base)
@@ -2980,6 +2992,10 @@ const char *drm_get_colorspace_name(enum drm_colorspace colorspace);
 
 int drm_connector_attach_color_format_property(struct drm_connector *connector,
 					       unsigned long supported_color_formats);
+int drm_connector_attach_luminance_property(struct drm_connector *connector,
+					    unsigned int min,
+					    unsigned int max,
+					    unsigned int value);
 
 /**
  * drm_for_each_connector_iter - connector_list iterator macro
diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
index 60b30cc269404..1c614f44de7ca 100644
--- a/include/drm/drm_modeset_helper_vtables.h
+++ b/include/drm/drm_modeset_helper_vtables.h
@@ -1147,6 +1147,34 @@ struct drm_connector_helper_funcs {
 	void (*atomic_commit)(struct drm_connector *connector,
 			      struct drm_atomic_commit *state);
 
+	/**
+	 * @set_luminance:
+	 *
+	 * Apply &drm_connector_state.luminance to an active panel. The
+	 * atomic helpers call this after a modeset enabled the display
+	 * pipeline, and for commits changing the luminance while the connector
+	 * remains active. It is not called when the luminance is unchanged,
+	 * unless a modeset other than a self refresh exit happened.
+	 *
+	 * This callback is optional and is only used by connectors exposing the
+	 * ``LUMINANCE`` property.
+	 */
+	void (*set_luminance)(const struct drm_connector_state *new_state);
+
+	/**
+	 * @disable_luminance:
+	 *
+	 * Turn panel light emission off before disabling the display pipeline.
+	 * It is not called when the pipeline enters self refresh.
+	 * This callback does not change the ``LUMINANCE`` property; the
+	 * stored value is restored through @set_luminance when the panel
+	 * is enabled again.
+	 *
+	 * This callback is optional and is only used by connectors exposing the
+	 * ``LUMINANCE`` property.
+	 */
+	void (*disable_luminance)(const struct drm_connector_state *old_state);
+
 	/**
 	 * @prepare_writeback_job:
 	 *

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 02/12] drm/amd/display: Pass up errors reading actual brightness
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 01/12] drm: add connector LUMINANCE property Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 03/12] drm/amdgpu: Check bios_scratch_reg_offset in backlight level helper Mario Limonciello (AMD)
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

From: Mario Limonciello <mario.limonciello@amd.com>

[Why]
If the DC API fails to return actual brightness when backlight control
API requests it, then the wrong value may be returned.

[How]
Change return type of amdgpu_dm_backlight_get_level() to an integer
and pass an error code up to the caller.

Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 13 +++++++++----
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h |  2 +-
 .../amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c  |  4 ++--
 3 files changed, 12 insertions(+), 7 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 cb74b00723fda..0f664c658a127 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
@@ -438,7 +438,7 @@ int amdgpu_dm_backlight_update_status(struct backlight_device *bd)
 EXPORT_IF_KUNIT(amdgpu_dm_backlight_update_status);
 
 STATIC_IFN_KUNIT
-u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx)
+int amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx)
 {
 	int ret;
 	struct amdgpu_dm_backlight_caps caps;
@@ -451,14 +451,14 @@ u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx)
 		u32 avg, peak;
 
 		if (!dc_link_get_backlight_level_nits(link, &avg, &peak))
-			return dm->brightness[bl_idx];
+			return -EINVAL;
 		return convert_brightness_to_user(&caps, avg);
 	}
 
 	ret = dc_link_get_backlight_level(link);
 
 	if (ret == DC_ERROR_UNEXPECTED)
-		return dm->brightness[bl_idx];
+		return -EINVAL;
 
 	return convert_brightness_to_user(&caps, ret);
 }
@@ -469,8 +469,13 @@ int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd)
 {
 	struct amdgpu_display_manager *dm = bl_get_data(bd);
 	int i = amdgpu_dm_backlight_get_device_index(dm, bd);
+	int ret;
+
+	ret = amdgpu_dm_backlight_get_level(dm, i);
+	if (ret < 0)
+		return dm->brightness[i];
 
-	return amdgpu_dm_backlight_get_level(dm, i);
+	return ret;
 }
 EXPORT_IF_KUNIT(amdgpu_dm_backlight_get_brightness);
 
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
index 9ad888d3ae7b2..891749322191c 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
@@ -57,7 +57,7 @@ void amdgpu_dm_validate_backlight_caps(struct amdgpu_display_manager *dm, int bl
 struct dc_stream_state *dm_find_stream_with_link(struct amdgpu_display_manager *dm,
 						 struct dc_link *link);
 int amdgpu_dm_backlight_update_status(struct backlight_device *bd);
-u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx);
+int amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, int bl_idx);
 int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd);
 ssize_t panel_power_savings_show(struct device *device,
 				 struct device_attribute *attr,
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 28dda5324dfa7..8fc1c2f997688 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
@@ -382,7 +382,7 @@ static void dm_test_backlight_get_level_pwm_error(struct kunit *test)
 	dm->brightness[0] = 4321;
 	dm->backlight_link[0] = link;
 
-	KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), 4321U);
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), -EINVAL);
 }
 
 /**
@@ -424,7 +424,7 @@ static void dm_test_backlight_get_level_aux_error(struct kunit *test)
 	caps->caps_valid = true;
 	caps->aux_support = true;
 
-	KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), 6789U);
+	KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_level(dm, 0), -EINVAL);
 }
 
 /**

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 03/12] drm/amdgpu: Check bios_scratch_reg_offset in backlight level helper
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 01/12] drm: add connector LUMINANCE property Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 02/12] drm/amd/display: Pass up errors reading actual brightness Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 04/12] drm/amd: add luminance property support Mario Limonciello (AMD)
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau

Ensure adev->bios_scratch_reg_offset is non-zero before accessing
scratch registers in amdgpu_atombios_scratch_regs_set_backlight_level(),
so they are not accessed when they were never set up, such as in KUnit
tests.

Signed-off-by: Mario Limonciello (AMD) <superm1@kernel.org>
---
v11:
 * Clarify the commit message
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c
index 27c0dc8f6137a..2d8c2ba04e670 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c
@@ -1622,7 +1622,12 @@ void amdgpu_atombios_scratch_regs_engine_hung(struct amdgpu_device *adev,
 void amdgpu_atombios_scratch_regs_set_backlight_level(struct amdgpu_device *adev,
 						      u32 backlight_level)
 {
-	u32 tmp = RREG32(adev->bios_scratch_reg_offset + 2);
+	u32 tmp;
+
+	if (!adev->bios_scratch_reg_offset)
+		return;
+
+	tmp = RREG32(adev->bios_scratch_reg_offset + 2);
 
 	tmp &= ~ATOM_S2_CURRENT_BL_LEVEL_MASK;
 	tmp |= (backlight_level << ATOM_S2_CURRENT_BL_LEVEL_SHIFT) &

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 04/12] drm/amd: add luminance property support
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
                   ` (2 preceding siblings ...)
  2026-11-03 22:42 ` [PATCH v11 03/12] drm/amdgpu: Check bios_scratch_reg_offset in backlight level helper Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 05/12] drm/i915/display: " Mario Limonciello (AMD)
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

From: Mario Limonciello <mario.limonciello@amd.com>

Expose the DRM LUMINANCE connector property for internal panels when
CONFIG_DRM_LUMINANCE is enabled. Keep registering the existing backlight
class device otherwise, preserving its userspace ABI.

Use the existing brightness scaling and programming paths from Display
Core connector helper callbacks. Apply atomic luminance after the display
pipeline is enabled so changes made while a connector is inactive take
effect on the next enable.

The legacy non-DC display path is not atomic, so it keeps registering the
backlight class device. Only attach LUMINANCE when ACPI video selects
native backlight control, otherwise keep the existing acpi_video fallback.
Reapply the cached level after modesets and GPU resets for both
interfaces.

With LUMINANCE, ATIF panel brightness change requests from firmware are
not forwarded, since there is no backlight device to update.

Update the backlight KUnit fixtures to initialise DRM mode configuration,
connector state and link service mocks needed by the LUMINANCE path.

Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
v11:
 * Drop the legacy (non-DC) changes; that path is not atomic and keeps the
   backlight class device (Sashiko)
 * Only attach LUMINANCE when acpi_video_backlight_use_native() allows it,
   otherwise register the class device or acpi_video fallback (Sashiko)
 * Restore brightness after GPU reset and modesets for LUMINANCE panels
   too, seeding dm->brightness[] with the initial value (Leo Li, Sashiko)
 * Skip programming an unchanged luminance to avoid PSR/Replay/IPS exits
   (Leo Li, Sashiko)
 * Keep selecting BACKLIGHT_CLASS_DEVICE (Sashiko)
 * Seed the property from the hardware level rather than the default
 * Leave the privacy screen update in commit_tail unchanged
 * Squash in "drm/amd/display: update KUnit luminance fixtures" so every
   commit passes KUnit, rebased on drm-tip fixture changes (Intel CI)
 * Initialise the KUnit test connector with the type matching the link
   signal to avoid freeing the ID into the wrong IDA (Sashiko)
---
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c  | 16 ++---
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h  |  7 ++
 .../amd/display/amdgpu_dm/amdgpu_dm_backlight.c    | 78 +++++++++++++++++++++-
 .../amd/display/amdgpu_dm/amdgpu_dm_backlight.h    |  3 +
 .../amd/display/amdgpu_dm/amdgpu_dm_connector.c    |  5 +-
 .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c     | 68 ++++++++++++++++---
 .../amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c      |  7 +-
 7 files changed, 160 insertions(+), 24 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 56fca4e22df8d..2a9ddd1dc6f70 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -1976,10 +1976,7 @@ static int dm_resume(struct amdgpu_ip_block *ip_block)
 		mutex_unlock(&dm->dc_lock);
 
 		/* set the backlight after a reset */
-		for (i = 0; i < dm->num_of_edps; i++) {
-			if (dm->backlight_dev[i])
-				amdgpu_dm_backlight_set_level(dm, i, dm->brightness[i]);
-		}
+		amdgpu_dm_backlight_restore(dm);
 
 		return 0;
 	}
@@ -4802,12 +4799,8 @@ static void amdgpu_dm_commit_streams(struct drm_atomic_commit *state,
 	 * level. (the dm->brightness is the correct panel level)
 	 * So we set the backlight level with dm->brightness value after set mode
 	 */
-	if (set_backlight_level) {
-		for (i = 0; i < dm->num_of_edps; i++) {
-			if (dm->backlight_dev[i])
-				amdgpu_dm_backlight_set_level(dm, i, dm->brightness[i]);
-		}
-	}
+	if (set_backlight_level)
+		amdgpu_dm_backlight_restore(dm);
 }
 
 STATIC_IFN_KUNIT void
@@ -5413,6 +5406,9 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state)
 	/* Update audio instances for each connector. */
 	amdgpu_dm_commit_audio(dev, state);
 
+	/* Apply connector luminance after the display pipeline is enabled. */
+	drm_atomic_helper_commit_luminance_enables(state);
+
 	/* restore the backlight level */
 	for (i = 0; i < dm->num_of_edps; i++) {
 		if (dm->backlight_dev[i] &&
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
index e4fed8c855abe..2e700cae52b67 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -682,6 +682,13 @@ struct amdgpu_display_manager {
 	 * last successfully applied backlight values.
 	 */
 	u32 actual_brightness[AMDGPU_DM_MAX_NUM_EDP];
+	/**
+	 * @luminance:
+	 *
+	 * backlight is controlled through the connector LUMINANCE property
+	 * instead of a backlight class device.
+	 */
+	bool luminance[AMDGPU_DM_MAX_NUM_EDP];
 
 	/**
 	 * @aux_hpd_discon_quirk:
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 0f664c658a127..f61e69bbdf69f 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
@@ -41,6 +41,7 @@
 #include <linux/backlight.h>
 #include <linux/minmax.h>
 #include <linux/power_supply.h>
+#include <drm/drm_connector.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_panel_quirks.h>
 
@@ -296,13 +297,17 @@ struct dc_stream_state *dm_find_stream_with_link(
 	struct amdgpu_display_manager *dm,
 	struct dc_link *link)
 {
-	struct dc_state *cur_dc_state = dm->dc->current_state;
+	struct dc_state *cur_dc_state;
 	struct dc_stream_state *stream = NULL;
 	int i;
 
+	if (!dm || !dm->dc || !dm->dc->current_state || !link)
+		return NULL;
+
+	cur_dc_state = dm->dc->current_state;
 	for (i = 0; i < cur_dc_state->stream_count; i++) {
 		stream = cur_dc_state->streams[i];
-		if (stream->link == link)
+		if (stream && stream->link == link)
 			return stream;
 	}
 
@@ -356,7 +361,7 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
 
 	dm->brightness[bl_idx] = user_brightness;
 	/* update scratch register */
-	if (bl_idx == 0)
+	if (bl_idx == 0 && dm->adev && dm->adev->rmmio && dm->adev->bios_scratch_reg_offset)
 		amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]);
 
 	link = (struct dc_link *)dm->backlight_link[bl_idx];
@@ -657,6 +662,10 @@ void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm,
 {
 	struct dc_link *link = aconnector->dc_link;
 	int bl_idx = dm->num_of_edps;
+	struct backlight_properties props = { 0 };
+	struct amdgpu_dm_backlight_caps *caps;
+	struct drm_connector *connector = &aconnector->base;
+	int ret;
 
 	if (!(link->connector_signal & (SIGNAL_TYPE_EDP | SIGNAL_TYPE_LVDS)) ||
 	    link->type == dc_connection_none)
@@ -675,6 +684,30 @@ void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm,
 
 	amdgpu_dm_update_connector_ext_caps(aconnector);
 
+	/*
+	 * The property must exist before the device is registered. If ACPI
+	 * video should handle the backlight, leave it to
+	 * amdgpu_dm_register_backlight_device() to register the fallback.
+	 */
+	if (IS_ENABLED(CONFIG_DRM_LUMINANCE) && acpi_video_backlight_use_native()) {
+		caps = &dm->backlight_caps[bl_idx];
+		amdgpu_dm_backlight_fill_props(caps, power_supply_is_system_supplied() > 0,
+					       !(amdgpu_dc_debug_mask &
+						 DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE),
+					       &props);
+		/* Start from the level the hardware is actually at */
+		ret = amdgpu_dm_backlight_get_level(dm, bl_idx);
+		if (ret >= 0 && ret <= props.max_brightness)
+			props.brightness = ret;
+		ret = drm_connector_attach_luminance_property(connector, 0,
+							      props.max_brightness,
+							      props.brightness);
+		if (!drm_WARN_ON(adev_to_drm(dm->adev), ret)) {
+			dm->brightness[bl_idx] = props.brightness;
+			dm->luminance[bl_idx] = true;
+		}
+	}
+
 	/* Offer ABM property when user didn't turn off by module parameter.
 	 * OLED panels are included to support CACP (Content Adaptive
 	 * Contrast and Power) feature via set_abm_level.
@@ -686,6 +719,45 @@ void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm,
 }
 EXPORT_IF_KUNIT(amdgpu_dm_setup_backlight_device);
 
+void amdgpu_dm_set_luminance(const struct drm_connector_state *state)
+{
+	struct amdgpu_dm_connector *aconnector =
+		to_amdgpu_dm_connector(state->connector);
+	struct amdgpu_display_manager *dm =
+		&drm_to_adev(state->connector->dev)->dm;
+
+	int bl_idx = aconnector->bl_idx;
+
+	if (bl_idx < 0)
+		return;
+
+	/* Skip if already applied, e.g. by amdgpu_dm_backlight_restore(). */
+	if (dm->brightness[bl_idx] == state->luminance &&
+	    dm->actual_brightness[bl_idx] == state->luminance)
+		return;
+
+	amdgpu_dm_backlight_set_level(dm, bl_idx, state->luminance);
+}
+
+/**
+ * amdgpu_dm_backlight_restore - reprogram the cached backlight levels
+ * @dm: display manager
+ *
+ * Display Core may reset the panel brightness when committing streams, so
+ * reapply the cached level of every panel whose backlight is controlled by
+ * the driver, either through a backlight class device or through the
+ * connector LUMINANCE property.
+ */
+void amdgpu_dm_backlight_restore(struct amdgpu_display_manager *dm)
+{
+	int i;
+
+	for (i = 0; i < dm->num_of_edps; i++) {
+		if (dm->backlight_dev[i] || dm->luminance[i])
+			amdgpu_dm_backlight_set_level(dm, i, dm->brightness[i]);
+	}
+}
+
 /**
  * DOC: panel power savings
  *
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
index 891749322191c..69704e0ff5b25 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
@@ -33,6 +33,7 @@ struct dc_stream_state;
 struct device;
 struct device_attribute;
 struct drm_connector;
+struct drm_connector_state;
 struct attribute_group;
 
 #define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12
@@ -47,6 +48,8 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
 void amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector);
 void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm,
 			    struct amdgpu_dm_connector *aconnector);
+void amdgpu_dm_set_luminance(const struct drm_connector_state *state);
+void amdgpu_dm_backlight_restore(struct amdgpu_display_manager *dm);
 void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector);
 bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *aconnector);
 
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
index 8db7956c01d32..71f7111dfb268 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c
@@ -1969,6 +1969,7 @@ STATIC_IFN_KUNIT void amdgpu_dm_connector_destroy(struct drm_connector *connecto
 	if (aconnector->bl_idx != -1) {
 		backlight_device_unregister(dm->backlight_dev[aconnector->bl_idx]);
 		dm->backlight_dev[aconnector->bl_idx] = NULL;
+		dm->luminance[aconnector->bl_idx] = false;
 	}
 
 	if (aconnector->dc_em_sink)
@@ -2061,7 +2062,8 @@ amdgpu_dm_connector_late_register(struct drm_connector *connector)
 			return r;
 	}
 
-	amdgpu_dm_register_backlight_device(amdgpu_dm_connector);
+	if (!connector->luminance_property)
+		amdgpu_dm_register_backlight_device(amdgpu_dm_connector);
 
 	if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) ||
 	    (connector->connector_type == DRM_MODE_CONNECTOR_eDP)) {
@@ -2724,6 +2726,7 @@ amdgpu_dm_connector_helper_funcs = {
 	.get_modes = get_modes,
 	.mode_valid = amdgpu_dm_connector_mode_valid,
 	.atomic_check = amdgpu_dm_connector_atomic_check,
+	.set_luminance = amdgpu_dm_set_luminance,
 };
 
 int amdgpu_dm_convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth)
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 8fc1c2f997688..c33c121a19b25 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
@@ -37,16 +37,39 @@ static const struct drm_connector_funcs dm_backlight_test_connector_funcs = {
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
 
+static int dm_test_signal_to_connector_type(enum signal_type signal)
+{
+	switch (signal) {
+	case SIGNAL_TYPE_EDP:
+		return DRM_MODE_CONNECTOR_eDP;
+	case SIGNAL_TYPE_LVDS:
+		return DRM_MODE_CONNECTOR_LVDS;
+	case SIGNAL_TYPE_HDMI_TYPE_A:
+		return DRM_MODE_CONNECTOR_HDMIA;
+	default:
+		return DRM_MODE_CONNECTOR_Unknown;
+	}
+}
+
 static void setup_test_connector(struct kunit *test,
 				 struct dm_backlight_connector_fixture *fixture,
 				 int bl_idx, enum signal_type signal)
 {
-	fixture->adev = kunit_kzalloc(test, sizeof(*fixture->adev), GFP_KERNEL);
-	fixture->aconnector = kunit_kzalloc(test, sizeof(*fixture->aconnector), GFP_KERNEL);
+	int ret;
+
+	fixture->adev = dm_kunit_alloc_adev(test);
+	ret = drmm_mode_config_init(&fixture->adev->ddev);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	fixture->aconnector = dm_kunit_alloc_connector(test, fixture->adev, NULL);
 	fixture->link = kunit_kzalloc(test, sizeof(*fixture->link), GFP_KERNEL);
-	KUNIT_ASSERT_NOT_NULL(test, fixture->adev);
-	KUNIT_ASSERT_NOT_NULL(test, fixture->aconnector);
 	KUNIT_ASSERT_NOT_NULL(test, fixture->link);
+	ret = drmm_connector_init(&fixture->adev->ddev,
+				  &fixture->aconnector->base,
+				  &dm_backlight_test_connector_funcs,
+				  dm_test_signal_to_connector_type(signal), NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	drm_atomic_helper_connector_reset(&fixture->aconnector->base);
 
 	fixture->aconnector->bl_idx = bl_idx;
 	fixture->aconnector->dc_link = fixture->link;
@@ -483,12 +506,17 @@ static void dm_test_register_backlight_device_success(struct kunit *test)
 	struct dc_link *link = dm_kunit_alloc_link(test);
 	struct drm_minor *primary;
 	unsigned int max;
+	int ret;
+
+	ret = drmm_mode_config_init(&adev->ddev);
+	KUNIT_ASSERT_EQ(test, ret, 0);
 
 	setup_test_link_service(test, link);
 	link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_error;
 	primary = kunit_kzalloc(test, sizeof(*primary), GFP_KERNEL);
 	KUNIT_ASSERT_NOT_NULL(test, primary);
 	adev->ddev.primary = primary;
+	adev->dm.ddev = &adev->ddev;
 	adev->dm.backlight_link[0] = link;
 
 	aconnector = dm_kunit_alloc_connector(test, adev, link);
@@ -1667,7 +1695,6 @@ static void dm_test_should_create_sysfs_non_edp(struct kunit *test)
 
 	amdgpu_dm_set_abm_level_param(-1);
 	setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_HDMI_TYPE_A);
-	fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA;
 
 	KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector));
 
@@ -1843,6 +1870,9 @@ static void dm_test_setup_backlight_device_oled_success(struct kunit *test)
 	setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP);
 	fixture.link->type = dc_connection_single;
 	fixture.link->dpcd_sink_ext_caps.bits.oled = 1;
+	setup_test_link_service(test, fixture.link);
+	fixture.link->dc->link_srv->edp_get_backlight_level_nits =
+		dm_test_get_backlight_level_nits;
 	dm = &fixture.adev->dm;
 	dm->adev = fixture.adev;
 	dm->num_of_edps = 0;
@@ -1872,6 +1902,7 @@ static void dm_test_setup_backlight_device_attaches_abm_property(struct kunit *t
 	struct dc_link *link;
 	int saved_abm_level = amdgpu_dm_get_abm_level_param();
 	int saved_backlight = amdgpu_dm_get_backlight_param();
+	int abm_index;
 	int old_count;
 	int ret;
 
@@ -1890,25 +1921,44 @@ static void dm_test_setup_backlight_device_attaches_abm_property(struct kunit *t
 				   &dm_backlight_test_connector_funcs,
 				   DRM_MODE_CONNECTOR_eDP, NULL);
 	KUNIT_ASSERT_EQ(test, ret, 0);
+	drm_atomic_helper_connector_reset(&aconnector->base);
 
 	link = dm_kunit_alloc_link(test);
 	link->connector_signal = SIGNAL_TYPE_EDP;
 	link->type = dc_connection_single;
+	setup_test_link_service(test, link);
+	link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_mid;
 	aconnector->dc_link = link;
 	aconnector->bl_idx = -1;
 	dm = &adev->dm;
 	dm->adev = adev;
 	dm->ddev = &adev->ddev;
 	old_count = aconnector->base.base.properties->count;
+	abm_index = old_count + IS_ENABLED(CONFIG_DRM_LUMINANCE);
 
 	amdgpu_dm_setup_backlight_device(dm, aconnector);
 
 	KUNIT_EXPECT_EQ(test, dm->num_of_edps, 1);
 	KUNIT_EXPECT_EQ(test, aconnector->bl_idx, 0);
-	KUNIT_EXPECT_EQ(test, aconnector->base.base.properties->count, old_count + 1);
-	KUNIT_EXPECT_PTR_EQ(test, aconnector->base.base.properties->properties[old_count], prop);
-	KUNIT_EXPECT_EQ(test, aconnector->base.base.properties->values[old_count],
-			 (uint64_t)ABM_SYSFS_CONTROL);
+	KUNIT_EXPECT_EQ(test, aconnector->base.base.properties->count,
+			old_count + 1 + IS_ENABLED(CONFIG_DRM_LUMINANCE));
+	if (IS_ENABLED(CONFIG_DRM_LUMINANCE)) {
+		KUNIT_EXPECT_NOT_NULL(test,
+				      aconnector->base.luminance_property);
+		KUNIT_EXPECT_LE(test,
+				aconnector->base.state->luminance,
+				(uint64_t)MAX_BACKLIGHT_LEVEL);
+		/* Restores after reset/modeset must use the initial value */
+		KUNIT_EXPECT_EQ(test, dm->brightness[0],
+				aconnector->base.state->luminance);
+	}
+	KUNIT_EXPECT_EQ(test, dm->luminance[0],
+			IS_ENABLED(CONFIG_DRM_LUMINANCE));
+	KUNIT_EXPECT_PTR_EQ(test,
+			    aconnector->base.base.properties->properties[abm_index],
+			    prop);
+	KUNIT_EXPECT_EQ(test, aconnector->base.base.properties->values[abm_index],
+			(uint64_t)ABM_SYSFS_CONTROL);
 
 	amdgpu_dm_set_backlight_param(saved_backlight);
 	amdgpu_dm_set_abm_level_param(saved_abm_level);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c
index f844b644bb776..7e9e8b1d0b10d 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c
@@ -20,6 +20,7 @@
 
 struct amdgpu_device *dm_kunit_alloc_adev(struct kunit *test)
 {
+	struct amdgpu_device *adev;
 	struct drm_device *drm;
 	struct device *dev;
 
@@ -32,7 +33,11 @@ struct amdgpu_device *dm_kunit_alloc_adev(struct kunit *test)
 						   DRIVER_MODESET | DRIVER_ATOMIC);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm);
 
-	return drm_to_adev(drm);
+	adev = drm_to_adev(drm);
+
+	mutex_init(&adev->dm.dc_lock);
+
+	return adev;
 }
 EXPORT_SYMBOL(dm_kunit_alloc_adev);
 

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 05/12] drm/i915/display: add luminance property support
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
                   ` (3 preceding siblings ...)
  2026-11-03 22:42 ` [PATCH v11 04/12] drm/amd: add luminance property support Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 06/12] drm/bridge: add connector luminance operations Mario Limonciello (AMD)
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

From: Mario Limonciello <mario.limonciello@amd.com>

Expose the DRM LUMINANCE connector property for eDP, LVDS, and DSI panels
when CONFIG_DRM_LUMINANCE is enabled. Keep registering the existing
backlight class device otherwise, preserving its userspace ABI.

Use the existing Intel brightness scaling and programming path from
connector helper callbacks. Apply atomic luminance after modeset enables
so updates made while a panel is inactive take effect when it is enabled
again. The shared display code provides the same behavior to i915 and Xe.

Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
v11:
 * Call acpi_video_backlight_use_native() before attaching LUMINANCE and
   leave the panel to acpi_video otherwise (Sashiko)
 * Enable the backlight at the committed LUMINANCE level (Sashiko)
 * Fix build with CONFIG_BACKLIGHT_CLASS_DEVICE=n (Sashiko)
 * Keep selecting BACKLIGHT_CLASS_DEVICE if ACPI (Sashiko)
 * Fall back to the backlight class device if attaching LUMINANCE fails
---
 drivers/gpu/drm/i915/display/icl_dsi.c         |  1 +
 drivers/gpu/drm/i915/display/intel_backlight.c | 74 ++++++++++++++++++++++----
 drivers/gpu/drm/i915/display/intel_backlight.h |  1 +
 drivers/gpu/drm/i915/display/intel_display.c   |  2 +
 drivers/gpu/drm/i915/display/intel_dp.c        |  1 +
 drivers/gpu/drm/i915/display/intel_lvds.c      |  1 +
 drivers/gpu/drm/i915/display/intel_panel.c     |  8 +--
 drivers/gpu/drm/i915/display/vlv_dsi.c         |  1 +
 8 files changed, 76 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/drm/i915/display/icl_dsi.c b/drivers/gpu/drm/i915/display/icl_dsi.c
index ea0cdb7822f39..37ff84c03ce05 100644
--- a/drivers/gpu/drm/i915/display/icl_dsi.c
+++ b/drivers/gpu/drm/i915/display/icl_dsi.c
@@ -1801,6 +1801,7 @@ static const struct drm_connector_helper_funcs gen11_dsi_connector_helper_funcs
 	.get_modes = intel_dsi_get_modes,
 	.mode_valid = gen11_dsi_mode_valid,
 	.atomic_check = intel_digital_connector_atomic_check,
+	.set_luminance = intel_backlight_set_luminance,
 };
 
 static int gen11_dsi_host_attach(struct mipi_dsi_host *host,
diff --git a/drivers/gpu/drm/i915/display/intel_backlight.c b/drivers/gpu/drm/i915/display/intel_backlight.c
index b128896cb1c2d..f9e84d17d3fb0 100644
--- a/drivers/gpu/drm/i915/display/intel_backlight.c
+++ b/drivers/gpu/drm/i915/display/intel_backlight.c
@@ -9,6 +9,7 @@
 #include <linux/string_helpers.h>
 #include <acpi/video.h>
 
+#include <drm/drm_connector.h>
 #include <drm/drm_file.h>
 #include <drm/drm_print.h>
 #include <drm/intel/pci_config.h>
@@ -76,6 +77,16 @@ static u32 clamp_user_to_hw(struct intel_connector *connector,
 	return hw_level;
 }
 
+/* Scale user_level in range [0..user_max] to [hw_min..hw_max]. */
+static u32 scale_user_to_hw(struct intel_connector *connector,
+			    u32 user_level, u32 user_max)
+{
+	struct intel_panel *panel = &connector->panel;
+
+	return scale(user_level, 0, user_max,
+		     panel->backlight.min, panel->backlight.max);
+}
+
 /* Scale hw_level in range [hw_min..hw_max] to [0..user_max]. */
 static u32 scale_hw_to_user(struct intel_connector *connector,
 			    u32 hw_level, u32 user_max)
@@ -778,6 +789,16 @@ static void __intel_backlight_enable(const struct intel_crtc_state *crtc_state,
 
 	WARN_ON(panel->backlight.max == 0);
 
+	/*
+	 * Enable at the luminance committed in this atomic state, rather than
+	 * at a stale level which would only be corrected afterwards by
+	 * drm_atomic_helper_commit_luminance_enables().
+	 */
+	if (connector->base.luminance_property)
+		panel->backlight.level =
+			scale_user_to_hw(connector, conn_state->luminance,
+					 panel->backlight.max);
+
 	if (panel->backlight.level < panel->backlight.min) {
 		panel->backlight.level = panel->backlight.min;
 		if (panel->backlight.device)
@@ -831,16 +852,6 @@ static u32 intel_panel_get_backlight(struct intel_connector *connector)
 	return val;
 }
 
-/* Scale user_level in range [0..user_max] to [hw_min..hw_max]. */
-static u32 scale_user_to_hw(struct intel_connector *connector,
-			    u32 user_level, u32 user_max)
-{
-	struct intel_panel *panel = &connector->panel;
-
-	return scale(user_level, 0, user_max,
-		     panel->backlight.min, panel->backlight.max);
-}
-
 /* set backlight brightness to level in range [0..max], scaling wrt hw min */
 static void intel_panel_set_backlight(const struct drm_connector_state *conn_state,
 				      u32 user_level, u32 user_max)
@@ -1670,6 +1681,9 @@ int intel_backlight_setup(struct intel_connector *connector, enum pipe pipe)
 {
 	struct intel_display *display = to_intel_display(connector);
 	struct intel_panel *panel = &connector->panel;
+	struct drm_connector *base = &connector->base;
+	u32 brightness;
+	u32 max;
 	int ret;
 
 	if (!connector->panel.vbt.backlight.present) {
@@ -1703,6 +1717,19 @@ int intel_backlight_setup(struct intel_connector *connector, enum pipe pipe)
 
 	panel->backlight.present = true;
 
+	/*
+	 * acpi_video_backlight_use_native() also tells the ACPI video code that
+	 * a native backlight is available. If native control should not be
+	 * used, leave the backlight to acpi_video, which is registered later
+	 * from intel_acpi_video_register().
+	 */
+	if (IS_ENABLED(CONFIG_DRM_LUMINANCE) && acpi_video_backlight_use_native()) {
+		max = panel->backlight.max;
+		brightness = scale_hw_to_user(connector, panel->backlight.level, max);
+		ret = drm_connector_attach_luminance_property(base, 0, max, brightness);
+		drm_WARN_ON(display->drm, ret);
+	}
+
 	drm_dbg_kms(display->drm,
 		    "[CONNECTOR:%d:%s] backlight initialized, %s, brightness %u/%u\n",
 		    connector->base.base.id, connector->base.name,
@@ -1712,6 +1739,33 @@ int intel_backlight_setup(struct intel_connector *connector, enum pipe pipe)
 	return 0;
 }
 
+void intel_backlight_set_luminance(const struct drm_connector_state *conn_state)
+{
+	struct intel_connector *connector =
+		to_intel_connector(conn_state->connector);
+	struct intel_display *display = to_intel_display(connector);
+	struct intel_panel *panel = &connector->panel;
+	u32 hw_level;
+
+	if (!panel->backlight.present)
+		return;
+
+	guard(mutex)(&display->backlight.lock);
+
+	/*
+	 * Nothing to do if __intel_backlight_enable() already programmed this
+	 * level as part of a modeset enable.
+	 */
+	hw_level = scale_user_to_hw(connector, conn_state->luminance,
+				    panel->backlight.max);
+	if (hw_level == panel->backlight.level)
+		return;
+
+	panel->backlight.level = hw_level;
+	if (panel->backlight.enabled)
+		intel_panel_actually_set_backlight(conn_state, hw_level);
+}
+
 void intel_backlight_destroy(struct intel_panel *panel)
 {
 	/* dispose of the pwm */
diff --git a/drivers/gpu/drm/i915/display/intel_backlight.h b/drivers/gpu/drm/i915/display/intel_backlight.h
index 339643f638978..1634ce414695a 100644
--- a/drivers/gpu/drm/i915/display/intel_backlight.h
+++ b/drivers/gpu/drm/i915/display/intel_backlight.h
@@ -32,6 +32,7 @@ void intel_backlight_set_acpi(const struct drm_connector_state *conn_state,
 			      u32 level, u32 max);
 void intel_backlight_set_pwm_level(const struct drm_connector_state *conn_state,
 				   u32 level);
+void intel_backlight_set_luminance(const struct drm_connector_state *conn_state);
 u32 intel_backlight_invert_pwm_level(struct intel_connector *connector, u32 level);
 u32 intel_backlight_level_to_pwm(struct intel_connector *connector, u32 level);
 u32 intel_backlight_level_from_pwm(struct intel_connector *connector, u32 val);
diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
index c1c2b99cfb53b..f87c78e316a5d 100644
--- a/drivers/gpu/drm/i915/display/intel_display.c
+++ b/drivers/gpu/drm/i915/display/intel_display.c
@@ -7845,6 +7845,8 @@ static void intel_atomic_commit_tail(struct intel_atomic_state *state)
 	/* Now enable the clocks, plane, pipe, and connectors that we set up. */
 	display->modeset.funcs->commit_modeset_enables(state);
 
+	drm_atomic_helper_commit_luminance_enables(&state->base);
+
 	intel_display_power_dc3co_compute(state);
 
 	/* FIXME probably need to sequence this properly */
diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
index ef164a7976187..905c8743a2a2b 100644
--- a/drivers/gpu/drm/i915/display/intel_dp.c
+++ b/drivers/gpu/drm/i915/display/intel_dp.c
@@ -7060,6 +7060,7 @@ static const struct drm_connector_helper_funcs intel_dp_connector_helper_funcs =
 	.get_modes = intel_dp_get_modes,
 	.mode_valid = intel_dp_mode_valid,
 	.atomic_check = intel_dp_connector_atomic_check,
+	.set_luminance = intel_backlight_set_luminance,
 };
 
 enum irqreturn
diff --git a/drivers/gpu/drm/i915/display/intel_lvds.c b/drivers/gpu/drm/i915/display/intel_lvds.c
index 872753478cf21..fa668b1debb21 100644
--- a/drivers/gpu/drm/i915/display/intel_lvds.c
+++ b/drivers/gpu/drm/i915/display/intel_lvds.c
@@ -502,6 +502,7 @@ static const struct drm_connector_helper_funcs intel_lvds_connector_helper_funcs
 	.get_modes = intel_lvds_get_modes,
 	.mode_valid = intel_lvds_mode_valid,
 	.atomic_check = intel_digital_connector_atomic_check,
+	.set_luminance = intel_backlight_set_luminance,
 };
 
 static const struct drm_connector_funcs intel_lvds_connector_funcs = {
diff --git a/drivers/gpu/drm/i915/display/intel_panel.c b/drivers/gpu/drm/i915/display/intel_panel.c
index 81e638d0c7b33..9e2a53f300d39 100644
--- a/drivers/gpu/drm/i915/display/intel_panel.c
+++ b/drivers/gpu/drm/i915/display/intel_panel.c
@@ -610,9 +610,11 @@ int intel_panel_register(struct intel_connector *connector)
 	struct intel_panel *panel = &connector->panel;
 	int ret;
 
-	ret = intel_backlight_device_register(connector);
-	if (ret)
-		return ret;
+	if (!connector->base.luminance_property) {
+		ret = intel_backlight_device_register(connector);
+		if (ret)
+			return ret;
+	}
 
 	if (connector->base.connector_type == DRM_MODE_CONNECTOR_DSI ||
 	    connector->base.connector_type == DRM_MODE_CONNECTOR_eDP) {
diff --git a/drivers/gpu/drm/i915/display/vlv_dsi.c b/drivers/gpu/drm/i915/display/vlv_dsi.c
index 8829f365592ef..7e60407e0af4a 100644
--- a/drivers/gpu/drm/i915/display/vlv_dsi.c
+++ b/drivers/gpu/drm/i915/display/vlv_dsi.c
@@ -1555,6 +1555,7 @@ static const struct drm_connector_helper_funcs intel_dsi_connector_helper_funcs
 	.get_modes = intel_dsi_get_modes,
 	.mode_valid = vlv_dsi_mode_valid,
 	.atomic_check = intel_digital_connector_atomic_check,
+	.set_luminance = intel_backlight_set_luminance,
 };
 
 static const struct drm_connector_funcs intel_dsi_connector_funcs = {

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 06/12] drm/bridge: add connector luminance operations
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
                   ` (4 preceding siblings ...)
  2026-11-03 22:42 ` [PATCH v11 05/12] drm/i915/display: " Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 07/12] drm/panel: add connector luminance forwarding Mario Limonciello (AMD)
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

From: Mario Limonciello <mario.limonciello@amd.com>

Allow bridges that own brightness control to advertise a luminance
operation and its supported relative range.

Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
v11:
 * Drop panel bridge declarations re-added by a bad rebase (Sashiko)
 * Document @luminance as the initial level
v10:
 * Split bridge operations out of the combined panel and bridge patch
---
 include/drm/drm_bridge.h | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 1981d24a700d2..240a69c1233e7 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -62,6 +62,15 @@ enum drm_bridge_attach_flags {
  * struct drm_bridge_funcs - drm_bridge control functions
  */
 struct drm_bridge_funcs {
+	/**
+	 * @set_luminance:
+	 *
+	 * Apply a relative luminance level to a bridge that controls panel
+	 * emission. Bridges implementing this callback shall set
+	 * @DRM_BRIDGE_OP_LUMINANCE.
+	 */
+	void (*set_luminance)(struct drm_bridge *bridge, unsigned int luminance);
+
 	/**
 	 * @attach:
 	 *
@@ -1021,6 +1030,11 @@ enum drm_bridge_ops {
 	 * &drm_bridge_funcs->hdmi_clear_spd_infoframe callbacks.
 	 */
 	DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME = BIT(10),
+
+	/**
+	 * @DRM_BRIDGE_OP_LUMINANCE: The bridge controls panel luminance.
+	 */
+	DRM_BRIDGE_OP_LUMINANCE = BIT(11),
 };
 
 /**
@@ -1071,6 +1085,12 @@ struct drm_bridge {
 	void *driver_private;
 	/** @ops: bitmask of operations supported by the bridge */
 	enum drm_bridge_ops ops;
+
+	/** @luminance_max: Maximum relative luminance level. */
+	unsigned int luminance_max;
+
+	/** @luminance: Initial relative luminance level. */
+	unsigned int luminance;
 	/**
 	 * @type: Type of the connection at the bridge output
 	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 07/12] drm/panel: add connector luminance forwarding
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
                   ` (5 preceding siblings ...)
  2026-11-03 22:42 ` [PATCH v11 06/12] drm/bridge: add connector luminance operations Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 08/12] drm/bridge_connector: add luminance support Mario Limonciello (AMD)
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

From: Mario Limonciello <mario.limonciello@amd.com>

Add helpers that expose a panel backlight through the connector
LUMINANCE property and forward connector updates to the backlight
backend.

Hook the helpers into panel bridges that create their own connector.
Panels consumed directly, or by a panel bridge attached with
DRM_BRIDGE_ATTACH_NO_CONNECTOR, remain unchanged. Turning the backlight
on and off is left to drm_panel_enable() and drm_panel_disable(), which
already honour self refresh.

The panel backlight remains the hardware backend and keeps its
/sys/class/backlight device, which is owned by the backlight driver.

Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
v11:
 * Don't remove drm_panel.h stubs (rebase artifact, Sashiko)
 * Use drm_connector_to_panel() rather than an invalid panel_bridge
   container_of() (Sashiko)
 * Clean up the connector if attaching the property fails (Sashiko)
 * Drop .disable_luminance and program the committed level before
   drm_panel_enable() (Sashiko)
 * Don't attach the property when the panel bridge is attached after the
   device is registered, which would WARN
 * Update brightness under the backlight ops_lock
v10:
 * Split panel support out of the combined panel and bridge patch
 * Limit integration to panel bridges that own their connector
---
 drivers/gpu/drm/drm_panel.c | 93 +++++++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_panel.h     |  4 ++
 2 files changed, 97 insertions(+)

diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index 74c96da45b7a5..bbd0e14401c18 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -64,9 +64,18 @@ static int panel_bridge_connector_get_modes(struct drm_connector *connector)
 	return drm_panel_get_modes(panel, connector);
 }
 
+static void
+panel_bridge_connector_set_luminance(const struct drm_connector_state *conn_state)
+{
+	struct drm_panel *panel = drm_connector_to_panel(conn_state->connector);
+
+	drm_panel_set_luminance(panel, conn_state->luminance);
+}
+
 static const struct drm_connector_helper_funcs
 panel_bridge_connector_helper_funcs = {
 	.get_modes = panel_bridge_connector_get_modes,
+	.set_luminance = panel_bridge_connector_set_luminance,
 };
 
 static const struct drm_connector_funcs panel_bridge_connector_funcs = {
@@ -101,6 +110,12 @@ static int panel_bridge_attach(struct drm_bridge *bridge,
 
 	drm_panel_bridge_set_orientation(connector, bridge);
 
+	ret = drm_panel_attach_luminance_property(panel, connector);
+	if (ret) {
+		drm_connector_cleanup(connector);
+		return ret;
+	}
+
 	drm_connector_attach_encoder(connector, encoder);
 
 	if (bridge->dev->registered) {
@@ -169,6 +184,19 @@ static void panel_bridge_atomic_enable(struct drm_bridge *bridge,
 	if (old_crtc_state && old_crtc_state->self_refresh_active)
 		return;
 
+	/*
+	 * Program the committed luminance before drm_panel_enable() turns the
+	 * backlight on, so the panel does not light up at a stale level.
+	 */
+	if (panel->connector.luminance_property) {
+		const struct drm_connector_state *conn_state =
+			drm_atomic_get_new_connector_state(atomic_state,
+							   &panel->connector);
+
+		if (conn_state)
+			drm_panel_set_luminance(panel, conn_state->luminance);
+	}
+
 	drm_panel_enable(panel);
 }
 
@@ -873,6 +901,71 @@ void drm_panel_disable(struct drm_panel *panel)
 }
 EXPORT_SYMBOL(drm_panel_disable);
 
+/**
+ * drm_panel_attach_luminance_property - expose a panel backlight through DRM
+ * @panel: panel whose backlight is controlled
+ * @connector: connector on which to expose the property
+ *
+ * The backlight class device remains the internal hardware backend. When the
+ * luminance UAPI is enabled, userspace controls that backend through the DRM
+ * connector property instead.
+ *
+ * Return: 0 on success or a negative error code
+ */
+int drm_panel_attach_luminance_property(struct drm_panel *panel,
+					struct drm_connector *connector)
+{
+	struct backlight_device *backlight;
+
+	if (!IS_ENABLED(CONFIG_DRM_LUMINANCE) || !panel)
+		return 0;
+
+	backlight = panel->backlight;
+	if (!backlight)
+		return 0;
+
+	/* Properties can't be created once the device is visible to userspace */
+	if (connector->dev->registered) {
+		drm_dbg_kms(connector->dev,
+			    "[CONNECTOR:%d:%s] registered, not exposing luminance\n",
+			    connector->base.id, connector->name);
+		return 0;
+	}
+
+	return drm_connector_attach_luminance_property(connector, 0,
+					backlight->props.max_brightness,
+					backlight->props.brightness);
+}
+EXPORT_SYMBOL(drm_panel_attach_luminance_property);
+
+/**
+ * drm_panel_set_luminance - set a panel backlight's relative luminance
+ * @panel: panel whose backlight is controlled
+ * @luminance: relative luminance level
+ *
+ * Updates the backlight brightness without changing its power state, under
+ * the same lock as brightness changes made through sysfs. Turning
+ * the backlight on and off is left to drm_panel_enable() and
+ * drm_panel_disable(), so a level set while the panel is disabled is applied
+ * when the panel is enabled again.
+ */
+void drm_panel_set_luminance(struct drm_panel *panel, unsigned int luminance)
+{
+	struct backlight_device *backlight;
+
+	if (!panel || !panel->backlight)
+		return;
+
+	backlight = panel->backlight;
+	guard(mutex)(&backlight->ops_lock);
+	if (!backlight->ops || luminance > backlight->props.max_brightness)
+		return;
+
+	backlight->props.brightness = luminance;
+	backlight_update_status(backlight);
+}
+EXPORT_SYMBOL(drm_panel_set_luminance);
+
 /**
  * drm_panel_get_modes - probe the available display modes of a panel
  * @panel: DRM panel
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index 58113ea7d63a0..233d30dd2e182 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -342,6 +342,10 @@ void drm_panel_unprepare(struct drm_panel *panel);
 void drm_panel_enable(struct drm_panel *panel);
 void drm_panel_disable(struct drm_panel *panel);
 
+int drm_panel_attach_luminance_property(struct drm_panel *panel,
+					struct drm_connector *connector);
+void drm_panel_set_luminance(struct drm_panel *panel, unsigned int luminance);
+
 int drm_panel_get_modes(struct drm_panel *panel, struct drm_connector *connector);
 
 #if defined(CONFIG_OF) && IS_ENABLED(CONFIG_DRM_PANEL)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 08/12] drm/bridge_connector: add luminance support
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
                   ` (6 preceding siblings ...)
  2026-11-03 22:42 ` [PATCH v11 07/12] drm/panel: add connector luminance forwarding Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 09/12] drm/nouveau: add connector " Mario Limonciello (AMD)
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

From: Mario Limonciello <mario.limonciello@amd.com>

Discover the last bridge in a chain that owns brightness control, attach
its relative luminance range to the connector, and forward atomic
luminance updates to it.

Do not infer panel luminance support from a terminal panel bridge. Panels
still have direct consumers, so their connector integration must remain
explicit.

Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
v11:
 * Drop .disable_luminance, it clobbered the bridge's saved level; the
   bridge disable path already turns the panel off
 * Fix checkpatch line length
v10:
 * Split bridge connector integration into a separate patch
 * Do not special-case panels in the generic bridge connector
---
 drivers/gpu/drm/display/drm_bridge_connector.c | 39 ++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index 2202e60444449..18927fed83732 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -125,6 +125,13 @@ struct drm_bridge_connector {
 	 */
 	struct drm_bridge *bridge_hdmi_cec;
 
+	/**
+	 * @bridge_luminance:
+	 *
+	 * The last bridge in the chain that controls panel luminance.
+	 */
+	struct drm_bridge *bridge_luminance;
+
 	/**
 	 * @hdmi_funcs:
 	 *
@@ -421,6 +428,17 @@ static int drm_bridge_connector_atomic_check(struct drm_connector *connector,
 	return 0;
 }
 
+static void
+drm_bridge_connector_set_luminance(const struct drm_connector_state *conn_state)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(conn_state->connector);
+	struct drm_bridge *bridge = bridge_connector->bridge_luminance;
+
+	if (bridge)
+		bridge->funcs->set_luminance(bridge, conn_state->luminance);
+}
+
 static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = {
 	.get_modes = drm_bridge_connector_get_modes,
 	.detect_ctx = drm_bridge_connector_detect_ctx,
@@ -429,6 +447,7 @@ static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs
 	.enable_hpd = drm_bridge_connector_enable_hpd,
 	.disable_hpd = drm_bridge_connector_disable_hpd,
 	.atomic_check = drm_bridge_connector_atomic_check,
+	.set_luminance = drm_bridge_connector_set_luminance,
 };
 
 static enum drm_mode_status
@@ -835,6 +854,7 @@ static void drm_bridge_connector_put_bridges(struct drm_device *dev, void *data)
 	drm_bridge_put(bridge_connector->bridge_hdmi_audio);
 	drm_bridge_put(bridge_connector->bridge_dp_audio);
 	drm_bridge_put(bridge_connector->bridge_hdmi_cec);
+	drm_bridge_put(bridge_connector->bridge_luminance);
 }
 
 /**
@@ -918,6 +938,13 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 			drm_bridge_put(bridge_connector->bridge_detect);
 			bridge_connector->bridge_detect = drm_bridge_get(bridge);
 		}
+		if (bridge->ops & DRM_BRIDGE_OP_LUMINANCE) {
+			if (!bridge->funcs->set_luminance)
+				return ERR_PTR(-EINVAL);
+
+			drm_bridge_put(bridge_connector->bridge_luminance);
+			bridge_connector->bridge_luminance = drm_bridge_get(bridge);
+		}
 		if (bridge->ops & DRM_BRIDGE_OP_HDMI) {
 			if (bridge_connector->bridge_hdmi)
 				return ERR_PTR(-EBUSY);
@@ -1144,6 +1171,18 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
 	if (panel_bridge)
 		drm_panel_bridge_set_orientation(connector, panel_bridge);
 
+	if (IS_ENABLED(CONFIG_DRM_LUMINANCE) &&
+	    bridge_connector->bridge_luminance &&
+	    !connector->luminance_property) {
+		struct drm_bridge *bridge = bridge_connector->bridge_luminance;
+
+		ret = drm_connector_attach_luminance_property(connector, 0,
+							      bridge->luminance_max,
+							      bridge->luminance);
+		if (ret)
+			return ERR_PTR(ret);
+	}
+
 	if (support_hdcp && IS_REACHABLE(CONFIG_DRM_DISPLAY_HELPER) &&
 	    IS_ENABLED(CONFIG_DRM_DISPLAY_HDCP_HELPER))
 		drm_connector_attach_content_protection_property(connector, true);

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 09/12] drm/nouveau: add connector luminance support
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
                   ` (7 preceding siblings ...)
  2026-11-03 22:42 ` [PATCH v11 08/12] drm/bridge_connector: add luminance support Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 10/12] drm/gud: " Mario Limonciello (AMD)
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

From: Mario Limonciello <mario.limonciello@amd.com>

Expose Nouveau panel brightness through the connector LUMINANCE property
when DRM_LUMINANCE is enabled and atomic modesetting is exposed. Reuse
the existing NV50 and DPCD hardware paths while preserving the backlight
class interface otherwise, including for pre-NV50 displays.

The panel backlight is probed and the property attached before the DRM
device is registered. The luminance helpers are called from the NV50
atomic commit tail.

Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
v11:
 * Call the luminance commit helpers from nv50_disp_atomic_commit_tail()
   (Sashiko)
 * Attach the property before drm_dev_register() (Sashiko)
 * Keep the class device for non-atomic NV04 and nouveau.atomic=0
   (Sashiko)
 * Fix stale brightness on eDP enable for the class device path (Sashiko)
 * Keep selecting BACKLIGHT_CLASS_DEVICE (Sashiko)
---
 drivers/gpu/drm/nouveau/dispnv50/disp.c     |  13 ++-
 drivers/gpu/drm/nouveau/nouveau_backlight.c | 152 ++++++++++++++++++++++++----
 drivers/gpu/drm/nouveau/nouveau_connector.c |   3 +
 drivers/gpu/drm/nouveau/nouveau_connector.h |  23 +++++
 drivers/gpu/drm/nouveau/nouveau_drm.c       |   6 ++
 5 files changed, 174 insertions(+), 23 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index e91130f93db67..57653fe720d95 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -1755,6 +1755,7 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_commit *st
 	struct nouveau_connector *nv_connector;
 #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
 	struct nouveau_backlight *backlight;
+	u16 level;
 #endif
 	struct nvbios *bios = &drm->vbios;
 	bool lvds_dual = false, lvds_8bpc = false, hda = false;
@@ -1836,9 +1837,14 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_commit *st
 
 #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
 		backlight = nv_connector->backlight;
-		if (backlight && backlight->uses_dpcd)
+		if (backlight && backlight->uses_dpcd) {
+			if (backlight->dev)
+				level = backlight->dev->props.brightness;
+			else
+				level = nv_connector->base.state->luminance;
 			drm_edp_backlight_enable(&nv_connector->aux, &backlight->edp_info,
-						 backlight->dev->props.brightness);
+						 level);
+		}
 #endif
 
 		break;
@@ -2188,6 +2194,7 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_commit *state)
 	drm_dp_mst_atomic_wait_for_dependencies(state);
 	drm_atomic_helper_update_legacy_modeset_state(dev, state);
 	drm_atomic_helper_calc_timestamping_constants(state);
+	drm_atomic_helper_commit_luminance_disables(state);
 
 	if (atom->lock_core)
 		mutex_lock(&disp->mutex);
@@ -2370,6 +2377,8 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_commit *state)
 	if (atom->lock_core)
 		mutex_unlock(&disp->mutex);
 
+	drm_atomic_helper_commit_luminance_enables(state);
+
 	list_for_each_entry_safe(outp, outt, &atom->outp, head) {
 		list_del(&outp->head);
 		kfree(outp);
diff --git a/drivers/gpu/drm/nouveau/nouveau_backlight.c b/drivers/gpu/drm/nouveau/nouveau_backlight.c
index da1ee24a2531d..2366901581077 100644
--- a/drivers/gpu/drm/nouveau/nouveau_backlight.c
+++ b/drivers/gpu/drm/nouveau/nouveau_backlight.c
@@ -234,12 +234,15 @@ nv50_backlight_init(struct nouveau_backlight *bl,
 		    const struct backlight_ops **ops)
 {
 	struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev);
+	int brightness;
+
+	brightness = nvif_outp_bl_get(&nv_encoder->outp);
 
 	/*
 	 * Note when this runs the connectors have not been probed yet,
 	 * so nv_conn->base.status is not set yet.
 	 */
-	if (nvif_outp_bl_get(&nv_encoder->outp) < 0 ||
+	if (brightness < 0 ||
 	    drm_helper_probe_detect(&nv_conn->base, NULL, false) != connector_status_connected)
 		return -ENODEV;
 
@@ -284,11 +287,25 @@ nv50_backlight_init(struct nouveau_backlight *bl,
 
 	*ops = &nv50_bl_ops;
 	props->max_brightness = 100;
+	props->brightness = brightness;
 	return 0;
 }
 
-int
-nouveau_backlight_init(struct drm_connector *connector)
+/*
+ * LUMINANCE is an atomic connector property. Pre-NV50 hardware and
+ * nouveau.atomic=0 don't expose atomic modesetting to userspace (even though
+ * NV50+ still commits atomically internally), so keep the backlight class
+ * device there.
+ */
+static bool
+nouveau_backlight_use_luminance(struct drm_device *dev)
+{
+	return IS_ENABLED(CONFIG_DRM_LUMINANCE) &&
+	       drm_core_check_feature(dev, DRIVER_ATOMIC);
+}
+
+static int
+nouveau_backlight_create(struct drm_connector *connector)
 {
 	struct nouveau_drm *drm = nouveau_drm(connector->dev);
 	struct nouveau_backlight *bl;
@@ -317,6 +334,8 @@ nouveau_backlight_init(struct drm_connector *connector)
 	bl = kzalloc_obj(*bl);
 	if (!bl)
 		return -ENOMEM;
+	bl->id = -1;
+	bl->encoder = nv_encoder;
 
 	switch (device->info.family) {
 	case NV_DEVICE_INFO_V0_CURIE:
@@ -349,26 +368,36 @@ nouveau_backlight_init(struct drm_connector *connector)
 		goto fail_alloc;
 	}
 
-	if (!nouveau_get_backlight_name(backlight_name, bl)) {
-		NV_ERROR(drm, "Failed to retrieve a unique name for the backlight interface\n");
-		goto fail_alloc;
-	}
-
 	props.type = BACKLIGHT_RAW;
-	bl->dev = backlight_device_register(backlight_name, connector->kdev,
-					    nv_encoder, ops, &props);
-	if (IS_ERR(bl->dev)) {
-		if (bl->id >= 0)
-			ida_free(&bl_ida, bl->id);
-		ret = PTR_ERR(bl->dev);
-		goto fail_alloc;
+	if (nouveau_backlight_use_luminance(connector->dev)) {
+		ret = drm_connector_attach_luminance_property(connector, 0, props.max_brightness,
+							      props.brightness);
+		if (ret)
+			goto fail_alloc;
+	} else {
+		if (!nouveau_get_backlight_name(backlight_name, bl)) {
+			NV_ERROR(drm,
+				 "Failed to retrieve a unique name for the backlight interface\n");
+			goto fail_alloc;
+		}
+
+		bl->dev = backlight_device_register(backlight_name, connector->kdev,
+						    nv_encoder, ops, &props);
+		if (IS_ERR(bl->dev)) {
+			if (bl->id >= 0)
+				ida_free(&bl_ida, bl->id);
+			ret = PTR_ERR(bl->dev);
+			goto fail_alloc;
+		}
 	}
 
 	nouveau_connector(connector)->backlight = bl;
-	if (!bl->dev->props.brightness)
-		bl->dev->props.brightness =
-			bl->dev->ops->get_brightness(bl->dev);
-	backlight_update_status(bl->dev);
+	if (bl->dev) {
+		if (!bl->dev->props.brightness)
+			bl->dev->props.brightness =
+				bl->dev->ops->get_brightness(bl->dev);
+		backlight_update_status(bl->dev);
+	}
 
 	return 0;
 
@@ -384,8 +413,40 @@ nouveau_backlight_init(struct drm_connector *connector)
 	return ret;
 }
 
-void
-nouveau_backlight_fini(struct drm_connector *connector)
+/* Called from .late_register, the backlight class device needs connector->kdev */
+int
+nouveau_backlight_init(struct drm_connector *connector)
+{
+	if (nouveau_backlight_use_luminance(connector->dev))
+		return 0;
+
+	return nouveau_backlight_create(connector);
+}
+
+/* Called before drm_dev_register(), the LUMINANCE property must exist by then */
+int
+nouveau_backlight_luminance_init(struct drm_device *dev)
+{
+	struct drm_connector_list_iter conn_iter;
+	struct drm_connector *connector;
+	int ret = 0;
+
+	if (!nouveau_backlight_use_luminance(dev))
+		return 0;
+
+	drm_connector_list_iter_begin(dev, &conn_iter);
+	nouveau_for_each_non_mst_connector_iter(connector, &conn_iter) {
+		ret = nouveau_backlight_create(connector);
+		if (ret)
+			break;
+	}
+	drm_connector_list_iter_end(&conn_iter);
+
+	return ret;
+}
+
+static void
+nouveau_backlight_free(struct drm_connector *connector)
 {
 	struct nouveau_connector *nv_conn = nouveau_connector(connector);
 	struct nouveau_backlight *bl = nv_conn->backlight;
@@ -401,6 +462,23 @@ nouveau_backlight_fini(struct drm_connector *connector)
 	kfree(bl);
 }
 
+/* Called from .early_unregister */
+void
+nouveau_backlight_fini(struct drm_connector *connector)
+{
+	/* LUMINANCE state is needed by commits until the connector is destroyed */
+	if (nouveau_backlight_use_luminance(connector->dev))
+		return;
+
+	nouveau_backlight_free(connector);
+}
+
+void
+nouveau_backlight_destroy(struct drm_connector *connector)
+{
+	nouveau_backlight_free(connector);
+}
+
 void
 nouveau_backlight_ctor(void)
 {
@@ -412,3 +490,35 @@ nouveau_backlight_dtor(void)
 {
 	ida_destroy(&bl_ida);
 }
+
+void
+nouveau_backlight_set_luminance(const struct drm_connector_state *conn_state)
+{
+	struct nouveau_connector *nv_connector = nouveau_connector(conn_state->connector);
+	struct nouveau_backlight *bl = nv_connector->backlight;
+	unsigned int luminance = conn_state->luminance;
+
+	if (!bl)
+		return;
+
+	if (bl->uses_dpcd)
+		drm_edp_backlight_set_level(&nv_connector->aux, &bl->edp_info,
+					    luminance);
+	else
+		nvif_outp_bl_set(&bl->encoder->outp, luminance);
+}
+
+void
+nouveau_backlight_disable_luminance(const struct drm_connector_state *conn_state)
+{
+	struct nouveau_connector *nv_connector = nouveau_connector(conn_state->connector);
+	struct nouveau_backlight *bl = nv_connector->backlight;
+
+	if (!bl)
+		return;
+
+	if (bl->uses_dpcd)
+		drm_edp_backlight_disable(&nv_connector->aux, &bl->edp_info);
+	else
+		nvif_outp_bl_set(&bl->encoder->outp, 0);
+}
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index cc3caf8de64b0..cbbdd36f49bb7 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -400,6 +400,7 @@ nouveau_connector_destroy(struct drm_connector *connector)
 	cancel_work_sync(&nv_connector->irq_work);
 	drm_edid_free(nv_connector->drm_edid);
 	drm_connector_unregister(connector);
+	nouveau_backlight_destroy(connector);
 	drm_connector_cleanup(connector);
 	if (nv_connector->aux.transfer)
 		drm_dp_cec_unregister_connector(&nv_connector->aux);
@@ -1225,6 +1226,8 @@ nouveau_connector_helper_funcs = {
 	.mode_valid = nouveau_connector_mode_valid,
 	.best_encoder = nouveau_connector_best_encoder,
 	.atomic_check = nouveau_connector_atomic_check,
+	.set_luminance = nouveau_backlight_set_luminance,
+	.disable_luminance = nouveau_backlight_disable_luminance,
 };
 
 static const struct drm_connector_funcs
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h
index eb292d2ba4bcd..f53107ec0bf3f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.h
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.h
@@ -50,6 +50,7 @@ struct nouveau_backlight {
 	struct backlight_device *dev;
 
 	struct drm_edp_backlight_info edp_info;
+	struct nouveau_encoder *encoder;
 	bool uses_dpcd : 1;
 
 	int id;
@@ -237,8 +238,12 @@ nouveau_conn_mode_clock_valid(const struct drm_display_mode *,
 #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
 extern int nouveau_backlight_init(struct drm_connector *);
 extern void nouveau_backlight_fini(struct drm_connector *);
+int nouveau_backlight_luminance_init(struct drm_device *dev);
+void nouveau_backlight_destroy(struct drm_connector *connector);
 extern void nouveau_backlight_ctor(void);
 extern void nouveau_backlight_dtor(void);
+void nouveau_backlight_set_luminance(const struct drm_connector_state *conn_state);
+void nouveau_backlight_disable_luminance(const struct drm_connector_state *conn_state);
 #else
 static inline int
 nouveau_backlight_init(struct drm_connector *connector)
@@ -250,6 +255,16 @@ static inline void
 nouveau_backlight_fini(struct drm_connector *connector) {
 }
 
+static inline int
+nouveau_backlight_luminance_init(struct drm_device *dev)
+{
+	return 0;
+}
+
+static inline void
+nouveau_backlight_destroy(struct drm_connector *connector) {
+}
+
 static inline void
 nouveau_backlight_ctor(void) {
 }
@@ -257,6 +272,14 @@ nouveau_backlight_ctor(void) {
 static inline void
 nouveau_backlight_dtor(void) {
 }
+
+static inline void
+nouveau_backlight_set_luminance(const struct drm_connector_state *conn_state) {
+}
+
+static inline void
+nouveau_backlight_disable_luminance(const struct drm_connector_state *conn_state) {
+}
 #endif
 
 #endif /* __NOUVEAU_CONNECTOR_H__ */
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index 5e1336615cf6d..53bf7c2caa164 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -673,6 +673,10 @@ nouveau_drm_device_init(struct nouveau_drm *drm)
 		ret = nouveau_display_init(dev, false, false);
 		if (ret)
 			goto fail_dispinit;
+
+		ret = nouveau_backlight_luminance_init(dev);
+		if (ret)
+			goto fail_backlight;
 	}
 
 	nouveau_debugfs_init(drm);
@@ -697,6 +701,8 @@ nouveau_drm_device_init(struct nouveau_drm *drm)
 	}
 
 	return 0;
+fail_backlight:
+	nouveau_display_fini(dev, false, false);
 fail_dispinit:
 	nouveau_display_destroy(dev);
 fail_dispctor:

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 10/12] drm/gud: add connector luminance support
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
                   ` (8 preceding siblings ...)
  2026-11-03 22:42 ` [PATCH v11 09/12] drm/nouveau: add connector " Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 11/12] drm/bridge: add PS8622 " Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 12/12] drm/solomon: add SSD130x " Mario Limonciello (AMD)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

From: Mario Limonciello <mario.limonciello@amd.com>

Expose GUD display brightness through the connector LUMINANCE property
when DRM_LUMINANCE is enabled. The value is mirrored into tv.brightness
during atomic check so it is sent to the device with
GUD_REQ_SET_STATE_CHECK in the same commit.

Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
v11:
 * Apply LUMINANCE in the atomic commit instead of a worker, fixing an
   endless commit loop, a use-after-free on disconnect and brightness
   being clobbered on DPMS off (Sashiko)
 * Attach the property at connector init (Sashiko)
 * Keep selecting BACKLIGHT_CLASS_DEVICE (Sashiko)
---
 drivers/gpu/drm/gud/gud_connector.c | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/gud/gud_connector.c b/drivers/gpu/drm/gud/gud_connector.c
index ddbf3b3ebbca8..bfb298bbc9b51 100644
--- a/drivers/gpu/drm/gud/gud_connector.c
+++ b/drivers/gpu/drm/gud/gud_connector.c
@@ -292,10 +292,19 @@ static int gud_connector_atomic_check(struct drm_connector *connector,
 	struct drm_connector_state *old_state;
 
 	new_state = drm_atomic_get_new_connector_state(state, connector);
+	old_state = drm_atomic_get_old_connector_state(state, connector);
+
+	/*
+	 * LUMINANCE is sent to the device as tv.brightness in the same commit,
+	 * the brightness change below then triggers GUD_REQ_SET_STATE_CHECK.
+	 */
+	if (connector->luminance_property &&
+	    old_state->luminance != new_state->luminance)
+		new_state->tv.brightness = new_state->luminance;
+
 	if (!new_state->crtc)
 		return 0;
 
-	old_state = drm_atomic_get_old_connector_state(state, connector);
 	new_crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc);
 
 	if (old_state->tv.margins.left != new_state->tv.margins.left ||
@@ -324,7 +333,7 @@ static int gud_connector_late_register(struct drm_connector *connector)
 {
 	struct gud_connector *gconn = to_gud_connector(connector);
 
-	if (gconn->initial_brightness < 0)
+	if (IS_ENABLED(CONFIG_DRM_LUMINANCE) || gconn->initial_brightness < 0)
 		return 0;
 
 	return gud_connector_backlight_register(gconn);
@@ -699,6 +708,13 @@ static int gud_connector_create(struct gud_device *gdrm, unsigned int index,
 		return ret;
 	}
 
+	if (IS_ENABLED(CONFIG_DRM_LUMINANCE) && gconn->initial_brightness >= 0) {
+		ret = drm_connector_attach_luminance_property(connector, 0, 100,
+							      gconn->initial_brightness);
+		if (ret)
+			return ret;
+	}
+
 	gconn->encoder.possible_crtcs = drm_crtc_mask(&gdrm->crtc);
 	ret = drm_encoder_init(drm, &gconn->encoder, &gud_drm_simple_encoder_funcs_cleanup,
 			       DRM_MODE_ENCODER_NONE, NULL);

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 11/12] drm/bridge: add PS8622 connector luminance support
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
                   ` (9 preceding siblings ...)
  2026-11-03 22:42 ` [PATCH v11 10/12] drm/gud: " Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  2026-11-03 22:42 ` [PATCH v11 12/12] drm/solomon: add SSD130x " Mario Limonciello (AMD)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

From: Mario Limonciello <mario.limonciello@amd.com>

Expose PS8622 brightness through bridge luminance operations when
DRM_LUMINANCE is enabled. Keep registering the backlight class device
otherwise.

Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
v11:
 * Track internal PWM mode explicitly so it is still configured with
   LUMINANCE (Sashiko)
 * Don't access I2C while the bridge is powered down (Sashiko)
 * Keep selecting BACKLIGHT_CLASS_DEVICE (Sashiko)
 * Program the committed luminance in pre_enable
---
 drivers/gpu/drm/bridge/parade-ps8622.c | 66 +++++++++++++++++++++++++++-------
 1 file changed, 53 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c
index 3a2ec482037df..c88bd7a97d4a4 100644
--- a/drivers/gpu/drm/bridge/parade-ps8622.c
+++ b/drivers/gpu/drm/bridge/parade-ps8622.c
@@ -45,6 +45,8 @@ struct ps8622_bridge {
 	struct drm_bridge *panel_bridge;
 	struct regulator *v12;
 	struct backlight_device *bl;
+	u8 brightness;
+	bool internal_pwm;
 
 	struct gpio_desc *gpio_slp;
 	struct gpio_desc *gpio_rst;
@@ -268,7 +270,7 @@ static int ps8622_send_config(struct ps8622_bridge *ps8622)
 		goto error;
 
 
-	if (ps8622->bl) {
+	if (ps8622->internal_pwm) {
 		/* DPCD720, internal PWM */
 		err = ps8622_set(cl, 0x01, 0xa5, 0xa0);
 		if (err)
@@ -276,7 +278,7 @@ static int ps8622_send_config(struct ps8622_bridge *ps8622)
 
 		/* FFh for 100% brightness, 0h for 0% brightness */
 		err = ps8622_set(cl, 0x01, 0xa7,
-				ps8622->bl->props.brightness);
+				ps8622->brightness);
 		if (err)
 			goto error;
 	} else {
@@ -324,6 +326,8 @@ static int ps8622_backlight_update(struct backlight_device *bl)
 	struct ps8622_bridge *ps8622 = dev_get_drvdata(&bl->dev);
 	int ret, brightness = backlight_get_brightness(bl);
 
+	ps8622->brightness = brightness;
+
 	if (!ps8622->enabled)
 		return -EINVAL;
 
@@ -332,6 +336,20 @@ static int ps8622_backlight_update(struct backlight_device *bl)
 	return ret;
 }
 
+static void ps8622_set_luminance(struct drm_bridge *bridge,
+				 unsigned int luminance)
+{
+	struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge);
+
+	ps8622->brightness = luminance;
+
+	/* Applied by ps8622_send_config() on the next pre_enable */
+	if (!ps8622->enabled)
+		return;
+
+	ps8622_set(ps8622->client, 0x01, 0xa7, luminance);
+}
+
 static const struct backlight_ops ps8622_backlight_ops = {
 	.update_status	= ps8622_backlight_update,
 };
@@ -340,11 +358,22 @@ static void ps8622_pre_enable(struct drm_bridge *bridge,
 			      struct drm_atomic_commit *commit)
 {
 	struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge);
+	struct drm_connector *connector;
 	int ret;
 
 	if (ps8622->enabled)
 		return;
 
+	/* Program the committed luminance from ps8622_send_config() */
+	connector = drm_atomic_get_new_connector_for_encoder(commit, bridge->encoder);
+	if (connector && connector->luminance_property) {
+		const struct drm_connector_state *conn_state =
+			drm_atomic_get_new_connector_state(commit, connector);
+
+		if (conn_state)
+			ps8622->brightness = conn_state->luminance;
+	}
+
 	gpiod_set_value(ps8622->gpio_rst, 0);
 
 	if (ps8622->v12) {
@@ -438,6 +467,7 @@ static const struct drm_bridge_funcs ps8622_bridge_funcs = {
 	.atomic_disable = ps8622_disable,
 	.atomic_post_disable = ps8622_post_disable,
 	.attach = ps8622_attach,
+	.set_luminance = ps8622_set_luminance,
 };
 
 static const struct of_device_id ps8622_devices[] = {
@@ -502,18 +532,28 @@ static int ps8622_probe(struct i2c_client *client)
 		ps8622->lane_count = ps8622->max_lane_count;
 	}
 
-	if (!of_property_read_bool(dev->of_node, "use-external-pwm")) {
-		ps8622->bl = backlight_device_register("ps8622-backlight",
-				dev, ps8622, &ps8622_backlight_ops,
-				NULL);
-		if (IS_ERR(ps8622->bl)) {
-			DRM_ERROR("failed to register backlight\n");
-			ret = PTR_ERR(ps8622->bl);
-			ps8622->bl = NULL;
-			return ret;
+	ps8622->brightness = PS8622_MAX_BRIGHTNESS;
+	ps8622->internal_pwm = !of_property_read_bool(dev->of_node,
+						      "use-external-pwm");
+	if (ps8622->internal_pwm) {
+		if (IS_ENABLED(CONFIG_DRM_LUMINANCE)) {
+			ps8622->bridge.ops |= DRM_BRIDGE_OP_LUMINANCE;
+			ps8622->bridge.luminance_max = PS8622_MAX_BRIGHTNESS;
+			ps8622->bridge.luminance = ps8622->brightness;
+		} else {
+			ps8622->bl = backlight_device_register("ps8622-backlight",
+							       dev, ps8622,
+							       &ps8622_backlight_ops,
+							       NULL);
+			if (IS_ERR(ps8622->bl)) {
+				DRM_ERROR("failed to register backlight\n");
+				ret = PTR_ERR(ps8622->bl);
+				ps8622->bl = NULL;
+				return ret;
+			}
+			ps8622->bl->props.max_brightness = PS8622_MAX_BRIGHTNESS;
+			ps8622->bl->props.brightness = ps8622->brightness;
 		}
-		ps8622->bl->props.max_brightness = PS8622_MAX_BRIGHTNESS;
-		ps8622->bl->props.brightness = PS8622_MAX_BRIGHTNESS;
 	}
 
 	ps8622->bridge.type = DRM_MODE_CONNECTOR_LVDS;

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v11 12/12] drm/solomon: add SSD130x connector luminance support
  2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
                   ` (10 preceding siblings ...)
  2026-11-03 22:42 ` [PATCH v11 11/12] drm/bridge: add PS8622 " Mario Limonciello (AMD)
@ 2026-11-03 22:42 ` Mario Limonciello (AMD)
  11 siblings, 0 replies; 13+ messages in thread
From: Mario Limonciello (AMD) @ 2026-11-03 22:42 UTC (permalink / raw)
  To: Maxime Ripard, Alex Deucher, Jani Nikula,
	Javier Martinez Canillas, Maarten Lankhorst, Thomas Zimmermann,
	David Airlie, Simona Vetter, Harry Wentland, Leo Li,
	Rodrigo Siqueira
  Cc: Mario Limonciello, Xaver Hugl, dri-devel, linux-kernel, amd-gfx,
	intel-gfx, intel-xe, nouveau, Mario Limonciello

From: Mario Limonciello <mario.limonciello@amd.com>

Expose SSD130x contrast through the connector LUMINANCE property when
DRM_LUMINANCE is enabled. Program contrast directly and register the
backlight class device only when DRM_LUMINANCE is disabled, so contrast
has a single owner.

Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
v11:
 * Keep selecting BACKLIGHT_CLASS_DEVICE and drop the guards (Sashiko)
 * Register the backlight device only without DRM_LUMINANCE (Sashiko)
 * Initialise the controller with the committed luminance and don't
   clobber the saved contrast on disable (Sashiko)
 * Skip contrast writes while the controller is powered down (Sashiko)
 * Unify contrast dispatch and backlight ops (Sashiko)
 * Drop Reviewed-by due to the rework
v10:
 * Guard the backlight operations and registration with
   BACKLIGHT_CLASS_DEVICE
 * Fix Kconfig recursive issue
 * Add the Reviewed-by tag
---
 drivers/gpu/drm/solomon/ssd130x.c | 149 ++++++++++++++++++++++++--------------
 drivers/gpu/drm/solomon/ssd130x.h |   2 +
 2 files changed, 98 insertions(+), 53 deletions(-)

diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c
index ee43cf0da8792..32e892194f578 100644
--- a/drivers/gpu/drm/solomon/ssd130x.c
+++ b/drivers/gpu/drm/solomon/ssd130x.c
@@ -627,7 +627,7 @@ static int ssd130x_init(struct ssd130x_device *ssd130x)
 static int ssd132x_init(struct ssd130x_device *ssd130x)
 {
 	const u8 cmds[] = {
-		2, SSD13XX_CONTRAST, 0x80,
+		2, SSD13XX_CONTRAST, ssd130x->contrast,
 		3, SSD132X_SET_COL_RANGE, 0x00, ssd130x->width / SSD132X_SEGMENT_WIDTH - 1,
 		3, SSD132X_SET_ROW_RANGE, 0x00, ssd130x->height - 1,
 		/*
@@ -696,6 +696,18 @@ static int ssd135x_set_contrast(struct ssd130x_device *ssd130x, u32 brightness)
 	return ssd130x_write_cmd(ssd130x, 4, SSD135X_SET_CONTRAST, a, b, c);
 }
 
+static int ssd130x_set_contrast(struct ssd130x_device *ssd130x, u32 contrast)
+{
+	switch (ssd130x->device_info->family_id) {
+	case SSD133X_FAMILY:
+		return ssd133x_set_contrast(ssd130x, contrast);
+	case SSD135X_FAMILY:
+		return ssd135x_set_contrast(ssd130x, contrast);
+	default:
+		return ssd130x_write_cmd(ssd130x, 2, SSD13XX_CONTRAST, contrast);
+	}
+}
+
 static int ssd133x_init(struct ssd130x_device *ssd130x)
 {
 	int ret;
@@ -1836,6 +1848,23 @@ static const struct drm_crtc_funcs ssd130x_crtc_funcs = {
 	.atomic_destroy_state = ssd130x_crtc_destroy_state,
 };
 
+/*
+ * With DRM_LUMINANCE, initialize the controller with the committed connector
+ * luminance, which may have changed while the display was disabled.
+ */
+static void ssd130x_load_luminance(struct ssd130x_device *ssd130x,
+				   struct drm_atomic_commit *state)
+{
+	struct drm_connector_state *conn_state;
+
+	if (!IS_ENABLED(CONFIG_DRM_LUMINANCE))
+		return;
+
+	conn_state = drm_atomic_get_new_connector_state(state, &ssd130x->connector);
+	if (conn_state)
+		ssd130x->contrast = conn_state->luminance;
+}
+
 static void ssd130x_encoder_atomic_enable(struct drm_encoder *encoder,
 					  struct drm_atomic_commit *state)
 {
@@ -1847,12 +1876,16 @@ static void ssd130x_encoder_atomic_enable(struct drm_encoder *encoder,
 	if (ret)
 		return;
 
+	ssd130x_load_luminance(ssd130x, state);
+
 	ret = ssd130x_init(ssd130x);
 	if (ret)
 		goto power_off;
 
 	ssd130x_write_cmd(ssd130x, 1, SSD13XX_DISPLAY_ON);
 
+	ssd130x->enabled = true;
+
 	backlight_enable(ssd130x->bl_dev);
 
 	return;
@@ -1873,12 +1906,16 @@ static void ssd132x_encoder_atomic_enable(struct drm_encoder *encoder,
 	if (ret)
 		return;
 
+	ssd130x_load_luminance(ssd130x, state);
+
 	ret = ssd132x_init(ssd130x);
 	if (ret)
 		goto power_off;
 
 	ssd130x_write_cmd(ssd130x, 1, SSD13XX_DISPLAY_ON);
 
+	ssd130x->enabled = true;
+
 	backlight_enable(ssd130x->bl_dev);
 
 	return;
@@ -1898,12 +1935,16 @@ static void ssd133x_encoder_atomic_enable(struct drm_encoder *encoder,
 	if (ret)
 		return;
 
+	ssd130x_load_luminance(ssd130x, state);
+
 	ret = ssd133x_init(ssd130x);
 	if (ret)
 		goto power_off;
 
 	ssd130x_write_cmd(ssd130x, 1, SSD13XX_DISPLAY_ON);
 
+	ssd130x->enabled = true;
+
 	backlight_enable(ssd130x->bl_dev);
 
 	return;
@@ -1923,12 +1964,16 @@ static void ssd135x_encoder_atomic_enable(struct drm_encoder *encoder,
 	if (ret)
 		return;
 
+	ssd130x_load_luminance(ssd130x, state);
+
 	ret = ssd135x_init(ssd130x);
 	if (ret)
 		goto power_off;
 
 	ssd130x_write_cmd(ssd130x, 1, SSD13XX_DISPLAY_ON);
 
+	ssd130x->enabled = true;
+
 	backlight_enable(ssd130x->bl_dev);
 
 	return;
@@ -1945,6 +1990,8 @@ static void ssd130x_encoder_atomic_disable(struct drm_encoder *encoder,
 
 	backlight_disable(ssd130x->bl_dev);
 
+	ssd130x->enabled = false;
+
 	ssd130x_write_cmd(ssd130x, 1, SSD13XX_DISPLAY_OFF);
 
 	ssd130x_power_off(ssd130x);
@@ -1980,8 +2027,34 @@ static int ssd130x_connector_get_modes(struct drm_connector *connector)
 	return drm_connector_helper_get_modes_fixed(connector, &ssd130x->mode);
 }
 
+static void ssd130x_connector_set_luminance(const struct drm_connector_state *conn_state)
+{
+	struct ssd130x_device *ssd130x = drm_to_ssd130x(conn_state->connector->dev);
+
+	/*
+	 * The encoder enable already initialized the controller with the
+	 * committed luminance. Skip if it failed to power up the controller.
+	 */
+	if (!ssd130x->enabled || ssd130x->contrast == conn_state->luminance)
+		return;
+
+	ssd130x->contrast = conn_state->luminance;
+	ssd130x_set_contrast(ssd130x, ssd130x->contrast);
+}
+
+static void ssd130x_connector_disable_luminance(const struct drm_connector_state *conn_state)
+{
+	struct ssd130x_device *ssd130x = drm_to_ssd130x(conn_state->connector->dev);
+
+	/* Keep ssd130x->contrast, it is restored when the encoder is enabled */
+	if (ssd130x->enabled)
+		ssd130x_set_contrast(ssd130x, 0);
+}
+
 static const struct drm_connector_helper_funcs ssd130x_connector_helper_funcs = {
 	.get_modes = ssd130x_connector_get_modes,
+	.set_luminance = ssd130x_connector_set_luminance,
+	.disable_luminance = ssd130x_connector_disable_luminance,
 };
 
 static const struct drm_connector_funcs ssd130x_connector_funcs = {
@@ -2016,55 +2089,16 @@ static const struct drm_driver ssd130x_drm_driver = {
 };
 
 static int ssd130x_update_bl(struct backlight_device *bdev)
-{
-	struct ssd130x_device *ssd130x = bl_get_data(bdev);
-	int brightness = backlight_get_brightness(bdev);
-	int ret;
-
-	ssd130x->contrast = brightness;
-
-	ret = ssd130x_write_cmd(ssd130x, 1, SSD13XX_CONTRAST);
-	if (ret < 0)
-		return ret;
-
-	ret = ssd130x_write_cmd(ssd130x, 1, ssd130x->contrast);
-	if (ret < 0)
-		return ret;
-
-	return 0;
-}
-
-static int ssd133x_update_bl(struct backlight_device *bdev)
-{
-	struct ssd130x_device *ssd130x = bl_get_data(bdev);
-
-	ssd130x->contrast = backlight_get_brightness(bdev);
-
-	return ssd133x_set_contrast(ssd130x, ssd130x->contrast);
-}
-
-static int ssd135x_update_bl(struct backlight_device *bdev)
 {
 	struct ssd130x_device *ssd130x = bl_get_data(bdev);
 
 	ssd130x->contrast = backlight_get_brightness(bdev);
 
-	return ssd135x_set_contrast(ssd130x, ssd130x->contrast);
+	return ssd130x_set_contrast(ssd130x, ssd130x->contrast);
 }
 
-static const struct backlight_ops ssd130xfb_bl_ops[] = {
-	[SSD130X_FAMILY] = {
-		.update_status	= ssd130x_update_bl,
-	},
-	[SSD132X_FAMILY] = {
-		.update_status	= ssd130x_update_bl,
-	},
-	[SSD133X_FAMILY] = {
-		.update_status	= ssd133x_update_bl,
-	},
-	[SSD135X_FAMILY] = {
-		.update_status	= ssd135x_update_bl,
-	},
+static const struct backlight_ops ssd130xfb_bl_ops = {
+	.update_status	= ssd130x_update_bl,
 };
 
 static void ssd130x_parse_properties(struct ssd130x_device *ssd130x)
@@ -2213,6 +2247,13 @@ static int ssd130x_init_modeset(struct ssd130x_device *ssd130x)
 	}
 
 	drm_connector_helper_add(connector, &ssd130x_connector_helper_funcs);
+	if (IS_ENABLED(CONFIG_DRM_LUMINANCE)) {
+		ret = drm_connector_attach_luminance_property(connector, 0,
+							      MAX_CONTRAST,
+							      ssd130x->contrast);
+		if (ret)
+			return ret;
+	}
 
 	ret = drm_connector_attach_encoder(connector, encoder);
 	if (ret) {
@@ -2270,16 +2311,18 @@ struct ssd130x_device *ssd130x_probe(struct device *dev, struct regmap *regmap)
 	if (ret)
 		return ERR_PTR(ret);
 
-	bl = devm_backlight_device_register(dev, dev_name(dev), dev, ssd130x,
-					    &ssd130xfb_bl_ops[ssd130x->device_info->family_id],
-					    NULL);
-	if (IS_ERR(bl))
-		return ERR_PTR(dev_err_probe(dev, PTR_ERR(bl),
-					     "Unable to register backlight device\n"));
-
-	bl->props.brightness = ssd130x->contrast;
-	bl->props.max_brightness = MAX_CONTRAST;
-	ssd130x->bl_dev = bl;
+	if (!IS_ENABLED(CONFIG_DRM_LUMINANCE)) {
+		bl = devm_backlight_device_register(dev, dev_name(dev), dev,
+						    ssd130x, &ssd130xfb_bl_ops,
+						    NULL);
+		if (IS_ERR(bl))
+			return ERR_PTR(dev_err_probe(dev, PTR_ERR(bl),
+						     "Unable to register backlight device\n"));
+
+		bl->props.brightness = ssd130x->contrast;
+		bl->props.max_brightness = MAX_CONTRAST;
+		ssd130x->bl_dev = bl;
+	}
 
 	ret = ssd130x_init_modeset(ssd130x);
 	if (ret)
diff --git a/drivers/gpu/drm/solomon/ssd130x.h b/drivers/gpu/drm/solomon/ssd130x.h
index fd81262f2ceb9..536a5649edffc 100644
--- a/drivers/gpu/drm/solomon/ssd130x.h
+++ b/drivers/gpu/drm/solomon/ssd130x.h
@@ -100,6 +100,8 @@ struct ssd130x_device {
 	u32 prechargep2;
 
 	struct backlight_device *bl_dev;
+	/* Controller is powered and initialized, set from atomic commits */
+	bool enabled;
 	struct pwm_device *pwm;
 	struct gpio_desc *reset;
 	struct regulator *vcc_reg;

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-11-03 22:42 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-03 22:42 [PATCH v11 00/12] Add support for a DRM backlight capability Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 01/12] drm: add connector LUMINANCE property Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 02/12] drm/amd/display: Pass up errors reading actual brightness Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 03/12] drm/amdgpu: Check bios_scratch_reg_offset in backlight level helper Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 04/12] drm/amd: add luminance property support Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 05/12] drm/i915/display: " Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 06/12] drm/bridge: add connector luminance operations Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 07/12] drm/panel: add connector luminance forwarding Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 08/12] drm/bridge_connector: add luminance support Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 09/12] drm/nouveau: add connector " Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 10/12] drm/gud: " Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 11/12] drm/bridge: add PS8622 " Mario Limonciello (AMD)
2026-11-03 22:42 ` [PATCH v11 12/12] drm/solomon: add SSD130x " Mario Limonciello (AMD)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®