diff --git a/drivers/gpu/drm/apple/dcp-m4-callback.h b/drivers/gpu/drm/apple/dcp-m4-callback.h index 08249a9f255d0..2cf3f73790ade 100644 --- a/drivers/gpu/drm/apple/dcp-m4-callback.h +++ b/drivers/gpu/drm/apple/dcp-m4-callback.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "dcp-m4-profile.h" @@ -18,6 +19,8 @@ struct dcp_m4_callback_ops { int (*event)(void *cookie, const char *name, const void *dictionary, size_t size); int (*property)(void *cookie, unsigned int method, const void *input, void *output); int (*memory)(void *cookie, unsigned int method, const void *input, void *output); + /* Idle caching: D117 state, D578/D579 fence, D580 cache-surface release. */ + int (*idle)(void *cookie, unsigned int method, const void *input, void *output); }; struct dcp_m4_callback_shape { @@ -33,16 +36,18 @@ static inline int dcp_m4_callback_result(int ret) /* Exact qualified J713 shapes. An absent behavior hook is failure, never an ACK. */ static const struct dcp_m4_callback_shape dcp_m4_callbacks[] = { - {0, 0, 4}, {1, 0, 4}, {3, 4, 60}, {6, 84, 80}, - {100, 0, 0}, {101, 0, 4}, {102, 68, 0}, {104, 68, 0}, {107, 64, 0}, + {0, 0, 4}, {1, 0, 4}, {2, 0, 0}, {3, 4, 60}, {6, 84, 80}, {8, 4, 0}, + {100, 0, 0}, {101, 0, 4}, {102, 68, 0}, {104, 68, 0}, {106, 0, 4}, {107, 64, 0}, {108, 0, 4}, {109, 0, 4}, {110, 0, 4}, {111, 0, 4}, {112, 0, 4}, {113, 0, 4}, - {114, 4164, 4100}, {121, 0, 4}, {122, 0, 4}, {123, 0, 4}, + {114, 4164, 4100}, {117, 12, 0}, {121, 0, 4}, {122, 0, 4}, {123, 0, 4}, {125, 100, 36}, {127, 4, 4}, {128, 4104, 4}, {129, 64, 4}, - {201, 12, 16}, {206, 0, 4}, {207, 0, 4}, {300, 16, 0}, + {201, 12, 16}, {203, 0, 4}, {206, 0, 4}, {207, 0, 4}, {209, 0, 8}, {300, 16, 0}, {400, 76, 3076}, {401, 80, 12}, {406, 72, 0}, {408, 8, 8}, {413, 4168, 4}, {414, 80, 4}, {415, 76, 4}, {451, 20, 28}, {454, 4, 4}, {552, 4164, 4}, {561, 4164, 4}, {563, 76, 4}, {565, 72, 4}, {567, 128, 4}, - {575, 88, 76}, {576, 4, 0}, {582, 8, 4}, {588, 0, 0}, {590, 1840, 0}, {599, 0, 0}, + {572, 4, 4}, {575, 88, 76}, {576, 4, 0}, {578, 4, 4}, {579, 0, 0}, {580, 0, 0}, + {582, 8, 4}, {588, 0, 0}, {590, 1840, 0}, + {592, 20, 0}, {594, 4, 0}, {599, 0, 0}, {700, 4, 4}, }; static inline int dcp_m4_callback_method(u32 tag) @@ -136,6 +141,29 @@ static inline int dcp_m4_callback_dispatch(const struct dcp_m4_callback_ops *ops return 0; case 300: case 406: case 576: case 588: return 0; + /* + * will_power_off_signal, set_power_trace_enable, swap_complete_intent, + * enable_backlight_message_ap_gated: notifications. + */ + case 2: case 8: case 592: case 594: + return 0; + /* + * set_idle_caching_state_ap, idle_fence_create, idle_fence_complete, + * idle_surface_release. + */ + case 117: case 578: case 579: case 580: + if (!ops->idle) + return -EOPNOTSUPP; + return dcp_m4_callback_result(ops->idle(cookie, method, input, output)); + /* + * acknowledgeSetPowerState, powerUpDART, set_kernel_power_assert: success; + * aot_supported_peek: false. + */ + case 106: case 572: case 700: case 203: + return 0; + case 209: /* get_calendar_time_ms */ + put_unaligned_le64(ktime_get_real_ns() / NSEC_PER_MSEC, out); + return 0; case 122: case 123: case 582: return 0; /* False, not an unimplemented service returning success. */ case 101: diff --git a/drivers/gpu/drm/apple/dcp-m4-frame.h b/drivers/gpu/drm/apple/dcp-m4-frame.h index 5ea5dc47f6443..cb84457f0794a 100644 --- a/drivers/gpu/drm/apple/dcp-m4-frame.h +++ b/drivers/gpu/drm/apple/dcp-m4-frame.h @@ -253,6 +253,22 @@ static inline int dcp_m4_swap_encode_full(void *output, size_t length, u32 swap_ return 0; } +/* Swap intent that registers surface 0 as the idle-cache surface. */ +#define DCP_M4_SWAP_INTENT_IDLE_CACHE 8 + +/* + * Retarget an encoded present: the DCP registers rather than presents its + * sole surface, which must be a distinct object (surface_id other than + * the primary's 1) from every surface on screen. + */ +static inline void dcp_m4_swap_set_intent(void *output, u32 intent, u32 surface_id) +{ + u8 *data = output; + + put_unaligned_le32(intent, data + dcp_m4_pf->swap_intent); + put_unaligned_le32(surface_id, data + 0x9c); +} + static inline int dcp_m4_swap_encode_checked(void *output, size_t length, u32 swap_id, const struct dcp_m4_frame *frame, bool brightness_change, u32 nits, diff --git a/drivers/gpu/drm/apple/dcp-m4-profile.h b/drivers/gpu/drm/apple/dcp-m4-profile.h index a7a91c1a4f397..4cbed603d3166 100644 --- a/drivers/gpu/drm/apple/dcp-m4-profile.h +++ b/drivers/gpu/drm/apple/dcp-m4-profile.h @@ -15,6 +15,7 @@ struct dcp_m4_profile { u32 swap_nullable; /* ten nullable surface bytes */ u32 swap_tail; /* final two bytes */ u32 swap_nits; /* binary64 brightness */ + u32 swap_intent; /* swap intent, 0 for a present */ u32 disabled_color; /* background with no surface */ u16 done_method; /* swap completion callback */ u32 done_size; @@ -28,6 +29,7 @@ static const struct dcp_m4_profile dcp_m4_profile_j713 = { .panel_width = 2560, .panel_height = 1664, .boot_stride = 10240, .swap_size = 0x1bd8, .swap_surface = 0x588, .swap_dva = 0xe38, .swap_nullable = 0x1bcb, .swap_tail = 0x1bd6, .swap_nits = 0x35e, + .swap_intent = 0x17c, .disabled_color = 0xff000000, .done_method = 590, .done_size = 0x730, .open_method = 457, .brightness_max = 500, .brightness_default = 120, diff --git a/drivers/gpu/drm/apple/dcp-m4-protocol.h b/drivers/gpu/drm/apple/dcp-m4-protocol.h index 6fccff5625557..3b5c8c29468c3 100644 --- a/drivers/gpu/drm/apple/dcp-m4-protocol.h +++ b/drivers/gpu/drm/apple/dcp-m4-protocol.h @@ -119,16 +119,36 @@ static inline int dcp_m4_decode_word_ext(u64 word, struct dcp_m4_packet *packet) return 0; } +/* + * While an AP call is outstanding on stream 1 (bit 10, the older contexts' + * out-of-band +4), its reply and callbacks come on stream 1 as well, besides + * any stream-0 traffic. + */ +static inline int dcp_m4_decode_word_oob(u64 word, struct dcp_m4_packet *packet) +{ + u8 stream = (word >> 10) & 3; + int ret; + + if (!stream) + return dcp_m4_decode_word(word, packet); + if (stream != 1) + return -EPROTO; + ret = dcp_m4_decode_word(word & ~0x400ULL, packet); + if (!ret) + packet->stream = 1; + return ret; +} + /* * Replies require their pending request record, not a new packet location. - * Only callbacks may name a nonzero stream (external profile); the internal - * decoder never produces one. + * Callbacks may name a nonzero stream (external profile, or stream 1 during + * an out-of-band call); AP calls use stream 0, or stream 1 out of band. */ static inline int dcp_m4_packet_offset(const struct dcp_m4_packet *packet, bool callback) { unsigned int base; - if (packet->reply || (callback ? packet->stream >= 4 : packet->stream) || + if (packet->reply || (callback ? packet->stream >= 4 : packet->stream > 1) || (packet->callback_flag && (!callback || !packet->remote)) || packet->offset % DCP_M4_ALIGNMENT || packet->offset >= DCP_M4_STREAM_SIZE || packet->length < DCP_M4_PACKET_HEADER || diff --git a/drivers/gpu/drm/apple/dcp-m4-transaction.h b/drivers/gpu/drm/apple/dcp-m4-transaction.h index 7736a7a50ddf3..b9d4c25cddd9b 100644 --- a/drivers/gpu/drm/apple/dcp-m4-transaction.h +++ b/drivers/gpu/drm/apple/dcp-m4-transaction.h @@ -32,13 +32,16 @@ struct dcp_m4_transaction { unsigned int callbacks; int error; bool external; /* DCPEXT profile: streams 0..3 and any flagged callback route */ + bool oob; /* an AP call is outstanding on stream 1 */ }; static inline int dcp_m4_transaction_decode(const struct dcp_m4_transaction *state, u64 word, struct dcp_m4_packet *packet) { - return state->external ? dcp_m4_decode_word_ext(word, packet) : - dcp_m4_decode_word(word, packet); + if (state->external) + return dcp_m4_decode_word_ext(word, packet); + return state->oob ? dcp_m4_decode_word_oob(word, packet) : + dcp_m4_decode_word(word, packet); } static inline int dcp_m4_transaction_fail(struct dcp_m4_transaction *state, int error) @@ -59,14 +62,15 @@ static inline bool dcp_m4_tag(u32 tag, u8 direction) } /* Prepare only; the caller publishes with its DMA barrier and mailbox send. */ -static inline int dcp_m4_call_begin(struct dcp_m4_transaction *state, void *rpc, - size_t rpc_size, u32 tag, bool remote, - const void *input, u32 input_size, u32 output_size, - u64 *word) +/* Stream 1 is out of band: top-level calls only, such as setPowerState. */ +static inline int dcp_m4_call_begin_stream(struct dcp_m4_transaction *state, void *rpc, + size_t rpc_size, u32 tag, u8 stream, bool remote, + const void *input, u32 input_size, u32 output_size, + u64 *word) { struct dcp_m4_packet packet = { .tag = tag, .input_size = input_size, .output_size = output_size, - .remote = remote, + .remote = remote, .stream = stream, }; const struct dcp_m4_packet *outer = NULL; u8 *data; @@ -77,6 +81,8 @@ static inline int dcp_m4_call_begin(struct dcp_m4_transaction *state, void *rpc, if (!rpc || rpc_size != DCP_M4_RPC_SIZE || !word || (input_size && !input) || !dcp_m4_tag(tag, 'A') || state->calls >= DCP_M4_CALL_DEPTH) return dcp_m4_transaction_fail(state, -EINVAL); + if (stream > 1 || (stream && (state->calls || state->callbacks || state->external))) + return dcp_m4_transaction_fail(state, -EINVAL); if (state->calls) { struct dcp_m4_call *pending = &state->call[state->calls - 1]; @@ -88,9 +94,16 @@ static inline int dcp_m4_call_begin(struct dcp_m4_transaction *state, void *rpc, if (state->callbacks && remote != state->callback[state->callbacks - 1].packet.remote) return dcp_m4_transaction_fail(state, -EPROTO); - /* AP calls use stream 0; a callback on another stream cannot recurse. */ - if (state->callbacks && state->callback[state->callbacks - 1].packet.stream) - return dcp_m4_transaction_fail(state, -EOPNOTSUPP); + /* + * AP calls use stream 0; a callback on another stream cannot recurse, + * except that calls nested in an out-of-band callback stay out of band. + */ + if (state->callbacks && state->callback[state->callbacks - 1].packet.stream) { + if (!state->oob || state->callback[state->callbacks - 1].packet.stream != 1) + return dcp_m4_transaction_fail(state, -EOPNOTSUPP); + stream = 1; + packet.stream = stream; + } ret = dcp_m4_packet_size(input_size, output_size, &packet.length); if (ret) return dcp_m4_transaction_fail(state, ret); @@ -119,11 +132,22 @@ static inline int dcp_m4_call_begin(struct dcp_m4_transaction *state, void *rpc, state->call[state->calls++] = (struct dcp_m4_call) { .packet = packet, .callbacks = state->callbacks, }; - *word = 2 | (remote ? 0x100 : 0) | ((u64)packet.offset << 16) | + *word = 2 | (remote ? 0x100 : 0) | ((u64)stream << 10) | ((u64)packet.offset << 16) | ((u64)packet.length << 32); + if (stream) + state->oob = true; return 0; } +static inline int dcp_m4_call_begin(struct dcp_m4_transaction *state, void *rpc, + size_t rpc_size, u32 tag, bool remote, + const void *input, u32 input_size, u32 output_size, + u64 *word) +{ + return dcp_m4_call_begin_stream(state, rpc, rpc_size, tag, 0, remote, input, + input_size, output_size, word); +} + /* Firmware may write output, but must not change the owned packet header. */ static inline int dcp_m4_owned_packet(const void *rpc, const struct dcp_m4_packet *owned, bool callback) @@ -160,6 +184,7 @@ static inline int dcp_m4_call_reply(struct dcp_m4_transaction *state, const void return dcp_m4_transaction_fail(state, ret); call = &state->call[state->calls - 1]; if (!reply.reply || reply.remote != call->packet.remote || + reply.stream != call->packet.stream || state->callbacks != call->callbacks || output_size != call->packet.output_size || (output_size && !output)) return dcp_m4_transaction_fail(state, -EPROTO); @@ -169,6 +194,9 @@ static inline int dcp_m4_call_reply(struct dcp_m4_transaction *state, const void if (output_size) memcpy(output, (const u8 *)rpc + offset + DCP_M4_PACKET_HEADER + call->packet.input_size, output_size); + /* The out-of-band call itself, not a call nested in its callbacks. */ + if (call->packet.stream && state->calls == 1) + state->oob = false; state->calls--; return 0; } diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 60fb8e561e86a..be07aff95d776 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -6386,9 +6386,17 @@ static int dcp_platform_suspend(struct device *dev) if (dcp_takeover_was_aborted(dcp)) return -EBUSY; - /* The retained T8132 session has no qualified system-sleep handshake. */ - if (iomfb_is_m4(dcp)) - return -EBUSY; + /* + * T8132: setPowerState powers the display down; the coprocessor keeps + * running and its power domains stay on, as below. + */ + if (iomfb_is_m4(dcp)) { + int ret = iomfb_m4_suspend(dcp); + + if (!ret) + device_set_wakeup_path(dev); + return ret; + } if (READ_ONCE(dcp->ext_dead)) return 0; iomfb_v14_7_backlight_suspend(dcp); @@ -6469,7 +6477,7 @@ static int dcp_platform_resume(struct device *dev) return 0; if (iomfb_is_m4(dcp)) - return iomfb_m4_health(dcp); + return iomfb_m4_resume(dcp); if (READ_ONCE(dcp->ext_dead)) return 0; @@ -6492,10 +6500,26 @@ static int dcp_platform_resume(struct device *dev) return 0; } +/* + * Hibernation: the T8132 power-down keeps the retained session, which a + * hibernation image cannot carry across the boot that restores it. + */ +static int dcp_platform_freeze(struct device *dev) +{ + if (iomfb_is_m4(dev_get_drvdata(dev))) + return -EBUSY; + return dcp_platform_suspend(dev); +} + static const struct dev_pm_ops dcp_platform_pm_ops = { .prepare = pm_sleep_ptr(dcp_platform_prepare), .complete = pm_sleep_ptr(dcp_platform_complete), - SYSTEM_SLEEP_PM_OPS(dcp_platform_suspend, dcp_platform_resume) + .suspend = pm_sleep_ptr(dcp_platform_suspend), + .resume = pm_sleep_ptr(dcp_platform_resume), + .freeze = pm_sleep_ptr(dcp_platform_freeze), + .thaw = pm_sleep_ptr(dcp_platform_resume), + .poweroff = pm_sleep_ptr(dcp_platform_freeze), + .restore = pm_sleep_ptr(dcp_platform_resume), }; static const struct apple_dcp_hw_data apple_dcp_hw_t6020 = { diff --git a/drivers/gpu/drm/apple/iomfb_m4.c b/drivers/gpu/drm/apple/iomfb_m4.c index ac68f001626aa..d11e640b761b5 100644 --- a/drivers/gpu/drm/apple/iomfb_m4.c +++ b/drivers/gpu/drm/apple/iomfb_m4.c @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include #include @@ -46,6 +48,22 @@ #include "dcp-m4-property.h" #include "dcp-m4-frame.h" +static unsigned int idle_cache_ms = 1000; + +static int idle_cache_ms_set(const char *val, const struct kernel_param *kp) +{ + return param_set_uint_minmax(val, kp, 0, 60000); +} + +static const struct kernel_param_ops idle_cache_ms_ops = { + .set = idle_cache_ms_set, + .get = param_get_uint, +}; + +module_param_cb(idle_cache_ms, &idle_cache_ms_ops, &idle_cache_ms, 0644); +MODULE_PARM_DESC(idle_cache_ms, + "J713: cache a static frame after this many ms without a flip (0: never, at most 60000; applies from the next swap)"); + struct iomfb_m4 { struct apple_dcp *owner; struct device *dev; @@ -69,14 +87,20 @@ struct iomfb_m4 { size_t allocated; int error; bool started, init_sent, ready, startup_started; + bool powered_off; /* setPowerState(0) for system sleep */ + bool display_off; /* setPowerState(0) while blanked, until the next frame */ + u64 display_off_cycles; + s64 display_off_us, display_off_us_max, display_on_us, display_on_us_max; /* Exact completed successor or blank is the only framebuffer retirement. */ struct drm_framebuffer *active_fb; struct drm_framebuffer *uncertain_fb; struct dcp_m4_frame active_frame; u64 flips, blanks, brightness_swaps; + struct delayed_work idle_cache; }; static void dcp_m4_runtime_wake(struct iomfb_m4 *dcp); +static int dcp_m4_set_power_state(struct iomfb_m4 *dcp, bool on); static void dcp_m4_runtime_receive(struct iomfb_m4 *dcp, u64 word); static int dcp_m4_dart_check(struct device *dev) @@ -428,6 +452,16 @@ struct dcp_m4_runtime { bool qualified; /* KMS startup qualification passed; see dcp_m4_runtime_live() */ void *scanout_cpu; struct dcp_m4_frame scanout_frame; + /* + * Idle caching: one cache surface, allocated at startup, retained like + * all DCP-visible DMA and never written by the host. + */ + void *idle_cpu; + struct dcp_m4_frame idle_frame; + u32 idle_state, idle_fences; + bool idle_registered; /* from its first intent swap until D580 */ + bool idle_unavailable; + u64 idle_entries; }; static void dcp_m4_runtime_wake(struct iomfb_m4 *dcp) @@ -564,12 +598,54 @@ static int dcp_m4_runtime_swap_done(void *cookie, u32 id) return dcp_m4_swap_record_done(&r->swap, id); } +static int dcp_m4_runtime_idle_cache(void *cookie, unsigned int method, + const void *input, void *output) +{ + struct dcp_m4_runtime *r = cookie; + struct iomfb_m4 *dcp = r->dcp; + unsigned int ms = READ_ONCE(idle_cache_ms); + bool grant; + + switch (method) { + case 117: /* set_idle_caching_state_ap(state, size, flag) */ + r->idle_state = get_unaligned_le32(input); + if (r->idle_state) + return 0; + /* Leaving the cache completes every fence first. */ + r->idle_fences = 0; + /* Retry after a rollback without a flip, backing off. */ + if (ms && !READ_ONCE(dcp->owner->quiescing)) + queue_delayed_work(r->wq, &dcp->idle_cache, msecs_to_jiffies(10 * ms)); + return 0; + case 578: /* idle_fence_create(state) */ + /* + * The host never writes the cache surface, so a fence has no + * host-side writer to wait for. Entry (state 1) and each cache + * refresh (state 6) ask for one; grant them only for the surface + * this host registered. + */ + grant = r->idle_registered && !dcp->display_off && !dcp->powered_off; + r->idle_fences += grant; + put_unaligned_le32(grant, output); + return 0; + case 579: /* idle_fence_complete */ + if (r->idle_fences) + r->idle_fences--; + return 0; + case 580: /* idle_surface_release */ + r->idle_registered = false; + return 0; + } + return -EOPNOTSUPP; +} + static const struct dcp_m4_callback_ops dcp_m4_runtime_ops = { .call = dcp_m4_runtime_call, .property = dcp_m4_runtime_property, .memory = dcp_m4_runtime_memory, .event = dcp_m4_runtime_event, .swap_done = dcp_m4_runtime_swap_done, + .idle = dcp_m4_runtime_idle_cache, }; static void dcp_m4_runtime_bad_word(struct dcp_m4_runtime *r, u64 word, int error) @@ -650,10 +726,10 @@ static int dcp_m4_runtime_callback(struct dcp_m4_runtime *r, u64 word) return ret; } -static int dcp_m4_runtime_call(void *cookie, unsigned int method, bool remote, - const void *input, u32 input_size, void *output, u32 output_size) +static int dcp_m4_runtime_call_stream(struct dcp_m4_runtime *r, unsigned int method, + u8 stream, bool remote, const void *input, + u32 input_size, void *output, u32 output_size) { - struct dcp_m4_runtime *r = cookie; struct iomfb_m4 *dcp = r->dcp; unsigned long previous = r->deadline; struct dcp_m4_packet message; @@ -671,8 +747,9 @@ static int dcp_m4_runtime_call(void *cookie, unsigned int method, bool remote, } tag = 'A' | (('0' + method / 100) << 8) | (('0' + method / 10 % 10) << 16) | (('0' + method % 10) << 24); - ret = dcp_m4_call_begin(&dcp->transaction, dcp->rpc, DCP_M4_RPC_SIZE, - tag, remote, input, input_size, output_size, &word); + ret = dcp_m4_call_begin_stream(&dcp->transaction, dcp->rpc, DCP_M4_RPC_SIZE, + tag, stream, remote, input, input_size, output_size, + &word); if (!ret) ret = dcp_m4_runtime_send(r, word); while (!ret) { @@ -680,7 +757,7 @@ static int dcp_m4_runtime_call(void *cookie, unsigned int method, bool remote, if (ret) break; dma_rmb(); - ret = dcp_m4_decode_word(word, &message); + ret = dcp_m4_transaction_decode(&dcp->transaction, word, &message); if (ret) { dcp_m4_runtime_bad_word(r, word, ret); break; @@ -702,6 +779,13 @@ static int dcp_m4_runtime_call(void *cookie, unsigned int method, bool remote, return ret; } +static int dcp_m4_runtime_call(void *cookie, unsigned int method, bool remote, + const void *input, u32 input_size, void *output, u32 output_size) +{ + return dcp_m4_runtime_call_stream(cookie, method, 0, remote, input, input_size, + output, output_size); +} + /* One bounded drain; a positive return asks the idle worker to reschedule. */ static int dcp_m4_runtime_drain(struct dcp_m4_runtime *r, unsigned int budget) { @@ -750,11 +834,12 @@ static void dcp_m4_runtime_idle(struct work_struct *work) /* * Serialized owner only; both the A407 reply and its D590 fence must complete. - * primary/frame/upper/keep as dcp_m4_swap_encode_full(). + * primary/frame/upper/keep as dcp_m4_swap_encode_full(); a nonzero intent + * retargets the swap as dcp_m4_swap_set_intent() describes. */ static int dcp_m4_runtime_swap_full(struct dcp_m4_runtime *r, bool primary, const struct dcp_m4_frame *frame, const struct dcp_m4_upper *upper, u32 keep, - bool brightness_change, u32 nits) + bool brightness_change, u32 nits, u32 intent, u32 surface_id) { struct iomfb_m4 *dcp = r->dcp; u8 start[16] = {0}, started[8], submitted[12]; @@ -783,6 +868,8 @@ static int dcp_m4_runtime_swap_full(struct dcp_m4_runtime *r, bool primary, if (!ret) ret = dcp_m4_swap_encode_full(input, DCP_M4_SWAP_SIZE, id, primary, frame, upper, keep, brightness_change, nits, dcp_m4_frame_check); + if (!ret && intent) + dcp_m4_swap_set_intent(input, intent, surface_id); if (!ret) ret = dcp_m4_runtime_call(r, 407, false, input, DCP_M4_SWAP_SIZE, submitted, sizeof(submitted)); @@ -813,7 +900,7 @@ static int dcp_m4_runtime_swap_full(struct dcp_m4_runtime *r, bool primary, static int dcp_m4_runtime_swap_brightness(struct dcp_m4_runtime *r, const struct dcp_m4_frame *frame, bool brightness_change, u32 nits) { - return dcp_m4_runtime_swap_full(r, true, frame, NULL, 0, brightness_change, nits); + return dcp_m4_runtime_swap_full(r, true, frame, NULL, 0, brightness_change, nits, 0, 0); } static int dcp_m4_runtime_swap(struct dcp_m4_runtime *r, const struct dcp_m4_frame *frame) @@ -965,6 +1052,17 @@ static int dcp_m4_prepare_scanout(struct iomfb_m4 *dcp) /* Prepare backing while simpledrm still owns the inherited surface. */ memset(r->scanout_cpu, 0, r->scanout_frame.backing_size); dma_wmb(); + /* The idle cache is optional: without its (zeroed) surface it stays off. */ + r->idle_cpu = apple_dart_dcp_scanout_alloc(dcp->scanout, r->scanout_frame.backing_size, + &dva); + if (IS_ERR(r->idle_cpu)) { + dev_warn(dcp->dev, "no idle-cache surface: %ld\n", PTR_ERR(r->idle_cpu)); + r->idle_cpu = NULL; + r->idle_unavailable = true; + } else { + r->idle_frame = r->scanout_frame; + r->idle_frame.dva = dva; + } return dcp_m4_runtime_health(r); } @@ -1002,6 +1100,7 @@ static int iomfb_m4_check_frame(const struct dcp_m4_frame *frame) } static void iomfb_m4_backlight_work(struct work_struct *work); +static void iomfb_m4_idle_cache_work(struct work_struct *work); /* Once adopted, even driver unbind must preserve the callback cookie and DMA. */ static void iomfb_m4_release(void *cookie) @@ -1067,6 +1166,7 @@ int iomfb_m4_init(struct apple_dcp *owner) spin_lock_init(&dcp->state_lock); init_completion(&dcp->reply); INIT_WORK(&dcp->backlight_work, iomfb_m4_backlight_work); + INIT_DELAYED_WORK(&dcp->idle_cache, iomfb_m4_idle_cache_work); if (!request_mem_region(res->start, resource_size(res), dev_name(dev))) { ret = -EBUSY; goto release; @@ -1248,6 +1348,71 @@ int iomfb_m4_health(struct apple_dcp *owner) return ret; } +/* Any surface ID other than the primary's 1 names a distinct surface object. */ +#define DCP_M4_IDLE_SURFACE_ID 2 + +/* + * Register the idle cache behind the frame on screen: IdleCachingMethod 2, + * then a swap with intent 8 whose sole surface is the cache surface. The + * DCP keeps presenting the frame, steps through idle states 1, 2 and 4 + * (D117), asking for a fence (D578) in state 1 and whenever it refreshes the + * cache (4, 6, 2, 4), and leaves the cache at the next swap. + * The surface stays registered across exits, so later entries resubmit it. + */ +static int dcp_m4_idle_cache_enter(struct iomfb_m4 *dcp) +{ + struct dcp_m4_runtime *r = dcp->runtime; + struct { __le32 id, value; } method = { cpu_to_le32(126), cpu_to_le32(2) }; + __le32 out = 0; + int ret; + + lockdep_assert_held(&dcp->lock); + if (r->idle_unavailable || dcp->powered_off || dcp->display_off || !dcp->active_fb || + r->idle_state || r->idle_fences) + return 0; + ret = dcp_m4_runtime_live(r); + if (ret) + return ret; + ret = dcp_m4_runtime_call_stream(r, 467, 0, false, &method, sizeof(method), + &out, sizeof(out)); + if (ret) + return ret; + /* applyProperty status: a refused method stays refused. */ + if (out) { + dev_warn(dcp->dev, "idle caching refused: %#x\n", le32_to_cpu(out)); + r->idle_unavailable = true; + return 0; + } + /* D578 can arrive before the swap completes. */ + r->idle_registered = true; + /* Like any primary present, this drops upper surfaces; none are in use yet. */ + ret = dcp_m4_runtime_swap_full(r, true, &r->idle_frame, NULL, 0, false, 0, + DCP_M4_SWAP_INTENT_IDLE_CACHE, DCP_M4_IDLE_SURFACE_ID); + if (!ret) + r->idle_entries++; + return ret; +} + +/* Called with dcp->lock after a swap: enter the cache once no flip follows. */ +static void iomfb_m4_idle_cache_kick(struct iomfb_m4 *dcp) +{ + unsigned int ms = READ_ONCE(idle_cache_ms); + + if (ms && !READ_ONCE(dcp->owner->quiescing)) + mod_delayed_work(dcp->runtime->wq, &dcp->idle_cache, msecs_to_jiffies(ms)); +} + +static void iomfb_m4_idle_cache_work(struct work_struct *work) +{ + struct iomfb_m4 *dcp = container_of(to_delayed_work(work), struct iomfb_m4, idle_cache); + + mutex_lock(&dcp->lock); + /* An RPC failure has already failed the session. */ + if (!READ_ONCE(dcp->owner->quiescing) && dcp->runtime && READ_ONCE(idle_cache_ms)) + dcp_m4_idle_cache_enter(dcp); + mutex_unlock(&dcp->lock); +} + /* dcp->lock is the single owner of swaps, active descriptors and brightness. */ static int iomfb_m4_swap(struct iomfb_m4 *dcp, const struct dcp_m4_frame *frame) { @@ -1285,8 +1450,22 @@ int iomfb_m4_present(struct apple_dcp *owner, struct drm_framebuffer *fb, return ret; mutex_lock(&dcp->lock); ret = dcp_m4_runtime_live(dcp->runtime); + if (!ret && dcp->powered_off) + ret = -EAGAIN; if (!ret) ret = iomfb_m4_check_frame(&frame); + /* The first frame after a blank powers the display back up. */ + if (!ret && dcp->display_off) { + ktime_t start = ktime_get(); + + ret = dcp_m4_set_power_state(dcp, true); + if (!ret) { + dcp->display_off = false; + dcp->display_on_us = ktime_us_delta(ktime_get(), start); + dcp->display_on_us_max = max(dcp->display_on_us, + dcp->display_on_us_max); + } + } if (ret) goto out; /* Keep the source before the first work descriptor can become visible. */ @@ -1302,6 +1481,7 @@ int iomfb_m4_present(struct apple_dcp *owner, struct drm_framebuffer *fb, dcp->active_fb = fb; dcp->active_frame = frame; dcp->flips++; + iomfb_m4_idle_cache_kick(dcp); out: mutex_unlock(&dcp->lock); if (retire) @@ -1319,6 +1499,11 @@ int iomfb_m4_blank(struct apple_dcp *owner) return -ENODEV; mutex_lock(&dcp->lock); ret = dcp_m4_runtime_live(dcp->runtime); + /* Already blanked and powered down: nothing to swap or power down */ + if (!ret && dcp->display_off && !dcp->active_frame.dva) { + mutex_unlock(&dcp->lock); + return 0; + } if (!ret) ret = iomfb_m4_swap(dcp, NULL); if (ret) { @@ -1328,6 +1513,21 @@ int iomfb_m4_blank(struct apple_dcp *owner) dcp->active_fb = NULL; memset(&dcp->active_frame, 0, sizeof(dcp->active_frame)); dcp->blanks++; + /* + * Nothing is scanned out until the next frame: power the display + * down as system sleep does. A refusal fails the session, as there. + */ + if (!dcp->display_off) { + ktime_t start = ktime_get(); + + if (!dcp_m4_set_power_state(dcp, false)) { + dcp->display_off = true; + dcp->display_off_cycles++; + dcp->display_off_us = ktime_us_delta(ktime_get(), start); + dcp->display_off_us_max = max(dcp->display_off_us, + dcp->display_off_us_max); + } + } } mutex_unlock(&dcp->lock); if (retire) @@ -1335,6 +1535,92 @@ int iomfb_m4_blank(struct apple_dcp *owner) return ret; } +/* setPowerState (A473), on the out-of-band stream on IOMFB 26.x. */ +struct dcp_m4_power_state_req { + __le64 state; + u8 flags[4]; + u8 result_null; + u8 padding[3]; +} __packed; +static_assert(sizeof(struct dcp_m4_power_state_req) == 16); + +struct dcp_m4_power_state_reply { + __le32 value; + __le32 status; +} __packed; +static_assert(sizeof(struct dcp_m4_power_state_reply) == 8); + +static int dcp_m4_set_power_state(struct iomfb_m4 *dcp, bool on) +{ + struct dcp_m4_power_state_req req = { .state = cpu_to_le64(on) }; + struct dcp_m4_power_state_reply reply = {}; + int ret; + + lockdep_assert_held(&dcp->lock); + ret = dcp_m4_runtime_live(dcp->runtime); + if (!ret) + ret = dcp_m4_runtime_call_stream(dcp->runtime, 473, 1, false, &req, + sizeof(req), &reply, sizeof(reply)); + if (!ret && reply.status) { + dev_err(dcp->dev, "display power %s refused: status %#x\n", + on ? "on" : "off", le32_to_cpu(reply.status)); + ret = -EIO; + dcp_m4_failed(dcp, ret); + } + /* A display powered back up starts outside the idle cache. */ + if (!ret && on) { + dcp->runtime->idle_state = 0; + dcp->runtime->idle_fences = 0; + } + return ret; +} + +/* apple-drm, a consumer of this device, has blanked the panel before this. */ +int iomfb_m4_suspend(struct apple_dcp *owner) +{ + struct iomfb_m4 *dcp = owner->v26_6; + int ret; + + if (!dcp || !dcp->runtime) + return -ENODEV; + mutex_lock(&dcp->lock); + /* Also the startup surface, shown before the first frame */ + if (dcp->active_fb || dcp->active_frame.dva) { + dev_warn(dcp->dev, "panel still scanning out: refusing system sleep\n"); + ret = -EBUSY; + } else if (dcp->display_off) { + /* Already powered down by the blank; the next frame powers it up. */ + ret = 0; + } else { + ret = dcp_m4_set_power_state(dcp, false); + if (!ret) + dcp->powered_off = true; + } + mutex_unlock(&dcp->lock); + return ret; +} + +int iomfb_m4_resume(struct apple_dcp *owner) +{ + struct iomfb_m4 *dcp = owner->v26_6; + int ret; + + if (!dcp || !dcp->runtime) + return -ENODEV; + mutex_lock(&dcp->lock); + if (dcp->powered_off) { + ret = dcp_m4_set_power_state(dcp, true); + if (!ret) + dcp->powered_off = false; + } else { + ret = dcp_m4_runtime_health(dcp->runtime); + if (ret) + dcp_m4_failed(dcp, ret); + } + mutex_unlock(&dcp->lock); + return ret; +} + static void iomfb_m4_backlight_work(struct work_struct *work) { struct iomfb_m4 *dcp = container_of(work, struct iomfb_m4, backlight_work); @@ -1347,10 +1633,12 @@ static void iomfb_m4_backlight_work(struct work_struct *work) ret = dcp_m4_runtime_live(dcp->runtime); if (!ret) ret = iomfb_m4_swap(dcp, &dcp->active_frame); - if (ret) + if (ret) { dcp_m4_failed(dcp, ret); - else + } else { dcp->brightness_swaps++; + iomfb_m4_idle_cache_kick(dcp); + } out: mutex_unlock(&dcp->lock); } @@ -1386,6 +1674,8 @@ void iomfb_m4_quiesce(struct apple_dcp *owner) cancel_work_sync(&dcp->backlight_work); if (dcp->runtime) flush_workqueue(dcp->runtime->wq); + /* After the flush, as drained callbacks can queue it. */ + cancel_delayed_work_sync(&dcp->idle_cache); } static int iomfb_m4_status_show(struct seq_file *s, void *unused) @@ -1411,6 +1701,14 @@ static int iomfb_m4_status_show(struct seq_file *s, void *unused) seq_printf(s, "flips=%llu blanks=%llu backlight_swaps=%llu active=%u uncertain=%u\n", dcp->flips, dcp->blanks, dcp->brightness_swaps, !!dcp->active_fb, !!dcp->uncertain_fb); + seq_printf(s, "display_off=%u off_cycles=%llu off_us=%lld/%lld on_us=%lld/%lld\n", + dcp->display_off, dcp->display_off_cycles, + dcp->display_off_us, dcp->display_off_us_max, + dcp->display_on_us, dcp->display_on_us_max); + if (r) + seq_printf(s, "idle_cache=%u state=%u fences=%u registered=%u entries=%llu\n", + !r->idle_unavailable && READ_ONCE(idle_cache_ms), r->idle_state, + r->idle_fences, r->idle_registered, r->idle_entries); mutex_unlock(&dcp->lock); return 0; } diff --git a/drivers/gpu/drm/apple/iomfb_m4.h b/drivers/gpu/drm/apple/iomfb_m4.h index 29296e527f1d4..f89ddcf0844e8 100644 --- a/drivers/gpu/drm/apple/iomfb_m4.h +++ b/drivers/gpu/drm/apple/iomfb_m4.h @@ -27,6 +27,8 @@ int iomfb_m4_error(struct apple_dcp *dcp); int iomfb_m4_present(struct apple_dcp *dcp, struct drm_framebuffer *fb, dma_addr_t dva, size_t mapped); int iomfb_m4_blank(struct apple_dcp *dcp); +int iomfb_m4_suspend(struct apple_dcp *dcp); +int iomfb_m4_resume(struct apple_dcp *dcp); void iomfb_m4_configure_backlight(struct apple_dcp *dcp); void iomfb_m4_quiesce(struct apple_dcp *dcp); void iomfb_m4_debugfs_init(struct apple_dcp *dcp, struct dentry *root); @@ -50,6 +52,8 @@ static inline int iomfb_m4_present(struct apple_dcp *dcp, struct drm_framebuffer return -ENODEV; } static inline int iomfb_m4_blank(struct apple_dcp *dcp) { return -ENODEV; } +static inline int iomfb_m4_suspend(struct apple_dcp *dcp) { return -ENODEV; } +static inline int iomfb_m4_resume(struct apple_dcp *dcp) { return -ENODEV; } static inline void iomfb_m4_configure_backlight(struct apple_dcp *dcp) { } static inline void iomfb_m4_quiesce(struct apple_dcp *dcp) { } static inline void iomfb_m4_debugfs_init(struct apple_dcp *dcp, struct dentry *root) { } diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/m4.py b/tools/testing/selftests/drivers/gpu/apple-dcp/m4.py index f11078c87b087..7fc32185e318d 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/m4.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/m4.py @@ -196,6 +196,20 @@ def protocol(root): assert(!dcp_m4_callback_ack(&packet, &ack)); assert(ack == (0x342U | (stream << 10))); } + /* Out of band: stream 1 for the AP packet, its reply and its callbacks. */ + assert(dcp_m4_decode_word(0x402 | (12ULL << 32), &packet) == -EPROTO); + assert(!dcp_m4_decode_word_oob(0x402 | (64ULL << 16) | (36ULL << 32), &packet)); + assert(packet.stream == 1 && packet.offset == 64 && !packet.reply); + assert(dcp_m4_packet_offset(&packet, false) == 0x8040); + assert(dcp_m4_packet_offset(&packet, true) == 0x68040); + assert(!dcp_m4_callback_ack(&packet, &ack) && ack == 0x442); + assert(!dcp_m4_nested_offset(&packet, false, 12, &offset) && offset == 128); + assert(!dcp_m4_decode_word_oob(0x442, &packet) && packet.reply && packet.stream == 1); + assert(!dcp_m4_decode_word_oob(2 | (12ULL << 32), &packet) && !packet.stream); + assert(dcp_m4_decode_word_oob(0x802 | (12ULL << 32), &packet) == -EPROTO); + assert(dcp_m4_decode_word_oob(0xc02 | (12ULL << 32), &packet) == -EPROTO); + packet = (struct dcp_m4_packet){.stream = 2, .length = 12}; + assert(dcp_m4_packet_offset(&packet, false) == -EINVAL); puts("PASS READY identity, bounded packet parsing, nested offset and callback routing"); } @@ -502,6 +516,12 @@ def blank(root): #define spin_lock_irqsave(p,f) do { (void)(p); (f)=0; } while(0) #define spin_unlock_irqrestore(p,f) do { (void)(p); (void)(f); } while(0) typedef u64 dma_addr_t; +typedef int64_t s64; +typedef s64 ktime_t; +#define max(x,y) ((x)>(y)?(x):(y)) +static ktime_t clock_ns; +static ktime_t ktime_get(void) { return clock_ns += 1000; } +static s64 ktime_us_delta(ktime_t later,ktime_t earlier) { return (later-earlier)/1000; } struct iomfb_m4; struct apple_dcp { struct iomfb_m4 *v26_6; @@ -524,6 +544,10 @@ def blank(root): struct drm_framebuffer *active_fb,*uncertain_fb; struct dcp_m4_frame active_frame; u64 flips,blanks; + bool powered_off; + bool display_off; + u64 display_off_cycles; + s64 display_off_us,display_off_us_max,display_on_us,display_on_us_max; }; static void mutex_lock(int *lock) { assert(!*lock); *lock=1; } static void mutex_unlock(int *lock) { assert(*lock==1); *lock=0; } @@ -535,6 +559,11 @@ def blank(root): static void drm_framebuffer_get(struct drm_framebuffer *fb) { fb->refs++; } static void drm_framebuffer_put(struct drm_framebuffer *fb) { assert(fb->refs); fb->refs--; } static void dcp_m4_failed(struct iomfb_m4 *dcp,int error) { if(!dcp->error) dcp->error=error; } +static unsigned cache_kicks; +static void iomfb_m4_idle_cache_kick(struct iomfb_m4 *dcp) { assert(dcp->lock==1); cache_kicks++; } +static unsigned power_offs,power_ons; +static int dcp_m4_set_power_state(struct iomfb_m4 *dcp,bool on) +{ assert(dcp->lock==1); if(on) power_ons++; else power_offs++; return 0; } static int dcp_m4_runtime_live(struct dcp_m4_runtime *r) { return r->dcp->error; } static int iomfb_m4_check_frame(const struct dcp_m4_frame *f) { return dcp_m4_frame_check(f); } @@ -579,13 +608,26 @@ def blank(root): assert(fb.refs==2 && dcp.active_fb==&fb && !dcp.uncertain_fb); assert(!dcp_backlight_dpms(&owner,false)); assert(deferred_kicks==kicks_before+1 && owner.backlight.state.actual==120); + unsigned offs_before=power_offs,ons_before=power_ons; /* No queued backlight worker has run: blank itself must carry zero. */ assert(!iomfb_m4_blank(&owner)); assert(carried_brightness && carried_nits==0 && owner.backlight.state.actual==0); assert(fb.refs==1 && !dcp.active_fb && !dcp.active_frame.dva && dcp.blanks==1); + /* The blank powered the display down, once. */ + assert(dcp.display_off && dcp.display_off_cycles==1 && power_offs==offs_before+1); + /* Powered down for system sleep: refused, no reference, session intact. */ + dcp.powered_off=true; + assert(iomfb_m4_present(&owner,&fb,0x10000100000ULL,0x2000000)==-EAGAIN); + assert(fb.refs==1 && !dcp.active_fb && !dcp.error); + assert(dcp.display_off && power_ons==ons_before); + dcp.powered_off=false; assert(!dcp_backlight_dpms(&owner,true)); assert(deferred_kicks==kicks_before+2); /* This second kick is still deferred. */ assert(!iomfb_m4_present(&owner,&fb,0x10000100000ULL,0x2000000)); + /* The first frame after the blank powered it back up. */ + assert(!dcp.display_off && power_ons==ons_before+1); + /* Every shown frame restarts the idle-cache countdown. */ + assert(cache_kicks==2*order+2); assert(carried_brightness && carried_nits==120 && owner.backlight.state.actual==120); assert(fb.refs==2 && dcp.active_fb==&fb); failure=1; @@ -597,6 +639,8 @@ def blank(root): } puts("PASS DPMS-off blank carries zero before delayed work and completes only after both exact receipts"); puts("PASS actual present/blank retain active+uncertain DMA framebuffer references across a missing receipt"); + puts("PASS a present while the display is powered off for sleep is refused without a reference"); + puts("PASS a blank powers the display down once, and the next frame powers it back up"); return 0; } '''