From: Leo Li <[email protected]>

Some drivers need to perform blocking 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 via a mutex,
for example.

To support this, introduce a new drm_crtc_vblank_prepare() helper that
calls back into the driver -- if implemented -- for the driver to do
such preparation work.

In DRM core, call this helper before drm_vblank_get(). Drivers can
choose to call this if they implement the callback in the future.

Signed-off-by: Leo Li <[email protected]>
---
 drivers/gpu/drm/drm_atomic_helper.c |  8 ++++
 drivers/gpu/drm/drm_fb_helper.c     |  4 ++
 drivers/gpu/drm/drm_plane.c         |  4 ++
 drivers/gpu/drm/drm_vblank.c        | 69 +++++++++++++++++++++++++++++
 drivers/gpu/drm/drm_vblank_work.c   |  8 ++++
 include/drm/drm_crtc.h              | 27 +++++++++++
 include/drm/drm_vblank.h            |  1 +
 7 files changed, 121 insertions(+)

diff --git a/drivers/gpu/drm/drm_atomic_helper.c 
b/drivers/gpu/drm/drm_atomic_helper.c
index ef56b474acf59..e52dd41f83117 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -1264,6 +1264,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
@@ -1815,6 +1819,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_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index 11a5b60cb9ce4..7400942fd7d1d 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -1103,6 +1103,10 @@ int drm_fb_helper_ioctl(struct fb_info *info, unsigned 
int cmd,
                 * enabled, otherwise just don't do anythintg,
                 * not even report an error.
                 */
+               ret = drm_crtc_vblank_prepare(crtc);
+               if (ret)
+                       break;
+
                ret = drm_crtc_vblank_get(crtc);
                if (!ret) {
                        drm_crtc_wait_one_vblank(crtc);
diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
index 38f82391bfda5..f2e40eaa385e6 100644
--- a/drivers/gpu/drm/drm_plane.c
+++ b/drivers/gpu/drm/drm_plane.c
@@ -1421,6 +1421,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 46f59883183d9..4dac3228c021f 100644
--- a/drivers/gpu/drm/drm_vblank.c
+++ b/drivers/gpu/drm/drm_vblank.c
@@ -1194,6 +1194,30 @@ 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 run blocking operations to prepare for enabling
+ * vblank interrupts. This function calls the prepare_enable_vblank callback, 
if
+ * available, to allow drivers to do that.
+ *
+ * The spin-up may call blocking functions, such as mutex_lock(). Therefore,
+ * this must be called from process context, where sleeping is allowed.
+ *
+ * Also see &drm_crtc_funcs.prepare_enable_vblank.
+ *
+ * Returns: Zero on success or a negative error code on failure.
+ */
+int drm_crtc_vblank_prepare(struct drm_crtc *crtc)
+{
+       if (crtc->funcs->prepare_enable_vblank)
+               return crtc->funcs->prepare_enable_vblank(crtc);
+       return 0;
+}
+EXPORT_SYMBOL(drm_crtc_vblank_prepare);
+
 int drm_vblank_get(struct drm_device *dev, unsigned int pipe)
 {
        struct drm_vblank_crtc *vblank = drm_vblank_crtc(dev, pipe);
@@ -1288,12 +1312,22 @@ EXPORT_SYMBOL(drm_crtc_vblank_put);
 void drm_wait_one_vblank(struct drm_device *dev, unsigned int pipe)
 {
        struct drm_vblank_crtc *vblank = drm_vblank_crtc(dev, pipe);
+       struct drm_crtc *crtc = drm_crtc_from_index(dev, pipe);
        int ret;
        u64 last;
 
        if (drm_WARN_ON(dev, pipe >= dev->num_crtcs))
                return;
 
+       crtc = drm_crtc_from_index(dev, pipe);
+       if (crtc) {
+               ret = drm_crtc_vblank_prepare(crtc);
+               if (drm_WARN(dev, ret,
+                            "prepare vblank failed on crtc %i, ret=%i\n",
+                            pipe, ret))
+                       return;
+       }
+
        ret = drm_vblank_get(dev, pipe);
        if (drm_WARN(dev, ret, "vblank not available on crtc %i, ret=%i\n",
                     pipe, ret))
@@ -1485,10 +1519,18 @@ void drm_crtc_vblank_on_config(struct drm_crtc *crtc,
        struct drm_device *dev = crtc->dev;
        unsigned int pipe = drm_crtc_index(crtc);
        struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
+       int ret;
 
        if (drm_WARN_ON(dev, pipe >= dev->num_crtcs))
                return;
 
+       if (crtc) {
+               ret = drm_crtc_vblank_prepare(crtc);
+               drm_WARN_ON(dev, ret);
+               if (ret)
+                       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 +1838,17 @@ 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) {
+                       drm_dbg_core(dev,
+                                    "prepare vblank failed on crtc %i, 
ret=%i\n",
+                                    pipe, ret);
+                       return ret;
+               }
+       }
+
        ret = drm_vblank_get(dev, pipe);
        if (ret) {
                drm_dbg_core(dev,
@@ -2031,6 +2084,14 @@ 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) {
+                       drm_dbg_core(dev,
+                                    "prepare vblank failed on crtc %i, 
ret=%i\n",
+                                    pipe, ret);
+                       return ret;
+               }
+
                ret = drm_crtc_vblank_get(crtc);
                if (ret) {
                        drm_dbg_core(dev,
@@ -2098,6 +2159,14 @@ 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) {
+               drm_dbg_core(dev,
+                            "prepare vblank failed on crtc %i, ret=%i\n",
+                            pipe, ret);
+               return ret;
+       }
+
        ret = drm_crtc_vblank_get(crtc);
        if (ret) {
                drm_dbg_core(dev,
diff --git a/drivers/gpu/drm/drm_vblank_work.c 
b/drivers/gpu/drm/drm_vblank_work.c
index e4e1873f0e1e1..582ee7fd94adf 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 caa56e039da2a..456cf9ba0143a 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -860,6 +860,33 @@ struct drm_crtc_funcs {
         */
        u32 (*get_vblank_counter)(struct drm_crtc *crtc);
 
+       /**
+        * @prepare_enable_vblank:
+        *
+        * An optional callback for preparing to enable vblank interrupts. It
+        * allows drivers to perform blocking operations, and thus is called
+        * without any vblank spinlocks. Consequently, this callback is not
+        * synchronized with the rest of drm_vblank management; drivers are
+        * responsible for ensuring it won't race with drm_vblank and it's other
+        * driver callbacks.
+        *
+        * For example, drivers may use this to spin-up hardware from a low
+        * power state -- which may require blocking operations -- such that
+        * hardware registers are available to read/write. However, the driver
+        * must be careful as to when to reenter low-power state, such that it
+        * won't race with enable_vblank.
+        *
+        * It is called unconditionally, regardless of whether vblank interrupts
+        * are already enabled or not.
+        *
+        * 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 151ab1e85b1b7..5abc367aa4376 100644
--- a/include/drm/drm_vblank.h
+++ b/include/drm/drm_vblank.h
@@ -272,6 +272,7 @@ void drm_vblank_set_event(struct drm_pending_vblank_event 
*e,
                          ktime_t *now);
 bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe);
 bool drm_crtc_handle_vblank(struct drm_crtc *crtc);
+int drm_crtc_vblank_prepare(struct drm_crtc *crtc);
 int drm_crtc_vblank_get(struct drm_crtc *crtc);
 void drm_crtc_vblank_put(struct drm_crtc *crtc);
 void drm_wait_one_vblank(struct drm_device *dev, unsigned int pipe);
-- 
2.51.0

Reply via email to