Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions drivers/gpu/drm/apple/dcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
14 changes: 8 additions & 6 deletions drivers/gpu/drm/apple/iomfb.c
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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);
Expand Down
75 changes: 75 additions & 0 deletions drivers/gpu/drm/apple/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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,
Expand All @@ -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;

Expand Down
8 changes: 8 additions & 0 deletions drivers/gpu/drm/apple/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/*
Expand Down Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions tools/testing/selftests/drivers/gpu/apple-dcp/rate.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,31 @@ def color(i, score, depth, eotf, colorimetry, encoding=0, dynamic=0):
ycc_raw = bytearray(struct.pack('<I', 0xd3))
wire(ycc_fixture, ycc_raw)
timing_source += 'static const unsigned char ycc_blob[] = {' + ','.join(map(str,ycc_raw)) + '};\n'
# Default mode: an external display's native resolution at its highest
# usable refresh rate; a panel keeps the timing its firmware prefers.
def timing(i, w, h, hz, pref, score, unsafe=False):
base = timing_fixture[0]
return dict(base, ID=i, Score=score, IsPreferred=pref,
HorizontalAttributes=dict(base['HorizontalAttributes'], Active=w, Total=w + 160),
VerticalAttributes=dict(base['VerticalAttributes'], Active=h, Total=h + 45,
PreciseSyncRate=hz << 16),
ColorModes=[color(200 + i, 1, 10, 0, 10)],
UnsafeColorElementIDs=[200 + i] if unsafe else [])
defaults = {
# EDID-preferred 1080p60, native 1920x1080 up to 180 Hz
'acer': [timing(1, 1920, 1080, 60, True, 16224), timing(2, 1920, 1080, 180, False, 15553),
timing(3, 1920, 1080, 144, False, 15443), timing(4, 1280, 720, 60, False, 16155)],
# IsPreferred on 1080p240 and 1440p60; 1440p300 only with an unsafe element
'msi': [timing(1, 1920, 1080, 240, True, 20000), timing(2, 2560, 1440, 60, True, 16000),
timing(3, 2560, 1440, 280, False, 15000), timing(4, 2560, 1440, 144, False, 15100),
timing(5, 2560, 1440, 300, False, 15200, unsafe=True)],
# a panel whose firmware prefers 60 Hz keeps it
'panel': [timing(1, 3456, 2234, 60, True, 16000), timing(2, 3456, 2234, 120, False, 17000)],
}
for name, catalog in defaults.items():
raw = bytearray(struct.pack('<I', 0xd3))
wire(catalog, raw)
timing_source += f'static const unsigned char default_{name}[] = {{' + ','.join(map(str, raw)) + '};\n'
proof_checks = []
for path, value, count in [
(("IsPreferred",), None, 1), (("IsPreferred",), False, 1),
Expand Down Expand Up @@ -288,6 +313,25 @@ def color(i, score, depth, eotf, colorimetry, encoding=0, dynamic=0):
assert(!IS_ERR(m) && n == 1 && m[0].pq.id == 95 && m[0].pq.depth == 12);
assert(m[0].color_mode_id == 85);
free(m);
/* The default modes; the timing ID that carries DRM_MODE_TYPE_PREFERRED. */
static const struct { const unsigned char *blob; size_t size; bool internal; u32 id; } d[] = {
{ default_acer, sizeof(default_acer), false, 2 },
{ default_msi, sizeof(default_msi), false, 3 },
{ default_panel, sizeof(default_panel), true, 1 },
};
for (unsigned int k = 0; k < ARRAY_SIZE(d); k++) {
unsigned int found = 0;
assert(!parse(d[k].blob, d[k].size, &c));
m = enumerate_modes(&c, &n, 600, 330, 0, d[k].internal, false);
assert(!IS_ERR(m) && n);
for (unsigned int j = 0; j < n; j++)
if (m[j].mode.type & DRM_MODE_TYPE_PREFERRED) {
assert(m[j].timing_mode_id == d[k].id);
found++;
}
assert(found == 1);
free(m);
}
/* The external SDR path keeps its SDR element and the same PQ one. */
assert(!parse(pq_blob, sizeof(pq_blob), &c));
m = enumerate_modes(&c, &n, 600, 330, 0, false, true);
Expand Down