[Nouveau] [RFC PATCH] drm/nv50-nvd0: implement precise vblank timing support on nv50/nvc0.
Maarten Lankhorst
maarten.lankhorst at canonical.com
Mon Aug 12 05:40:09 PDT 2013
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 at lynxeye.de>
Cc: Mario Kleiner <mario.kleiner at tuebingen.mpg.de>
Signed-off-by: Maarten Lankhorst <maarten.lankhorst at canonical.com>
---
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c
index c168ae3..96268b7 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c
@@ -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;
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h
index 1ae6ceb..e3900ce 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h
@@ -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
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c
index d8c74c0..df357cf 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c
@@ -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;
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c
index a66f949..38eafdf 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c
@@ -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;
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c
index 6cf8eef..4f02b8d 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c
@@ -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;
diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c b/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c
index b754131..186f22e 100644
--- a/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c
+++ b/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c
@@ -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;
diff --git a/drivers/gpu/drm/nouveau/core/include/engine/disp.h b/drivers/gpu/drm/nouveau/core/include/engine/disp.h
index 4b21fab..8ba03db 100644
--- a/drivers/gpu/drm/nouveau/core/include/engine/disp.h
+++ b/drivers/gpu/drm/nouveau/core/include/engine/disp.h
@@ -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 *
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
index 2573604..edd8d07 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.c
+++ b/drivers/gpu/drm/nouveau/nouveau_display.c
@@ -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)
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index bd301f4..9a73aa2 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -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,
diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c
index 738d7a2..df8e24a 100644
--- a/drivers/gpu/drm/nouveau/nv50_display.c
+++ b/drivers/gpu/drm/nouveau/nv50_display.c
@@ -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;
More information about the Nouveau
mailing list