diff --git a/linux-core/drm_irq.c b/linux-core/drm_irq.c index 367d2dd..0e6fdd4 100644 --- a/linux-core/drm_irq.c +++ b/linux-core/drm_irq.c @@ -381,9 +381,9 @@ EXPORT_SYMBOL(drm_vblank_get); void drm_vblank_put(struct drm_device *dev, int crtc) { /* Last user schedules interrupt disable */ - if (atomic_dec_and_test(&dev->vblank_refcount[crtc])) + if (!atomic_dec_and_test(&dev->vblank_refcount[crtc])) mod_timer(&dev->vblank_disable_timer, - round_jiffies_relative(DRM_HZ)); + round_jiffies_relative(3*DRM_HZ)); } EXPORT_SYMBOL(drm_vblank_put); diff --git a/shared-core/i915_drv.h b/shared-core/i915_drv.h index c92758f..e639839 100644 --- a/shared-core/i915_drv.h +++ b/shared-core/i915_drv.h @@ -147,6 +147,10 @@ typedef struct drm_i915_private { drm_i915_vbl_swap_t vbl_swaps; unsigned int swaps_pending; + spinlock_t vbl_count_lock; + atomic_t vbl_a_count; + atomic_t vbl_b_count; + /* Register state */ u8 saveLBB; u32 saveDSPACNTR; diff --git a/shared-core/i915_irq.c b/shared-core/i915_irq.c index d463f6e..42143b3 100644 --- a/shared-core/i915_irq.c +++ b/shared-core/i915_irq.c @@ -341,42 +341,50 @@ 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) +/** + * i915_in_vblank - accurately count vblank periods + * @dev: DRM device + * @pipe: pipe to check + * @count: current frame count + * + * Since the frame counter counts active frames rather than vblank periods, + * we have to return frame_counter + 1 if we just received a vblank event. + * If we wait until the actual frame counter increments, clients will end + * up drawing during the active period rather than the vblank period. + * + * In order to track this, we snapshot the frame count register in the + * interrupt handler. As long as it hasn't incremented relative to that + * snapshot, we know we're in a vblank period, so we return 1 here. + */ +static int i915_in_vblank(struct drm_device *dev, int pipe, int count) { 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); + atomic_t *vbl_frame = pipe ? &vbl_b_frame : &vbl_a_frame; + int pipedsl = pipe ? PIPEBDSL : PIPEADSL; + int vblank = pipe ? VBLANK_B : VBLANK_A; - if (cur_line >= vbl_start) +// if (I915_READ(pipedsl) >= (I915_READ(vblank) & VBLANK_START_MASK)) + if (atomic_read(vbl_frame) == count) 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 +405,18 @@ 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 pipe ? atomic_read(&dev_priv->vbl_b_count) : + atomic_read(&dev_priv->vbl_a_count); +} + irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS) { struct drm_device *dev = (struct drm_device *) arg; @@ -432,12 +440,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_a_count); 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_b_count); drm_handle_vblank(dev, i915_get_plane(dev, 1)); I915_WRITE(I915REG_PIPEBSTAT, pipeb_stats | I915_VBLANK_INTERRUPT_ENABLE | @@ -575,17 +585,56 @@ 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 = pipe ? &dev_priv->vbl_b_count : &dev_priv->vbl_a_count; + 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; + unsigned long ier = I915_READ16(I915REG_INT_ENABLE_R); int pipe = i915_get_pipe(dev, plane); + if (pipe == 0 && (ier & VSYNC_PIPEA_FLAG)) + return 0; + if (pipe == 1 && (ier & VSYNC_PIPEB_FLAG)) + return 0; + + 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 +642,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; @@ -603,6 +653,8 @@ 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); + printk(KERN_ERR "disabling vblank\n"); + switch (pipe) { case 0: dev_priv->irq_enable_reg &= ~VSYNC_PIPEA_FLAG; @@ -616,6 +668,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); } @@ -858,6 +912,10 @@ int i915_driver_irq_postinstall(struct drm_device * dev) dev->max_vblank_count = 0xffffff; /* only 24 bits of frame count */ + atomic_set(&dev_priv->vbl_a_count, i915_read_frame_count(dev, 0)); + atomic_set(&dev_priv->vbl_b_count, i915_read_frame_count(dev, 1)); + spin_lock_init(&dev_priv->vbl_count_lock); + i915_enable_interrupt(dev); DRM_INIT_WAITQUEUE(&dev_priv->irq_queue);