@@ -1285,6 +1285,57 @@ nv50_disp_intr(struct nouveau_subdev *subdev)
}
}
+u32 nv50_disp_get_vblank_count(struct nouveau_disp *disp, int head)
+{
+ if (head < 0 || head >= 2)
+ return 0;
+
+ return nv_rd32(disp, 0x616340 + head * 0x800) >> 16;
+}
+
+int nv50_disp_get_scanoutpos(struct nouveau_disp *disp, int head, int *vpos, int *hpos)
+{
+ u32 reg, vbias, hbias, vbl_start, vbl_end, hline, vline;
+
+ if (head < 0 || head >= 2)
+ return -1;
+
+ reg = nv_rd32(disp, 0x610ae8 + head * 4);
+ vbias = reg >> 16;
+ hbias = reg & 0xffff;
+
+ vbl_start = nv_rd32(disp, 0x610af0 + head * 4) >> 16;
+ vbl_end = nv_rd32(disp, 0x610af8 + head * 4) >> 16;
+
+ reg = nv_rd32(disp, 0x616340 + head * 0x800) & 0xffff;
+ while (1) {
+ hline = nv_rd32(disp, 0x616344 + head * 0x800) & 0xffff;
+
+ vline = nv_rd32(disp, 0x616340 + head * 0x800) & 0xffff;
+ if (vline == reg)
+ break;
+
+ reg = vline;
+ }
+
+ if((vline >= vbl_start) || (vline < vbias)) {
+ /* we are in vblank so do a neg countdown */
+ vline -= vbias;
+ hline -= hbias;
+
+ if (vline > 0)
+ vline -= vbl_end;
+ } else {
+ /* apply corrective offset */
+ vline -= vbias;
+ hline -= hbias;
+ }
+
+ *vpos = vline;
+ *hpos = hline;
+ return 0;
+}
+
static int
nv50_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
struct nouveau_oclass *oclass, void *data, u32 size,
@@ -1302,6 +1353,9 @@ nv50_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
nv_engine(priv)->sclass = nv50_disp_base_oclass;
nv_engine(priv)->cclass = &nv50_disp_cclass;
nv_subdev(priv)->intr = nv50_disp_intr;
+ priv->base.max_vblank_count = 0xffff;
+ priv->base.get_vblank_count = nv50_disp_get_vblank_count;
+ priv->base.get_scanoutpos = nv50_disp_get_scanoutpos;
INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor);
priv->sclass = nv50_disp_sclass;
priv->head.nr = 2;
@@ -144,4 +144,7 @@ extern struct nouveau_oclass nvd0_disp_cclass;
void nvd0_disp_intr_supervisor(struct work_struct *);
void nvd0_disp_intr(struct nouveau_subdev *);
+u32 nv50_disp_get_vblank_count(struct nouveau_disp *disp, int head);
+int nv50_disp_get_scanoutpos(struct nouveau_disp *disp, int head, int *vpos, int *hpos);
+
#endif
@@ -75,6 +75,9 @@ nv84_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
nv_engine(priv)->sclass = nv84_disp_base_oclass;
nv_engine(priv)->cclass = &nv50_disp_cclass;
nv_subdev(priv)->intr = nv50_disp_intr;
+ priv->base.max_vblank_count = 0xffff;
+ priv->base.get_vblank_count = nv50_disp_get_vblank_count;
+ priv->base.get_scanoutpos = nv50_disp_get_scanoutpos;
INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor);
priv->sclass = nv84_disp_sclass;
priv->head.nr = 2;
@@ -75,6 +75,9 @@ nv94_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
nv_engine(priv)->sclass = nv94_disp_base_oclass;
nv_engine(priv)->cclass = &nv50_disp_cclass;
nv_subdev(priv)->intr = nv50_disp_intr;
+ priv->base.max_vblank_count = 0xffff;
+ priv->base.get_vblank_count = nv50_disp_get_vblank_count;
+ priv->base.get_scanoutpos = nv50_disp_get_scanoutpos;
INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor);
priv->sclass = nv94_disp_sclass;
priv->head.nr = 2;
@@ -62,6 +62,9 @@ nva0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
nv_engine(priv)->sclass = nva0_disp_base_oclass;
nv_engine(priv)->cclass = &nv50_disp_cclass;
nv_subdev(priv)->intr = nv50_disp_intr;
+ priv->base.max_vblank_count = 0xffff;
+ priv->base.get_vblank_count = nv50_disp_get_vblank_count;
+ priv->base.get_scanoutpos = nv50_disp_get_scanoutpos;
INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor);
priv->sclass = nva0_disp_sclass;
priv->head.nr = 2;
@@ -77,6 +77,9 @@ nva3_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
nv_engine(priv)->cclass = &nv50_disp_cclass;
nv_subdev(priv)->intr = nv50_disp_intr;
INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor);
+ priv->base.max_vblank_count = 0xffff;
+ priv->base.get_vblank_count = nv50_disp_get_vblank_count;
+ priv->base.get_scanoutpos = nv50_disp_get_scanoutpos;
priv->sclass = nva3_disp_sclass;
priv->head.nr = 2;
priv->dac.nr = 3;
@@ -9,6 +9,9 @@
struct nouveau_disp {
struct nouveau_engine base;
struct nouveau_event *vblank;
+ u32 (*get_vblank_count)(struct nouveau_disp *disp, int head);
+ int (*get_scanoutpos)(struct nouveau_disp *disp, int head, int *vpos, int *hpos);
+ u32 max_vblank_count;
};
static inline struct nouveau_disp *
@@ -579,7 +579,7 @@ nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
/* Emit a page flip */
if (nv_device(drm->device)->card_type >= NV_50) {
- ret = nv50_display_flip_next(crtc, fb, chan, 0);
+ ret = nv50_display_flip_next(crtc, fb, chan, 1);
if (ret)
goto fail_unreserve;
}
@@ -634,7 +634,7 @@ nouveau_finish_page_flip(struct nouveau_channel *chan,
s = list_first_entry(&fctx->flip, struct nouveau_page_flip_state, head);
if (s->event)
- drm_send_vblank_event(dev, -1, s->event);
+ drm_send_vblank_event(dev, s->crtc, s->event);
list_del(&s->head);
if (ps)
@@ -104,6 +104,62 @@ nouveau_drm_vblank_disable(struct drm_device *dev, int head)
nouveau_event_disable_locked(pdisp->vblank, head, 1);
}
+static u32
+nouveau_drm_vblank_count(struct drm_device *dev, int head)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_disp *pdisp = nouveau_disp(drm->device);
+
+ if (!pdisp->get_vblank_count)
+ return drm_vblank_count(dev, head);
+ return pdisp->get_vblank_count(pdisp, head);
+}
+
+static int
+nouveau_drm_get_scanoutpos(struct drm_device *dev, int head, int *vpos, int *hpos)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_disp *pdisp = nouveau_disp(drm->device);
+ int ret = DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_ACCURATE;
+
+ if (pdisp->get_scanoutpos(pdisp, head, vpos, hpos))
+ return 0;
+
+ if (*vpos < 0)
+ ret |= DRM_SCANOUTPOS_INVBL;
+ return ret;
+}
+
+static int
+nouveau_drm_get_vblank_timestamp(struct drm_device *dev, int crtc,
+ int *max_error,
+ struct timeval *vblank_time,
+ unsigned flags)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_disp *pdisp = nouveau_disp(drm->device);
+ struct drm_crtc *drmcrtc;
+
+ if (!pdisp->get_scanoutpos)
+ return -EOPNOTSUPP;
+
+ list_for_each_entry(drmcrtc, &dev->mode_config.crtc_list, head) {
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(drmcrtc);
+
+ if (nv_crtc->index != crtc)
+ continue;
+
+ /* Helper routine in DRM core does all the work: */
+ return drm_calc_vbltimestamp_from_scanoutpos(dev, crtc,
+ max_error,
+ vblank_time,
+ flags,
+ drmcrtc);
+ }
+ return -EINVAL;
+}
+
+
static u64
nouveau_name(struct pci_dev *pdev)
{
@@ -701,7 +757,9 @@ driver = {
.debugfs_cleanup = nouveau_debugfs_takedown,
#endif
- .get_vblank_counter = drm_vblank_count,
+ .get_vblank_counter = nouveau_drm_vblank_count,
+ .get_scanout_position = nouveau_drm_get_scanoutpos,
+ .get_vblank_timestamp = nouveau_drm_get_vblank_timestamp,
.enable_vblank = nouveau_drm_vblank_enable,
.disable_vblank = nouveau_drm_vblank_disable,
@@ -41,6 +41,7 @@
#include <core/class.h>
#include <engine/disp.h>
+#include <engine/disp.h>
#include <subdev/timer.h>
#include <subdev/bar.h>
#include <subdev/fb.h>
@@ -1030,12 +1031,14 @@ nv50_crtc_commit(struct drm_crtc *crtc)
nv50_crtc_cursor_show_hide(nv_crtc, nv_crtc->cursor.visible, true);
nv50_display_flip_next(crtc, crtc->fb, NULL, 1);
+ drm_vblank_post_modeset(crtc->dev, nv_crtc->index);
}
static bool
nv50_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
+ drm_mode_set_crtcinfo(adjusted_mode, 0);
return true;
}
@@ -1091,9 +1094,12 @@ nv50_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *umode,
vactive = (vactive * 2) + 1;
}
+ drm_vblank_pre_modeset(crtc->dev, nv_crtc->index);
ret = nv50_crtc_swap_fbs(crtc, old_fb);
- if (ret)
+ if (ret) {
+ drm_vblank_post_modeset(crtc->dev, nv_crtc->index);
return ret;
+ }
push = evo_wait(mast, 64);
if (push) {
@@ -2229,9 +2235,18 @@ nv50_display_create(struct drm_device *dev)
struct dcb_table *dcb = &drm->vbios.dcb;
struct drm_connector *connector, *tmp;
struct nv50_disp *disp;
+ struct nouveau_disp *dev_disp;
struct dcb_output *dcbe;
int crtcs, ret, i;
+ dev_disp = nouveau_disp(device);
+ if (!dev_disp) {
+ NV_ERROR(drm, "Cannot enable display engine without display support\n");
+ return -ENODEV;
+ }
+ if (dev_disp->max_vblank_count)
+ dev->max_vblank_count = dev_disp->max_vblank_count;
+
disp = kzalloc(sizeof(*disp), GFP_KERNEL);
if (!disp)
return -ENOMEM;
Not as thoroughly tested as I would like. Newer nvd0 and kepler are unsupported, as I don't know the registers yet. Information of the scanout position is based on Lucas Stach's original patch, with a teak to read vline twice, to prevent a race of hline with vline. Cc: Lucas Stach <dev@lynxeye.de> Cc: Mario Kleiner <mario.kleiner@tuebingen.mpg.de> Signed-off-by: Maarten Lankhorst <maarten.lankhorst@canonical.com> ---