summaryrefslogtreecommitdiffstats
path: root/meta-moblin/packages/linux/linux-moblin-2.6.27-rc6/0011-drm-vblank-rework.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-moblin/packages/linux/linux-moblin-2.6.27-rc6/0011-drm-vblank-rework.patch')
-rw-r--r--meta-moblin/packages/linux/linux-moblin-2.6.27-rc6/0011-drm-vblank-rework.patch1534
1 files changed, 1534 insertions, 0 deletions
diff --git a/meta-moblin/packages/linux/linux-moblin-2.6.27-rc6/0011-drm-vblank-rework.patch b/meta-moblin/packages/linux/linux-moblin-2.6.27-rc6/0011-drm-vblank-rework.patch
new file mode 100644
index 0000000000..6161a71f04
--- /dev/null
+++ b/meta-moblin/packages/linux/linux-moblin-2.6.27-rc6/0011-drm-vblank-rework.patch
@@ -0,0 +1,1534 @@
+commit 2aebb4e4e62d09b4a95be7be7c24a7f6528385b7
+Author: Jesse Barnes <jbarnes@virtuousgeek.org>
+Date: Tue Sep 30 12:14:26 2008 -0700
+
+ drm: Rework vblank-wait handling to allow interrupt reduction.
+
+ Previously, drivers supporting vblank interrupt waits would run the interrupt
+ all the time, or all the time that any 3d client was running, preventing the
+ CPU from sleeping for long when the system was otherwise idle. Now, interrupts
+ are disabled any time that no client is waiting on a vblank event. The new
+ method uses vblank counters on the chipsets when the interrupts are turned
+ off, rather than counting interrupts, so that we can continue to present
+ accurate vblank numbers.
+
+ Co-author: Michel Dänzer <michel@tungstengraphics.com>
+ Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
+ Signed-off-by: Eric Anholt <eric@anholt.net>
+ Signed-off-by: Dave Airlie <airlied@redhat.com>
+
+diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
+index 452c2d8..fb45fe7 100644
+--- a/drivers/gpu/drm/drm_drv.c
++++ b/drivers/gpu/drm/drm_drv.c
+@@ -116,6 +116,8 @@ static struct drm_ioctl_desc drm_ioctls[] = {
+
+ DRM_IOCTL_DEF(DRM_IOCTL_WAIT_VBLANK, drm_wait_vblank, 0),
+
++ DRM_IOCTL_DEF(DRM_IOCTL_MODESET_CTL, drm_modeset_ctl, 0),
++
+ DRM_IOCTL_DEF(DRM_IOCTL_UPDATE_DRAW, drm_update_drawable_info, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
+ };
+
+diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c
+index 61ed515..d0c13d9 100644
+--- a/drivers/gpu/drm/drm_irq.c
++++ b/drivers/gpu/drm/drm_irq.c
+@@ -71,19 +71,131 @@ int drm_irq_by_busid(struct drm_device *dev, void *data,
+ return 0;
+ }
+
++static void vblank_disable_fn(unsigned long arg)
++{
++ 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);
++ }
++}
++
++static void drm_vblank_cleanup(struct drm_device *dev)
++{
++ /* 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);
++
++ drm_free(dev->vbl_queue, sizeof(*dev->vbl_queue) * dev->num_crtcs,
++ DRM_MEM_DRIVER);
++ drm_free(dev->vbl_sigs, sizeof(*dev->vbl_sigs) * dev->num_crtcs,
++ DRM_MEM_DRIVER);
++ drm_free(dev->_vblank_count, sizeof(*dev->_vblank_count) *
++ dev->num_crtcs, DRM_MEM_DRIVER);
++ drm_free(dev->vblank_refcount, sizeof(*dev->vblank_refcount) *
++ dev->num_crtcs, DRM_MEM_DRIVER);
++ drm_free(dev->vblank_enabled, sizeof(*dev->vblank_enabled) *
++ dev->num_crtcs, DRM_MEM_DRIVER);
++ drm_free(dev->last_vblank, sizeof(*dev->last_vblank) * dev->num_crtcs,
++ DRM_MEM_DRIVER);
++ drm_free(dev->vblank_inmodeset, sizeof(*dev->vblank_inmodeset) *
++ dev->num_crtcs, DRM_MEM_DRIVER);
++
++ dev->num_crtcs = 0;
++}
++
++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);
++ atomic_set(&dev->vbl_signal_pending, 0);
++ dev->num_crtcs = num_crtcs;
++
++ dev->vbl_queue = drm_alloc(sizeof(wait_queue_head_t) * num_crtcs,
++ DRM_MEM_DRIVER);
++ if (!dev->vbl_queue)
++ goto err;
++
++ dev->vbl_sigs = drm_alloc(sizeof(struct list_head) * num_crtcs,
++ DRM_MEM_DRIVER);
++ if (!dev->vbl_sigs)
++ goto err;
++
++ dev->_vblank_count = drm_alloc(sizeof(atomic_t) * num_crtcs,
++ DRM_MEM_DRIVER);
++ if (!dev->_vblank_count)
++ goto err;
++
++ dev->vblank_refcount = drm_alloc(sizeof(atomic_t) * num_crtcs,
++ DRM_MEM_DRIVER);
++ if (!dev->vblank_refcount)
++ goto err;
++
++ dev->vblank_enabled = drm_calloc(num_crtcs, sizeof(int),
++ DRM_MEM_DRIVER);
++ if (!dev->vblank_enabled)
++ goto err;
++
++ dev->last_vblank = drm_calloc(num_crtcs, sizeof(u32), DRM_MEM_DRIVER);
++ if (!dev->last_vblank)
++ goto err;
++
++ dev->vblank_inmodeset = drm_calloc(num_crtcs, sizeof(int),
++ DRM_MEM_DRIVER);
++ if (!dev->vblank_inmodeset)
++ goto err;
++
++ /* Zero per-crtc vblank stuff */
++ for (i = 0; i < num_crtcs; i++) {
++ init_waitqueue_head(&dev->vbl_queue[i]);
++ INIT_LIST_HEAD(&dev->vbl_sigs[i]);
++ atomic_set(&dev->_vblank_count[i], 0);
++ atomic_set(&dev->vblank_refcount[i], 0);
++ }
++
++ dev->vblank_disable_allowed = 0;
++
++ return 0;
++
++err:
++ drm_vblank_cleanup(dev);
++ return ret;
++}
++EXPORT_SYMBOL(drm_vblank_init);
++
+ /**
+ * Install IRQ handler.
+ *
+ * \param dev DRM device.
+- * \param irq IRQ number.
+ *
+- * Initializes the IRQ related data, and setups drm_device::vbl_queue. Installs the handler, calling the driver
++ * Initializes the IRQ related data. Installs the handler, calling the driver
+ * \c drm_driver_irq_preinstall() and \c drm_driver_irq_postinstall() functions
+ * before and after the installation.
+ */
+-static int drm_irq_install(struct drm_device * dev)
++int drm_irq_install(struct drm_device *dev)
+ {
+- int ret;
++ int ret = 0;
+ unsigned long sh_flags = 0;
+
+ if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+@@ -109,17 +221,6 @@ static int drm_irq_install(struct drm_device * dev)
+
+ DRM_DEBUG("irq=%d\n", dev->pdev->irq);
+
+- if (drm_core_check_feature(dev, DRIVER_IRQ_VBL)) {
+- init_waitqueue_head(&dev->vbl_queue);
+-
+- spin_lock_init(&dev->vbl_lock);
+-
+- INIT_LIST_HEAD(&dev->vbl_sigs);
+- INIT_LIST_HEAD(&dev->vbl_sigs2);
+-
+- dev->vbl_pending = 0;
+- }
+-
+ /* Before installing handler */
+ dev->driver->irq_preinstall(dev);
+
+@@ -141,10 +242,16 @@ static int drm_irq_install(struct drm_device * dev)
+ }
+
+ /* After installing handler */
+- dev->driver->irq_postinstall(dev);
++ ret = dev->driver->irq_postinstall(dev);
++ if (ret < 0) {
++ mutex_lock(&dev->struct_mutex);
++ dev->irq_enabled = 0;
++ mutex_unlock(&dev->struct_mutex);
++ }
+
+- return 0;
++ return ret;
+ }
++EXPORT_SYMBOL(drm_irq_install);
+
+ /**
+ * Uninstall the IRQ handler.
+@@ -174,11 +281,12 @@ int drm_irq_uninstall(struct drm_device * dev)
+
+ free_irq(dev->pdev->irq, dev);
+
++ drm_vblank_cleanup(dev);
++
+ dev->locked_tasklet_func = NULL;
+
+ return 0;
+ }
+-
+ EXPORT_SYMBOL(drm_irq_uninstall);
+
+ /**
+@@ -218,6 +326,174 @@ int drm_control(struct drm_device *dev, void *data,
+ }
+
+ /**
++ * drm_vblank_count - retrieve "cooked" vblank counter value
++ * @dev: DRM device
++ * @crtc: which counter to retrieve
++ *
++ * Fetches the "cooked" vblank count value that represents the number of
++ * vblank events since the system was booted, including lost events due to
++ * modesetting activity.
++ */
++u32 drm_vblank_count(struct drm_device *dev, int crtc)
++{
++ return atomic_read(&dev->_vblank_count[crtc]);
++}
++EXPORT_SYMBOL(drm_vblank_count);
++
++/**
++ * drm_update_vblank_count - update the master vblank counter
++ * @dev: DRM device
++ * @crtc: counter to update
++ *
++ * Call back into the driver to update the appropriate vblank counter
++ * (specified by @crtc). Deal with wraparound, if it occurred, and
++ * update the last read value so we can deal with wraparound on the next
++ * call if necessary.
++ *
++ * Only necessary when going from off->on, to account for frames we
++ * didn't get an interrupt for.
++ *
++ * 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)
++{
++ u32 cur_vblank, diff;
++
++ /*
++ * Interrupts were disabled prior to this call, so deal with counter
++ * wrap if needed.
++ * NOTE! It's possible we lost a full dev->max_vblank_count events
++ * here if the register is small or we had vblank interrupts off for
++ * a long time.
++ */
++ cur_vblank = dev->driver->get_vblank_counter(dev, crtc);
++ diff = cur_vblank - dev->last_vblank[crtc];
++ if (cur_vblank < dev->last_vblank[crtc]) {
++ diff += dev->max_vblank_count;
++
++ DRM_DEBUG("last_vblank[%d]=0x%x, cur_vblank=0x%x => diff=0x%x\n",
++ crtc, dev->last_vblank[crtc], cur_vblank, diff);
++ }
++
++ DRM_DEBUG("enabling vblank interrupts on crtc %d, missed %d\n",
++ crtc, diff);
++
++ atomic_add(diff, &dev->_vblank_count[crtc]);
++}
++
++/**
++ * drm_vblank_get - get a reference count on vblank events
++ * @dev: DRM device
++ * @crtc: which CRTC to own
++ *
++ * Acquire a reference count on vblank events to avoid having them disabled
++ * while in use.
++ *
++ * RETURNS
++ * Zero on success, nonzero on failure.
++ */
++int drm_vblank_get(struct drm_device *dev, int crtc)
++{
++ unsigned long irqflags;
++ 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 &&
++ !dev->vblank_enabled[crtc]) {
++ ret = dev->driver->enable_vblank(dev, crtc);
++ DRM_DEBUG("enabling vblank on crtc %d, ret: %d\n", crtc, ret);
++ if (ret)
++ atomic_dec(&dev->vblank_refcount[crtc]);
++ else {
++ dev->vblank_enabled[crtc] = 1;
++ drm_update_vblank_count(dev, crtc);
++ }
++ }
++ spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
++
++ return ret;
++}
++EXPORT_SYMBOL(drm_vblank_get);
++
++/**
++ * drm_vblank_put - give up ownership of vblank events
++ * @dev: DRM device
++ * @crtc: which counter to give up
++ *
++ * Release ownership of a given vblank counter, turning off interrupts
++ * if possible.
++ */
++void drm_vblank_put(struct drm_device *dev, int crtc)
++{
++ /* Last user schedules interrupt disable */
++ if (atomic_dec_and_test(&dev->vblank_refcount[crtc]))
++ mod_timer(&dev->vblank_disable_timer, jiffies + 5*DRM_HZ);
++}
++EXPORT_SYMBOL(drm_vblank_put);
++
++/**
++ * drm_modeset_ctl - handle vblank event counter changes across mode switch
++ * @DRM_IOCTL_ARGS: standard ioctl arguments
++ *
++ * Applications should call the %_DRM_PRE_MODESET and %_DRM_POST_MODESET
++ * ioctls around modesetting so that any lost vblank events are accounted for.
++ *
++ * Generally the counter will reset across mode sets. If interrupts are
++ * enabled around this call, we don't have to do anything since the counter
++ * will have already been incremented.
++ */
++int drm_modeset_ctl(struct drm_device *dev, void *data,
++ struct drm_file *file_priv)
++{
++ struct drm_modeset_ctl *modeset = data;
++ unsigned long irqflags;
++ int crtc, ret = 0;
++
++ /* If drm_vblank_init() hasn't been called yet, just no-op */
++ if (!dev->num_crtcs)
++ goto out;
++
++ crtc = modeset->crtc;
++ if (crtc >= dev->num_crtcs) {
++ ret = -EINVAL;
++ goto out;
++ }
++
++ /*
++ * To avoid all the problems that might happen if interrupts
++ * were enabled/disabled around or between these calls, we just
++ * have the kernel take a reference on the CRTC (just once though
++ * to avoid corrupting the count if multiple, mismatch calls occur),
++ * so that interrupts remain enabled in the interim.
++ */
++ switch (modeset->cmd) {
++ case _DRM_PRE_MODESET:
++ if (!dev->vblank_inmodeset[crtc]) {
++ dev->vblank_inmodeset[crtc] = 1;
++ drm_vblank_get(dev, crtc);
++ }
++ break;
++ case _DRM_POST_MODESET:
++ if (dev->vblank_inmodeset[crtc]) {
++ spin_lock_irqsave(&dev->vbl_lock, irqflags);
++ dev->vblank_disable_allowed = 1;
++ dev->vblank_inmodeset[crtc] = 0;
++ spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
++ drm_vblank_put(dev, crtc);
++ }
++ break;
++ default:
++ ret = -EINVAL;
++ break;
++ }
++
++out:
++ return ret;
++}
++
++/**
+ * Wait for VBLANK.
+ *
+ * \param inode device inode.
+@@ -236,12 +512,12 @@ int drm_control(struct drm_device *dev, void *data,
+ *
+ * If a signal is not requested, then calls vblank_wait().
+ */
+-int drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_priv)
++int drm_wait_vblank(struct drm_device *dev, void *data,
++ struct drm_file *file_priv)
+ {
+ union drm_wait_vblank *vblwait = data;
+- struct timeval now;
+ int ret = 0;
+- unsigned int flags, seq;
++ unsigned int flags, seq, crtc;
+
+ if ((!dev->pdev->irq) || (!dev->irq_enabled))
+ return -EINVAL;
+@@ -255,13 +531,17 @@ int drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_pr
+ }
+
+ flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK;
++ crtc = flags & _DRM_VBLANK_SECONDARY ? 1 : 0;
+
+- if (!drm_core_check_feature(dev, (flags & _DRM_VBLANK_SECONDARY) ?
+- DRIVER_IRQ_VBL2 : DRIVER_IRQ_VBL))
++ if (crtc >= dev->num_crtcs)
+ return -EINVAL;
+
+- seq = atomic_read((flags & _DRM_VBLANK_SECONDARY) ? &dev->vbl_received2
+- : &dev->vbl_received);
++ ret = drm_vblank_get(dev, crtc);
++ if (ret) {
++ DRM_ERROR("failed to acquire vblank counter, %d\n", ret);
++ return ret;
++ }
++ seq = drm_vblank_count(dev, crtc);
+
+ switch (vblwait->request.type & _DRM_VBLANK_TYPES_MASK) {
+ case _DRM_VBLANK_RELATIVE:
+@@ -270,7 +550,8 @@ int drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_pr
+ case _DRM_VBLANK_ABSOLUTE:
+ break;
+ default:
+- return -EINVAL;
++ ret = -EINVAL;
++ goto done;
+ }
+
+ if ((flags & _DRM_VBLANK_NEXTONMISS) &&
+@@ -280,8 +561,7 @@ int drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_pr
+
+ if (flags & _DRM_VBLANK_SIGNAL) {
+ unsigned long irqflags;
+- struct list_head *vbl_sigs = (flags & _DRM_VBLANK_SECONDARY)
+- ? &dev->vbl_sigs2 : &dev->vbl_sigs;
++ struct list_head *vbl_sigs = &dev->vbl_sigs[crtc];
+ struct drm_vbl_sig *vbl_sig;
+
+ spin_lock_irqsave(&dev->vbl_lock, irqflags);
+@@ -302,22 +582,29 @@ int drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_pr
+ }
+ }
+
+- if (dev->vbl_pending >= 100) {
++ if (atomic_read(&dev->vbl_signal_pending) >= 100) {
+ spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
+- return -EBUSY;
++ ret = -EBUSY;
++ goto done;
+ }
+
+- dev->vbl_pending++;
+-
+ spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
+
+- if (!
+- (vbl_sig =
+- drm_alloc(sizeof(struct drm_vbl_sig), DRM_MEM_DRIVER))) {
+- return -ENOMEM;
++ vbl_sig = drm_calloc(1, sizeof(struct drm_vbl_sig),
++ DRM_MEM_DRIVER);
++ if (!vbl_sig) {
++ ret = -ENOMEM;
++ goto done;
++ }
++
++ ret = drm_vblank_get(dev, crtc);
++ if (ret) {
++ drm_free(vbl_sig, sizeof(struct drm_vbl_sig),
++ DRM_MEM_DRIVER);
++ return ret;
+ }
+
+- memset((void *)vbl_sig, 0, sizeof(*vbl_sig));
++ atomic_inc(&dev->vbl_signal_pending);
+
+ vbl_sig->sequence = vblwait->request.sequence;
+ vbl_sig->info.si_signo = vblwait->request.signal;
+@@ -331,20 +618,29 @@ int drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_pr
+
+ vblwait->reply.sequence = seq;
+ } else {
+- if (flags & _DRM_VBLANK_SECONDARY) {
+- if (dev->driver->vblank_wait2)
+- ret = dev->driver->vblank_wait2(dev, &vblwait->request.sequence);
+- } else if (dev->driver->vblank_wait)
+- ret =
+- dev->driver->vblank_wait(dev,
+- &vblwait->request.sequence);
+-
+- do_gettimeofday(&now);
+- vblwait->reply.tval_sec = now.tv_sec;
+- vblwait->reply.tval_usec = now.tv_usec;
++ DRM_DEBUG("waiting on vblank count %d, crtc %d\n",
++ vblwait->request.sequence, crtc);
++ DRM_WAIT_ON(ret, dev->vbl_queue[crtc], 3 * DRM_HZ,
++ ((drm_vblank_count(dev, crtc)
++ - vblwait->request.sequence) <= (1 << 23)));
++
++ if (ret != -EINTR) {
++ struct timeval now;
++
++ do_gettimeofday(&now);
++
++ vblwait->reply.tval_sec = now.tv_sec;
++ vblwait->reply.tval_usec = now.tv_usec;
++ vblwait->reply.sequence = drm_vblank_count(dev, crtc);
++ DRM_DEBUG("returning %d to client\n",
++ vblwait->reply.sequence);
++ } else {
++ DRM_DEBUG("vblank wait interrupted by signal\n");
++ }
+ }
+
+- done:
++done:
++ drm_vblank_put(dev, crtc);
+ return ret;
+ }
+
+@@ -352,44 +648,57 @@ int drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_pr
+ * Send the VBLANK signals.
+ *
+ * \param dev DRM device.
++ * \param crtc CRTC where the vblank event occurred
+ *
+ * Sends a signal for each task in drm_device::vbl_sigs and empties the list.
+ *
+ * If a signal is not requested, then calls vblank_wait().
+ */
+-void drm_vbl_send_signals(struct drm_device * dev)
++static void drm_vbl_send_signals(struct drm_device *dev, int crtc)
+ {
++ struct drm_vbl_sig *vbl_sig, *tmp;
++ struct list_head *vbl_sigs;
++ unsigned int vbl_seq;
+ unsigned long flags;
+- int i;
+
+ spin_lock_irqsave(&dev->vbl_lock, flags);
+
+- for (i = 0; i < 2; i++) {
+- struct drm_vbl_sig *vbl_sig, *tmp;
+- struct list_head *vbl_sigs = i ? &dev->vbl_sigs2 : &dev->vbl_sigs;
+- unsigned int vbl_seq = atomic_read(i ? &dev->vbl_received2 :
+- &dev->vbl_received);
++ vbl_sigs = &dev->vbl_sigs[crtc];
++ vbl_seq = drm_vblank_count(dev, crtc);
+
+- list_for_each_entry_safe(vbl_sig, tmp, vbl_sigs, head) {
+- if ((vbl_seq - vbl_sig->sequence) <= (1 << 23)) {
+- vbl_sig->info.si_code = vbl_seq;
+- send_sig_info(vbl_sig->info.si_signo,
+- &vbl_sig->info, vbl_sig->task);
++ list_for_each_entry_safe(vbl_sig, tmp, vbl_sigs, head) {
++ if ((vbl_seq - vbl_sig->sequence) <= (1 << 23)) {
++ vbl_sig->info.si_code = vbl_seq;
++ send_sig_info(vbl_sig->info.si_signo,
++ &vbl_sig->info, vbl_sig->task);
+
+- list_del(&vbl_sig->head);
+-
+- drm_free(vbl_sig, sizeof(*vbl_sig),
+- DRM_MEM_DRIVER);
++ list_del(&vbl_sig->head);
+
+- dev->vbl_pending--;
+- }
+- }
++ drm_free(vbl_sig, sizeof(*vbl_sig),
++ DRM_MEM_DRIVER);
++ atomic_dec(&dev->vbl_signal_pending);
++ drm_vblank_put(dev, crtc);
++ }
+ }
+
+ spin_unlock_irqrestore(&dev->vbl_lock, flags);
+ }
+
+-EXPORT_SYMBOL(drm_vbl_send_signals);
++/**
++ * drm_handle_vblank - handle a vblank event
++ * @dev: DRM device
++ * @crtc: where this event occurred
++ *
++ * Drivers should call this routine in their vblank interrupt handlers to
++ * update the vblank counter and send any signals that may be pending.
++ */
++void drm_handle_vblank(struct drm_device *dev, int crtc)
++{
++ atomic_inc(&dev->_vblank_count[crtc]);
++ DRM_WAKEUP(&dev->vbl_queue[crtc]);
++ drm_vbl_send_signals(dev, crtc);
++}
++EXPORT_SYMBOL(drm_handle_vblank);
+
+ /**
+ * Tasklet wrapper function.
+diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c
+index cead62f..8609ec2 100644
+--- a/drivers/gpu/drm/i915/i915_dma.c
++++ b/drivers/gpu/drm/i915/i915_dma.c
+@@ -673,7 +673,7 @@ static int i915_getparam(struct drm_device *dev, void *data,
+
+ switch (param->param) {
+ case I915_PARAM_IRQ_ACTIVE:
+- value = dev->irq_enabled;
++ value = dev->pdev->irq ? 1 : 0;
+ break;
+ case I915_PARAM_ALLOW_BATCHBUFFER:
+ value = dev_priv->allow_batchbuffer ? 1 : 0;
+@@ -808,7 +808,8 @@ int i915_driver_load(struct drm_device *dev, unsigned long flags)
+ * and the registers being closely associated.
+ */
+ if (!IS_I945G(dev) && !IS_I945GM(dev))
+- pci_enable_msi(dev->pdev);
++ if (pci_enable_msi(dev->pdev))
++ DRM_ERROR("failed to enable MSI\n");
+
+ intel_opregion_init(dev);
+
+diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c
+index eff66ed..37af03f 100644
+--- a/drivers/gpu/drm/i915/i915_drv.c
++++ b/drivers/gpu/drm/i915/i915_drv.c
+@@ -85,10 +85,8 @@ static struct drm_driver driver = {
+ /* don't use mtrr's here, the Xserver or user space app should
+ * deal with them for intel hardware.
+ */
+- .driver_features =
+- DRIVER_USE_AGP | DRIVER_REQUIRE_AGP | /* DRIVER_USE_MTRR |*/
+- DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED | DRIVER_IRQ_VBL |
+- DRIVER_IRQ_VBL2,
++ .driver_features = DRIVER_USE_AGP | DRIVER_REQUIRE_AGP |
++ DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED,
+ .load = i915_driver_load,
+ .unload = i915_driver_unload,
+ .lastclose = i915_driver_lastclose,
+@@ -96,8 +94,9 @@ static struct drm_driver driver = {
+ .suspend = i915_suspend,
+ .resume = i915_resume,
+ .device_is_agp = i915_driver_device_is_agp,
+- .vblank_wait = i915_driver_vblank_wait,
+- .vblank_wait2 = i915_driver_vblank_wait2,
++ .get_vblank_counter = i915_get_vblank_counter,
++ .enable_vblank = i915_enable_vblank,
++ .disable_vblank = i915_disable_vblank,
+ .irq_preinstall = i915_driver_irq_preinstall,
+ .irq_postinstall = i915_driver_irq_postinstall,
+ .irq_uninstall = i915_driver_irq_uninstall,
+diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
+index 71326ca..d1a02be 100644
+--- a/drivers/gpu/drm/i915/i915_drv.h
++++ b/drivers/gpu/drm/i915/i915_drv.h
+@@ -83,10 +83,15 @@ struct mem_block {
+ typedef struct _drm_i915_vbl_swap {
+ struct list_head head;
+ drm_drawable_t drw_id;
+- unsigned int pipe;
++ unsigned int plane;
+ unsigned int sequence;
+ } drm_i915_vbl_swap_t;
+
++struct opregion_header;
++struct opregion_acpi;
++struct opregion_swsci;
++struct opregion_asle;
++
+ struct intel_opregion {
+ struct opregion_header *header;
+ struct opregion_acpi *acpi;
+@@ -105,7 +110,7 @@ typedef struct drm_i915_private {
+ drm_dma_handle_t *status_page_dmah;
+ void *hw_status_page;
+ dma_addr_t dma_status_page;
+- unsigned long counter;
++ uint32_t counter;
+ unsigned int status_gfx_addr;
+ drm_local_map_t hws_map;
+
+@@ -247,16 +252,17 @@ extern int i915_irq_emit(struct drm_device *dev, void *data,
+ extern int i915_irq_wait(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+
+-extern int i915_driver_vblank_wait(struct drm_device *dev, unsigned int *sequence);
+-extern int i915_driver_vblank_wait2(struct drm_device *dev, unsigned int *sequence);
+ extern irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS);
+ extern void i915_driver_irq_preinstall(struct drm_device * dev);
+-extern void i915_driver_irq_postinstall(struct drm_device * dev);
++extern int i915_driver_irq_postinstall(struct drm_device *dev);
+ extern void i915_driver_irq_uninstall(struct drm_device * dev);
+ extern int i915_vblank_pipe_set(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+ extern int i915_vblank_pipe_get(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
++extern int i915_enable_vblank(struct drm_device *dev, int crtc);
++extern void i915_disable_vblank(struct drm_device *dev, int crtc);
++extern u32 i915_get_vblank_counter(struct drm_device *dev, int crtc);
+ extern int i915_vblank_swap(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+ extern void i915_enable_irq(drm_i915_private_t *dev_priv, u32 mask);
+@@ -278,6 +284,10 @@ extern void i915_mem_release(struct drm_device * dev,
+ extern int i915_save_state(struct drm_device *dev);
+ extern int i915_restore_state(struct drm_device *dev);
+
++/* i915_suspend.c */
++extern int i915_save_state(struct drm_device *dev);
++extern int i915_restore_state(struct drm_device *dev);
++
+ /* i915_opregion.c */
+ extern int intel_opregion_init(struct drm_device *dev);
+ extern void intel_opregion_free(struct drm_device *dev);
+diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c
+index ae7d3a8..f875959 100644
+--- a/drivers/gpu/drm/i915/i915_irq.c
++++ b/drivers/gpu/drm/i915/i915_irq.c
+@@ -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
+@@ -61,6 +60,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.
+ *
+ * This function will be called with the HW lock held.
+@@ -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);
+ }
+diff --git a/include/drm/drm.h b/include/drm/drm.h
+index 0864c69..15e5503 100644
+--- a/include/drm/drm.h
++++ b/include/drm/drm.h
+@@ -454,6 +454,7 @@ struct drm_irq_busid {
+ enum drm_vblank_seq_type {
+ _DRM_VBLANK_ABSOLUTE = 0x0, /**< Wait for specific vblank sequence number */
+ _DRM_VBLANK_RELATIVE = 0x1, /**< Wait for given number of vblanks */
++ _DRM_VBLANK_FLIP = 0x8000000, /**< Scheduled buffer swap should flip */
+ _DRM_VBLANK_NEXTONMISS = 0x10000000, /**< If missed, wait for next vblank */
+ _DRM_VBLANK_SECONDARY = 0x20000000, /**< Secondary display controller */
+ _DRM_VBLANK_SIGNAL = 0x40000000 /**< Send signal instead of blocking */
+@@ -486,6 +487,19 @@ union drm_wait_vblank {
+ struct drm_wait_vblank_reply reply;
+ };
+
++#define _DRM_PRE_MODESET 1
++#define _DRM_POST_MODESET 2
++
++/**
++ * DRM_IOCTL_MODESET_CTL ioctl argument type
++ *
++ * \sa drmModesetCtl().
++ */
++struct drm_modeset_ctl {
++ uint32_t crtc;
++ uint32_t cmd;
++};
++
+ /**
+ * DRM_IOCTL_AGP_ENABLE ioctl argument type.
+ *
+@@ -570,6 +584,7 @@ struct drm_set_version {
+ #define DRM_IOCTL_GET_CLIENT DRM_IOWR(0x05, struct drm_client)
+ #define DRM_IOCTL_GET_STATS DRM_IOR( 0x06, struct drm_stats)
+ #define DRM_IOCTL_SET_VERSION DRM_IOWR(0x07, struct drm_set_version)
++#define DRM_IOCTL_MODESET_CTL DRM_IOW(0x08, struct drm_modeset_ctl)
+
+ #define DRM_IOCTL_SET_UNIQUE DRM_IOW( 0x10, struct drm_unique)
+ #define DRM_IOCTL_AUTH_MAGIC DRM_IOW( 0x11, struct drm_auth)
+diff --git a/include/drm/drmP.h b/include/drm/drmP.h
+index 1c1b13e..e79ce07 100644
+--- a/include/drm/drmP.h
++++ b/include/drm/drmP.h
+@@ -580,11 +580,54 @@ struct drm_driver {
+ int (*kernel_context_switch) (struct drm_device *dev, int old,
+ int new);
+ void (*kernel_context_switch_unlock) (struct drm_device *dev);
+- int (*vblank_wait) (struct drm_device *dev, unsigned int *sequence);
+- int (*vblank_wait2) (struct drm_device *dev, unsigned int *sequence);
+ int (*dri_library_name) (struct drm_device *dev, char *buf);
+
+ /**
++ * get_vblank_counter - get raw hardware vblank counter
++ * @dev: DRM device
++ * @crtc: counter to fetch
++ *
++ * Driver callback for fetching a raw hardware vblank counter
++ * for @crtc. If a device doesn't have a hardware counter, the
++ * driver can simply return the value of drm_vblank_count and
++ * make the enable_vblank() and disable_vblank() hooks into no-ops,
++ * leaving interrupts enabled at all times.
++ *
++ * Wraparound handling and loss of events due to modesetting is dealt
++ * with in the DRM core code.
++ *
++ * RETURNS
++ * Raw vblank counter value.
++ */
++ u32 (*get_vblank_counter) (struct drm_device *dev, int crtc);
++
++ /**
++ * enable_vblank - enable vblank interrupt events
++ * @dev: DRM device
++ * @crtc: which irq to enable
++ *
++ * Enable vblank interrupts for @crtc. If the device doesn't have
++ * a hardware vblank counter, this routine should be a no-op, since
++ * interrupts will have to stay on to keep the count accurate.
++ *
++ * RETURNS
++ * Zero on success, appropriate errno if the given @crtc's vblank
++ * interrupt cannot be enabled.
++ */
++ int (*enable_vblank) (struct drm_device *dev, int crtc);
++
++ /**
++ * disable_vblank - disable vblank interrupt events
++ * @dev: DRM device
++ * @crtc: which irq to enable
++ *
++ * Disable vblank interrupts for @crtc. If the device doesn't have
++ * a hardware vblank counter, this routine should be a no-op, since
++ * interrupts will have to stay on to keep the count accurate.
++ */
++ void (*disable_vblank) (struct drm_device *dev, int crtc);
++
++ /**
+ * Called by \c drm_device_is_agp. Typically used to determine if a
+ * card is really attached to AGP or not.
+ *
+@@ -601,7 +644,7 @@ struct drm_driver {
+
+ irqreturn_t(*irq_handler) (DRM_IRQ_ARGS);
+ void (*irq_preinstall) (struct drm_device *dev);
+- void (*irq_postinstall) (struct drm_device *dev);
++ int (*irq_postinstall) (struct drm_device *dev);
+ void (*irq_uninstall) (struct drm_device *dev);
+ void (*reclaim_buffers) (struct drm_device *dev,
+ struct drm_file * file_priv);
+@@ -730,13 +773,28 @@ struct drm_device {
+ /** \name VBLANK IRQ support */
+ /*@{ */
+
+- wait_queue_head_t vbl_queue; /**< VBLANK wait queue */
+- atomic_t vbl_received;
+- atomic_t vbl_received2; /**< number of secondary VBLANK interrupts */
++ /*
++ * At load time, disabling the vblank interrupt won't be allowed since
++ * old clients may not call the modeset ioctl and therefore misbehave.
++ * Once the modeset ioctl *has* been called though, we can safely
++ * disable them when unused.
++ */
++ int vblank_disable_allowed;
++
++ wait_queue_head_t *vbl_queue; /**< VBLANK wait queue */
++ atomic_t *_vblank_count; /**< number of VBLANK interrupts (driver must alloc the right number of counters) */
+ spinlock_t vbl_lock;
+- struct list_head vbl_sigs; /**< signal list to send on VBLANK */
+- struct list_head vbl_sigs2; /**< signals to send on secondary VBLANK */
+- unsigned int vbl_pending;
++ struct list_head *vbl_sigs; /**< signal list to send on VBLANK */
++ atomic_t vbl_signal_pending; /* number of signals pending on all crtcs*/
++ atomic_t *vblank_refcount; /* number of users of vblank interruptsper crtc */
++ u32 *last_vblank; /* protected by dev->vbl_lock, used */
++ /* for wraparound handling */
++ int *vblank_enabled; /* so we don't call enable more than
++ once per disable */
++ int *vblank_inmodeset; /* Display driver is setting mode */
++ struct timer_list vblank_disable_timer;
++
++ u32 max_vblank_count; /**< size of vblank counter register */
+ spinlock_t tasklet_lock; /**< For drm_locked_tasklet */
+ void (*locked_tasklet_func)(struct drm_device *dev);
+
+@@ -757,6 +815,7 @@ struct drm_device {
+ struct pci_controller *hose;
+ #endif
+ struct drm_sg_mem *sg; /**< Scatter gather memory */
++ int num_crtcs; /**< Number of CRTCs on this device */
+ void *dev_private; /**< device private data */
+ struct drm_sigdata sigdata; /**< For block_all_signals */
+ sigset_t sigmask;
+@@ -990,10 +1049,19 @@ extern void drm_driver_irq_preinstall(struct drm_device *dev);
+ extern void drm_driver_irq_postinstall(struct drm_device *dev);
+ extern void drm_driver_irq_uninstall(struct drm_device *dev);
+
++extern int drm_vblank_init(struct drm_device *dev, int num_crtcs);
+ extern int drm_wait_vblank(struct drm_device *dev, void *data,
+- struct drm_file *file_priv);
++ struct drm_file *filp);
+ extern int drm_vblank_wait(struct drm_device *dev, unsigned int *vbl_seq);
+-extern void drm_vbl_send_signals(struct drm_device *dev);
++extern void drm_locked_tasklet(struct drm_device *dev,
++ void(*func)(struct drm_device *));
++extern u32 drm_vblank_count(struct drm_device *dev, int crtc);
++extern void drm_handle_vblank(struct drm_device *dev, int crtc);
++extern int drm_vblank_get(struct drm_device *dev, int crtc);
++extern void drm_vblank_put(struct drm_device *dev, int crtc);
++/* Modesetting support */
++extern int drm_modeset_ctl(struct drm_device *dev, void *data,
++ struct drm_file *file_priv);
+ extern void drm_locked_tasklet(struct drm_device *dev, void(*func)(struct drm_device*));
+
+ /* AGP/GART support (drm_agpsupport.h) */