]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/gpu/drm/i915/i915_irq.c
drm: Rework vblank-wait handling to allow interrupt reduction.
[linux-2.6-omap-h63xx.git] / drivers / gpu / drm / i915 / i915_irq.c
index ae7d3a82a6d1279e726d603a3e739ce4e1e504b5..f8759597233bd366e652f0d43006579de2b8c2ff 100644 (file)
@@ -35,9 +35,8 @@
 
 /** These are the interrupts used by the driver */
 #define I915_INTERRUPT_ENABLE_MASK (I915_USER_INTERRUPT |              \
-                                   I915_DISPLAY_PIPE_A_VBLANK_INTERRUPT | \
-                                   I915_DISPLAY_PIPE_B_VBLANK_INTERRUPT | \
                                    I915_ASLE_INTERRUPT |               \
+                                   I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | \
                                    I915_DISPLAY_PIPE_B_EVENT_INTERRUPT)
 
 void
@@ -60,6 +59,64 @@ i915_disable_irq(drm_i915_private_t *dev_priv, u32 mask)
        }
 }
 
+/**
+ * i915_get_pipe - return the the pipe associated with a given plane
+ * @dev: DRM device
+ * @plane: plane to look for
+ *
+ * The Intel Mesa & 2D drivers call the vblank routines with a plane number
+ * rather than a pipe number, since they may not always be equal.  This routine
+ * maps the given @plane back to a pipe number.
+ */
+static int
+i915_get_pipe(struct drm_device *dev, int plane)
+{
+       drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+       u32 dspcntr;
+
+       dspcntr = plane ? I915_READ(DSPBCNTR) : I915_READ(DSPACNTR);
+
+       return dspcntr & DISPPLANE_SEL_PIPE_MASK ? 1 : 0;
+}
+
+/**
+ * i915_get_plane - return the the plane associated with a given pipe
+ * @dev: DRM device
+ * @pipe: pipe to look for
+ *
+ * The Intel Mesa & 2D drivers call the vblank routines with a plane number
+ * rather than a plane number, since they may not always be equal.  This routine
+ * maps the given @pipe back to a plane number.
+ */
+static int
+i915_get_plane(struct drm_device *dev, int pipe)
+{
+       if (i915_get_pipe(dev, 0) == pipe)
+               return 0;
+       return 1;
+}
+
+/**
+ * i915_pipe_enabled - check if a pipe is enabled
+ * @dev: DRM device
+ * @pipe: pipe to check
+ *
+ * Reading certain registers when the pipe is disabled can hang the chip.
+ * Use this routine to make sure the PLL is running and the pipe is active
+ * before reading such registers if unsure.
+ */
+static int
+i915_pipe_enabled(struct drm_device *dev, int pipe)
+{
+       drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+       unsigned long pipeconf = pipe ? PIPEBCONF : PIPEACONF;
+
+       if (I915_READ(pipeconf) & PIPEACONF_ENABLE)
+               return 1;
+
+       return 0;
+}
+
 /**
  * Emit blits for scheduled buffer swaps.
  *
@@ -71,8 +128,7 @@ static void i915_vblank_tasklet(struct drm_device *dev)
        unsigned long irqflags;
        struct list_head *list, *tmp, hits, *hit;
        int nhits, nrects, slice[2], upper[2], lower[2], i;
-       unsigned counter[2] = { atomic_read(&dev->vbl_received),
-                               atomic_read(&dev->vbl_received2) };
+       unsigned counter[2];
        struct drm_drawable_info *drw;
        drm_i915_sarea_t *sarea_priv = dev_priv->sarea_priv;
        u32 cpp = dev_priv->cpp;
@@ -94,6 +150,9 @@ static void i915_vblank_tasklet(struct drm_device *dev)
                src_pitch >>= 2;
        }
 
+       counter[0] = drm_vblank_count(dev, 0);
+       counter[1] = drm_vblank_count(dev, 1);
+
        DRM_DEBUG("\n");
 
        INIT_LIST_HEAD(&hits);
@@ -106,12 +165,14 @@ static void i915_vblank_tasklet(struct drm_device *dev)
        list_for_each_safe(list, tmp, &dev_priv->vbl_swaps.head) {
                drm_i915_vbl_swap_t *vbl_swap =
                        list_entry(list, drm_i915_vbl_swap_t, head);
+               int pipe = i915_get_pipe(dev, vbl_swap->plane);
 
-               if ((counter[vbl_swap->pipe] - vbl_swap->sequence) > (1<<23))
+               if ((counter[pipe] - vbl_swap->sequence) > (1<<23))
                        continue;
 
                list_del(list);
                dev_priv->swaps_pending--;
+               drm_vblank_put(dev, pipe);
 
                spin_unlock(&dev_priv->swaps_lock);
                spin_lock(&dev->drw_lock);
@@ -204,7 +265,7 @@ static void i915_vblank_tasklet(struct drm_device *dev)
                        drm_i915_vbl_swap_t *swap_hit =
                                list_entry(hit, drm_i915_vbl_swap_t, head);
                        struct drm_clip_rect *rect;
-                       int num_rects, pipe;
+                       int num_rects, plane;
                        unsigned short top, bottom;
 
                        drw = drm_get_drawable_info(dev, swap_hit->drw_id);
@@ -213,9 +274,9 @@ static void i915_vblank_tasklet(struct drm_device *dev)
                                continue;
 
                        rect = drw->rects;
-                       pipe = swap_hit->pipe;
-                       top = upper[pipe];
-                       bottom = lower[pipe];
+                       plane = swap_hit->plane;
+                       top = upper[plane];
+                       bottom = lower[plane];
 
                        for (num_rects = drw->num_rects; num_rects--; rect++) {
                                int y1 = max(rect->y1, top);
@@ -252,22 +313,54 @@ static void i915_vblank_tasklet(struct drm_device *dev)
        }
 }
 
+u32 i915_get_vblank_counter(struct drm_device *dev, int plane)
+{
+       drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+       unsigned long high_frame;
+       unsigned long low_frame;
+       u32 high1, high2, low, count;
+       int pipe;
+
+       pipe = i915_get_pipe(dev, plane);
+       high_frame = pipe ? PIPEBFRAMEHIGH : PIPEAFRAMEHIGH;
+       low_frame = pipe ? PIPEBFRAMEPIXEL : PIPEAFRAMEPIXEL;
+
+       if (!i915_pipe_enabled(dev, pipe)) {
+               DRM_ERROR("trying to get vblank count for disabled pipe %d\n", pipe);
+               return 0;
+       }
+
+       /*
+        * High & low register fields aren't synchronized, so make sure
+        * we get a low value that's stable across two reads of the high
+        * register.
+        */
+       do {
+               high1 = ((I915_READ(high_frame) & PIPE_FRAME_HIGH_MASK) >>
+                        PIPE_FRAME_HIGH_SHIFT);
+               low =  ((I915_READ(low_frame) & PIPE_FRAME_LOW_MASK) >>
+                       PIPE_FRAME_LOW_SHIFT);
+               high2 = ((I915_READ(high_frame) & PIPE_FRAME_HIGH_MASK) >>
+                        PIPE_FRAME_HIGH_SHIFT);
+       } while (high1 != high2);
+
+       count = (high1 << 8) | low;
+
+       return count;
+}
+
 irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
 {
        struct drm_device *dev = (struct drm_device *) arg;
        drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
-       u32 pipea_stats, pipeb_stats;
        u32 iir;
-
-       pipea_stats = I915_READ(PIPEASTAT);
-       pipeb_stats = I915_READ(PIPEBSTAT);
+       u32 pipea_stats, pipeb_stats;
+       int vblank = 0;
 
        if (dev->pdev->msi_enabled)
                I915_WRITE(IMR, ~0);
        iir = I915_READ(IIR);
 
-       DRM_DEBUG("iir=%08x\n", iir);
-
        if (iir == 0) {
                if (dev->pdev->msi_enabled) {
                        I915_WRITE(IMR, dev_priv->irq_mask_reg);
@@ -276,48 +369,56 @@ irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
                return IRQ_NONE;
        }
 
-       I915_WRITE(PIPEASTAT, pipea_stats);
-       I915_WRITE(PIPEBSTAT, pipeb_stats);
-
-       I915_WRITE(IIR, iir);
-       if (dev->pdev->msi_enabled)
-               I915_WRITE(IMR, dev_priv->irq_mask_reg);
-       (void) I915_READ(IIR); /* Flush posted writes */
-
-       dev_priv->sarea_priv->last_dispatch = READ_BREADCRUMB(dev_priv);
-
-       if (iir & I915_USER_INTERRUPT)
-               DRM_WAKEUP(&dev_priv->irq_queue);
-
-       if (iir & (I915_DISPLAY_PIPE_A_VBLANK_INTERRUPT |
-                  I915_DISPLAY_PIPE_B_VBLANK_INTERRUPT)) {
-               int vblank_pipe = dev_priv->vblank_pipe;
-
-               if ((vblank_pipe &
-                    (DRM_I915_VBLANK_PIPE_A | DRM_I915_VBLANK_PIPE_B))
-                   == (DRM_I915_VBLANK_PIPE_A | DRM_I915_VBLANK_PIPE_B)) {
-                       if (iir & I915_DISPLAY_PIPE_A_VBLANK_INTERRUPT)
-                               atomic_inc(&dev->vbl_received);
-                       if (iir & I915_DISPLAY_PIPE_B_VBLANK_INTERRUPT)
-                               atomic_inc(&dev->vbl_received2);
-               } else if (((iir & I915_DISPLAY_PIPE_A_VBLANK_INTERRUPT) &&
-                           (vblank_pipe & DRM_I915_VBLANK_PIPE_A)) ||
-                          ((iir & I915_DISPLAY_PIPE_B_VBLANK_INTERRUPT) &&
-                           (vblank_pipe & DRM_I915_VBLANK_PIPE_B)))
-                       atomic_inc(&dev->vbl_received);
+       /*
+        * Clear the PIPE(A|B)STAT regs before the IIR otherwise
+        * we may get extra interrupts.
+        */
+       if (iir & I915_DISPLAY_PIPE_A_EVENT_INTERRUPT) {
+               pipea_stats = I915_READ(PIPEASTAT);
+               if (!(dev_priv->vblank_pipe & DRM_I915_VBLANK_PIPE_A))
+                       pipea_stats &= ~(PIPE_START_VBLANK_INTERRUPT_ENABLE |
+                                        PIPE_VBLANK_INTERRUPT_ENABLE);
+               else if (pipea_stats & (PIPE_START_VBLANK_INTERRUPT_STATUS|
+                                       PIPE_VBLANK_INTERRUPT_STATUS)) {
+                       vblank++;
+                       drm_handle_vblank(dev, i915_get_plane(dev, 0));
+               }
 
-               DRM_WAKEUP(&dev->vbl_queue);
-               drm_vbl_send_signals(dev);
+               I915_WRITE(PIPEASTAT, pipea_stats);
+       }
+       if (iir & I915_DISPLAY_PIPE_B_EVENT_INTERRUPT) {
+               pipeb_stats = I915_READ(PIPEBSTAT);
+               /* Ack the event */
+               I915_WRITE(PIPEBSTAT, pipeb_stats);
+
+               /* The vblank interrupt gets enabled even if we didn't ask for
+                  it, so make sure it's shut down again */
+               if (!(dev_priv->vblank_pipe & DRM_I915_VBLANK_PIPE_B))
+                       pipeb_stats &= ~(PIPE_START_VBLANK_INTERRUPT_ENABLE |
+                                        PIPE_VBLANK_INTERRUPT_ENABLE);
+               else if (pipeb_stats & (PIPE_START_VBLANK_INTERRUPT_STATUS|
+                                       PIPE_VBLANK_INTERRUPT_STATUS)) {
+                       vblank++;
+                       drm_handle_vblank(dev, i915_get_plane(dev, 1));
+               }
 
-               if (dev_priv->swaps_pending > 0)
-                       drm_locked_tasklet(dev, i915_vblank_tasklet);
+               if (pipeb_stats & I915_LEGACY_BLC_EVENT_STATUS)
+                       opregion_asle_intr(dev);
+               I915_WRITE(PIPEBSTAT, pipeb_stats);
        }
 
        if (iir & I915_ASLE_INTERRUPT)
                opregion_asle_intr(dev);
 
-       if (iir & I915_DISPLAY_PIPE_B_EVENT_INTERRUPT)
-               opregion_asle_intr(dev);
+       dev_priv->sarea_priv->last_dispatch = READ_BREADCRUMB(dev_priv);
+
+       if (dev->pdev->msi_enabled)
+               I915_WRITE(IMR, dev_priv->irq_mask_reg);
+       I915_WRITE(IIR, iir);
+       (void) I915_READ(IIR);
+
+       if (vblank && dev_priv->swaps_pending > 0)
+               drm_locked_tasklet(dev, i915_vblank_tasklet);
 
        return IRQ_HANDLED;
 }
@@ -358,7 +459,7 @@ static void i915_user_irq_get(struct drm_device *dev)
        spin_unlock(&dev_priv->user_irq_lock);
 }
 
-static void i915_user_irq_put(struct drm_device *dev)
+void i915_user_irq_put(struct drm_device *dev)
 {
        drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
 
@@ -395,41 +496,10 @@ static int i915_wait_irq(struct drm_device * dev, int irq_nr)
        }
 
        dev_priv->sarea_priv->last_dispatch = READ_BREADCRUMB(dev_priv);
-       return ret;
-}
-
-static int i915_driver_vblank_do_wait(struct drm_device *dev, unsigned int *sequence,
-                                     atomic_t *counter)
-{
-       drm_i915_private_t *dev_priv = dev->dev_private;
-       unsigned int cur_vblank;
-       int ret = 0;
-
-       if (!dev_priv) {
-               DRM_ERROR("called with no initialization\n");
-               return -EINVAL;
-       }
-
-       DRM_WAIT_ON(ret, dev->vbl_queue, 3 * DRM_HZ,
-                   (((cur_vblank = atomic_read(counter))
-                       - *sequence) <= (1<<23)));
-
-       *sequence = cur_vblank;
 
        return ret;
 }
 
-
-int i915_driver_vblank_wait(struct drm_device *dev, unsigned int *sequence)
-{
-       return i915_driver_vblank_do_wait(dev, sequence, &dev->vbl_received);
-}
-
-int i915_driver_vblank_wait2(struct drm_device *dev, unsigned int *sequence)
-{
-       return i915_driver_vblank_do_wait(dev, sequence, &dev->vbl_received2);
-}
-
 /* Needs the lock as it touches the ring.
  */
 int i915_irq_emit(struct drm_device *dev, void *data,
@@ -472,40 +542,88 @@ int i915_irq_wait(struct drm_device *dev, void *data,
        return i915_wait_irq(dev, irqwait->irq_seq);
 }
 
+int i915_enable_vblank(struct drm_device *dev, int plane)
+{
+       drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+       int pipe = i915_get_pipe(dev, plane);
+       u32     pipestat_reg = 0;
+       u32     pipestat;
+
+       switch (pipe) {
+       case 0:
+               pipestat_reg = PIPEASTAT;
+               i915_enable_irq(dev_priv, I915_DISPLAY_PIPE_A_EVENT_INTERRUPT);
+               break;
+       case 1:
+               pipestat_reg = PIPEBSTAT;
+               i915_enable_irq(dev_priv, I915_DISPLAY_PIPE_B_EVENT_INTERRUPT);
+               break;
+       default:
+               DRM_ERROR("tried to enable vblank on non-existent pipe %d\n",
+                         pipe);
+               break;
+       }
+
+       if (pipestat_reg) {
+               pipestat = I915_READ(pipestat_reg);
+               if (IS_I965G(dev))
+                       pipestat |= PIPE_START_VBLANK_INTERRUPT_ENABLE;
+               else
+                       pipestat |= PIPE_VBLANK_INTERRUPT_ENABLE;
+               /* Clear any stale interrupt status */
+               pipestat |= (PIPE_START_VBLANK_INTERRUPT_STATUS |
+                            PIPE_VBLANK_INTERRUPT_STATUS);
+               I915_WRITE(pipestat_reg, pipestat);
+       }
+
+       return 0;
+}
+
+void i915_disable_vblank(struct drm_device *dev, int plane)
+{
+       drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+       int pipe = i915_get_pipe(dev, plane);
+       u32     pipestat_reg = 0;
+       u32     pipestat;
+
+       switch (pipe) {
+       case 0:
+               pipestat_reg = PIPEASTAT;
+               i915_disable_irq(dev_priv, I915_DISPLAY_PIPE_A_EVENT_INTERRUPT);
+               break;
+       case 1:
+               pipestat_reg = PIPEBSTAT;
+               i915_disable_irq(dev_priv, I915_DISPLAY_PIPE_B_EVENT_INTERRUPT);
+               break;
+       default:
+               DRM_ERROR("tried to disable vblank on non-existent pipe %d\n",
+                         pipe);
+               break;
+       }
+
+       if (pipestat_reg) {
+               pipestat = I915_READ(pipestat_reg);
+               pipestat &= ~(PIPE_START_VBLANK_INTERRUPT_ENABLE |
+                             PIPE_VBLANK_INTERRUPT_ENABLE);
+               /* Clear any stale interrupt status */
+               pipestat |= (PIPE_START_VBLANK_INTERRUPT_STATUS |
+                            PIPE_VBLANK_INTERRUPT_STATUS);
+               I915_WRITE(pipestat_reg, pipestat);
+       }
+}
+
 /* Set the vblank monitor pipe
  */
 int i915_vblank_pipe_set(struct drm_device *dev, void *data,
                         struct drm_file *file_priv)
 {
        drm_i915_private_t *dev_priv = dev->dev_private;
-       drm_i915_vblank_pipe_t *pipe = data;
-       u32 enable_mask = 0, disable_mask = 0;
 
        if (!dev_priv) {
                DRM_ERROR("called with no initialization\n");
                return -EINVAL;
        }
 
-       if (pipe->pipe & ~(DRM_I915_VBLANK_PIPE_A|DRM_I915_VBLANK_PIPE_B)) {
-               DRM_ERROR("called with invalid pipe 0x%x\n", pipe->pipe);
-               return -EINVAL;
-       }
-
-       if (pipe->pipe & DRM_I915_VBLANK_PIPE_A)
-               enable_mask |= I915_DISPLAY_PIPE_A_VBLANK_INTERRUPT;
-       else
-               disable_mask |= I915_DISPLAY_PIPE_A_VBLANK_INTERRUPT;
-
-       if (pipe->pipe & DRM_I915_VBLANK_PIPE_B)
-               enable_mask |= I915_DISPLAY_PIPE_B_VBLANK_INTERRUPT;
-       else
-               disable_mask |= I915_DISPLAY_PIPE_B_VBLANK_INTERRUPT;
-
-       i915_enable_irq(dev_priv, enable_mask);
-       i915_disable_irq(dev_priv, disable_mask);
-
-       dev_priv->vblank_pipe = pipe->pipe;
-
        return 0;
 }
 
@@ -514,19 +632,13 @@ int i915_vblank_pipe_get(struct drm_device *dev, void *data,
 {
        drm_i915_private_t *dev_priv = dev->dev_private;
        drm_i915_vblank_pipe_t *pipe = data;
-       u16 flag;
 
        if (!dev_priv) {
                DRM_ERROR("called with no initialization\n");
                return -EINVAL;
        }
 
-       flag = I915_READ(IMR);
-       pipe->pipe = 0;
-       if (flag & I915_DISPLAY_PIPE_A_VBLANK_INTERRUPT)
-               pipe->pipe |= DRM_I915_VBLANK_PIPE_A;
-       if (flag & I915_DISPLAY_PIPE_B_VBLANK_INTERRUPT)
-               pipe->pipe |= DRM_I915_VBLANK_PIPE_B;
+       pipe->pipe = DRM_I915_VBLANK_PIPE_A | DRM_I915_VBLANK_PIPE_B;
 
        return 0;
 }
@@ -540,9 +652,10 @@ int i915_vblank_swap(struct drm_device *dev, void *data,
        drm_i915_private_t *dev_priv = dev->dev_private;
        drm_i915_vblank_swap_t *swap = data;
        drm_i915_vbl_swap_t *vbl_swap;
-       unsigned int pipe, seqtype, curseq;
+       unsigned int pipe, seqtype, curseq, plane;
        unsigned long irqflags;
        struct list_head *list;
+       int ret;
 
        if (!dev_priv) {
                DRM_ERROR("%s called with no initialization\n", __func__);
@@ -560,7 +673,8 @@ int i915_vblank_swap(struct drm_device *dev, void *data,
                return -EINVAL;
        }
 
-       pipe = (swap->seqtype & _DRM_VBLANK_SECONDARY) ? 1 : 0;
+       plane = (swap->seqtype & _DRM_VBLANK_SECONDARY) ? 1 : 0;
+       pipe = i915_get_pipe(dev, plane);
 
        seqtype = swap->seqtype & (_DRM_VBLANK_RELATIVE | _DRM_VBLANK_ABSOLUTE);
 
@@ -579,7 +693,14 @@ int i915_vblank_swap(struct drm_device *dev, void *data,
 
        spin_unlock_irqrestore(&dev->drw_lock, irqflags);
 
-       curseq = atomic_read(pipe ? &dev->vbl_received2 : &dev->vbl_received);
+       /*
+        * We take the ref here and put it when the swap actually completes
+        * in the tasklet.
+        */
+       ret = drm_vblank_get(dev, pipe);
+       if (ret)
+               return ret;
+       curseq = drm_vblank_count(dev, pipe);
 
        if (seqtype == _DRM_VBLANK_RELATIVE)
                swap->sequence += curseq;
@@ -589,6 +710,7 @@ int i915_vblank_swap(struct drm_device *dev, void *data,
                        swap->sequence = curseq + 1;
                } else {
                        DRM_DEBUG("Missed target sequence\n");
+                       drm_vblank_put(dev, pipe);
                        return -EINVAL;
                }
        }
@@ -599,7 +721,7 @@ int i915_vblank_swap(struct drm_device *dev, void *data,
                vbl_swap = list_entry(list, drm_i915_vbl_swap_t, head);
 
                if (vbl_swap->drw_id == swap->drawable &&
-                   vbl_swap->pipe == pipe &&
+                   vbl_swap->plane == plane &&
                    vbl_swap->sequence == swap->sequence) {
                        spin_unlock_irqrestore(&dev_priv->swaps_lock, irqflags);
                        DRM_DEBUG("Already scheduled\n");
@@ -611,6 +733,7 @@ int i915_vblank_swap(struct drm_device *dev, void *data,
 
        if (dev_priv->swaps_pending >= 100) {
                DRM_DEBUG("Too many swaps queued\n");
+               drm_vblank_put(dev, pipe);
                return -EBUSY;
        }
 
@@ -618,13 +741,14 @@ int i915_vblank_swap(struct drm_device *dev, void *data,
 
        if (!vbl_swap) {
                DRM_ERROR("Failed to allocate memory to queue swap\n");
+               drm_vblank_put(dev, pipe);
                return -ENOMEM;
        }
 
        DRM_DEBUG("\n");
 
        vbl_swap->drw_id = swap->drawable;
-       vbl_swap->pipe = pipe;
+       vbl_swap->plane = plane;
        vbl_swap->sequence = swap->sequence;
 
        spin_lock_irqsave(&dev_priv->swaps_lock, irqflags);
@@ -643,28 +767,32 @@ void i915_driver_irq_preinstall(struct drm_device * dev)
 {
        drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
 
-       I915_WRITE(HWSTAM, 0xfffe);
-       I915_WRITE(IMR, 0x0);
+       I915_WRITE(HWSTAM, 0xeffe);
+       I915_WRITE(IMR, 0xffffffff);
        I915_WRITE(IER, 0x0);
 }
 
-void i915_driver_irq_postinstall(struct drm_device * dev)
+int i915_driver_irq_postinstall(struct drm_device *dev)
 {
        drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+       int ret, num_pipes = 2;
 
        spin_lock_init(&dev_priv->swaps_lock);
        INIT_LIST_HEAD(&dev_priv->vbl_swaps.head);
        dev_priv->swaps_pending = 0;
 
-       if (!dev_priv->vblank_pipe)
-               dev_priv->vblank_pipe = DRM_I915_VBLANK_PIPE_A;
-
        /* Set initial unmasked IRQs to just the selected vblank pipes. */
        dev_priv->irq_mask_reg = ~0;
-       if (dev_priv->vblank_pipe & DRM_I915_VBLANK_PIPE_A)
-               dev_priv->irq_mask_reg &= ~I915_DISPLAY_PIPE_A_VBLANK_INTERRUPT;
-       if (dev_priv->vblank_pipe & DRM_I915_VBLANK_PIPE_B)
-               dev_priv->irq_mask_reg &= ~I915_DISPLAY_PIPE_B_VBLANK_INTERRUPT;
+
+       ret = drm_vblank_init(dev, num_pipes);
+       if (ret)
+               return ret;
+
+       dev_priv->vblank_pipe = DRM_I915_VBLANK_PIPE_A | DRM_I915_VBLANK_PIPE_B;
+       dev_priv->irq_mask_reg &= ~I915_DISPLAY_PIPE_A_VBLANK_INTERRUPT;
+       dev_priv->irq_mask_reg &= ~I915_DISPLAY_PIPE_B_VBLANK_INTERRUPT;
+
+       dev->max_vblank_count = 0xffffff; /* only 24 bits of frame count */
 
        dev_priv->irq_mask_reg &= I915_INTERRUPT_ENABLE_MASK;
 
@@ -673,22 +801,29 @@ void i915_driver_irq_postinstall(struct drm_device * dev)
        (void) I915_READ(IER);
 
        opregion_enable_asle(dev);
-
        DRM_INIT_WAITQUEUE(&dev_priv->irq_queue);
+
+       return 0;
 }
 
 void i915_driver_irq_uninstall(struct drm_device * dev)
 {
        drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
-       u16 temp;
+       u32 temp;
 
        if (!dev_priv)
                return;
 
-       I915_WRITE(HWSTAM, 0xffff);
-       I915_WRITE(IMR, 0xffff);
+       dev_priv->vblank_pipe = 0;
+
+       I915_WRITE(HWSTAM, 0xffffffff);
+       I915_WRITE(IMR, 0xffffffff);
        I915_WRITE(IER, 0x0);
 
+       temp = I915_READ(PIPEASTAT);
+       I915_WRITE(PIPEASTAT, temp);
+       temp = I915_READ(PIPEBSTAT);
+       I915_WRITE(PIPEBSTAT, temp);
        temp = I915_READ(IIR);
        I915_WRITE(IIR, temp);
 }