diff mbox

drm: Aggressively disable vblanks

Message ID 372cb262d2d49bd796fa6636d04d431b50a14f69.1292869603.git.luto@mit.edu (mailing list archive)
State New, archived
Headers show

Commit Message

Andrew Lutomirski Dec. 20, 2010, 7 p.m. UTC
None
diff mbox

Patch

diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c
index 9d3a503..2f107c5 100644
--- a/drivers/gpu/drm/drm_irq.c
+++ b/drivers/gpu/drm/drm_irq.c
@@ -77,45 +77,59 @@  int drm_irq_by_busid(struct drm_device *dev, void *data,
 	return 0;
 }
 
-static void vblank_disable_fn(unsigned long arg)
+/* After VBLANK_CONSEC_THRESHOLD consecutive non-ignored vblank interrupts,
+ * vblanks will be left on. */
+#define VBLANK_CONSEC_THRESHOLD 3
+
+static void __vblank_disable_now(struct drm_device *dev, int crtc, int force)
+{
+	if (!dev->vblank_disable_allowed)
+		return;
+
+	if (atomic_read(&dev->vblank_refcount[crtc]) == 0 && dev->vblank_enabled[crtc] &&
+		(dev->vblank_consecutive[crtc] < VBLANK_CONSEC_THRESHOLD || force))
+	{
+		DRM_DEBUG("disabling vblank on crtc %d\n", crtc);
+		dev->last_vblank[crtc] =
+			dev->driver->get_vblank_counter(dev, crtc);
+		dev->driver->disable_vblank(dev, crtc);
+		dev->vblank_enabled[crtc] = 0;
+		if (force)
+			dev->vblank_consecutive[crtc] = 0;
+	}
+}
+
+static void vblank_disable_now(struct drm_device *dev, int crtc, int force)
 {
-	struct drm_device *dev = (struct drm_device *)arg;
 	unsigned long irqflags;
-	int i;
 
 	if (!dev->vblank_disable_allowed)
 		return;
 
-	for (i = 0; i < dev->num_crtcs; i++) {
-		spin_lock_irqsave(&dev->vbl_lock, irqflags);
-		if (atomic_read(&dev->vblank_refcount[i]) == 0 &&
-		    dev->vblank_enabled[i]) {
-			DRM_DEBUG("disabling vblank on crtc %d\n", i);
-			dev->last_vblank[i] =
-				dev->driver->get_vblank_counter(dev, i);
-			dev->driver->disable_vblank(dev, i);
-			dev->vblank_enabled[i] = 0;
-		}
-		spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
-	}
+	spin_lock_irqsave(&dev->vbl_lock, irqflags);
+	__vblank_disable_now(dev, crtc, force);
+	spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
 }
 
 void drm_vblank_cleanup(struct drm_device *dev)
 {
+	int i;
+
 	/* Bail if the driver didn't call drm_vblank_init() */
 	if (dev->num_crtcs == 0)
 		return;
 
-	del_timer(&dev->vblank_disable_timer);
-
-	vblank_disable_fn((unsigned long)dev);
+	for (i = 0; i < dev->num_crtcs; i++)
+		vblank_disable_now(dev, i, 1);
 
 	kfree(dev->vbl_queue);
 	kfree(dev->_vblank_count);
 	kfree(dev->vblank_refcount);
 	kfree(dev->vblank_enabled);
 	kfree(dev->last_vblank);
+	kfree(dev->last_vblank_enable);
 	kfree(dev->last_vblank_wait);
+	kfree(dev->vblank_consecutive);
 	kfree(dev->vblank_inmodeset);
 
 	dev->num_crtcs = 0;
@@ -126,8 +140,6 @@  int drm_vblank_init(struct drm_device *dev, int num_crtcs)
 {
 	int i, ret = -ENOMEM;
 
-	setup_timer(&dev->vblank_disable_timer, vblank_disable_fn,
-		    (unsigned long)dev);
 	spin_lock_init(&dev->vbl_lock);
 	dev->num_crtcs = num_crtcs;
 
@@ -153,10 +165,18 @@  int drm_vblank_init(struct drm_device *dev, int num_crtcs)
 	if (!dev->last_vblank)
 		goto err;
 
+	dev->last_vblank_enable = kcalloc(num_crtcs, sizeof(u32), GFP_KERNEL);
+	if (!dev->last_vblank)
+		goto err;
+
 	dev->last_vblank_wait = kcalloc(num_crtcs, sizeof(u32), GFP_KERNEL);
 	if (!dev->last_vblank_wait)
 		goto err;
 
+	dev->vblank_consecutive = kcalloc(num_crtcs, sizeof(int), GFP_KERNEL);
+	if (!dev->vblank_consecutive)
+		goto err;
+
 	dev->vblank_inmodeset = kcalloc(num_crtcs, sizeof(int), GFP_KERNEL);
 	if (!dev->vblank_inmodeset)
 		goto err;
@@ -387,10 +407,12 @@  EXPORT_SYMBOL(drm_vblank_count);
  * Only necessary when going from off->on, to account for frames we
  * didn't get an interrupt for.
  *
+ * Returns the number of vblanks missed.
+ *
  * Note: caller must hold dev->vbl_lock since this reads & writes
  * device vblank fields.
  */
-static void drm_update_vblank_count(struct drm_device *dev, int crtc)
+static u32 drm_update_vblank_count(struct drm_device *dev, int crtc)
 {
 	u32 cur_vblank, diff;
 
@@ -414,6 +436,17 @@  static void drm_update_vblank_count(struct drm_device *dev, int crtc)
 		  crtc, diff);
 
 	atomic_add(diff, &dev->_vblank_count[crtc]);
+	return diff;
+}
+
+static void vblank_enabled_consecutively(struct drm_device *dev, int crtc)
+{
+	if (dev->vblank_consecutive[crtc] < VBLANK_CONSEC_THRESHOLD) {
+		dev->vblank_consecutive[crtc]++;
+		if (dev->vblank_consecutive[crtc] == VBLANK_CONSEC_THRESHOLD)
+			DRM_DEBUG("consecutive vblank usage on crtc %d\n", crtc);
+	}
+
 }
 
 /**
@@ -433,7 +466,6 @@  int drm_vblank_get(struct drm_device *dev, int crtc)
 	int ret = 0;
 
 	spin_lock_irqsave(&dev->vbl_lock, irqflags);
-	/* Going from 0->1 means we have to enable interrupts again */
 	if (atomic_add_return(1, &dev->vblank_refcount[crtc]) == 1) {
 		if (!dev->vblank_enabled[crtc]) {
 			ret = dev->driver->enable_vblank(dev, crtc);
@@ -442,7 +474,12 @@  int drm_vblank_get(struct drm_device *dev, int crtc)
 				atomic_dec(&dev->vblank_refcount[crtc]);
 			else {
 				dev->vblank_enabled[crtc] = 1;
-				drm_update_vblank_count(dev, crtc);
+				if (drm_update_vblank_count(dev, crtc) == 0)
+					vblank_enabled_consecutively(dev, crtc);
+				else
+					dev->vblank_consecutive[crtc] = 0;
+				dev->last_vblank_enable[crtc] =
+					drm_vblank_count(dev, crtc);
 			}
 		}
 	} else {
@@ -467,11 +504,18 @@  EXPORT_SYMBOL(drm_vblank_get);
  */
 void drm_vblank_put(struct drm_device *dev, int crtc)
 {
+	unsigned long irqflags;
+
 	BUG_ON (atomic_read (&dev->vblank_refcount[crtc]) == 0);
 
-	/* Last user schedules interrupt disable */
-	if (atomic_dec_and_test(&dev->vblank_refcount[crtc]))
-		mod_timer(&dev->vblank_disable_timer, jiffies + 5*DRM_HZ);
+	if (atomic_dec_and_test(&dev->vblank_refcount[crtc])) {
+		spin_lock_irqsave(&dev->vbl_lock, irqflags);
+		if (atomic_read(&dev->_vblank_count[crtc]) !=
+			dev->last_vblank_enable[crtc]
+		    && dev->vblank_consecutive[crtc] < VBLANK_CONSEC_THRESHOLD)
+			__vblank_disable_now(dev, crtc, 0);
+		spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
+	}
 }
 EXPORT_SYMBOL(drm_vblank_put);
 
@@ -780,11 +824,18 @@  void drm_handle_vblank_events(struct drm_device *dev, int crtc)
  */
 void drm_handle_vblank(struct drm_device *dev, int crtc)
 {
+	int vblank_refcount;
 	if (!dev->num_crtcs)
 		return;
 
+	vblank_refcount = atomic_read(&dev->vblank_refcount[crtc]);
 	atomic_inc(&dev->_vblank_count[crtc]);
 	DRM_WAKEUP(&dev->vbl_queue[crtc]);
 	drm_handle_vblank_events(dev, crtc);
+
+	if (vblank_refcount == 0) {
+		/* This interrupt was unnecessary. */
+		vblank_disable_now(dev, crtc, 1);
+	}
 }
 EXPORT_SYMBOL(drm_handle_vblank);
diff --git a/include/drm/drmP.h b/include/drm/drmP.h
index 4c9461a..c15918b 100644
--- a/include/drm/drmP.h
+++ b/include/drm/drmP.h
@@ -997,11 +997,14 @@  struct drm_device {
 	atomic_t *vblank_refcount;      /* number of users of vblank interruptsper crtc */
 	u32 *last_vblank;               /* protected by dev->vbl_lock, used */
 					/* for wraparound handling */
+	u32 *last_vblank_enable;        /* protected by dev->vbl_lock, used */
+					/* for early disable path */
 	int *vblank_enabled;            /* so we don't call enable more than
 					   once per disable */
 	int *vblank_inmodeset;          /* Display driver is setting mode */
 	u32 *last_vblank_wait;		/* Last vblank seqno waited per CRTC */
-	struct timer_list vblank_disable_timer;
+	int *vblank_consecutive;        /* protected by dev->vbl_lock, counts
+					   consecutive interesting vblanks */
 
 	u32 max_vblank_count;           /**< size of vblank counter register */