diff --git a/linux-core/drmP.h b/linux-core/drmP.h index 4e8b087..efd046b 100644 --- a/linux-core/drmP.h +++ b/linux-core/drmP.h @@ -836,6 +836,7 @@ struct drm_device { u32 *last_vblank; /* protected by dev->vbl_lock, used */ /* for wraparound handling */ u32 *vblank_offset; /* used to track how many vblanks */ + int *vblank_enabled; u32 *vblank_premodeset; /* were lost during modeset */ struct timer_list vblank_disable_timer; diff --git a/linux-core/drm_irq.c b/linux-core/drm_irq.c index 367d2dd..e4940bb 100644 --- a/linux-core/drm_irq.c +++ b/linux-core/drm_irq.c @@ -74,11 +74,18 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, static void vblank_disable_fn(unsigned long arg) { struct drm_device *dev = (struct drm_device *)arg; + unsigned long irqflags; int i; - for (i = 0; i < dev->num_crtcs; i++) - if (atomic_read(&dev->vblank_refcount[i]) == 0) + 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]) { dev->driver->disable_vblank(dev, i); + dev->vblank_enabled[i] = 0; + } + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); + } } int drm_vblank_init(struct drm_device *dev, int num_crtcs) @@ -111,6 +118,11 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs) 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; @@ -143,6 +155,8 @@ err: DRM_MEM_DRIVER); drm_free(dev->vblank_refcount, sizeof(*dev->vblank_refcount) * num_crtcs, DRM_MEM_DRIVER); + drm_free(dev->vblank_enabled, sizeof(*dev->vblank_enabled) * num_crtcs, + DRM_MEM_DRIVER); drm_free(dev->last_vblank, sizeof(*dev->last_vblank) * num_crtcs, DRM_MEM_DRIVER); drm_free(dev->vblank_premodeset, sizeof(*dev->vblank_premodeset) * @@ -357,14 +371,20 @@ EXPORT_SYMBOL(drm_update_vblank_count); */ 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) { + if (atomic_add_return(1, &dev->vblank_refcount[crtc]) == 1 && + !dev->vblank_enabled[crtc]) { ret = dev->driver->enable_vblank(dev, crtc); if (ret) atomic_dec(&dev->vblank_refcount[crtc]); + else + dev->vblank_enabled[crtc] = 1; } + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); return ret; } @@ -382,8 +402,7 @@ 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, - round_jiffies_relative(DRM_HZ)); + mod_timer(&dev->vblank_disable_timer, jiffies + 5*DRM_HZ); } EXPORT_SYMBOL(drm_vblank_put); diff --git a/shared-core/i915_drv.h b/shared-core/i915_drv.h index c92758f..7ee9db1 100644 --- a/shared-core/i915_drv.h +++ b/shared-core/i915_drv.h @@ -147,6 +147,9 @@ typedef struct drm_i915_private { drm_i915_vbl_swap_t vbl_swaps; unsigned int swaps_pending; + spinlock_t vbl_count_lock; + atomic_t *vbl_count; + /* Register state */ u8 saveLBB; u32 saveDSPACNTR; diff --git a/shared-core/i915_irq.c b/shared-core/i915_irq.c index d463f6e..51ac78a 100644 --- a/shared-core/i915_irq.c +++ b/shared-core/i915_irq.c @@ -341,42 +341,19 @@ static void i915_vblank_tasklet(struct drm_device *dev) drm_free(swap_hit, sizeof(*swap_hit), DRM_MEM_DRIVER); } } -#if 0 -static int i915_in_vblank(struct drm_device *dev, int pipe) -{ - drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; - unsigned long pipedsl, vblank, vtotal; - unsigned long vbl_start, vbl_end, cur_line; - - pipedsl = pipe ? PIPEBDSL : PIPEADSL; - vblank = pipe ? VBLANK_B : VBLANK_A; - vtotal = pipe ? VTOTAL_B : VTOTAL_A; - - vbl_start = I915_READ(vblank) & VBLANK_START_MASK; - vbl_end = (I915_READ(vblank) >> VBLANK_END_SHIFT) & VBLANK_END_MASK; - cur_line = I915_READ(pipedsl); - - if (cur_line >= vbl_start) - return 1; - - return 0; -} -#endif -u32 i915_get_vblank_counter(struct drm_device *dev, int plane) +u32 i915_read_frame_count(struct drm_device *dev, int pipe) { 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)) { - printk(KERN_ERR "trying to get vblank count for disabled " + printk(KERN_ERR "tried to read frame count for disabled " "pipe %d\n", pipe); return 0; } @@ -397,18 +374,17 @@ u32 i915_get_vblank_counter(struct drm_device *dev, int plane) count = (high1 << 8) | low; - /* - * If we're in the middle of the vblank period, the - * above regs won't have been updated yet, so return - * an incremented count to stay accurate - */ -#if 0 - if (i915_in_vblank(dev, pipe)) - count++; -#endif return count; } +u32 i915_get_vblank_counter(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); + + return atomic_read(&dev_priv->vbl_count[pipe]); +} + irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS) { struct drm_device *dev = (struct drm_device *) arg; @@ -432,12 +408,14 @@ irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS) * we may get extra interrupts. */ if (temp & VSYNC_PIPEA_FLAG) { + atomic_inc(&dev_priv->vbl_count[0]); drm_handle_vblank(dev, i915_get_plane(dev, 0)); I915_WRITE(I915REG_PIPEASTAT, pipea_stats | I915_VBLANK_INTERRUPT_ENABLE | I915_VBLANK_CLEAR); } if (temp & VSYNC_PIPEB_FLAG) { + atomic_inc(&dev_priv->vbl_count[1]); drm_handle_vblank(dev, i915_get_plane(dev, 1)); I915_WRITE(I915REG_PIPEBSTAT, pipeb_stats | I915_VBLANK_INTERRUPT_ENABLE | @@ -575,17 +553,50 @@ int i915_irq_wait(struct drm_device *dev, void *data, return i915_wait_irq(dev, irqwait->irq_seq); } +void i915_add_missed_vbls(struct drm_device *dev, int pipe) +{ + drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; + atomic_t *vbl_count; + unsigned long missed_count, last_count, cur_count, irqflags; + + vbl_count = &dev_priv->vbl_count[pipe]; + last_count = atomic_read(vbl_count); + + cur_count = i915_read_frame_count(dev, pipe); + + spin_lock_irqsave(&dev_priv->vbl_count_lock, irqflags); + if (cur_count + 1 == last_count) { + printk(KERN_ERR "frame vs. vblank count mismatch\n"); + missed_count = 0; + } else if (cur_count < last_count) { + printk(KERN_ERR "driver count wrapped\n"); + missed_count = dev->max_vblank_count - last_count; + missed_count += cur_count; + } else { + missed_count = cur_count - last_count; + } + spin_unlock_irqrestore(&dev_priv->vbl_count_lock, irqflags); + + atomic_add(missed_count, vbl_count); +} + 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); + i915_add_missed_vbls(dev, pipe); + switch (pipe) { case 0: dev_priv->irq_enable_reg |= VSYNC_PIPEA_FLAG; + I915_WRITE(I915REG_PIPEASTAT, I915_VBLANK_INTERRUPT_ENABLE | + I915_VBLANK_CLEAR); break; case 1: dev_priv->irq_enable_reg |= VSYNC_PIPEB_FLAG; + I915_WRITE(I915REG_PIPEBSTAT, I915_VBLANK_INTERRUPT_ENABLE | + I915_VBLANK_CLEAR); break; default: DRM_ERROR("tried to enable vblank on non-existent pipe %d\n", @@ -593,6 +604,7 @@ int i915_enable_vblank(struct drm_device *dev, int plane) break; } + printk(KERN_ERR "enabled irqs for pipe %d\n", pipe); I915_WRITE16(I915REG_INT_ENABLE_R, dev_priv->irq_enable_reg); return 0; @@ -616,6 +628,8 @@ void i915_disable_vblank(struct drm_device *dev, int plane) break; } + printk(KERN_ERR "disabled irqs for pipe %d\n", pipe); + I915_WRITE16(I915REG_INT_ENABLE_R, dev_priv->irq_enable_reg); } @@ -852,12 +866,22 @@ int i915_driver_irq_postinstall(struct drm_device * dev) dev_priv->user_irq_refcount = 0; dev_priv->irq_enable_reg = 0; + + spin_lock_init(&dev_priv->vbl_count_lock); + dev->max_vblank_count = 0xffffff; /* only 24 bits of frame count */ + + dev_priv->vbl_count = drm_alloc(sizeof(atomic_t) * num_pipes, + GFP_KERNEL); + if (!dev_priv->vbl_count) + return -ENOMEM; + + atomic_set(&dev_priv->vbl_count[0], i915_read_frame_count(dev, 0)); + atomic_set(&dev_priv->vbl_count[1], i915_read_frame_count(dev, 1)); + ret = drm_vblank_init(dev, num_pipes); if (ret) return ret; - dev->max_vblank_count = 0xffffff; /* only 24 bits of frame count */ - i915_enable_interrupt(dev); DRM_INIT_WAITQUEUE(&dev_priv->irq_queue);