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); 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); -- 2.52.0
