From 432f5cb730a1107112d90938303063b795d5dd61 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Thu, 29 Oct 2026 11:55:17 +1100 Subject: [PATCH 1/3] drm/apple: Keep the desktop panel's native refresh preferred GNOME brings the J616s panel up at 60 Hz with a fixed refresh whenever it makes a new monitor configuration: it takes the mode the kernel marks preferred. The panel's firmware ranks its 120 Hz timing first (IsPreferred, highest score), and the parser marks that one, but the takeover trial set the boot-like 60 Hz mode first and then moved the preferred mark to whichever mode it accepted, so 60 Hz it always was. Try the preferred mode first and the boot-like one only if it fails. The mode that worked stays the preferred one, as before, and is now the panel's native 120 Hz. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/iomfb.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index d2c7273e75e24..924d0063e8e2e 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -2195,7 +2195,11 @@ int dcp_takeover_trial(struct platform_device *pdev, const char **reason) *reason = "not-connected"; return -ENODEV; } - /* The boot-like mode first, then the preferred one. */ + /* + * The preferred mode first, the one the firmware ranks highest (the + * panel's native refresh), then the boot-like one: the mode that works + * stays the preferred one, which is also what a desktop picks. + */ mutex_lock(&dcp->modes_lock); for (i = 0; i < dcp->nr_modes; i++) { const struct dcp_display_mode *m = &dcp->modes[i]; @@ -2214,13 +2218,11 @@ int dcp_takeover_trial(struct platform_device *pdev, const char **reason) } if (pref == UINT_MAX) pref = 0; + cand[n++] = dcp->modes[pref].mode; /* Only a mode within 1% of the boot loader's clock counts as boot-like. */ - if (boot != UINT_MAX && best <= DCP_DESKTOP_BOOT_PIXEL_CLOCK_KHZ / 100) + if (boot != UINT_MAX && best <= DCP_DESKTOP_BOOT_PIXEL_CLOCK_KHZ / 100 && + boot != pref) cand[n++] = dcp->modes[boot].mode; - else - boot = UINT_MAX; - if (pref != boot) - cand[n++] = dcp->modes[pref].mode; mutex_unlock(&dcp->modes_lock); st = kzalloc_obj(*st); From 425e6f5298dcc20856fabd2be9d055700999dbe4 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Thu, 29 Oct 2026 12:05:00 +1100 Subject: [PATCH 2/3] drm/apple: Default external displays to native resolution at full refresh An external display came up in the timing its EDID lists first: the firmware marks that timing IsPreferred, and the parser preferred IsPreferred timings, then the highest score. On an Acer QG270 S3 that is 1920x1080 at 60 Hz of a 180 Hz panel; an MSI MPG 325CQRXF marks 1920x1080 at 240 Hz and 2560x1440 at 60 Hz, and the score picks 1920x1080 for a 2560x1440 panel. Default an external display to its native resolution at the highest refresh rate it can take, as other systems do: the native resolution is the largest the firmware marks IsPreferred (the largest of all when it marks none), and among its timings the higher refresh rate ranks first, then IsPreferred, then the firmware score. A timing whose SDR color elements are all unsafe or DSC only is never the default. Panels keep the timing their firmware prefers. The host selftests cover the three cases with synthetic catalogs modelled on both displays and a panel. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/parser.c | 75 +++++++++++++++++++ drivers/gpu/drm/apple/parser.h | 8 ++ .../selftests/drivers/gpu/apple-dcp/rate.py | 44 +++++++++++ 3 files changed, 127 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 9dc6b0eb27b3b..63b1f55ce9aa5 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -608,6 +608,7 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, out->sdr.score = -1; out->best.score = -1; out->pq.score = -1; + out->sdr_usable = false; out->has_bootstrap = false; ret = iterator_begin(handle, &outer_it, false); @@ -693,6 +694,11 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, if (limits_known) collect_pq_mode(&out->pq, &best_pq_rank, &cmode, limits); + if (cmode.eotf == DCP_EOTF_SDR_GAMMA && + (!limits_known || + (!timing_color_contains(&limits->unsafe, cmode.id) && + !timing_color_contains(&limits->dsc_required, cmode.id)))) + out->sdr_usable = true; if (external_sdr) { unsigned int rank; @@ -987,6 +993,7 @@ static int parse_mode(struct dcp_parse_ctx *handle, out->timing_mode_id = id; out->color_mode_id = best_color_mode; out->preferred = is_preferred; + out->firmware_preferred = external_sdr ? preferred : is_preferred; trace_iomfb_timing_mode(handle->dcp, id, *score, horiz.active, vert.active, vert.precise_sync_rate, @@ -1097,6 +1104,66 @@ static bool dcp_180hz_ids_current(struct dcp_parse_ctx handle, * A timing mode that the firmware marks as preferred ranks above every mode * that it does not mark; the firmware score orders the modes of each group. */ +/* + * An external display defaults to its native resolution at the highest + * refresh rate it can take, as other systems pick it, rather than to the + * timing its EDID lists first. Among the timings of that resolution with a + * usable SDR element, the higher refresh rate ranks first, then IsPreferred, + * then the firmware score. + */ +VISIBLE_IF_KUNIT bool dcp_mode_default_above(const struct dcp_display_mode *mode, + s64 score, + const struct dcp_display_mode *best, + s64 best_score) +{ + /* clock / (htotal * vtotal), compared without dividing */ + u64 refresh = (u64)mode->mode.clock * best->mode.htotal * best->mode.vtotal; + u64 best_refresh = (u64)best->mode.clock * mode->mode.htotal * mode->mode.vtotal; + + if (refresh != best_refresh) + return refresh > best_refresh; + if (mode->firmware_preferred != best->firmware_preferred) + return mode->firmware_preferred; + return score > best_score; +} + +/* + * The native resolution: the largest the firmware marks IsPreferred, or the + * largest of all when it marks none. + */ +static struct dcp_display_mode *dcp_external_default(struct dcp_display_mode *modes, + unsigned int count) +{ + struct dcp_display_mode *native = NULL, *best = NULL; + unsigned int i; + + for (i = 0; i < count; i++) { + const struct dcp_display_mode *m = &modes[i]; + u64 area = (u64)m->mode.hdisplay * m->mode.vdisplay; + + if (m->automatic_rgb8_quirk || !m->sdr_usable) + continue; + if (!native || + (m->firmware_preferred && !native->firmware_preferred) || + (m->firmware_preferred == native->firmware_preferred && + area > (u64)native->mode.hdisplay * native->mode.vdisplay)) + native = &modes[i]; + } + if (!native) + return NULL; + for (i = 0; i < count; i++) { + struct dcp_display_mode *m = &modes[i]; + + if (m->automatic_rgb8_quirk || !m->sdr_usable || + m->mode.hdisplay != native->mode.hdisplay || + m->mode.vdisplay != native->mode.vdisplay) + continue; + if (!best || dcp_mode_default_above(m, m->score, best, best->score)) + best = m; + } + return best; +} + VISIBLE_IF_KUNIT bool dcp_mode_ranks_above(bool preferred, s64 score, bool best_preferred, s64 best_score) { @@ -1162,6 +1229,7 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, /* Process a successful mode */ (*count)++; + mode->score = score; if (!mode->automatic_rgb8_quirk && dcp_mode_ranks_above(mode->preferred, score, best_preferred, @@ -1172,6 +1240,13 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, } } + /* A panel keeps the timing its firmware prefers. */ + if (!internal) { + struct dcp_display_mode *def = dcp_external_default(modes, *count); + + if (def) + best_mode = def; + } if (best_mode != NULL) best_mode->mode.type |= DRM_MODE_TYPE_PREFERRED; diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 605629b9af27d..81c75143c8f9d 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -140,6 +140,12 @@ struct dcp_display_mode { bool preferred; bool metadata_timing_valid; bool automatic_rgb8_quirk; + /* IsPreferred on every path; an external display's native resolution */ + bool firmware_preferred; + /* An SDR color element that is neither unsafe nor DSC only */ + bool sdr_usable; + /* The firmware score of the timing */ + s64 score; /* The sync polarity flags of the timing, if it reports them */ u32 metadata_sync_flags; /* @@ -175,6 +181,8 @@ struct dimension { int parse(const void *blob, size_t size, struct dcp_parse_ctx *ctx); bool dcp_property_complete(const void *blob, size_t received, size_t advertised); #if IS_ENABLED(CONFIG_KUNIT) +bool dcp_mode_default_above(const struct dcp_display_mode *mode, s64 score, + const struct dcp_display_mode *best, s64 best_score); bool dcp_mode_ranks_above(bool preferred, s64 score, bool best_preferred, s64 best_score); #endif diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index 9ca6841c74755..6224f3e6c721c 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -89,6 +89,31 @@ def color(i, score, depth, eotf, colorimetry, encoding=0, dynamic=0): ycc_raw = bytearray(struct.pack(' Date: Thu, 29 Oct 2026 12:47:21 +1100 Subject: [PATCH 3/3] drm/apple: Describe an IsPreferred timing in firmware-derived EDIDs The detailed timing of a firmware-derived EDID was the preferred mode, accepted only when the firmware marks that timing IsPreferred and describes it fully. Now that an external display defaults to its native resolution at its highest refresh rate, the preferred mode is often a timing the firmware does not mark, and the EDID was not built at all: an MSI MPG 325CQRXF defaulting to 2560x1440 at 280 Hz came up with no EDID. Take the preferred mode when it qualifies, otherwise the highest-scored timing the firmware marks IsPreferred, the one the sink's own EDID lists first. The EDID describes the sink; the mode set is unaffected. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/dcp.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 58767d653ee8d..7244b8946f643 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -754,7 +754,7 @@ void dcp_firmware_metadata_attributes(struct apple_dcp *dcp, void dcp_firmware_metadata_timing(struct apple_dcp *dcp) { unsigned long flags; - unsigned int i; + unsigned int i, best = UINT_MAX; if (!dcp_firmware_metadata_enabled(dcp)) return; @@ -764,16 +764,30 @@ void dcp_firmware_metadata_timing(struct apple_dcp *dcp) dcp->metadata.timing_valid = false; if (!dcp_firmware_metadata_complete(dcp)) goto unlock; + /* + * The EDID's detailed timing: the preferred mode when the firmware + * describes it fully, otherwise the highest-scored timing it marks + * IsPreferred, the one the sink's own EDID lists first. An external + * display's preferred mode need not be one of those. + */ for (i = 0; i < dcp->nr_modes; i++) { - if (!(dcp->modes[i].mode.type & DRM_MODE_TYPE_PREFERRED) || - !dcp->modes[i].metadata_timing_valid) + const struct dcp_display_mode *m = &dcp->modes[i]; + + if (!m->metadata_timing_valid) continue; - dcp->metadata.preferred = dcp->modes[i].mode; + if (best == UINT_MAX || (m->mode.type & DRM_MODE_TYPE_PREFERRED) || + (!(dcp->modes[best].mode.type & DRM_MODE_TYPE_PREFERRED) && + m->score > dcp->modes[best].score)) + best = i; + if (m->mode.type & DRM_MODE_TYPE_PREFERRED) + break; + } + if (best != UINT_MAX) { + dcp->metadata.preferred = dcp->modes[best].mode; /* Desktop 26.x publishes its modes without their polarity. */ - dcp->metadata.preferred.flags |= dcp->modes[i].metadata_sync_flags; + dcp->metadata.preferred.flags |= dcp->modes[best].metadata_sync_flags; dcp->metadata.timing_ticket = dcp->chunks.metadata_ticket; dcp->metadata.timing_valid = true; - break; } unlock: spin_unlock_irqrestore(&dcp->metadata.lock, flags);