diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 4fc018f4132392..08156394b233f0 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -656,6 +656,7 @@ static int dcp_register_typec_routes(struct apple_dcp *dcp) return 0; } + /* copied and simplified from drm_vblank.c */ static void send_vblank_event(struct drm_device *dev, struct drm_pending_vblank_event *e, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 452a35731f7cfd..3ec33353d3abe0 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -406,6 +406,8 @@ int dcp_get_modes(struct drm_connector *connector) struct drm_device *dev = connector->dev; struct drm_display_mode *mode; + u16 min_vfreq = 0, max_vfreq = 0; + bool vrr_capable = false; int i; /* A Type-C port has no pipeline while the fabric is moving it. */ @@ -414,6 +416,16 @@ int dcp_get_modes(struct drm_connector *connector) dcp = platform_get_drvdata(pdev); for (i = 0; i < dcp->nr_modes; ++i) { + if (dcp->modes[i].vrr) { + u16 lo = dcp->modes[i].min_vrr >> 16; + u16 hi = dcp->modes[i].max_vrr >> 16; + + if (!min_vfreq || lo < min_vfreq) + min_vfreq = lo; + if (hi > max_vfreq) + max_vfreq = hi; + } + vrr_capable |= dcp->modes[i].vrr; mode = drm_mode_duplicate(dev, &dcp->modes[i].mode); if (!mode) { @@ -423,6 +435,8 @@ int dcp_get_modes(struct drm_connector *connector) drm_mode_probed_add(connector, mode); } + drm_connector_set_vrr_capable_property(connector, vrr_capable); + if (dcp->nr_modes && dcp->dcpavserv.enabled && !apple_connector->drm_edid) { const struct drm_edid *edid; @@ -437,6 +451,18 @@ int dcp_get_modes(struct drm_connector *connector) if (dcp->nr_modes && apple_connector->drm_edid) drm_edid_connector_update(connector, apple_connector->drm_edid); + /* + * An internal panel has no EDID, so nothing fills in the refresh range + * that userspace needs before it will drive VRR. Supply the range DCP + * reported for the mode. This has to follow the EDID update, which + * resets display_info. + */ + if (vrr_capable && max_vfreq && + !connector->display_info.monitor_range.max_vfreq) { + connector->display_info.monitor_range.min_vfreq = min_vfreq; + connector->display_info.monitor_range.max_vfreq = max_vfreq; + } + return dcp->nr_modes; } diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 44902912c8a344..c0b533c4f8a3c7 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -4,6 +4,7 @@ * Copyright The Asahi Linux Contributors */ +#include #include #include #include @@ -34,6 +35,9 @@ /* Register defines used in bandwidth setup structure */ #define REG_DOORBELL_BIT(idx) (2 + (idx)) +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), timestamp[6]) == 0x30); +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), flags1) == 0x40); + struct dcp_wait_cookie { struct kref refcount; struct completion done; @@ -567,7 +571,9 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, if (!strcmp(req->key, "TimingElements")) { dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, dcp->width_mm, dcp->height_mm, - dcp->notch_height); + dcp->notch_height, + dcp->fixed_connector_type == + DRM_MODE_CONNECTOR_eDP); if (IS_ERR(dcp->modes)) { dev_warn(dcp->dev, "failed to parse modes\n"); @@ -1152,6 +1158,7 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) return; } dcp->swap_start = ktime_get(); + dcp->swap_submit_timestamp = arch_timer_read_counter(); while (!list_empty(&dcp->swapped_out_fbs)) { struct dcp_fb_reference *entry; @@ -1198,6 +1205,35 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, } } +/* DCP applies Adaptive Sync changes when the display mode is reselected. */ +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 min_vrr, + void *cookie) +{ + struct dcp_set_parameter_dcp param = { + .param = IOMFBPARAM_ADAPTIVE_SYNC, + .value = { + min_vrr, /* minRR, 16.16 fixed-point Hz */ + 0, /* mediaTargetRate */ + 0, /* fractional rate */ + }, +#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) { @@ -1237,8 +1273,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; + /* Built-in ProMotion panels require timestamps even in fixed-120 mode. */ + dcp->use_timestamps = mode->vrr && dcp->main_display; cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) { @@ -1251,9 +1287,15 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, kref_get(&cookie->refcount); dcp->during_modeset = true; + dcp->swap_submit_timestamp = 0; - dcp_set_digital_out_mode(dcp, false, &dcp->mode, - complete_set_digital_out_mode, cookie); + if (mode->vrr) + 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); /* * The DCP firmware has an internal timeout of ~8 seconds for @@ -1263,7 +1305,6 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(8500)); - kref_put(&cookie->refcount, release_wait_cookie); dcp->during_modeset = false; if (dcp->pending_hotplug) { @@ -1287,18 +1328,21 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, if (ret == 0) { dev_info(dcp->dev, "set_digital_out_mode timed out\n"); + kref_put(&cookie->refcount, release_wait_cookie); return -EIO; } else if (ret < 0) { dev_info(dcp->dev, "waiting on set_digital_out_mode failed:%d\n", ret); + kref_put(&cookie->refcount, release_wait_cookie); return -EIO; - - } else if (ret > 0) { + } else { dev_dbg(dcp->dev, "set_digital_out_mode finished with %d to spare\n", jiffies_to_msecs(ret)); } + kref_put(&cookie->refcount, release_wait_cookie); dcp->valid_mode = true; + dcp->vrr_enabled = mode->vrr && crtc_state->vrr_enabled; return 0; } @@ -1405,14 +1449,26 @@ 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 || dcp->vrr_enabled)) { + u64 submit_timestamp = dcp->swap_submit_timestamp; + u64 timestamp = arch_timer_read_counter(); + /* - * Fake timstamps to get 120hz refresh rate. It looks - * like the actual value does not matter, as long as it is non zero. + * IOMobileFramebuffer uses Mach continuous-time values here. On + * Apple Silicon that is the ARM architectural counter. Empirical + * testing shows that using the current submission time together + * with the previous accepted swap makes DCP follow swap pacing. */ - req->swap.ts1 = 120; - req->swap.ts2 = 120; - req->swap.ts3 = 120; + if (!submit_timestamp) + submit_timestamp = timestamp; + + /* Firmware 12.x/13.x requires timestamp types 1, 2, and 7. */ + req->swap.timestamp[0] = timestamp; + req->swap.timestamp[1] = submit_timestamp; + req->swap.timestamp[6] = timestamp; + + trace_iomfb_vrr_timestamps(dcp, dcp->vrr_enabled, timestamp, + submit_timestamp); } /* 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..ce9fb92513cd1b 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -18,14 +18,8 @@ #include "version_utils.h" struct DCP_FW_NAME(dcp_swap) { - u64 ts1; - u64 ts2; - - u64 unk_10; - u64 unk_18; - u64 ts64_unk; - u64 unk_28; - u64 ts3; + /* IOMobileFramebuffer timestamp types 1 through 7, in wire order. */ + u64 timestamp[7]; u64 unk_38; u64 flags1; diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 56960fa1fda265..3d520938a2900f 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -443,7 +443,7 @@ static u32 calculate_clock(struct dimension *horiz, struct dimension *vert) static int parse_mode(struct dcp_parse_ctx *handle, struct dcp_display_mode *out, s64 *score, int width_mm, - int height_mm, unsigned notch_height) + int height_mm, unsigned notch_height, bool internal) { int ret = 0; struct iterator it; @@ -515,20 +515,21 @@ static int parse_mode(struct dcp_parse_ctx *handle, return -EINVAL; /* - * 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. - */ - if (vert.precise_sync_rate >> 16 == 120 && - ((horiz.active == 3024 && vert.active == 1964) || - (horiz.active == 3456 && vert.active == 2234))) + * An internal ProMotion panel carries no EDID or DisplayID, so DCP + * reports no adaptive-sync range for it. Assume the ProMotion floor of + * 24 Hz up to whatever rate the mode itself advertises. + */ + if (internal && vert.precise_sync_rate >> 16 == 120) { + out->min_vrr = 24 << 16; + out->max_vrr = vert.precise_sync_rate; out->vrr = true; + } - if (min_vrr && max_vrr) { + /* Refresh rates are reported by DCP as 16.16 fixed-point Hz. */ + if (min_vrr && max_vrr > min_vrr) { out->min_vrr = min_vrr; out->max_vrr = max_vrr; + out->vrr = true; } vert.active -= notch_height; @@ -568,7 +569,8 @@ static int parse_mode(struct dcp_parse_ctx *handle, struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, - int height_mm, unsigned notch_height) + int height_mm, unsigned notch_height, + bool internal) { struct iterator it; int ret; @@ -590,7 +592,8 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, for (; it.idx < it.len; ++it.idx) { mode = &modes[*count]; - ret = parse_mode(it.handle, mode, &score, width_mm, height_mm, notch_height); + ret = parse_mode(it.handle, mode, &score, width_mm, height_mm, + notch_height, internal); /* Errors for a single mode are recoverable -- just skip it. */ if (ret) diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 52c4de46d2f79f..35f4e81e762125 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -104,7 +104,8 @@ struct dimension { int parse(const void *blob, size_t size, struct dcp_parse_ctx *ctx); struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, - int height_mm, unsigned notch_height); + int height_mm, unsigned notch_height, + bool internal); int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm); int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index a13dd34fb7aab1..c0acac6ee690ab 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -263,6 +263,27 @@ TRACE_EVENT(iomfb_swap_submit, __entry->swap_id) ); +TRACE_EVENT(iomfb_vrr_timestamps, + TP_PROTO(struct apple_dcp *dcp, bool enabled, u64 timestamp, + u64 submit_timestamp), + TP_ARGS(dcp, enabled, timestamp, submit_timestamp), + TP_STRUCT__entry( + __field(u64, dcp) + __field(bool, enabled) + __field(u64, timestamp) + __field(u64, submit_timestamp) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->enabled = enabled; + __entry->timestamp = timestamp; + __entry->submit_timestamp = submit_timestamp; + ), + TP_printk("dcp=%llx, enabled=%u, timestamp=%llu, previous=%llu", + __entry->dcp, __entry->enabled, __entry->timestamp, + __entry->submit_timestamp) +); + TRACE_EVENT(iomfb_swap_complete, TP_PROTO(struct apple_dcp *dcp, u32 swap_id), TP_ARGS(dcp, swap_id),