diff --git a/linux-core/drmP.h b/linux-core/drmP.h index dd3a69d..3c33316 100644 --- a/linux-core/drmP.h +++ b/linux-core/drmP.h @@ -627,8 +627,9 @@ 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); + u32 (*update_vblank_count) (struct drm_device *dev, int crtc); + void (*enable_vblank) (struct drm_device *dev, int crtc); + void (*disable_vblank) (struct drm_device *dev, int crtc); int (*dri_library_name) (struct drm_device * dev, char * buf); /** diff --git a/linux-core/drm_irq.c b/linux-core/drm_irq.c index 8871671..0042bd0 100644 --- a/linux-core/drm_irq.c +++ b/linux-core/drm_irq.c @@ -248,7 +248,7 @@ int drm_wait_vblank(DRM_IOCTL_ARGS) drm_wait_vblank_t vblwait; struct timeval now; int ret = 0; - unsigned int flags, seq; + unsigned int flags, seq, crtc, cur_vblank; if ((!dev->irq) || (!dev->irq_enabled)) return -EINVAL; @@ -265,13 +265,13 @@ int drm_wait_vblank(DRM_IOCTL_ARGS) } 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)) return -EINVAL; - seq = atomic_read((flags & _DRM_VBLANK_SECONDARY) ? &dev->vbl_received2 - : &dev->vbl_received); + seq = dev->driver->update_vblank_count(dev, crtc); switch (vblwait.request.type & _DRM_VBLANK_TYPES_MASK) { case _DRM_VBLANK_RELATIVE: @@ -332,6 +332,8 @@ int drm_wait_vblank(DRM_IOCTL_ARGS) vbl_sig->info.si_signo = vblwait.request.signal; vbl_sig->task = current; + dev->driver->enable_vblank(dev, crtc); + spin_lock_irqsave(&dev->vbl_lock, irqflags); list_add_tail(&vbl_sig->head, vbl_sigs); @@ -340,17 +342,15 @@ int drm_wait_vblank(DRM_IOCTL_ARGS) 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); - + dev->driver->enable_vblank(dev, crtc); + DRM_WAIT_ON(ret, dev->vbl_queue, 3 * DRM_HZ, + (((cur_vblank = + dev->driver->update_vblank_count(dev, crtc)) + - seq) <= (1 << 23))); do_gettimeofday(&now); vblwait.reply.tval_sec = now.tv_sec; vblwait.reply.tval_usec = now.tv_usec; + dev->driver->disable_vblank(dev, crtc); } done: @@ -396,6 +396,13 @@ void drm_vbl_send_signals(drm_device_t * dev) dev->vbl_pending--; } } + /* + * If there are no more signals requested, we can disable + * interrupts. + * FIXME: make sure we don't disable an active waiter! + */ + if (list_empty(vbl_sigs)) + dev->driver->disable_vblank(dev, i); } spin_unlock_irqrestore(&dev->vbl_lock, flags); diff --git a/linux-core/i915_drv.c b/linux-core/i915_drv.c index 7fdb083..46a1061 100644 --- a/linux-core/i915_drv.c +++ b/linux-core/i915_drv.c @@ -83,8 +83,9 @@ static struct drm_driver driver = { .lastclose = i915_driver_lastclose, .preclose = i915_driver_preclose, .device_is_agp = i915_driver_device_is_agp, - .vblank_wait = i915_driver_vblank_wait, - .vblank_wait2 = i915_driver_vblank_wait2, + .update_vblank_count = i915_update_vblank_count, + .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/shared-core/i915_drv.h b/shared-core/i915_drv.h index 9deee8e..917428c 100644 --- a/shared-core/i915_drv.h +++ b/shared-core/i915_drv.h @@ -132,6 +132,8 @@ typedef struct drm_i915_private { spinlock_t swaps_lock; drm_i915_vbl_swap_t vbl_swaps; unsigned int swaps_pending; + unsigned long last_vblank_count[2]; + atomic_t vblank_count[2]; } drm_i915_private_t; enum intel_chip_family { @@ -161,8 +163,6 @@ extern int i915_driver_firstopen(struct drm_device *dev); extern int i915_irq_emit(DRM_IOCTL_ARGS); extern int i915_irq_wait(DRM_IOCTL_ARGS); -extern int i915_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence); -extern int i915_driver_vblank_wait2(drm_device_t *dev, unsigned int *sequence); extern irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS); extern void i915_driver_irq_preinstall(drm_device_t * dev); extern void i915_driver_irq_postinstall(drm_device_t * dev); @@ -173,6 +173,9 @@ extern int i915_emit_irq(drm_device_t * dev); extern void i915_user_irq_on(drm_i915_private_t *dev_priv); extern void i915_user_irq_off(drm_i915_private_t *dev_priv); extern int i915_vblank_swap(DRM_IOCTL_ARGS); +extern void i915_enable_vblank(drm_device_t *dev, int crtc); +extern void i915_disable_vblank(drm_device_t *dev, int crtc); +extern u32 i915_update_vblank_count(drm_device_t *dev, int crtc); /* i915_mem.c */ extern int i915_mem_alloc(DRM_IOCTL_ARGS); @@ -271,6 +274,36 @@ extern int i915_wait_ring(drm_device_t * dev, int n, const char *caller); #define I915REG_PIPEASTAT 0x70024 #define I915REG_PIPEBSTAT 0x71024 +/* + * The two pipe frame counter registers are not synchronized, so + * reading a stable value is somewhat tricky. The following code + * should work: + * + * do { + * high1 = ((INREG(PIPEAFRAMEHIGH) & PIPE_FRAME_HIGH_MASK) >> + * PIPE_FRAME_HIGH_SHIFT; + * low1 = ((INREG(PIPEAFRAMEPIXEL) & PIPE_FRAME_LOW_MASK) >> + * PIPE_FRAME_LOW_SHIFT); + * high2 = ((INREG(PIPEAFRAMEHIGH) & PIPE_FRAME_HIGH_MASK) >> + * PIPE_FRAME_HIGH_SHIFT); + * } while (high1 != high2); + * frame = (high1 << 8) | low1; + */ +#define PIPEAFRAMEHIGH 0x70040 +#define PIPEBFRAMEHIGH 0x71040 +#define PIPE_FRAME_HIGH_MASK 0x0000ffff +#define PIPE_FRAME_HIGH_SHIFT 0 +#define PIPEAFRAMEPIXEL 0x70044 +#define PIPEBFRAMEPIXEL 0x71044 + +#define PIPE_FRAME_LOW_MASK 0xff000000 +#define PIPE_FRAME_LOW_SHIFT 24 +/* + * Pixel within the current frame is counted in the PIPEAFRAMEPIXEL register + * and is 24 bits wide. + */ +#define PIPE_PIXEL_MASK 0x00ffffff +#define PIPE_PIXEL_SHIFT 0 #define I915_VBLANK_INTERRUPT_ENABLE (1UL<<17) #define I915_VBLANK_CLEAR (1UL<<1) diff --git a/shared-core/i915_irq.c b/shared-core/i915_irq.c index dc00f98..bfcdc14 100644 --- a/shared-core/i915_irq.c +++ b/shared-core/i915_irq.c @@ -431,12 +431,12 @@ static int i915_driver_vblank_do_wait(drm_device_t *dev, unsigned int *sequence, return ret; } -int i915_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence) +int i915_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence, int relative) { return i915_driver_vblank_do_wait(dev, sequence, &dev->vbl_received); } -int i915_driver_vblank_wait2(drm_device_t *dev, unsigned int *sequence) +int i915_driver_vblank_wait2(drm_device_t *dev, unsigned int *sequence, int relative) { return i915_driver_vblank_do_wait(dev, sequence, &dev->vbl_received2); } @@ -489,15 +489,95 @@ int i915_irq_wait(DRM_IOCTL_ARGS) return i915_wait_irq(dev, irqwait.irq_seq); } -static void i915_enable_interrupt (drm_device_t *dev) +void i915_enable_vblank(drm_device_t *dev, int crtc) { drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; - dev_priv->irq_enable_reg = USER_INT_FLAG; - if (dev_priv->vblank_pipe & DRM_I915_VBLANK_PIPE_A) + switch (crtc) { + case 0: dev_priv->irq_enable_reg |= VSYNC_PIPEA_FLAG; - if (dev_priv->vblank_pipe & DRM_I915_VBLANK_PIPE_B) + break; + case 1: dev_priv->irq_enable_reg |= VSYNC_PIPEB_FLAG; + break; + default: + DRM_ERROR("tried to enable vblank on non-existent crtc %d\n", + crtc); + break; + } + + I915_WRITE16(I915REG_INT_ENABLE_R, dev_priv->irq_enable_reg); +} + +void i915_disable_vblank(drm_device_t *dev, int crtc) +{ + drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; + + switch (crtc) { + case 0: + dev_priv->irq_enable_reg &= ~VSYNC_PIPEA_FLAG; + break; + case 1: + dev_priv->irq_enable_reg &= ~VSYNC_PIPEB_FLAG; + break; + default: + DRM_ERROR("tried to disable vblank on non-existent crtc %d\n", + crtc); + break; + } + + I915_WRITE16(I915REG_INT_ENABLE_R, dev_priv->irq_enable_reg); +} + +u32 i915_update_vblank_count(drm_device_t *dev, int crtc) +{ + drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; + unsigned long high_frame = crtc ? PIPEBFRAMEHIGH : PIPEAFRAMEHIGH; + unsigned long low_frame = crtc ? PIPEBFRAMEPIXEL : PIPEAFRAMEPIXEL; + unsigned long irqflags; + u32 high1, high2, low, count, diff; + + /* + * 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; + + /* + * Now update the appropriate counter. + * Assume we haven't missed a full 24 bits of vblank + * events, or that it won't matter if they're not accounted + * for when we adjust for wrapping. + * FIXME: if count was zero'd due to modeset, need to rebase + */ + spin_lock_irqsave(&dev->vbl_lock, irqflags); + if (count < dev_priv->last_vblank_count[crtc]) { + diff = 0xffffff - dev_priv->last_vblank_count[crtc]; + diff += count; + } else { + diff = count - dev_priv->last_vblank_count[crtc]; + } + dev_priv->last_vblank_count[crtc] = count; + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); + atomic_add(diff, &dev_priv->vblank_count[crtc]); + return atomic_read(&dev_priv->vblank_count[crtc]); +} + +static void i915_enable_interrupt (drm_device_t *dev) +{ + drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; + + dev_priv->irq_enable_reg |= USER_INT_FLAG; I915_WRITE16(I915REG_INT_ENABLE_R, dev_priv->irq_enable_reg); dev_priv->irq_enabled = 1; @@ -714,6 +794,7 @@ void i915_driver_irq_preinstall(drm_device_t * dev) void i915_driver_irq_postinstall(drm_device_t * dev) { drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; + int i; spin_lock_init(&dev_priv->swaps_lock); INIT_LIST_HEAD(&dev_priv->vbl_swaps.head); @@ -721,6 +802,12 @@ void i915_driver_irq_postinstall(drm_device_t * dev) dev_priv->user_irq_lock = SPIN_LOCK_UNLOCKED; dev_priv->user_irq_refcount = 0; + dev_priv->irq_enable_reg = 0; + /* Zero per-crtc vblank stuff */ + for (i = 0; i < 2; i++) { + atomic_set(&dev_priv->vblank_count[i], 0); + dev_priv->last_vblank_count[i] = 0; + } i915_enable_interrupt(dev); DRM_INIT_WAITQUEUE(&dev_priv->irq_queue);