diff options
Diffstat (limited to 'meta/packages/linux/linux-moblin-2.6.27-rc6/0011-drm-vblank-rework.patch')
-rw-r--r-- | meta/packages/linux/linux-moblin-2.6.27-rc6/0011-drm-vblank-rework.patch | 1534 |
1 files changed, 1534 insertions, 0 deletions
diff --git a/meta/packages/linux/linux-moblin-2.6.27-rc6/0011-drm-vblank-rework.patch b/meta/packages/linux/linux-moblin-2.6.27-rc6/0011-drm-vblank-rework.patch new file mode 100644 index 0000000000..6161a71f04 --- /dev/null +++ b/meta/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) */ |