---
drivers/gpu/drm/Makefile | 3 +-
drivers/gpu/drm/drm_vblank_timer.c | 100 +++++++++++++++++++++++++++++
include/drm/drm_vblank_timer.h | 26 ++++++++
3 files changed, 128 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/drm/drm_vblank_timer.c
create mode 100644 include/drm/drm_vblank_timer.h
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index b5d5561bbe5f..6722e2d1aa7e 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -146,7 +146,8 @@ drm_kms_helper-y := \
drm_plane_helper.o \
drm_probe_helper.o \
drm_self_refresh_helper.o \
- drm_simple_kms_helper.o
+ drm_simple_kms_helper.o \
+ drm_vblank_timer.o
drm_kms_helper-$(CONFIG_DRM_PANEL_BRIDGE) += bridge/panel.o
drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
diff --git a/drivers/gpu/drm/drm_vblank_timer.c
b/drivers/gpu/drm/drm_vblank_timer.c
new file mode 100644
index 000000000000..be46d3135c8e
--- /dev/null
+++ b/drivers/gpu/drm/drm_vblank_timer.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <linux/hrtimer.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_vblank_timer.h>
+
+static enum hrtimer_restart drm_vblank_timer_function(struct hrtimer
*timer)
+{
+ struct drm_vblank_timer *vtimer = container_of(timer, struct
drm_vblank_timer, timer);
+ struct drm_crtc *crtc = vtimer->crtc;
+ struct drm_device *dev = crtc->dev;
+ u64 ret_overrun;
+ bool succ;
+
+ ret_overrun = hrtimer_forward_now(&vtimer->timer,
vtimer->period_ns);
+ if (ret_overrun != 1)
+ drm_warn(dev, "vblank timer overrun\n");
+
+ if (vtimer->crtc_handle_vblank)
+ succ = vtimer->crtc_handle_vblank(crtc);
+ else
+ succ = drm_crtc_handle_vblank(crtc);
+ if (!succ)
+ return HRTIMER_NORESTART;
+
+ return HRTIMER_RESTART;
+}
+
+static void drmm_vblank_timer_release(struct drm_device *dev, void
*res)
+{
+ struct drm_vblank_timer *vtimer = res;
+
+ hrtimer_cancel(&vtimer->timer);
+}
+
+int drmm_vblank_timer_init(struct drm_vblank_timer *vtimer, struct
drm_crtc *crtc,
+ bool (*crtc_handle_vblank)(struct drm_crtc *crtc))
+{
+ struct hrtimer *timer = &vtimer->timer;
+
+ vtimer->crtc = crtc;
+ vtimer->crtc_handle_vblank = crtc_handle_vblank;
+
+ hrtimer_setup(timer, drm_vblank_timer_function, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
+
+ return drmm_add_action_or_reset(crtc->dev,
drmm_vblank_timer_release, vtimer);
+}
+EXPORT_SYMBOL(drmm_vblank_timer_init);
+
+void drm_vblank_timer_start(struct drm_vblank_timer *vtimer)
+{
+ struct drm_crtc *crtc = vtimer->crtc;
+ struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
+
+ drm_calc_timestamping_constants(crtc, &crtc->mode);
+
+ vtimer->period_ns = ktime_set(0, vblank->framedur_ns);
+ hrtimer_start(&vtimer->timer, vtimer->period_ns, HRTIMER_MODE_REL);
+}
+EXPORT_SYMBOL(drm_vblank_timer_start);
+
+void drm_vblank_timer_cancel(struct drm_vblank_timer *vtimer)
+{
+ hrtimer_cancel(&vtimer->timer);
+}
+EXPORT_SYMBOL(drm_vblank_timer_cancel);
+
+bool drm_vblank_timer_get_vblank_timestamp(struct drm_vblank_timer
*vtimer,
+ int *max_error, ktime_t *vblank_time,
+ bool in_vblank_irq)
+{
+ struct drm_crtc *crtc = vtimer->crtc;
+ struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
+
+ if (!READ_ONCE(vblank->enabled)) {
+ *vblank_time = ktime_get();
+ return true;
+ }
+
+ *vblank_time = READ_ONCE(vtimer->timer.node.expires);
+
+ if (WARN_ON(*vblank_time == vblank->time))
+ return true;
+
+ /*
+ * To prevent races we roll the hrtimer forward before we do any
+ * interrupt processing - this is how real hw works (the
interrupt is
+ * only generated after all the vblank registers are updated)
and what
+ * the vblank core expects. Therefore we need to always correct the
+ * timestampe by one frame.
+ */
+ *vblank_time -= vtimer->period_ns;
+
+ return true;
+}
+EXPORT_SYMBOL(drm_vblank_timer_get_vblank_timestamp);
diff --git a/include/drm/drm_vblank_timer.h
b/include/drm/drm_vblank_timer.h
new file mode 100644
index 000000000000..0b827ff1f59c
--- /dev/null
+++ b/include/drm/drm_vblank_timer.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+#ifndef _DRM_VBLANK_TIMER_H_
+#define _DRM_VBLANK_TIMER_H_
+
+#include <linux/hrtimer_types.h>
+#include <linux/types.h>
+
+struct drm_crtc;
+
+struct drm_vblank_timer {
+ struct drm_crtc *crtc;
+ bool (*crtc_handle_vblank)(struct drm_crtc *crtc);
+ ktime_t period_ns;
+ struct hrtimer timer;
+};
+
+int drmm_vblank_timer_init(struct drm_vblank_timer *vtimer, struct
drm_crtc *crtc,
+ bool (*handle_vblank)(struct drm_crtc *crtc));
+void drm_vblank_timer_start(struct drm_vblank_timer *vtimer);
+void drm_vblank_timer_cancel(struct drm_vblank_timer *vtimer);
+bool drm_vblank_timer_get_vblank_timestamp(struct drm_vblank_timer
*vtimer,
+ int *max_error, ktime_t *vblank_time,
+ bool in_vblank_irq);
+
+#endif