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
1 change: 1 addition & 0 deletions drivers/gpu/drm/apple/dcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 26 additions & 0 deletions drivers/gpu/drm/apple/iomfb.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down
84 changes: 70 additions & 14 deletions drivers/gpu/drm/apple/iomfb_template.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Copyright The Asahi Linux Contributors
*/

#include <clocksource/arm_arch_timer.h>
#include <linux/align.h>
#include <linux/bitmap.h>
#include <linux/clk.h>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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, &param, dcp_on_set_adaptive_sync,
cookie);
}

int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp,
struct drm_crtc_state *crtc_state)
{
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 */
Expand Down
10 changes: 2 additions & 8 deletions drivers/gpu/drm/apple/iomfb_template.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
29 changes: 16 additions & 13 deletions drivers/gpu/drm/apple/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion drivers/gpu/drm/apple/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions drivers/gpu/drm/apple/trace.h
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down