From fee2cc856357d0ba85b42f1e7b4fe1299ff9974a Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 6 Apr 2026 10:21:44 +1000 Subject: [PATCH 1/7] drm: apple: Do not set IOMFBParameter_adaptive_sync on poweron This was actually unnecessary, and having dcp_on_set_parameter as a dcp_callback_t will introduce some complicated duplication when enabling VRR. Remove this callback and just set the display handle on poweron instead. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/iomfb_template.c | 29 +++++--------------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 553134aad80c9c..82a5fce70bd442 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -784,21 +784,6 @@ static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cooki dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie); } -static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_set_parameter_dcp param = { - .param = IOMFBPARAM_ADAPTIVE_SYNC, - .value = { 0 }, -#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) - .count = 3, -#else - .count = 1, -#endif - }; - - dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_power_state, cookie); -} - void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) { struct dcp_wait_cookie *cookie; @@ -815,15 +800,11 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) /* increase refcount to ensure the receiver has a reference */ kref_get(&cookie->refcount); - if (dcp->main_display) { - handle = 0; - dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state, - cookie); - } else { - handle = 2; - dcp_set_display_device(dcp, false, &handle, - dcp_on_set_parameter, cookie); - } + handle = dcp->main_display ? 0 : 2; + + dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state, + cookie); + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(10000)); if (ret == 0) { From 7ea83551ef6e2e209d9857246282a7055cb4b3fa Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Fri, 3 Apr 2026 20:31:17 +1000 Subject: [PATCH 2/7] drm: apple: Add preliminary VRR support DCP supports VRR/Adaptive Sync, with its enormous firmware blob handling the low-level details for us. Display refresh rate is determined by the swap timing values provided to DCP on each swap request. VRR is activated by setting IOMFBadaptive_sync_parameter::minRR and then requesting a modeset. Wire up all of the required KMS properties to expose VRR to userspace, and tell DCP to enable it when supported. This enables VRR *unconditionally* for supported sinks, which will be fixed in a future commit. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/apple_drv.c | 4 +++ drivers/gpu/drm/apple/iomfb.c | 1 + drivers/gpu/drm/apple/iomfb_template.c | 46 ++++++++++++++++++++++++-- drivers/gpu/drm/apple/parser.c | 6 ++-- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 0f36dad6f96351..31b86e909014ad 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -335,6 +335,10 @@ static int apple_probe_per_dcp(struct device *dev, if (ret) return ret; + ret = drm_connector_attach_vrr_capable_property(&connector->base); + if (ret) + return ret; + connector->base.polled = DRM_CONNECTOR_POLL_HPD; connector->connected = false; connector->dcp = dcp; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 1d9448f0f4dc47..1d90e4a2597303 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -244,6 +244,7 @@ void dcp_hotplug(struct work_struct *work) if (!connector->connected) { drm_edid_free(connector->drm_edid); + drm_connector_set_vrr_capable_property(&connector->base, false); connector->drm_edid = NULL; } diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 82a5fce70bd442..e090797743831f 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -546,8 +546,9 @@ static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, static bool dcpep_process_chunks(struct apple_dcp *dcp, struct dcp_set_dcpav_prop_end_req *req) { + struct apple_connector *connector = dcp->connector; struct dcp_parse_ctx ctx; - int ret; + int ret, i; if (!dcp->chunks.data) { dev_warn(dcp->dev, "ignoring spurious end\n"); @@ -589,6 +590,15 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, dcp_set_dimensions(dcp); } + if (connector) { + for (i = 0; i < dcp->nr_modes; i++) { + if (dcp->modes[i].vrr) { + drm_connector_set_vrr_capable_property(&connector->base, true); + break; + } + } + } + return true; } @@ -1171,6 +1181,33 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, } } +/* Changes to Adaptive Sync require a trip through set_digital_out_mode */ +static void dcp_on_set_adaptive_sync(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); +} + +static void dcp_set_adaptive_sync(struct apple_dcp *dcp, u32 rate, void *cookie) +{ + struct dcp_set_parameter_dcp param = { + .param = IOMFBPARAM_ADAPTIVE_SYNC, + .value = { + rate, /* minRR */ + 0, /* mediaTargetRate */ + 0, /* Fractional Rate (?) */ + 0, /* unused */ + }, +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + .count = 3, +#else + .count = 1, +#endif + }; + + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_adaptive_sync, cookie); +} + int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, struct drm_crtc_state *crtc_state) { @@ -1225,8 +1262,11 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, dcp->during_modeset = true; - dcp_set_digital_out_mode(dcp, false, &dcp->mode, - complete_set_digital_out_mode, cookie); + if (mode->vrr) + dcp_set_adaptive_sync(dcp, mode->min_vrr, cookie); + else + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); /* * The DCP firmware has an internal timeout of ~8 seconds for diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 56960fa1fda265..a6e215d3b7e346 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -517,9 +517,8 @@ static int parse_mode(struct dcp_parse_ctx *handle, /* * HACK: * Mark the 120 Hz mode on j314/j316 (identified by resolution) as vrr. - * We still do not know how to drive VRR but at least seetinng timestamps - * in the the swap_surface message to non-zero values drives the display - * at 120 fps. + * Setting timestamps in the the swap_surface message to non-zero + * values drives the display at 120 fps. */ if (vert.precise_sync_rate >> 16 == 120 && ((horiz.active == 3024 && vert.active == 1964) || @@ -529,6 +528,7 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (min_vrr && max_vrr) { out->min_vrr = min_vrr; out->max_vrr = max_vrr; + out->vrr = true; } vert.active -= notch_height; From d9cca000f47bf97cccc8054eadfe27ee27602f50 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sat, 4 Apr 2026 17:43:49 +1000 Subject: [PATCH 3/7] drm: apple: Force modeset when VRR is toggled DCP requires a "modeset" to trigger the upload of the SDP to the display. On some monitors, this is instant. On others, this seems to take as long as a real modeset. Given that in either case we still blank the display, let's just force a full modeset when VRR is toggled on or off. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp.c | 4 ++++ drivers/gpu/drm/apple/iomfb_template.c | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index f2eb2483c9a880..0a6859448e19eb 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -186,6 +186,7 @@ struct apple_dcp { bool during_modeset; bool valid_mode; bool use_timestamps; + bool vrr_enabled; struct dcp_set_digital_out_mode_req mode; /* completion for active turning true */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 9dfc3fd002f530..dc69d1a9ef7f7e 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -361,6 +361,10 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) return -EINVAL; } + if (dcp->vrr_enabled != crtc_state->vrr_enabled) { + crtc_state->mode_changed = true; + } + return 0; } diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index e090797743831f..fa998dcf5ce4f1 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1263,7 +1263,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, dcp->during_modeset = true; if (mode->vrr) - dcp_set_adaptive_sync(dcp, mode->min_vrr, cookie); + dcp_set_adaptive_sync(dcp, crtc_state->vrr_enabled ? mode->min_vrr : 0, cookie); else dcp_set_digital_out_mode(dcp, false, &dcp->mode, complete_set_digital_out_mode, cookie); @@ -1294,6 +1294,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, jiffies_to_msecs(ret)); } dcp->valid_mode = true; + dcp->vrr_enabled = crtc_state->vrr_enabled; return 0; } From da9b26f6ffac16cd1ab924f1b26a720797188717 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Fri, 3 Apr 2026 21:59:47 +1000 Subject: [PATCH 4/7] drm: apple: Set swap timestamps to sane values for Adaptive Sync Setting these timestamps to a dummy value worked fine for enabling a fixed 120 Hz mode on the MacBook Pros, however doing so causes Adaptive Sync displays to simply switch between full and minimum refresh rates. Setting these timestamps based on the swap pacing seems to fix this, and makes the display's refresh rate match the incoming swap rate. Note that the names and values are best-guess only. These seem to work fine for driving VRR displays, but may still be incorrect. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/iomfb_template.c | 21 ++++++++++++++++----- drivers/gpu/drm/apple/iomfb_template.h | 6 +++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index fa998dcf5ce4f1..d37b7c77e73241 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1299,6 +1299,15 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, return 0; } +/* + * DCP timestamps are expressed in system timer ticks. Approximate + * this by converting from ktime nanoseconds to 24 MHz ticks. + */ +static u64 ns_to_mach(u64 ns) +{ + return ns * 3 / 125; +} + void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state) { struct drm_plane *plane; @@ -1415,12 +1424,14 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru if (has_surface && dcp->use_timestamps) { /* - * Fake timstamps to get 120hz refresh rate. It looks - * like the actual value does not matter, as long as it is non zero. + * TODO: ascertain with certainty what these timestamps + * are. They are something to do with presentation timing, + * but that is all we know for sure. These values seem to + * work well with VRR. */ - req->swap.ts1 = 120; - req->swap.ts2 = 120; - req->swap.ts3 = 120; + req->swap.unk_pres_ts1 = ns_to_mach(ktime_get_ns()); + req->swap.unk_pres_ts2 = ns_to_mach(ktime_to_ns(dcp->swap_start)); + req->swap.unk_pres_ts3 = req->swap.unk_pres_ts1; } /* These fields should be set together */ diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index 8efab49cc53d08..e74672da712efa 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -18,14 +18,14 @@ #include "version_utils.h" struct DCP_FW_NAME(dcp_swap) { - u64 ts1; - u64 ts2; + u64 unk_pres_ts1; + u64 unk_pres_ts2; u64 unk_10; u64 unk_18; u64 ts64_unk; u64 unk_28; - u64 ts3; + u64 unk_pres_ts3; u64 unk_38; u64 flags1; From 9a019d7eafb2d1511d17d7deea30975ca8c84a1b Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sat, 4 Apr 2026 23:32:06 +1000 Subject: [PATCH 5/7] drm: apple: Set min and max VRRs for MacBook Pros Since these machines do not have proper EDID/DisplayID data, we need to help the driver along a little bit. We know that "ProMotion" displays can do 24-120 Hz VRR, so let's populate the mode with those values. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/parser.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index a6e215d3b7e346..cc559c8bd7196b 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -522,8 +522,11 @@ static int parse_mode(struct dcp_parse_ctx *handle, */ if (vert.precise_sync_rate >> 16 == 120 && ((horiz.active == 3024 && vert.active == 1964) || - (horiz.active == 3456 && vert.active == 2234))) + (horiz.active == 3456 && vert.active == 2234))) { + out->min_vrr = 24 << 16; + out->max_vrr = 120 << 16; out->vrr = true; + } if (min_vrr && max_vrr) { out->min_vrr = min_vrr; From f34ea28724eae743450f49083dd3572bd92aef14 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 5 Apr 2026 13:08:29 +1000 Subject: [PATCH 6/7] drm: apple: Only use swap timestamps if VRR is actually active macOS is inconsistent with how it uses DCP timestamps. Some swaps don't use them at all. We know they are required for VRR display modes to work properly, so let's just turn them on when we are connected to a VRR display. This includes the 120 Hz mode on the 14" and 16" MacBook Pros. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/iomfb_template.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index d37b7c77e73241..2390be534dfdfd 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1247,8 +1247,8 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, .timing_mode_id = mode->timing_mode_id }; - /* Keep track of suspected vrr modes */ - dcp->use_timestamps = mode->vrr; + /* Use DCP swap timestamps on MacBook Pros with VRR */ + dcp->use_timestamps = mode->vrr && dcp->main_display; cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) { @@ -1422,7 +1422,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->clear = 1; } - if (has_surface && dcp->use_timestamps) { + if (has_surface && (dcp->use_timestamps || crtc_state->vrr_enabled)) { /* * TODO: ascertain with certainty what these timestamps * are. They are something to do with presentation timing, From fd05dff1bc81d3089b0af1453b0c1f4177cf6339 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 7 Apr 2026 21:43:32 +1000 Subject: [PATCH 7/7] NOUPSTREAM: drm: apple: Hide VRR behind a module parameter Given that DCP requires a modeset to activate VRR, and given that this is explicitly banned by KMS API contract and VESA DisplayPort specification, hide this experimental support behind a module param. Interestingly, the HDMI spec does not require a modeset-free VRR transition. For this reason, it is expected that the KMS API contract may change in the future, as both Intel and AMD hardware require a modeset to enable VRR in some circumstances. Either VRR will be expected to be enabled whenever it is supported, *or* modesetting to toggle it on or off will be allowed. When that happens, this commit *must* be reverted. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/dcp.c | 10 +++++--- drivers/gpu/drm/apple/iomfb_template.c | 33 ++++++++++++++------------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index dc69d1a9ef7f7e..83ba20f7f02568 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -58,6 +58,10 @@ static bool unstable_edid = true; module_param(unstable_edid, bool, 0644); MODULE_PARM_DESC(unstable_edid, "Enable unstable EDID retrival support"); +bool force_vrr; +module_param(force_vrr, bool, 0644); +MODULE_PARM_DESC(force_vrr, "Always enable Adaptive Sync/ProMotion on supported displays"); + /* copied and simplified from drm_vblank.c */ static void send_vblank_event(struct drm_device *dev, struct drm_pending_vblank_event *e, @@ -361,9 +365,9 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) return -EINVAL; } - if (dcp->vrr_enabled != crtc_state->vrr_enabled) { - crtc_state->mode_changed = true; - } + // if (dcp->vrr_enabled != crtc_state->vrr_enabled) { + // crtc_state->mode_changed = true; + // } return 0; } diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 2390be534dfdfd..0e5d5908a3c9b6 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -34,6 +34,8 @@ /* Register defines used in bandwidth setup structure */ #define REG_DOORBELL_BIT(idx) (2 + (idx)) +extern bool force_vrr; + struct dcp_wait_cookie { struct kref refcount; struct completion done; @@ -546,9 +548,9 @@ static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, static bool dcpep_process_chunks(struct apple_dcp *dcp, struct dcp_set_dcpav_prop_end_req *req) { - struct apple_connector *connector = dcp->connector; + // struct apple_connector *connector = dcp->connector; struct dcp_parse_ctx ctx; - int ret, i; + int ret; //, i; if (!dcp->chunks.data) { dev_warn(dcp->dev, "ignoring spurious end\n"); @@ -590,14 +592,14 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, dcp_set_dimensions(dcp); } - if (connector) { - for (i = 0; i < dcp->nr_modes; i++) { - if (dcp->modes[i].vrr) { - drm_connector_set_vrr_capable_property(&connector->base, true); - break; - } - } - } + // if (connector) { + // for (i = 0; i < dcp->nr_modes; i++) { + // if (dcp->modes[i].vrr) { + // drm_connector_set_vrr_capable_property(&connector->base, true); + // break; + // } + // } + // } return true; } @@ -1262,11 +1264,12 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, dcp->during_modeset = true; - if (mode->vrr) - dcp_set_adaptive_sync(dcp, crtc_state->vrr_enabled ? mode->min_vrr : 0, cookie); - else + if (mode->vrr) { + dcp_set_adaptive_sync(dcp, force_vrr ? mode->min_vrr : 0, cookie); + } else { dcp_set_digital_out_mode(dcp, false, &dcp->mode, complete_set_digital_out_mode, cookie); + } /* * The DCP firmware has an internal timeout of ~8 seconds for @@ -1294,7 +1297,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, jiffies_to_msecs(ret)); } dcp->valid_mode = true; - dcp->vrr_enabled = crtc_state->vrr_enabled; + dcp->vrr_enabled = mode->vrr && force_vrr; return 0; } @@ -1422,7 +1425,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->clear = 1; } - if (has_surface && (dcp->use_timestamps || crtc_state->vrr_enabled)) { + if (has_surface && (dcp->use_timestamps || crtc_state->vrr_enabled || force_vrr)) { /* * TODO: ascertain with certainty what these timestamps * are. They are something to do with presentation timing,