diff options
Diffstat (limited to 'drivers')
105 files changed, 10693 insertions, 5084 deletions
diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index bede10a0340..c6b2e27a446 100644 --- a/drivers/gpu/drm/drm_crtc_helper.c +++ b/drivers/gpu/drm/drm_crtc_helper.c @@ -336,7 +336,7 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc, struct drm_framebuffer *old_fb) { struct drm_device *dev = crtc->dev; - struct drm_display_mode *adjusted_mode, saved_mode; + struct drm_display_mode *adjusted_mode, saved_mode, saved_hwmode; struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; struct drm_encoder_helper_funcs *encoder_funcs; int saved_x, saved_y; @@ -350,6 +350,7 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc, if (!crtc->enabled) return true; + saved_hwmode = crtc->hwmode; saved_mode = crtc->mode; saved_x = crtc->x; saved_y = crtc->y; @@ -427,11 +428,21 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc, } + /* Store real post-adjustment hardware mode. */ + crtc->hwmode = *adjusted_mode; + + /* Calculate and store various constants which + * are later needed by vblank and swap-completion + * timestamping. They are derived from true hwmode. + */ + drm_calc_timestamping_constants(crtc); + /* XXX free adjustedmode */ drm_mode_destroy(dev, adjusted_mode); /* FIXME: add subpixel order */ done: if (!ret) { + crtc->hwmode = saved_hwmode; crtc->mode = saved_mode; crtc->x = saved_x; crtc->y = saved_y; diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 722700d5d73..77c875db5aa 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -40,6 +40,22 @@ #include <linux/slab.h> #include <linux/vgaarb.h> + +/* Access macro for slots in vblank timestamp ringbuffer. */ +#define vblanktimestamp(dev, crtc, count) ( \ + (dev)->_vblank_time[(crtc) * DRM_VBLANKTIME_RBSIZE + \ + ((count) % DRM_VBLANKTIME_RBSIZE)]) + +/* Retry timestamp calculation up to 3 times to satisfy + * drm_timestamp_precision before giving up. + */ +#define DRM_TIMESTAMP_MAXRETRIES 3 + +/* Threshold in nanoseconds for detection of redundant + * vblank irq in drm_handle_vblank(). 1 msec should be ok. + */ +#define DRM_REDUNDANT_VBLIRQ_THRESH_NS 1000000 + /** * Get interrupt from bus id. * @@ -77,6 +93,87 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, return 0; } +/* + * Clear vblank timestamp buffer for a crtc. + */ +static void clear_vblank_timestamps(struct drm_device *dev, int crtc) +{ + memset(&dev->_vblank_time[crtc * DRM_VBLANKTIME_RBSIZE], 0, + DRM_VBLANKTIME_RBSIZE * sizeof(struct timeval)); +} + +/* + * Disable vblank irq's on crtc, make sure that last vblank count + * of hardware and corresponding consistent software vblank counter + * are preserved, even if there are any spurious vblank irq's after + * disable. + */ +static void vblank_disable_and_save(struct drm_device *dev, int crtc) +{ + unsigned long irqflags; + u32 vblcount; + s64 diff_ns; + int vblrc; + struct timeval tvblank; + + /* Prevent vblank irq processing while disabling vblank irqs, + * so no updates of timestamps or count can happen after we've + * disabled. Needed to prevent races in case of delayed irq's. + * Disable preemption, so vblank_time_lock is held as short as + * possible, even under a kernel with PREEMPT_RT patches. + */ + preempt_disable(); + spin_lock_irqsave(&dev->vblank_time_lock, irqflags); + + dev->driver->disable_vblank(dev, crtc); + dev->vblank_enabled[crtc] = 0; + + /* No further vblank irq's will be processed after + * this point. Get current hardware vblank count and + * vblank timestamp, repeat until they are consistent. + * + * FIXME: There is still a race condition here and in + * drm_update_vblank_count() which can cause off-by-one + * reinitialization of software vblank counter. If gpu + * vblank counter doesn't increment exactly at the leading + * edge of a vblank interval, then we can lose 1 count if + * we happen to execute between start of vblank and the + * delayed gpu counter increment. + */ + do { + dev->last_vblank[crtc] = dev->driver->get_vblank_counter(dev, crtc); + vblrc = drm_get_last_vbltimestamp(dev, crtc, &tvblank, 0); + } while (dev->last_vblank[crtc] != dev->driver->get_vblank_counter(dev, crtc)); + + /* Compute time difference to stored timestamp of last vblank + * as updated by last invocation of drm_handle_vblank() in vblank irq. + */ + vblcount = atomic_read(&dev->_vblank_count[crtc]); + diff_ns = timeval_to_ns(&tvblank) - + timeval_to_ns(&vblanktimestamp(dev, crtc, vblcount)); + + /* If there is at least 1 msec difference between the last stored + * timestamp and tvblank, then we are currently executing our + * disable inside a new vblank interval, the tvblank timestamp + * corresponds to this new vblank interval and the irq handler + * for this vblank didn't run yet and won't run due to our disable. + * Therefore we need to do the job of drm_handle_vblank() and + * increment the vblank counter by one to account for this vblank. + * + * Skip this step if there isn't any high precision timestamp + * available. In that case we can't account for this and just |