On 2026-01-09 14:20, [email protected] wrote:
> From: Leo Li <[email protected]>
>
> Some drivers need to perform sleepable operations prior to enabling
> vblank interrupts. A display hardware spin-up from a low-power state
> that requires synchronization with the rest of the driver, for example.
>
> To support this, introduce a DRM-internal drm_crtc_vblank_prepare()
> helper that calls back into the driver -- if implemented -- for DRM to
> do such preparation work before enabling vblank.
>
> v3:
> * Unexport drm_crtc_vblank_prepare() and make it DRM internal
> * Drop warnings in drm core for vblank_prepare(), drivers can do so in
> their implementations
> * Drop unnecessary crtc null checks
> * Check for drm_dev_has_vblank()
> * Rebase on latest drm-misc-next
>
> Signed-off-by: Leo Li <[email protected]>
> ---
> drivers/gpu/drm/drm_atomic_helper.c | 9 ++++++
> drivers/gpu/drm/drm_client_modeset.c | 4 +++
> drivers/gpu/drm/drm_internal.h | 1 +
> drivers/gpu/drm/drm_plane.c | 5 +++
> drivers/gpu/drm/drm_vblank.c | 48 ++++++++++++++++++++++++++++
> drivers/gpu/drm/drm_vblank_helper.c | 5 ++-
> drivers/gpu/drm/drm_vblank_work.c | 8 +++++
> include/drm/drm_crtc.h | 21 ++++++++++++
> include/drm/drm_vblank.h | 1 +
> 9 files changed, 101 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c
> b/drivers/gpu/drm/drm_atomic_helper.c
> index 5840e9cc6f666..2b9fa4aa48a1a 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -48,6 +48,7 @@
>
> #include "drm_crtc_helper_internal.h"
> #include "drm_crtc_internal.h"
> +#include "drm_internal.h"
>
> /**
> * DOC: overview
> @@ -1268,6 +1269,10 @@ crtc_disable(struct drm_device *dev, struct
> drm_atomic_state *state)
> if (!drm_dev_has_vblank(dev))
> continue;
>
> + ret = drm_crtc_vblank_prepare(crtc);
> + if (ret)
> + continue;
> +
> ret = drm_crtc_vblank_get(crtc);
> /*
> * Self-refresh is not a true "disable"; ensure vblank remains
> @@ -1823,6 +1828,10 @@ drm_atomic_helper_wait_for_vblanks(struct drm_device
> *dev,
> if (!new_crtc_state->active)
> continue;
>
> + ret = drm_crtc_vblank_prepare(crtc);
> + if (ret != 0)
> + continue;
> +
> ret = drm_crtc_vblank_get(crtc);
> if (ret != 0)
> continue;
> diff --git a/drivers/gpu/drm/drm_client_modeset.c
> b/drivers/gpu/drm/drm_client_modeset.c
> index fc4caf7da5fcd..6ccbde921dde4 100644
> --- a/drivers/gpu/drm/drm_client_modeset.c
> +++ b/drivers/gpu/drm/drm_client_modeset.c
> @@ -1325,6 +1325,10 @@ int drm_client_modeset_wait_for_vblank(struct
> drm_client_dev *client, unsigned i
> * Only wait for a vblank event if the CRTC is enabled, otherwise
> * just don't do anything, not even report an error.
> */
> + ret = drm_crtc_vblank_prepare(crtc);
> + if (ret)
> + return ret;
> +
> ret = drm_crtc_vblank_get(crtc);
> if (!ret) {
> drm_crtc_wait_one_vblank(crtc);
> diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h
> index f893b1e3a596e..8e3e21d734075 100644
> --- a/drivers/gpu/drm/drm_internal.h
> +++ b/drivers/gpu/drm/drm_internal.h
> @@ -112,6 +112,7 @@ static inline bool drm_vblank_passed(u64 seq, u64 ref)
> }
>
> void drm_vblank_disable_and_save(struct drm_device *dev, unsigned int pipe);
> +int drm_crtc_vblank_prepare(struct drm_crtc *crtc);
> int drm_vblank_get(struct drm_device *dev, unsigned int pipe);
> void drm_vblank_put(struct drm_device *dev, unsigned int pipe);
> u64 drm_vblank_count(struct drm_device *dev, unsigned int pipe);
> diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
> index bed2562bf911b..41681a3d96b15 100644
> --- a/drivers/gpu/drm/drm_plane.c
> +++ b/drivers/gpu/drm/drm_plane.c
> @@ -35,6 +35,7 @@
> #include <drm/drm_vblank.h>
>
> #include "drm_crtc_internal.h"
> +#include "drm_internal.h"
>
> /**
> * DOC: overview
> @@ -1421,6 +1422,10 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
> u32 current_vblank;
> int r;
>
> + r = drm_crtc_vblank_prepare(crtc);
> + if (r)
> + return r;
> +
> r = drm_crtc_vblank_get(crtc);
> if (r)
> return r;
> diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c
> index 42fe11cc139b9..b8a967a4ba7e5 100644
> --- a/drivers/gpu/drm/drm_vblank.c
> +++ b/drivers/gpu/drm/drm_vblank.c
> @@ -1208,6 +1208,32 @@ static int drm_vblank_enable(struct drm_device *dev,
> unsigned int pipe)
> return ret;
> }
>
> +/**
> + * drm_crtc_vblank_prepare - prepare to enable vblank interrupts
> + *
> + * @crtc: which CRTC to prepare
> + *
> + * Some drivers may need to spin-up hardware from a low power state before
> + * enabling vblank interrupts. This function calls the prepare_enable_vblank
> + * callback, if available, to allow drivers to do that.
> + *
> + * This is a DRM-internal function, and is a thin wrapper around a driver
> + * callback. Drivers are expected to sequence their own prepare work
> internally.
> + *
> + * The spin-up may call sleeping functions, such as mutex_lock(). Therefore,
> + * this must be called from process context, where sleeping is allowed.
> + */
> +int drm_crtc_vblank_prepare(struct drm_crtc *crtc)
> +{
> + if (!drm_dev_has_vblank(crtc->dev))
> + return -EINVAL;
> +
> + if (crtc->funcs->prepare_enable_vblank)
> + return crtc->funcs->prepare_enable_vblank(crtc);
> +
> + return 0;
> +}
> +
> int drm_vblank_get(struct drm_device *dev, unsigned int pipe)
> {
> struct drm_vblank_crtc *vblank = drm_vblank_crtc(dev, pipe);
> @@ -1306,6 +1332,10 @@ int drm_crtc_wait_one_vblank(struct drm_crtc *crtc)
> int ret;
> u64 last;
>
> + ret = drm_crtc_vblank_prepare(crtc);
> + if (ret)
> + return ret;
> +
> ret = drm_vblank_get(dev, pipe);
> if (drm_WARN(dev, ret, "vblank not available on crtc %i, ret=%i\n",
> pipe, ret))
> @@ -1489,6 +1519,9 @@ void drm_crtc_vblank_on_config(struct drm_crtc *crtc,
> if (drm_WARN_ON(dev, pipe >= dev->num_crtcs))
> return;
>
> + if (drm_crtc_vblank_prepare(crtc))
> + return;
> +
> spin_lock_irq(&dev->vbl_lock);
> drm_dbg_vbl(dev, "crtc %d, vblank enabled %d, inmodeset %d\n",
> pipe, vblank->enabled, vblank->inmodeset);
> @@ -1796,6 +1829,13 @@ int drm_wait_vblank_ioctl(struct drm_device *dev, void
> *data,
> return 0;
> }
>
> + crtc = drm_crtc_from_index(dev, vblank->pipe);
> + if (crtc) {
> + ret = drm_crtc_vblank_prepare(crtc);
> + if (ret)
> + return ret;
> + }
> +
> ret = drm_vblank_get(dev, pipe);
> if (ret) {
> drm_dbg_core(dev,
> @@ -2031,6 +2071,10 @@ int drm_crtc_get_sequence_ioctl(struct drm_device
> *dev, void *data,
> READ_ONCE(vblank->enabled);
>
> if (!vblank_enabled) {
> + ret = drm_crtc_vblank_prepare(crtc);
> + if (ret)
> + return ret;
> +
> ret = drm_crtc_vblank_get(crtc);
> if (ret) {
> drm_dbg_core(dev,
> @@ -2098,6 +2142,10 @@ int drm_crtc_queue_sequence_ioctl(struct drm_device
> *dev, void *data,
> if (e == NULL)
> return -ENOMEM;
>
> + ret = drm_crtc_vblank_prepare(crtc);
> + if (ret)
> + return ret;
> +
> ret = drm_crtc_vblank_get(crtc);
> if (ret) {
> drm_dbg_core(dev,
> diff --git a/drivers/gpu/drm/drm_vblank_helper.c
> b/drivers/gpu/drm/drm_vblank_helper.c
> index a04a6ba1b0ca0..fc5915acfa7f3 100644
> --- a/drivers/gpu/drm/drm_vblank_helper.c
> +++ b/drivers/gpu/drm/drm_vblank_helper.c
> @@ -8,6 +8,8 @@
> #include <drm/drm_vblank.h>
> #include <drm/drm_vblank_helper.h>
>
> +#include "drm_internal.h"
> +
> /**
> * DOC: overview
> *
> @@ -61,7 +63,8 @@ void drm_crtc_vblank_atomic_flush(struct drm_crtc *crtc,
> crtc_state->event = NULL;
>
> if (event) {
> - if (drm_crtc_vblank_get(crtc) == 0)
> + if (drm_crtc_vblank_prepare(crtc) == 0 &&
> + drm_crtc_vblank_get(crtc) == 0)
> drm_crtc_arm_vblank_event(crtc, event);
> else
> drm_crtc_send_vblank_event(crtc, event);
> diff --git a/drivers/gpu/drm/drm_vblank_work.c
> b/drivers/gpu/drm/drm_vblank_work.c
> index 70f0199251ea0..252f60007781b 100644
> --- a/drivers/gpu/drm/drm_vblank_work.c
> +++ b/drivers/gpu/drm/drm_vblank_work.c
> @@ -113,11 +113,19 @@ int drm_vblank_work_schedule(struct drm_vblank_work
> *work,
> {
> struct drm_vblank_crtc *vblank = work->vblank;
> struct drm_device *dev = vblank->dev;
> + struct drm_crtc *crtc;
> u64 cur_vbl;
> unsigned long irqflags;
> bool passed, inmodeset, rescheduling = false, wake = false;
> int ret = 0;
>
> + crtc = drm_crtc_from_index(dev, vblank->pipe);
> + if (crtc) {
> + ret = drm_crtc_vblank_prepare(crtc);
> + if (ret)
> + return ret;
> + }
> +
> spin_lock_irqsave(&dev->event_lock, irqflags);
> if (work->cancelling)
> goto out;
> diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> index 66278ffeebd68..e5cf232d604c9 100644
> --- a/include/drm/drm_crtc.h
> +++ b/include/drm/drm_crtc.h
> @@ -871,6 +871,27 @@ struct drm_crtc_funcs {
> */
> u32 (*get_vblank_counter)(struct drm_crtc *crtc);
>
> + /**
> + * @prepare_enable_vblank:
> + *
> + * An optional callback to prepare driver for enabling of vblank
> + * interrupts. It allows drivers to perform any blocking operations for
> + * hardware setup that might be needed, and thus is called before any
> + * vblank spinlocks are acquired. It is called unconditionally,
> + * regardless of whether vblank interrupts are already enabled or not.
> + *
> + * Consequently, this callback is not synchronized with the rest of
> + * vblank management. Drivers should not access spinlock protected
> + * states here.
> + *
> + * This callback is optional. If not set, no preparation is performed.
> + *
> + * Returns:
> + *
> + * Zero on success, negative errno on failure.
> + */
> + int (*prepare_enable_vblank)(struct drm_crtc *crtc);
> +
> /**
> * @enable_vblank:
> *
> diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h
> index 2fcef9c0f5b1b..c91384ee2617b 100644
> --- a/include/drm/drm_vblank.h
> +++ b/include/drm/drm_vblank.h
> @@ -301,6 +301,7 @@ void drm_vblank_set_event(struct drm_pending_vblank_event
> *e,
> bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe);
> bool drm_crtc_handle_vblank(struct drm_crtc *crtc);
> int drm_crtc_vblank_get(struct drm_crtc *crtc);
> +int drm_crtc_vblank_prepare_and_get(struct drm_crtc *crtc);
Oops, please ignore this line -- a remenant of a previous idea that I missed
cleaning up.
- Leo
> void drm_crtc_vblank_put(struct drm_crtc *crtc);
> int drm_crtc_wait_one_vblank(struct drm_crtc *crtc);
> void drm_crtc_vblank_off(struct drm_crtc *crtc);