From 2924f21d75a08ab64b43cb184f9dbdf46d8fb308 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Thu, 5 Nov 2026 04:00:57 -0500 Subject: [PATCH] drm/apple: fade the panel backlight on DAC firmware On M1 and M2, the DCP firmware takes the panel level as a DAC value in each swap and shows it at once. Every brightness change is therefore a visible jump. Automatic brightness makes this obvious: GNOME moves the level a step every 0.8 s, and each step snaps. The 250 ms fade the driver already has covers only the firmware that presents nits (H17P, 14.7 panels) and the M4. Fade there too. A new level starts a fade from the level set last, and a worker steps it every DCP_BL_FADE_STEP_MS over DCP_BL_FADE_MS. Each step rides on the compositor's next swap, or is presented on its own when no swap took it within a step. Blanking, unblanking and a display with no mode still jump. A write no longer sleeps 42 ms before returning. Tested on a J414s (M2 Pro, firmware 13.5) with GNOME automatic brightness. A change from 380 to 120 nits ramps through about 13 levels over 250 ms instead of jumping, and a 500 to 153 drop after the room went dark looked smooth. Assisted-by: Claude Opus 5.5 Signed-off-by: DjDeveloperr --- drivers/gpu/drm/apple/dcp-internal.h | 10 ++++ drivers/gpu/drm/apple/dcp_backlight.c | 78 ++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index a9056097bbae8..f27b6cfefb882 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -179,6 +179,16 @@ struct dcp_brightness { bool update; /* A level was written through the backlight device (desktop 26.x). */ bool requested; + /* + * Fade between levels where the firmware takes a DAC value in each + * swap. level is the one set last, in nits; crtc mutex. + */ + struct delayed_work fade_work; + unsigned long fade_start; + u32 level; + u32 fade_from; + u32 fade_to; + bool fading; }; struct audiosrv_data; diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 223a462afced8..b151887274a26 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -365,6 +365,7 @@ static void dcp_backlight_retry_work(struct work_struct *work) void dcp_backlight_stop(struct apple_dcp *dcp) { cancel_delayed_work_sync(&dcp->backlight.fade_work); + cancel_delayed_work_sync(&dcp->brightness.fade_work); cancel_delayed_work_sync(&dcp->backlight.retry_work); if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) cancel_work_sync(&dcp->backlight.notify_work); @@ -375,6 +376,8 @@ static void dcp_backlight_release(void *data) dcp_backlight_stop(data); } +static void dcp_dac_fade_work(struct work_struct *work); + /* * The backlight device is a devres of @dev, registered after probe, so it is * unregistered before this action runs, and no request can start a fade @@ -384,6 +387,7 @@ int dcp_backlight_init(struct apple_dcp *dcp, struct device *dev) { spin_lock_init(&dcp->backlight.lock); INIT_DELAYED_WORK(&dcp->backlight.fade_work, dcp_backlight_fade_work); + INIT_DELAYED_WORK(&dcp->brightness.fade_work, dcp_dac_fade_work); INIT_WORK(&dcp->backlight.notify_work, dcp_backlight_notify_work); INIT_DELAYED_WORK(&dcp->backlight.retry_work, dcp_backlight_retry_work); return devm_add_action_or_reset(dev, dcp_backlight_release, dcp); @@ -701,6 +705,64 @@ int dcp_backlight_update(struct apple_dcp *dcp) return drm_crtc_set_brightness(dcp); } +/* + * The firmware shows a swap's DAC value at once, so a new level would be a + * visible jump. Step from the level set last towards it instead. Each step + * rides on the compositor's next swap, or is presented on its own if no swap + * took it within a step. + */ +static void dcp_dac_fade_work(struct work_struct *work) +{ + struct apple_dcp *dcp = container_of(to_delayed_work(work), + struct apple_dcp, brightness.fade_work); + struct dcp_brightness *b = &dcp->brightness; + bool again; + u32 elapsed; + + if (READ_ONCE(dcp->quiescing) || READ_ONCE(dcp->crashed)) + return; + if (READ_ONCE(b->update) && READ_ONCE(dcp->mode_state.valid)) + drm_crtc_set_brightness(dcp); + + if (drm_modeset_lock_single_interruptible(&dcp->crtc->base.mutex)) + return; + if (b->fading) { + u32 level; + + elapsed = jiffies_to_msecs(jiffies - b->fade_start); + if (elapsed >= DCP_BL_FADE_MS) { + level = b->fade_to; + b->fading = false; + } else if (b->fade_to >= b->fade_from) { + level = b->fade_from + + mult_frac(b->fade_to - b->fade_from, elapsed, DCP_BL_FADE_MS); + } else { + level = b->fade_from - + mult_frac(b->fade_from - b->fade_to, elapsed, DCP_BL_FADE_MS); + } + if (level != b->level) { + b->level = level; + b->dac = calculate_dac(dcp, level); + b->update = true; + } + } + /* With no mode set, the next enable swap carries the pending level. */ + again = b->fading || (b->update && READ_ONCE(dcp->mode_state.valid)); + drm_modeset_unlock(&dcp->crtc->base.mutex); + if (again) + queue_delayed_work(system_freezable_wq, &b->fade_work, + msecs_to_jiffies(DCP_BL_FADE_STEP_MS)); +} + +/* A lit panel with a mode fades to a new lit level; anything else jumps. */ +static bool dcp_dac_fade_starts(struct apple_dcp *dcp, u32 nits) +{ + struct dcp_brightness *b = &dcp->brightness; + + return dcp_has_panel(dcp) && READ_ONCE(dcp->mode_state.valid) && + b->level && nits && b->level != nits; +} + static int dcp_set_brightness(struct backlight_device *bd) { int ret = 0; @@ -739,6 +801,18 @@ static int dcp_set_brightness(struct backlight_device *bd) if (ret) return ret; + /* A new level restarts the fade from the level set last. */ + if (dcp_dac_fade_starts(dcp, brightness)) { + dcp->brightness.fade_from = dcp->brightness.level; + dcp->brightness.fade_to = brightness; + dcp->brightness.fade_start = jiffies; + dcp->brightness.fading = true; + drm_modeset_unlock(&dcp->crtc->base.mutex); + mod_delayed_work(system_freezable_wq, &dcp->brightness.fade_work, 0); + return 0; + } + dcp->brightness.fading = false; + dcp->brightness.level = brightness; dcp->brightness.dac = calculate_dac(dcp, brightness); dcp->brightness.update = true; @@ -860,8 +934,10 @@ int dcp_backlight_register(struct apple_dcp *dcp) if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) dcp_backlight_sync_hint(dcp); if (!dcp_backlight_active(dcp) && - dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP) + dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP) { + dcp->brightness.level = dcp->brightness.nits; dcp->brightness.dac = calculate_dac(dcp, dcp->brightness.nits); + } return 0; }