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); 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); 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('