aboutsummaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/nouveau
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/nouveau')
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/crtc.c33
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/disp.c3
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/overlay.c6
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/Kbuild4
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/atom.h21
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/base507c.c2
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/core.h4
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/core507d.c2
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/core907d.c3
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/core917d.c3
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/corec37d.c3
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/corec57d.c3
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/crc.c751
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/crc.h131
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/crc907d.c139
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/crcc37d.c153
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/disp.c69
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/disp.h24
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/handles.h16
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/head.c98
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/head.h12
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/head907d.c14
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/headc37d.c27
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/headc57d.c20
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/ovly507e.c2
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/wndw.c5
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/wndwc37e.c2
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_abi16.c10
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_bo.c25
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_bo.h3
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_chan.c2
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_connector.c5
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_display.c66
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_dmem.c2
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_fbcon.c2
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_gem.c24
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_ttm.c8
37 files changed, 1528 insertions, 169 deletions
diff --git a/drivers/gpu/drm/nouveau/dispnv04/crtc.c b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
index 2de589caf508..4989627b7802 100644
--- a/drivers/gpu/drm/nouveau/dispnv04/crtc.c
+++ b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
@@ -44,6 +44,9 @@
#include <subdev/bios/pll.h>
#include <subdev/clk.h>
+#include <nvif/event.h>
+#include <nvif/cl0046.h>
+
static int
nv04_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
struct drm_framebuffer *old_fb);
@@ -756,6 +759,7 @@ static void nv_crtc_destroy(struct drm_crtc *crtc)
nouveau_bo_unmap(nv_crtc->cursor.nvbo);
nouveau_bo_unpin(nv_crtc->cursor.nvbo);
nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo);
+ nvif_notify_fini(&nv_crtc->vblank);
kfree(nv_crtc);
}
@@ -845,7 +849,7 @@ nv04_crtc_do_mode_set_base(struct drm_crtc *crtc,
}
nvbo = nouveau_gem_object(drm_fb->obj[0]);
- nv_crtc->fb.offset = nvbo->bo.offset;
+ nv_crtc->fb.offset = nvbo->offset;
if (nv_crtc->lut.depth != drm_fb->format->depth) {
nv_crtc->lut.depth = drm_fb->format->depth;
@@ -1013,11 +1017,11 @@ nv04_crtc_cursor_set(struct drm_crtc *crtc, struct drm_file *file_priv,
nv04_cursor_upload(dev, cursor, nv_crtc->cursor.nvbo);
nouveau_bo_unmap(cursor);
- nv_crtc->cursor.offset = nv_crtc->cursor.nvbo->bo.offset;
+ nv_crtc->cursor.offset = nv_crtc->cursor.nvbo->offset;
nv_crtc->cursor.set_offset(nv_crtc, nv_crtc->cursor.offset);
nv_crtc->cursor.show(nv_crtc, true);
out:
- drm_gem_object_put_unlocked(gem);
+ drm_gem_object_put(gem);
return ret;
}
@@ -1192,7 +1196,7 @@ nv04_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
/* Initialize a page flip struct */
*s = (struct nv04_page_flip_state)
{ { }, event, crtc, fb->format->cpp[0] * 8, fb->pitches[0],
- new_bo->bo.offset };
+ new_bo->offset };
/* Keep vblanks on during flip, for the target crtc of this flip */
drm_crtc_vblank_get(crtc);
@@ -1297,9 +1301,19 @@ create_primary_plane(struct drm_device *dev)
return primary;
}
+static int nv04_crtc_vblank_handler(struct nvif_notify *notify)
+{
+ struct nouveau_crtc *nv_crtc =
+ container_of(notify, struct nouveau_crtc, vblank);
+
+ drm_crtc_handle_vblank(&nv_crtc->base);
+ return NVIF_NOTIFY_KEEP;
+}
+
int
nv04_crtc_create(struct drm_device *dev, int crtc_num)
{
+ struct nouveau_display *disp = nouveau_display(dev);
struct nouveau_crtc *nv_crtc;
int ret;
@@ -1337,5 +1351,14 @@ nv04_crtc_create(struct drm_device *dev, int crtc_num)
nv04_cursor_init(nv_crtc);
- return 0;
+ ret = nvif_notify_init(&disp->disp.object, nv04_crtc_vblank_handler,
+ false, NV04_DISP_NTFY_VBLANK,
+ &(struct nvif_notify_head_req_v0) {
+ .head = nv_crtc->index,
+ },
+ sizeof(struct nvif_notify_head_req_v0),
+ sizeof(struct nvif_notify_head_rep_v0),
+ &nv_crtc->vblank);
+
+ return ret;
}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/disp.c b/drivers/gpu/drm/nouveau/dispnv04/disp.c
index 0f4ebefed1fd..76be805fc488 100644
--- a/drivers/gpu/drm/nouveau/dispnv04/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv04/disp.c
@@ -152,7 +152,8 @@ nv04_display_init(struct drm_device *dev, bool resume, bool runtime)
continue;
if (nv_crtc->cursor.set_offset)
- nv_crtc->cursor.set_offset(nv_crtc, nv_crtc->cursor.nvbo->bo.offset);
+ nv_crtc->cursor.set_offset(nv_crtc,
+ nv_crtc->cursor.nvbo->offset);
nv_crtc->cursor.set_pos(nv_crtc, nv_crtc->cursor_saved_x,
nv_crtc->cursor_saved_y);
}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/overlay.c b/drivers/gpu/drm/nouveau/dispnv04/overlay.c
index 6248fd1dbc6d..193ba9498f3d 100644
--- a/drivers/gpu/drm/nouveau/dispnv04/overlay.c
+++ b/drivers/gpu/drm/nouveau/dispnv04/overlay.c
@@ -152,7 +152,7 @@ nv10_update_plane(struct drm_plane *plane, struct drm_crtc *crtc,
nvif_mask(dev, NV_PCRTC_ENGINE_CTRL + soff2, NV_CRTC_FSEL_OVERLAY, 0);
nvif_wr32(dev, NV_PVIDEO_BASE(flip), 0);
- nvif_wr32(dev, NV_PVIDEO_OFFSET_BUFF(flip), nvbo->bo.offset);
+ nvif_wr32(dev, NV_PVIDEO_OFFSET_BUFF(flip), nvbo->offset);
nvif_wr32(dev, NV_PVIDEO_SIZE_IN(flip), src_h << 16 | src_w);
nvif_wr32(dev, NV_PVIDEO_POINT_IN(flip), src_y << 16 | src_x);
nvif_wr32(dev, NV_PVIDEO_DS_DX(flip), (src_w << 20) / crtc_w);
@@ -174,7 +174,7 @@ nv10_update_plane(struct drm_plane *plane, struct drm_crtc *crtc,
if (format & NV_PVIDEO_FORMAT_PLANAR) {
nvif_wr32(dev, NV_PVIDEO_UVPLANE_BASE(flip), 0);
nvif_wr32(dev, NV_PVIDEO_UVPLANE_OFFSET_BUFF(flip),
- nvbo->bo.offset + fb->offsets[1]);
+ nvbo->offset + fb->offsets[1]);
}
nvif_wr32(dev, NV_PVIDEO_FORMAT(flip), format | fb->pitches[0]);
nvif_wr32(dev, NV_PVIDEO_STOP, 0);
@@ -399,7 +399,7 @@ nv04_update_plane(struct drm_plane *plane, struct drm_crtc *crtc,
for (i = 0; i < 2; i++) {
nvif_wr32(dev, NV_PVIDEO_BUFF0_START_ADDRESS + 4 * i,
- nvbo->bo.offset);
+ nvbo->offset);
nvif_wr32(dev, NV_PVIDEO_BUFF0_PITCH_LENGTH + 4 * i,
fb->pitches[0]);
nvif_wr32(dev, NV_PVIDEO_BUFF0_OFFSET + 4 * i, 0);
diff --git a/drivers/gpu/drm/nouveau/dispnv50/Kbuild b/drivers/gpu/drm/nouveau/dispnv50/Kbuild
index e0c435eae664..6fdddb266fb1 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/Kbuild
+++ b/drivers/gpu/drm/nouveau/dispnv50/Kbuild
@@ -10,6 +10,10 @@ nouveau-y += dispnv50/core917d.o
nouveau-y += dispnv50/corec37d.o
nouveau-y += dispnv50/corec57d.o
+nouveau-$(CONFIG_DEBUG_FS) += dispnv50/crc.o
+nouveau-$(CONFIG_DEBUG_FS) += dispnv50/crc907d.o
+nouveau-$(CONFIG_DEBUG_FS) += dispnv50/crcc37d.o
+
nouveau-y += dispnv50/dac507d.o
nouveau-y += dispnv50/dac907d.o
diff --git a/drivers/gpu/drm/nouveau/dispnv50/atom.h b/drivers/gpu/drm/nouveau/dispnv50/atom.h
index 24f7700768da..3d82b3c67dec 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/atom.h
+++ b/drivers/gpu/drm/nouveau/dispnv50/atom.h
@@ -2,6 +2,9 @@
#define __NV50_KMS_ATOM_H__
#define nv50_atom(p) container_of((p), struct nv50_atom, state)
#include <drm/drm_atomic.h>
+#include "crc.h"
+
+struct nouveau_encoder;
struct nv50_atom {
struct drm_atomic_state state;
@@ -18,6 +21,7 @@ struct nv50_head_atom {
struct {
u32 mask;
+ u32 owned;
u32 olut;
} wndw;
@@ -114,9 +118,12 @@ struct nv50_head_atom {
u8 nhsync:1;
u8 nvsync:1;
u8 depth:4;
+ u8 crc_raster:2;
u8 bpc;
} or;
+ struct nv50_crc_atom crc;
+
/* Currently only used for MST */
struct {
int pbn;
@@ -134,6 +141,7 @@ struct nv50_head_atom {
bool ovly:1;
bool dither:1;
bool procamp:1;
+ bool crc:1;
bool or:1;
};
u16 mask;
@@ -149,6 +157,19 @@ nv50_head_atom_get(struct drm_atomic_state *state, struct drm_crtc *crtc)
return nv50_head_atom(statec);
}
+static inline struct drm_encoder *
+nv50_head_atom_get_encoder(struct nv50_head_atom *atom)
+{
+ struct drm_encoder *encoder = NULL;
+
+ /* We only ever have a single encoder */
+ drm_for_each_encoder_mask(encoder, atom->state.crtc->dev,
+ atom->state.encoder_mask)
+ break;
+
+ return encoder;
+}
+
#define nv50_wndw_atom(p) container_of((p), struct nv50_wndw_atom, state)
struct nv50_wndw_atom {
diff --git a/drivers/gpu/drm/nouveau/dispnv50/base507c.c b/drivers/gpu/drm/nouveau/dispnv50/base507c.c
index 511258bfbcbc..ba20a7722900 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/base507c.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/base507c.c
@@ -276,7 +276,7 @@ base507c_new_(const struct nv50_wndw_func *func, const u32 *format,
ret = nv50_dmac_create(&drm->client.device, &disp->disp.object,
&oclass, head, &args, sizeof(args),
- disp50->sync->bo.offset, &wndw->wndw);
+ disp50->sync->offset, &wndw->wndw);
if (ret) {
NV_ERROR(drm, "base%04x allocation failed: %d\n", oclass, ret);
return ret;
diff --git a/drivers/gpu/drm/nouveau/dispnv50/core.h b/drivers/gpu/drm/nouveau/dispnv50/core.h
index 99157dc94d23..e021cb340569 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/core.h
+++ b/drivers/gpu/drm/nouveau/dispnv50/core.h
@@ -2,6 +2,7 @@
#define __NV50_KMS_CORE_H__
#include "disp.h"
#include "atom.h"
+#include "crc.h"
#include <nouveau_encoder.h>
struct nv50_core {
@@ -26,6 +27,9 @@ struct nv50_core_func {
} wndw;
const struct nv50_head_func *head;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ const struct nv50_crc_func *crc;
+#endif
const struct nv50_outp_func {
void (*ctrl)(struct nv50_core *, int or, u32 ctrl,
struct nv50_head_atom *);
diff --git a/drivers/gpu/drm/nouveau/dispnv50/core507d.c b/drivers/gpu/drm/nouveau/dispnv50/core507d.c
index e341f572c269..1d66f694b945 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/core507d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/core507d.c
@@ -115,7 +115,7 @@ core507d_new_(const struct nv50_core_func *func, struct nouveau_drm *drm,
ret = nv50_dmac_create(&drm->client.device, &disp->disp->object,
&oclass, 0, &args, sizeof(args),
- disp->sync->bo.offset, &core->chan);
+ disp->sync->offset, &core->chan);
if (ret) {
NV_ERROR(drm, "core%04x allocation failed: %d\n", oclass, ret);
return ret;
diff --git a/drivers/gpu/drm/nouveau/dispnv50/core907d.c b/drivers/gpu/drm/nouveau/dispnv50/core907d.c
index 271629832629..b17c03529c78 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/core907d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/core907d.c
@@ -30,6 +30,9 @@ core907d = {
.ntfy_wait_done = core507d_ntfy_wait_done,
.update = core507d_update,
.head = &head907d,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ .crc = &crc907d,
+#endif
.dac = &dac907d,
.sor = &sor907d,
};
diff --git a/drivers/gpu/drm/nouveau/dispnv50/core917d.c b/drivers/gpu/drm/nouveau/dispnv50/core917d.c
index 5cc072d4c30f..66846f372080 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/core917d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/core917d.c
@@ -30,6 +30,9 @@ core917d = {
.ntfy_wait_done = core507d_ntfy_wait_done,
.update = core507d_update,
.head = &head917d,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ .crc = &crc907d,
+#endif
.dac = &dac907d,
.sor = &sor907d,
};
diff --git a/drivers/gpu/drm/nouveau/dispnv50/corec37d.c b/drivers/gpu/drm/nouveau/dispnv50/corec37d.c
index e0c8811fb8e4..ec83189a1d48 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/corec37d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/corec37d.c
@@ -142,6 +142,9 @@ corec37d = {
.wndw.owner = corec37d_wndw_owner,
.head = &headc37d,
.sor = &sorc37d,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ .crc = &crcc37d,
+#endif
};
int
diff --git a/drivers/gpu/drm/nouveau/dispnv50/corec57d.c b/drivers/gpu/drm/nouveau/dispnv50/corec57d.c
index 10ba9e9e4ae6..e1c11eba0ce1 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/corec57d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/corec57d.c
@@ -52,6 +52,9 @@ corec57d = {
.wndw.owner = corec37d_wndw_owner,
.head = &headc57d,
.sor = &sorc37d,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ .crc = &crcc37d,
+#endif
};
int
diff --git a/drivers/gpu/drm/nouveau/dispnv50/crc.c b/drivers/gpu/drm/nouveau/dispnv50/crc.c
new file mode 100644
index 000000000000..f17fb6d56757
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv50/crc.c
@@ -0,0 +1,751 @@
+// SPDX-License-Identifier: MIT
+#include <linux/string.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_vblank_work.h>
+
+#include <nvif/class.h>
+#include <nvif/cl0002.h>
+#include <nvif/timer.h>
+
+#include "nouveau_drv.h"
+#include "core.h"
+#include "head.h"
+#include "wndw.h"
+#include "handles.h"
+#include "crc.h"
+
+static const char * const nv50_crc_sources[] = {
+ [NV50_CRC_SOURCE_NONE] = "none",
+ [NV50_CRC_SOURCE_AUTO] = "auto",
+ [NV50_CRC_SOURCE_RG] = "rg",
+ [NV50_CRC_SOURCE_OUTP_ACTIVE] = "outp-active",
+ [NV50_CRC_SOURCE_OUTP_COMPLETE] = "outp-complete",
+ [NV50_CRC_SOURCE_OUTP_INACTIVE] = "outp-inactive",
+};
+
+static int nv50_crc_parse_source(const char *buf, enum nv50_crc_source *s)
+{
+ int i;
+
+ if (!buf) {
+ *s = NV50_CRC_SOURCE_NONE;
+ return 0;
+ }
+
+ i = match_string(nv50_crc_sources, ARRAY_SIZE(nv50_crc_sources), buf);
+ if (i < 0)
+ return i;
+
+ *s = i;
+ return 0;
+}
+
+int
+nv50_crc_verify_source(struct drm_crtc *crtc, const char *source_name,
+ size_t *values_cnt)
+{
+ struct nouveau_drm *drm = nouveau_drm(crtc->dev);
+ enum nv50_crc_source source;
+
+ if (nv50_crc_parse_source(source_name, &source) < 0) {
+ NV_DEBUG(drm, "unknown source %s\n", source_name);
+ return -EINVAL;
+ }
+
+ *values_cnt = 1;
+ return 0;
+}
+
+const char *const *nv50_crc_get_sources(struct drm_crtc *crtc, size_t *count)
+{
+ *count = ARRAY_SIZE(nv50_crc_sources);
+ return nv50_crc_sources;
+}
+
+static void
+nv50_crc_program_ctx(struct nv50_head *head,
+ struct nv50_crc_notifier_ctx *ctx)
+{
+ struct nv50_disp *disp = nv50_disp(head->base.base.dev);
+ struct nv50_core *core = disp->core;
+ u32 interlock[NV50_DISP_INTERLOCK__SIZE] = { 0 };
+
+ core->func->crc->set_ctx(head, ctx);
+ core->func->update(core, interlock, false);
+}
+
+static void nv50_crc_ctx_flip_work(struct kthread_work *base)
+{
+ struct drm_vblank_work *work = to_drm_vblank_work(base);
+ struct nv50_crc *crc = container_of(work, struct nv50_crc, flip_work);
+ struct nv50_head *head = container_of(crc, struct nv50_head, crc);
+ struct drm_crtc *crtc = &head->base.base;
+ struct nv50_disp *disp = nv50_disp(crtc->dev);
+ u8 new_idx = crc->ctx_idx ^ 1;
+
+ /*
+ * We don't want to accidentally wait for longer then the vblank, so
+ * try again for the next vblank if we don't grab the lock
+ */
+ if (!mutex_trylock(&disp->mutex)) {
+ DRM_DEV_DEBUG_KMS(crtc->dev->dev,
+ "Lock contended, delaying CRC ctx flip for head-%d\n",
+ head->base.index);
+ drm_vblank_work_schedule(work,
+ drm_crtc_vblank_count(crtc) + 1,
+ true);
+ return;
+ }
+
+ DRM_DEV_DEBUG_KMS(crtc->dev->dev,
+ "Flipping notifier ctx for head %d (%d -> %d)\n",
+ drm_crtc_index(crtc), crc->ctx_idx, new_idx);
+
+ nv50_crc_program_ctx(head, NULL);
+ nv50_crc_program_ctx(head, &crc->ctx[new_idx]);
+ mutex_unlock(&disp->mutex);
+
+ spin_lock_irq(&crc->lock);
+ crc->ctx_changed = true;
+ spin_unlock_irq(&crc->lock);
+}
+
+static inline void nv50_crc_reset_ctx(struct nv50_crc_notifier_ctx *ctx)
+{
+ memset_io(ctx->mem.object.map.ptr, 0, ctx->mem.object.map.size);
+}
+
+static void
+nv50_crc_get_entries(struct nv50_head *head,
+ const struct nv50_crc_func *func,
+ enum nv50_crc_source source)
+{
+ struct drm_crtc *crtc = &head->base.base;
+ struct nv50_crc *crc = &head->crc;
+ u32 output_crc;
+
+ while (crc->entry_idx < func->num_entries) {
+ /*
+ * While Nvidia's documentation says CRCs are written on each
+ * subsequent vblank after being enabled, in practice they
+ * aren't written immediately.
+ */
+ output_crc = func->get_entry(head, &crc->ctx[crc->ctx_idx],
+ source, crc->entry_idx);
+ if (!output_crc)
+ return;
+
+ drm_crtc_add_crc_entry(crtc, true, crc->frame, &output_crc);
+ crc->frame++;
+ crc->entry_idx++;
+ }
+}
+
+void nv50_crc_handle_vblank(struct nv50_head *head)
+{
+ struct drm_crtc *crtc = &head->base.base;
+ struct nv50_crc *crc = &head->crc;
+ const struct nv50_crc_func *func =
+ nv50_disp(head->base.base.dev)->core->func->crc;
+ struct nv50_crc_notifier_ctx *ctx;
+ bool need_reschedule = false;
+
+ if (!func)
+ return;
+
+ /*
+ * We don't lose events if we aren't able to report CRCs until the
+ * next vblank, so only report CRCs if the locks we need aren't
+ * contended to prevent missing an actual vblank event
+ */
+ if (!spin_trylock(&crc->lock))
+ return;
+
+ if (!crc->src)
+ goto out;
+
+ ctx = &crc->ctx[crc->ctx_idx];
+ if (crc->ctx_changed && func->ctx_finished(head, ctx)) {
+ nv50_crc_get_entries(head, func, crc->src);
+
+ crc->ctx_idx ^= 1;
+ crc->entry_idx = 0;
+ crc->ctx_changed = false;
+
+ /*
+ * Unfortunately when notifier contexts are changed during CRC
+ * capture, we will inevitably lose the CRC entry for the
+ * frame where the hardware actually latched onto the first
+ * UPDATE. According to Nvidia's hardware engineers, there's
+ * no workaround for this.
+ *
+ * Now, we could try to be smart here and calculate the number
+ * of missed CRCs based on audit timestamps, but those were
+ * removed starting with volta. Since we always flush our
+ * updates back-to-back without waiting, we'll just be
+ * optimistic and assume we always miss exactly one frame.
+ */
+ DRM_DEV_DEBUG_KMS(head->base.base.dev->dev,
+ "Notifier ctx flip for head-%d finished, lost CRC for frame %llu\n",
+ head->base.index, crc->frame);
+ crc->frame++;
+
+ nv50_crc_reset_ctx(ctx);
+ need_reschedule = true;
+ }
+
+ nv50_crc_get_entries(head, func, crc->src);
+
+ if (need_reschedule)
+ drm_vblank_work_schedule(&crc->flip_work,
+ drm_crtc_vblank_count(crtc)
+ + crc->flip_threshold
+ - crc->entry_idx,
+ true);
+
+out:
+ spin_unlock(&crc->lock);
+}
+
+static void nv50_crc_wait_ctx_finished(struct nv50_head *head,
+ const struct nv50_crc_func *func,
+ struct nv50_crc_notifier_ctx *ctx)
+{
+ struct drm_device *dev = head->base.base.dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ s64 ret;
+
+ ret = nvif_msec(&drm->client.device, 50,
+ if (func->ctx_finished(head, ctx)) break;);
+ if (ret == -ETIMEDOUT)
+ NV_ERROR(drm,
+ "CRC notifier ctx for head %d not finished after 50ms\n",
+ head->base.index);
+ else if (ret)
+ NV_ATOMIC(drm,
+ "CRC notifier ctx for head-%d finished after %lldns\n",
+ head->base.index, ret);
+}
+
+void nv50_crc_atomic_stop_reporting(struct drm_atomic_state *state)
+{
+ struct drm_crtc_state *crtc_state;
+ struct drm_crtc *crtc;
+ int i;
+
+ for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
+ struct nv50_head *head = nv50_head(crtc);
+ struct nv50_head_atom *asyh = nv50_head_atom(crtc_state);
+ struct nv50_crc *crc = &head->crc;
+
+ if (!asyh->clr.crc)
+ continue;
+
+ spin_lock_irq(&crc->lock);
+ crc->src = NV50_CRC_SOURCE_NONE;
+ spin_unlock_irq(&crc->lock);
+
+ drm_crtc_vblank_put(crtc);
+ drm_vblank_work_cancel_sync(&crc->flip_work);
+
+ NV_ATOMIC(nouveau_drm(crtc->dev),
+ "CRC reporting on vblank for head-%d disabled\n",
+ head->base.index);
+
+ /* CRC generation is still enabled in hw, we'll just report
+ * any remaining CRC entries ourselves after it gets disabled
+ * in hardware
+ */
+ }
+}
+
+void nv50_crc_atomic_init_notifier_contexts(struct drm_atomic_state *state)
+{
+ struct drm_crtc_state *new_crtc_state;
+ struct drm_crtc *crtc;
+ int i;
+
+ for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
+ struct nv50_head *head = nv50_head(crtc);
+ struct nv50_head_atom *asyh = nv50_head_atom(new_crtc_state);
+ struct nv50_crc *crc = &head->crc;
+ int i;
+
+ if (!asyh->set.crc)
+ continue;
+
+ crc->entry_idx = 0;
+ crc->ctx_changed = false;
+ for (i = 0; i < ARRAY_SIZE(crc->ctx); i++)
+ nv50_crc_reset_ctx(&crc->ctx[i]);
+ }
+}
+
+void nv50_crc_atomic_release_notifier_contexts(struct drm_atomic_state *state)
+{
+ const struct nv50_crc_func *func =
+ nv50_disp(state->dev)->core->func->crc;
+ struct drm_crtc_state *new_crtc_state;
+ struct drm_crtc *crtc;
+ int i;
+
+ for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
+ struct nv50_head *head = nv50_head(crtc);
+ struct nv50_head_atom *asyh = nv50_head_atom(new_crtc_state);
+ struct nv50_crc *crc = &head->crc;
+ struct nv50_crc_notifier_ctx *ctx = &crc->ctx[crc->ctx_idx];
+
+ if (!asyh->clr.crc)
+ continue;
+
+ if (crc->ctx_changed) {
+ nv50_crc_wait_ctx_finished(head, func, ctx);
+ ctx = &crc->ctx[crc->ctx_idx ^ 1];
+ }
+ nv50_crc_wait_ctx_finished(head, func, ctx);
+ }
+}
+
+void nv50_crc_atomic_start_reporting(struct drm_atomic_state *state)
+{
+ struct drm_crtc_state *crtc_state;
+ struct drm_crtc *crtc;
+ int i;
+
+ for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
+ struct nv50_head *head = nv50_head(crtc);
+ struct nv50_head_atom *asyh = nv50_head_atom(crtc_state);
+ struct nv50_crc *crc = &head->crc;
+ u64 vbl_count;
+
+ if (!asyh->set.crc)
+ continue;
+
+ drm_crtc_vblank_get(crtc);
+
+ spin_lock_irq(&crc->lock);
+ vbl_count = drm_crtc_vblank_count(crtc);
+ crc->frame = vbl_count;
+ crc->src = asyh->crc.src;
+ drm_vblank_work_schedule(&crc->flip_work,
+ vbl_count + crc->flip_threshold,
+ true);
+ spin_unlock_irq(&crc->lock);
+
+ NV_ATOMIC(nouveau_drm(crtc->dev),
+ "CRC reporting on vblank for head-%d enabled\n",
+ head->base.index);
+ }
+}
+
+int nv50_crc_atomic_check_head(struct nv50_head *head,
+ struct nv50_head_atom *asyh,
+ struct nv50_head_atom *armh)
+{
+ struct nv50_atom *atom = nv50_atom(asyh->state.state);
+ struct drm_device *dev = head->base.base.dev;
+ struct nv50_disp *disp = nv50_disp(dev);
+ bool changed = armh->crc.src != asyh->crc.src;
+
+ if (!armh->crc.src && !asyh->crc.src) {
+ asyh->set.crc = false;
+ asyh->clr.crc = false;
+ return 0;
+ }
+
+ /* While we don't care about entry tags, Volta+ hw always needs the
+ * controlling wndw channel programmed to a wndw that's owned by our
+ * head
+ */
+ if (asyh->crc.src && disp->disp->object.oclass >= GV100_DISP &&
+ !(BIT(asyh->crc.wndw) & asyh->wndw.owned)) {
+ if (!asyh->wndw.owned) {
+ /* TODO: once we support flexible channel ownership,
+ * we should write some code here to handle attempting
+ * to "steal" a plane: e.g. take a plane that is
+ * currently not-visible and owned by another head,
+ * and reassign it to this head. If we fail to do so,
+ * we shuld reject the mode outright as CRC capture
+ * then becomes impossible.
+ */
+ NV_ATOMIC(nouveau_drm(dev),
+ "No available wndws for CRC readback\n");
+ return -EINVAL;
+ }
+ asyh->crc.wndw = ffs(asyh->wndw.owned) - 1;
+ }
+
+ if (drm_atomic_crtc_needs_modeset(&asyh->state) || changed ||
+ armh->crc.wndw != asyh->crc.wndw) {
+ asyh->clr.crc = armh->crc.src && armh->state.active;
+ asyh->set.crc = asyh->crc.src && asyh->state.active;
+ if (changed)
+ asyh->set.or |= armh->or.crc_raster !=
+ asyh->or.crc_raster;
+
+ if (asyh->clr.crc && asyh->set.crc)
+ atom->flush_disable = true;
+ } else {
+ asyh->set.crc = false;
+ asyh->clr.crc = false;
+ }
+
+ return 0;
+}
+
+void nv50_crc_atomic_check_outp(struct nv50_atom *atom)
+{
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+ int i;
+
+ if (atom->flush_disable)
+ return;
+
+ for_each_oldnew_crtc_in_state(&atom->state, crtc, old_crtc_state,
+ new_crtc_state, i) {
+ struct nv50_head_atom *armh = nv50_head_atom(old_crtc_state);
+ struct nv50_head_atom *asyh = nv50_head_atom(new_crtc_state);
+ struct nv50_outp_atom *outp_atom;
+ struct nouveau_encoder *outp =
+ nv50_real_outp(nv50_head_atom_get_encoder(armh));
+ struct drm_encoder *encoder = &outp->base.base;
+
+ if (!asyh->clr.crc)
+ continue;
+
+ /*
+ * Re-programming ORs can't be done in the same flush as
+ * disabling CRCs
+ */
+ list_for_each_entry(outp_atom, &atom->outp, head) {
+ if (outp_atom->encoder == encoder) {
+ if (outp_atom->set.mask) {
+ atom->flush_disable = true;
+ return;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+}
+
+static enum nv50_crc_source_type
+nv50_crc_source_type(struct nouveau_encoder *outp,
+ enum nv50_crc_source source)
+{
+ struct dcb_output *dcbe = outp->dcb;
+
+ switch (source) {
+ case NV50_CRC_SOURCE_NONE: return NV50_CRC_SOURCE_TYPE_NONE;
+ case NV50_CRC_SOURCE_RG: return NV50_CRC_SOURCE_TYPE_RG;
+ default: break;
+ }
+
+ if (dcbe->location != DCB_LOC_ON_CHIP)
+ return NV50_CRC_SOURCE_TYPE_PIOR;
+
+ switch (dcbe->type) {
+ case DCB_OUTPUT_DP: return NV50_CRC_SOURCE_TYPE_SF;
+ case DCB_OUTPUT_ANALOG: return NV50_CRC_SOURCE_TYPE_DAC;
+ default: return NV50_CRC_SOURCE_TYPE_SOR;
+ }
+}
+
+void nv50_crc_atomic_set(struct nv50_head *head,
+ struct nv50_head_atom *asyh)
+{
+ struct drm_crtc *crtc = &head->base.base;
+ struct drm_device *dev = crtc->dev;
+ struct nv50_crc *crc = &head->crc;
+ const struct nv50_crc_func *func = nv50_disp(dev)->core->func->crc;
+ struct nouveau_encoder *outp =
+ nv50_real_outp(nv50_head_atom_get_encoder(asyh));
+
+ func->set_src(head, outp->or,
+ nv50_crc_source_type(outp, asyh->crc.src),
+ &crc->ctx[crc->ctx_idx], asyh->crc.wndw);
+}
+
+void nv50_crc_atomic_clr(struct nv50_head *head)
+{
+ const struct nv50_crc_func *func =
+ nv50_disp(head->base.base.dev)->core->func->crc;
+
+ func->set_src(head, 0, NV50_CRC_SOURCE_TYPE_NONE, NULL, 0);
+}
+
+#define NV50_CRC_RASTER_ACTIVE 0
+#define NV50_CRC_RASTER_COMPLETE 1
+#define NV50_CRC_RASTER_INACTIVE 2
+
+static inline int
+nv50_crc_raster_type(enum nv50_crc_source source)
+{
+ switch (source) {
+ case NV50_CRC_SOURCE_NONE:
+ case NV50_CRC_SOURCE_AUTO:
+ case NV50_CRC_SOURCE_RG:
+ case NV50_CRC_SOURCE_OUTP_ACTIVE:
+ return NV50_CRC_RASTER_ACTIVE;
+ case NV50_CRC_SOURCE_OUTP_COMPLETE:
+ return NV50_CRC_RASTER_COMPLETE;
+ case NV50_CRC_SOURCE_OUTP_INACTIVE:
+ return NV50_CRC_RASTER_INACTIVE;
+ }
+
+ return 0;
+}
+
+/* We handle mapping the memory for CRC notifiers ourselves, since each
+ * notifier needs it's own handle
+ */
+static inline int
+nv50_crc_ctx_init(struct nv50_head *head, struct nvif_mmu *mmu,
+ struct nv50_crc_notifier_ctx *ctx, size_t len, int idx)
+{
+ struct nv50_core *core = nv50_disp(head->base.base.dev)->core;
+ int ret;
+
+ ret = nvif_mem_init_map(mmu, NVIF_MEM_VRAM, len, &ctx->mem);
+ if (ret)
+ return ret;
+
+ ret = nvif_object_init(&core->chan.base.user,
+ NV50_DISP_HANDLE_CRC_CTX(head, idx),
+ NV_DMA_IN_MEMORY,
+ &(struct nv_dma_v0) {
+ .target = NV_DMA_V0_TARGET_VRAM,
+ .access = NV_DMA_V0_ACCESS_RDWR,
+ .start = ctx->mem.addr,
+ .limit = ctx->mem.addr
+ + ctx->mem.size - 1,
+ }, sizeof(struct nv_dma_v0),
+ &ctx->ntfy);
+ if (ret)
+ goto fail_fini;
+
+ return 0;
+
+fail_fini:
+ nvif_mem_fini(&ctx->mem);
+ return ret;
+}
+
+static inline void
+nv50_crc_ctx_fini(struct nv50_crc_notifier_ctx *ctx)
+{
+ nvif_object_fini(&ctx->ntfy);
+ nvif_mem_fini(&ctx->mem);
+}
+
+int nv50_crc_set_source(struct drm_crtc *crtc, const char *source_str)
+{
+ struct drm_device *dev = crtc->dev;
+ struct drm_atomic_state *state;
+ struct drm_modeset_acquire_ctx ctx;
+ struct nv50_head *head = nv50_head(crtc);
+ struct nv50_crc *crc = &head->crc;
+ const struct nv50_crc_func *func = nv50_disp(dev)->core->func->crc;
+ struct nvif_mmu *mmu = &nouveau_drm(dev)->client.mmu;
+ struct nv50_head_atom *asyh;
+ struct drm_crtc_state *crtc_state;
+ enum nv50_crc_source source;
+ int ret = 0, ctx_flags = 0, i;
+
+ ret = nv50_crc_parse_source(source_str, &source);
+ if (ret)
+ return ret;
+
+ /*
+ * Since we don't want the user to accidentally interrupt us as we're
+ * disabling CRCs
+ */
+ if (source)
+ ctx_flags |= DRM_MODESET_ACQUIRE_INTERRUPTIBLE;
+ drm_modeset_acquire_init(&ctx, ctx_flags);
+
+ state = drm_atomic_state_alloc(dev);
+ if (!state) {
+ ret = -ENOMEM;
+ goto out_acquire_fini;
+ }
+ state->acquire_ctx = &ctx;
+
+ if (source) {
+ for (i = 0; i < ARRAY_SIZE(head->crc.ctx); i++) {
+ ret = nv50_crc_ctx_init(head, mmu, &crc->ctx[i],
+ func->notifier_len, i);
+ if (ret)
+ goto out_ctx_fini;
+ }
+ }
+
+retry:
+ crtc_state = drm_atomic_get_crtc_state(state, &head->base.base);
+ if (IS_ERR(crtc_state)) {
+ ret = PTR_ERR(crtc_state);
+ if (ret == -EDEADLK)
+ goto deadlock;
+ else if (ret)
+ goto out_drop_locks;
+ }
+ asyh = nv50_head_atom(crtc_state);
+ asyh->crc.src = source;
+ asyh->or.crc_raster = nv50_crc_raster_type(source);
+
+ ret = drm_atomic_commit(state);
+ if (ret == -EDEADLK)
+ goto deadlock;
+ else if (ret)
+ goto out_drop_locks;
+
+ if (!source) {
+ /*
+ * If the user specified a custom flip threshold through
+ * debugfs, reset it
+ */
+ crc->flip_threshold = func->flip_threshold;
+ }
+
+out_drop_locks:
+ drm_modeset_drop_locks(&ctx);
+out_ctx_fini:
+ if (!source || ret) {
+ for (i = 0; i < ARRAY_SIZE(crc->ctx); i++)
+ nv50_crc_ctx_fini(&crc->ctx[i]);
+ }
+ drm_atomic_state_put(state);
+out_acquire_fini:
+ drm_modeset_acquire_fini(&ctx);
+ return ret;
+
+deadlock:
+ drm_atomic_state_clear(state);
+ drm_modeset_backoff(&ctx);
+ goto retry;
+}
+
+static int
+nv50_crc_debugfs_flip_threshold_get(struct seq_file *m, void *data)
+{
+ struct nv50_head *head = m->private;
+ struct drm_crtc *crtc = &head->base.base;
+ struct nv50_crc *crc = &head->crc;
+ int ret;
+
+ ret = drm_modeset_lock_single_interruptible(&crtc->mutex);
+ if (ret)
+ return ret;
+
+ seq_printf(m, "%d\n", crc->flip_threshold);
+
+ drm_modeset_unlock(&crtc->mutex);
+ return ret;
+}
+
+static int
+nv50_crc_debugfs_flip_threshold_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, nv50_crc_debugfs_flip_threshold_get,
+ inode->i_private);
+}
+
+static ssize_t
+nv50_crc_debugfs_flip_threshold_set(struct file *file,
+ const char __user *ubuf, size_t len,
+ loff_t *offp)
+{
+ struct seq_file *m = file->private_data;
+ struct nv50_head *head = m->private;
+ struct nv50_head_atom *armh;
+ struct drm_crtc *crtc = &head->base.base;
+ struct nouveau_drm *drm = nouveau_drm(crtc->dev);
+ struct nv50_crc *crc = &head->crc;
+ const struct nv50_crc_func *func =
+ nv50_disp(crtc->dev)->core->func->crc;
+ int value, ret;
+
+ ret = kstrtoint_from_user(ubuf, len, 10, &value);
+ if (ret)
+ return ret;
+
+ if (value > func->flip_threshold)
+ return -EINVAL;
+ else if (value == -1)
+ value = func->flip_threshold;
+ else if (value < -1)
+ return -EINVAL;
+
+ ret = drm_modeset_lock_single_interruptible(&crtc->mutex);
+ if (ret)
+ return ret;
+
+ armh = nv50_head_atom(crtc->state);
+ if (armh->crc.src) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ NV_DEBUG(drm,
+ "Changing CRC flip threshold for next capture on head-%d to %d\n",
+ head->base.index, value);
+ crc->flip_threshold = value;
+ ret = len;
+
+out:
+ drm_modeset_unlock(&crtc->mutex);
+ return ret;
+}
+
+static const struct file_operations nv50_crc_flip_threshold_fops = {
+ .owner = THIS_MODULE,
+ .open = nv50_crc_debugfs_flip_threshold_open,
+ .read = seq_read,
+ .write = nv50_crc_debugfs_flip_threshold_set,
+};
+
+int nv50_head_crc_late_register(struct nv50_head *head)
+{
+ struct drm_crtc *crtc = &head->base.base;
+ const struct nv50_crc_func *func =
+ nv50_disp(crtc->dev)->core->func->crc;
+ struct dentry *root;
+
+ if (!func || !crtc->debugfs_entry)
+ return 0;
+
+ root = debugfs_create_dir("nv_crc", crtc->debugfs_entry);
+ debugfs_create_file("flip_threshold", 0644, root, head,
+ &nv50_crc_flip_threshold_fops);
+
+ return 0;
+}
+
+static inline void
+nv50_crc_init_head(struct nv50_disp *disp, const struct nv50_crc_func *func,
+ struct nv50_head *head)
+{
+ struct nv50_crc *crc = &head->crc;
+
+ crc->flip_threshold = func->flip_threshold;
+ spin_lock_init(&crc->lock);
+ drm_vblank_work_init(&crc->flip_work, &head->base.base,
+ nv50_crc_ctx_flip_work);
+}
+
+void nv50_crc_init(struct drm_device *dev)
+{
+ struct nv50_disp *disp = nv50_disp(dev);
+ struct drm_crtc *crtc;
+ const struct nv50_crc_func *func = disp->core->func->crc;
+
+ if (!func)
+ return;
+
+ drm_for_each_crtc(crtc, dev)
+ nv50_crc_init_head(disp, func, nv50_head(crtc));
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv50/crc.h b/drivers/gpu/drm/nouveau/dispnv50/crc.h
new file mode 100644
index 000000000000..4bc59e779315
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv50/crc.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: MIT */
+#ifndef __NV50_CRC_H__
+#define __NV50_CRC_H__
+
+#include <linux/mutex.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_vblank_work.h>
+
+#include <nvif/mem.h>
+#include <nvkm/subdev/bios.h>
+#include "nouveau_encoder.h"
+
+struct nv50_atom;
+struct nv50_disp;
+struct nv50_head;
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+enum nv50_crc_source {
+ NV50_CRC_SOURCE_NONE = 0,
+ NV50_CRC_SOURCE_AUTO,
+ NV50_CRC_SOURCE_RG,
+ NV50_CRC_SOURCE_OUTP_ACTIVE,
+ NV50_CRC_SOURCE_OUTP_COMPLETE,
+ NV50_CRC_SOURCE_OUTP_INACTIVE,
+};
+
+/* RG -> SF (DP only)
+ * -> SOR
+ * -> PIOR
+ * -> DAC
+ */
+enum nv50_crc_source_type {
+ NV50_CRC_SOURCE_TYPE_NONE = 0,
+ NV50_CRC_SOURCE_TYPE_SOR,
+ NV50_CRC_SOURCE_TYPE_PIOR,
+ NV50_CRC_SOURCE_TYPE_DAC,
+ NV50_CRC_SOURCE_TYPE_RG,
+ NV50_CRC_SOURCE_TYPE_SF,
+};
+
+struct nv50_crc_notifier_ctx {
+ struct nvif_mem mem;
+ struct nvif_object ntfy;
+};
+
+struct nv50_crc_atom {
+ enum nv50_crc_source src;
+ /* Only used for gv100+ */
+ u8 wndw : 4;
+};
+
+struct nv50_crc_func {
+ void (*set_src)(struct nv50_head *, int or, enum nv50_crc_source_type,
+ struct nv50_crc_notifier_ctx *, u32 wndw);
+ void (*set_ctx)(struct nv50_head *, struct nv50_crc_notifier_ctx *);
+ u32 (*get_entry)(struct nv50_head *, struct nv50_crc_notifier_ctx *,
+ enum nv50_crc_source, int idx);
+ bool (*ctx_finished)(struct nv50_head *,
+ struct nv50_crc_notifier_ctx *);
+ short flip_threshold;
+ short num_entries;
+ size_t notifier_len;
+};
+
+struct nv50_crc {
+ spinlock_t lock;
+ struct nv50_crc_notifier_ctx ctx[2];
+ struct drm_vblank_work flip_work;
+ enum nv50_crc_source src;
+
+ u64 frame;
+ short entry_idx;
+ short flip_threshold;
+ u8 ctx_idx : 1;
+ bool ctx_changed : 1;
+};
+
+void nv50_crc_init(struct drm_device *dev);
+int nv50_head_crc_late_register(struct nv50_head *);
+void nv50_crc_handle_vblank(struct nv50_head *head);
+
+int nv50_crc_verify_source(struct drm_crtc *, const char *, size_t *);
+const char *const *nv50_crc_get_sources(struct drm_crtc *, size_t *);
+int nv50_crc_set_source(struct drm_crtc *, const char *);
+
+int nv50_crc_atomic_check_head(struct nv50_head *, struct nv50_head_atom *,
+ struct nv50_head_atom *);
+void nv50_crc_atomic_check_outp(struct nv50_atom *atom);
+void nv50_crc_atomic_stop_reporting(struct drm_atomic_state *);
+void nv50_crc_atomic_init_notifier_contexts(struct drm_atomic_state *);
+void nv50_crc_atomic_release_notifier_contexts(struct drm_atomic_state *);
+void nv50_crc_atomic_start_reporting(struct drm_atomic_state *);
+void nv50_crc_atomic_set(struct nv50_head *, struct nv50_head_atom *);
+void nv50_crc_atomic_clr(struct nv50_head *);
+
+extern const struct nv50_crc_func crc907d;
+extern const struct nv50_crc_func crcc37d;
+
+#else /* IS_ENABLED(CONFIG_DEBUG_FS) */
+struct nv50_crc {};
+struct nv50_crc_func {};
+struct nv50_crc_atom {};
+
+#define nv50_crc_verify_source NULL
+#define nv50_crc_get_sources NULL
+#define nv50_crc_set_source NULL
+
+static inline void nv50_crc_init(struct drm_device *dev) {}
+static inline int nv50_head_crc_late_register(struct nv50_head *) {}
+static inline void
+nv50_crc_handle_vblank(struct nv50_head *head) { return 0; }
+
+static inline int
+nv50_crc_atomic_check_head(struct nv50_head *, struct nv50_head_atom *,
+ struct nv50_head_atom *) {}
+static inline void nv50_crc_atomic_check_outp(struct nv50_atom *atom) {}
+static inline void
+nv50_crc_atomic_stop_reporting(struct drm_atomic_state *) {}
+static inline void
+nv50_crc_atomic_init_notifier_contexts(struct drm_atomic_state *) {}
+static inline void
+nv50_crc_atomic_release_notifier_contexts(struct drm_atomic_state *) {}
+static inline void
+nv50_crc_atomic_start_reporting(struct drm_atomic_state *) {}
+static inline void
+nv50_crc_atomic_set(struct nv50_head *, struct nv50_head_atom *) {}
+static inline void
+nv50_crc_atomic_clr(struct nv50_head *) {}
+
+#endif /* IS_ENABLED(CONFIG_DEBUG_FS) */
+#endif /* !__NV50_CRC_H__ */
diff --git a/drivers/gpu/drm/nouveau/dispnv50/crc907d.c b/drivers/gpu/drm/nouveau/dispnv50/crc907d.c
new file mode 100644
index 000000000000..92e907de7645
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv50/crc907d.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: MIT
+#include <drm/drm_crtc.h>
+
+#include "crc.h"
+#include "core.h"
+#include "disp.h"
+#include "head.h"
+
+#define CRC907D_MAX_ENTRIES 255
+
+struct crc907d_notifier {
+ u32 status;
+ u32 :32; /* reserved */
+ struct crc907d_entry {
+ u32 status;
+ u32 compositor_crc;
+ u32 output_crc[2];
+ } entries[CRC907D_MAX_ENTRIES];
+} __packed;
+
+static void
+crc907d_set_src(struct nv50_head *head, int or,
+ enum nv50_crc_source_type source,
+ struct nv50_crc_notifier_ctx *ctx, u32 wndw)
+{
+ struct drm_crtc *crtc = &head->base.base;
+ struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
+ const u32 hoff = head->base.index * 0x300;
+ u32 *push;
+ u32 crc_args = 0xfff00000;
+
+ switch (source) {
+ case NV50_CRC_SOURCE_TYPE_SOR:
+ crc_args |= (0x00000f0f + or * 16) << 8;
+ break;
+ case NV50_CRC_SOURCE_TYPE_PIOR:
+ crc_args |= (0x000000ff + or * 256) << 8;
+ break;
+ case NV50_CRC_SOURCE_TYPE_DAC:
+ crc_args |= (0x00000ff0 + or) << 8;
+ break;
+ case NV50_CRC_SOURCE_TYPE_RG:
+ crc_args |= (0x00000ff8 + drm_crtc_index(crtc)) << 8;
+ break;
+ case NV50_CRC_SOURCE_TYPE_SF:
+ crc_args |= (0x00000f8f + drm_crtc_index(crtc) * 16) << 8;
+ break;
+ case NV50_CRC_SOURCE_NONE:
+ crc_args |= 0x000fff00;
+ break;
+ }
+
+ push = evo_wait(core, 4);
+ if (!push)
+ return;
+
+ if (source) {
+ evo_mthd(push, 0x0438 + hoff, 1);
+ evo_data(push, ctx->ntfy.handle);
+ evo_mthd(push, 0x0430 + hoff, 1);
+ evo_data(push, crc_args);
+ } else {
+ evo_mthd(push, 0x0430 + hoff, 1);
+ evo_data(push, crc_args);
+ evo_mthd(push, 0x0438 + hoff, 1);
+ evo_data(push, 0);
+ }
+ evo_kick(push, core);
+}
+
+static void crc907d_set_ctx(struct nv50_head *head,
+ struct nv50_crc_notifier_ctx *ctx)
+{
+ struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
+ u32 *push = evo_wait(core, 2);
+
+ if (!push)
+ return;
+
+ evo_mthd(push, 0x0438 + (head->base.index * 0x300), 1);
+ evo_data(push, ctx ? ctx->ntfy.handle : 0);
+ evo_kick(push, core);
+}
+
+static u32 crc907d_get_entry(struct nv50_head *head,
+ struct nv50_crc_notifier_ctx *ctx,
+ enum nv50_crc_source source, int idx)
+{
+ struct crc907d_notifier __iomem *notifier = ctx->mem.object.map.ptr;
+
+ return ioread32_native(&notifier->entries[idx].output_crc[0]);
+}
+
+static bool crc907d_ctx_finished(struct nv50_head *head,
+ struct nv50_crc_notifier_ctx *ctx)
+{
+ struct nouveau_drm *drm = nouveau_drm(head->base.base.dev);
+ struct crc907d_notifier __iomem *notifier = ctx->mem.object.map.ptr;
+ const u32 status = ioread32_native(&notifier->status);
+ const u32 overflow = status & 0x0000003e;
+
+ if (!(status & 0x00000001))
+ return false;
+
+ if (overflow) {
+ const char *engine = NULL;
+
+ switch (overflow) {
+ case 0x00000004: engine = "DSI"; break;
+ case 0x00000008: engine = "Compositor"; break;
+ case 0x00000010: engine = "CRC output 1"; break;
+ case 0x00000020: engine = "CRC output 2"; break;
+ }
+
+ if (engine)
+ NV_ERROR(drm,
+ "CRC notifier context for head %d overflowed on %s: %x\n",
+ head->base.index, engine, status);
+ else
+ NV_ERROR(drm,
+ "CRC notifier context for head %d overflowed: %x\n",
+ head->base.index, status);
+ }
+
+ NV_DEBUG(drm, "Head %d CRC context status: %x\n",
+ head->base.index, status);
+
+ return true;
+}
+
+const struct nv50_crc_func crc907d = {
+ .set_src = crc907d_set_src,
+ .set_ctx = crc907d_set_ctx,
+ .get_entry = crc907d_get_entry,
+ .ctx_finished = crc907d_ctx_finished,
+ .flip_threshold = CRC907D_MAX_ENTRIES - 10,
+ .num_entries = CRC907D_MAX_ENTRIES,
+ .notifier_len = sizeof(struct crc907d_notifier),
+};
diff --git a/drivers/gpu/drm/nouveau/dispnv50/crcc37d.c b/drivers/gpu/drm/nouveau/dispnv50/crcc37d.c
new file mode 100644
index 000000000000..940cefd5517d
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv50/crcc37d.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: MIT
+#include <drm/drm_crtc.h>
+
+#include "crc.h"
+#include "core.h"
+#include "disp.h"
+#include "head.h"
+
+#define CRCC37D_MAX_ENTRIES 2047
+
+struct crcc37d_notifier {
+ u32 status;
+
+ /* reserved */
+ u32 :32;
+ u32 :32;
+ u32 :32;
+ u32 :32;
+ u32 :32;
+ u32 :32;
+ u32 :32;
+
+ struct crcc37d_entry {
+ u32 status[2];
+ u32 :32; /* reserved */
+ u32 compositor_crc;
+ u32 rg_crc;
+ u32 output_crc[2];
+ u32 :32; /* reserved */
+ } entries[CRCC37D_MAX_ENTRIES];
+} __packed;
+
+static void
+crcc37d_set_src(struct nv50_head *head, int or,
+ enum nv50_crc_source_type source,
+ struct nv50_crc_notifier_ctx *ctx, u32 wndw)
+{
+ struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
+ const u32 hoff = head->base.index * 0x400;
+ u32 *push;
+ u32 crc_args;
+
+ switch (source) {
+ case NV50_CRC_SOURCE_TYPE_SOR:
+ crc_args = (0x00000050 + or) << 12;
+ break;
+ case NV50_CRC_SOURCE_TYPE_PIOR:
+ crc_args = (0x00000060 + or) << 12;
+ break;
+ case NV50_CRC_SOURCE_TYPE_SF:
+ crc_args = 0x00000030 << 12;
+ break;
+ default:
+ crc_args = 0;
+ break;
+ }
+
+ push = evo_wait(core, 4);
+ if (!push)
+ return;
+
+ if (source) {
+ evo_mthd(push, 0x2180 + hoff, 1);
+ evo_data(push, ctx->ntfy.handle);
+ evo_mthd(push, 0x2184 + hoff, 1);
+ evo_data(push, crc_args | wndw);
+ } else {
+ evo_mthd(push, 0x2184 + hoff, 1);
+ evo_data(push, 0);
+ evo_mthd(push, 0x2180 + hoff, 1);
+ evo_data(push, 0);
+ }
+
+ evo_kick(push, core);
+}
+
+static void crcc37d_set_ctx(struct nv50_head *head,
+ struct nv50_crc_notifier_ctx *ctx)
+{
+ struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
+ u32 *push = evo_wait(core, 2);
+
+ if (!push)
+ return;
+
+ evo_mthd(push, 0x2180 + (head->base.index * 0x400), 1);
+ evo_data(push, ctx ? ctx->ntfy.handle : 0);
+ evo_kick(push, core);
+}
+
+static u32 crcc37d_get_entry(struct nv50_head *head,
+ struct nv50_crc_notifier_ctx *ctx,
+ enum nv50_crc_source source, int idx)
+{
+ struct crcc37d_notifier __iomem *notifier = ctx->mem.object.map.ptr;
+ struct crcc37d_entry __iomem *entry = &notifier->entries[idx];
+ u32 __iomem *crc_addr;
+
+ if (source == NV50_CRC_SOURCE_RG)
+ crc_addr = &entry->rg_crc;
+ else
+ crc_addr = &entry->output_crc[0];
+
+ return ioread32_native(crc_addr);
+}
+
+static bool crcc37d_ctx_finished(struct nv50_head *head,
+ struct nv50_crc_notifier_ctx *ctx)
+{
+ struct nouveau_drm *drm = nouveau_drm(head->base.base.dev);
+ struct crcc37d_notifier __iomem *notifier = ctx->mem.object.map.ptr;
+ const u32 status = ioread32_native(&notifier->status);
+ const u32 overflow = status & 0x0000007e;
+
+ if (!(status & 0x00000001))
+ return false;
+
+ if (overflow) {
+ const char *engine = NULL;
+
+ switch (overflow) {
+ case 0x00000004: engine = "Front End"; break;
+ case 0x00000008: engine = "Compositor"; break;
+ case 0x00000010: engine = "RG"; break;
+ case 0x00000020: engine = "CRC output 1"; break;
+ case 0x00000040: engine = "CRC output 2"; break;
+ }
+
+ if (engine)
+ NV_ERROR(drm,
+ "CRC notifier context for head %d overflowed on %s: %x\n",
+ head->base.index, engine, status);
+ else
+ NV_ERROR(drm,
+ "CRC notifier context for head %d overflowed: %x\n",
+ head->base.index, status);
+ }
+
+ NV_DEBUG(drm, "Head %d CRC context status: %x\n",
+ head->base.index, status);
+
+ return true;
+}
+
+const struct nv50_crc_func crcc37d = {
+ .set_src = crcc37d_set_src,
+ .set_ctx = crcc37d_set_ctx,
+ .get_entry = crcc37d_get_entry,
+ .ctx_finished = crcc37d_ctx_finished,
+ .flip_threshold = CRCC37D_MAX_ENTRIES - 30,
+ .num_entries = CRCC37D_MAX_ENTRIES,
+ .notifier_len = sizeof(struct crcc37d_notifier),
+};
diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index 519f99868e35..4c2894d8e15b 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -26,6 +26,7 @@
#include "core.h"
#include "head.h"
#include "wndw.h"
+#include "handles.h"
#include <linux/dma-mapping.h>
#include <linux/hdmi.h>
@@ -58,24 +59,6 @@
#include <subdev/bios/dp.h>
/******************************************************************************
- * Atomic state
- *****************************************************************************/
-
-struct nv50_outp_atom {
- struct list_head head;
-
- struct drm_encoder *encoder;
- bool flush_disable;
-
- union nv50_outp_atom_mask {
- struct {
- bool ctrl:1;
- };
- u8 mask;
- } set, clr;
-};
-
-/******************************************************************************
* EVO channel
*****************************************************************************/
@@ -172,7 +155,8 @@ nv50_dmac_create(struct nvif_device *device, struct nvif_object *disp,
if (!syncbuf)
return 0;
- ret = nvif_object_init(&dmac->base.user, 0xf0000000, NV_DMA_IN_MEMORY,
+ ret = nvif_object_init(&dmac->base.user, NV50_DISP_HANDLE_SYNCBUF,
+ NV_DMA_IN_MEMORY,
&(struct nv_dma_v0) {
.target = NV_DMA_V0_TARGET_VRAM,
.access = NV_DMA_V0_ACCESS_RDWR,
@@ -183,7 +167,8 @@ nv50_dmac_create(struct nvif_device *device, struct nvif_object *disp,
if (ret)
return ret;
- ret = nvif_object_init(&dmac->base.user, 0xf0000001, NV_DMA_IN_MEMORY,
+ ret = nvif_object_init(&dmac->base.user, NV50_DISP_HANDLE_VRAM,
+ NV_DMA_IN_MEMORY,
&(struct nv_dma_v0) {
.target = NV_DMA_V0_TARGET_VRAM,
.access = NV_DMA_V0_ACCESS_RDWR,
@@ -801,6 +786,19 @@ struct nv50_msto {
bool disabled;
};
+struct nouveau_encoder *nv50_real_outp(struct drm_encoder *encoder)
+{
+ struct nv50_msto *msto;
+
+ if (encoder->encoder_type != DRM_MODE_ENCODER_DPMST)
+ return nouveau_encoder(encoder);
+
+ msto = nv50_msto(encoder);
+ if (!msto->mstc)
+ return NULL;
+ return msto->mstc->mstm->outp;
+}
+
static struct drm_dp_payload *
nv50_msto_payload(struct nv50_msto *msto)
{
@@ -1948,8 +1946,10 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
struct nv50_outp_atom *outp, *outt;
u32 interlock[NV50_DISP_INTERLOCK__SIZE] = {};
int i;
+ bool flushed = false;
NV_ATOMIC(drm, "commit %d %d\n", atom->lock_core, atom->flush_disable);
+ nv50_crc_atomic_stop_reporting(state);
drm_atomic_helper_wait_for_fences(dev, state, false);
drm_atomic_helper_wait_for_dependencies(state);
drm_atomic_helper_update_legacy_modeset_state(dev, state);
@@ -2007,6 +2007,8 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
nv50_disp_atomic_commit_wndw(state, interlock);
nv50_disp_atomic_commit_core(state, interlock);
memset(interlock, 0x00, sizeof(interlock));
+
+ flushed = true;
}
}
}
@@ -2017,9 +2019,15 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
nv50_disp_atomic_commit_wndw(state, interlock);
nv50_disp_atomic_commit_core(state, interlock);
memset(interlock, 0x00, sizeof(interlock));
+
+ flushed = true;
}
}
+ if (flushed)
+ nv50_crc_atomic_release_notifier_contexts(state);
+ nv50_crc_atomic_init_notifier_contexts(state);
+
/* Update output path(s). */
list_for_each_entry_safe(outp, outt, &atom->outp, head) {
const struct drm_encoder_helper_funcs *help;
@@ -2133,6 +2141,9 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
}
}
+ nv50_crc_atomic_start_reporting(state);
+ if (!flushed)
+ nv50_crc_atomic_release_notifier_contexts(state);
drm_atomic_helper_commit_hw_done(state);
drm_atomic_helper_cleanup_planes(dev, state);
drm_atomic_helper_commit_cleanup_done(state);
@@ -2290,12 +2301,28 @@ static int
nv50_disp_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
{
struct nv50_atom *atom = nv50_atom(state);
+ struct nv50_core *core = nv50_disp(dev)->core;
struct drm_connector_state *old_connector_state, *new_connector_state;
struct drm_connector *connector;
struct drm_crtc_state *new_crtc_state;
struct drm_crtc *crtc;
+ struct nv50_head *head;
+ struct nv50_head_atom *asyh;
int ret, i;
+ if (core->assign_windows && core->func->head->static_wndw_map) {
+ drm_for_each_crtc(crtc, dev) {
+ new_crtc_state = drm_atomic_get_crtc_state(state,
+ crtc);
+ if (IS_ERR(new_crtc_state))
+ return PTR_ERR(new_crtc_state);
+
+ head = nv50_head(crtc);
+ asyh = nv50_head_atom(new_crtc_state);
+ core->func->head->static_wndw_map(head, asyh);
+ }
+ }
+
/* We need to handle colour management on a per-plane basis. */
for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
if (new_crtc_state->color_mgmt_changed) {
@@ -2323,6 +2350,8 @@ nv50_disp_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
if (ret)
return ret;
+ nv50_crc_atomic_check_outp(atom);
+
return 0;
}
diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.h b/drivers/gpu/drm/nouveau/dispnv50/disp.h
index 696e70a6b98b..1968c6921f9e 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.h
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.h
@@ -1,10 +1,12 @@
#ifndef __NV50_KMS_H__
#define __NV50_KMS_H__
+#include <linux/workqueue.h>
#include <nvif/mem.h>
#include "nouveau_display.h"
struct nv50_msto;
+struct nouveau_encoder;
struct nv50_disp {
struct nvif_disp *disp;
@@ -71,11 +73,33 @@ struct nv50_dmac {
struct mutex lock;
};
+struct nv50_outp_atom {
+ struct list_head head;
+
+ struct drm_encoder *encoder;
+ bool flush_disable;
+
+ union nv50_outp_atom_mask {
+ struct {
+ bool ctrl:1;
+ };
+ u8 mask;
+ } set, clr;
+};
+
int nv50_dmac_create(struct nvif_device *device, struct nvif_object *disp,
const s32 *oclass, u8 head, void *data, u32 size,
u64 syncbuf, struct nv50_dmac *dmac);
void nv50_dmac_destroy(struct nv50_dmac *);
+/*
+ * For normal encoders this just returns the encoder. For active MST encoders,
+ * this returns the real outp that's driving displays on the topology.
+ * Inactive MST encoders return NULL, since they would have no real outp to
+ * return anyway.
+ */
+struct nouveau_encoder *nv50_real_outp(struct drm_encoder *encoder);
+
u32 *evo_wait(struct nv50_dmac *, int nr);
void evo_kick(u32 *, struct nv50_dmac *);
diff --git a/drivers/gpu/drm/nouveau/dispnv50/handles.h b/drivers/gpu/drm/nouveau/dispnv50/handles.h
new file mode 100644
index 000000000000..a97a7bd29243
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv50/handles.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: MIT */
+#ifndef __NV50_KMS_HANDLES_H__
+#define __NV50_KMS_HANDLES_H__
+
+/*
+ * Various hard-coded object handles that nouveau uses. These are made-up by
+ * nouveau developers, not Nvidia. The only significance of the handles chosen
+ * is that they must all be unique.
+ */
+#define NV50_DISP_HANDLE_SYNCBUF 0xf0000000
+#define NV50_DISP_HANDLE_VRAM 0xf0000001
+
+#define NV50_DISP_HANDLE_WNDW_CTX(kind) (0xfb000000 | kind)
+#define NV50_DISP_HANDLE_CRC_CTX(head, i) (0xfc000000 | head->base.index << 1 | i)
+
+#endif /* !__NV50_KMS_HANDLES_H__ */
diff --git a/drivers/gpu/drm/nouveau/dispnv50/head.c b/drivers/gpu/drm/nouveau/dispnv50/head.c
index 8f6455697ba7..9a10ec267d1f 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/head.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/head.c
@@ -24,13 +24,17 @@
#include "core.h"
#include "curs.h"
#include "ovly.h"
+#include "crc.h"
#include <nvif/class.h>
+#include <nvif/event.h>
+#include <nvif/cl0046.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_vblank.h>
#include "nouveau_connector.h"
+
void
nv50_head_flush_clr(struct nv50_head *head,
struct nv50_head_atom *asyh, bool flush)
@@ -38,6 +42,7 @@ nv50_head_flush_clr(struct nv50_head *head,
union nv50_head_atom_mask clr = {
.mask = asyh->clr.mask & ~(flush ? 0 : asyh->set.mask),
};
+ if (clr.crc) nv50_crc_atomic_clr(head);
if (clr.olut) head->func->olut_clr(head);
if (clr.core) head->func->core_clr(head);
if (clr.curs) head->func->curs_clr(head);
@@ -61,6 +66,7 @@ nv50_head_flush_set(struct nv50_head *head, struct nv50_head_atom *asyh)
if (asyh->set.ovly ) head->func->ovly (head, asyh);
if (asyh->set.dither ) head->func->dither (head, asyh);
if (asyh->set.procamp) head->func->procamp (head, asyh);
+ if (asyh->set.crc ) nv50_crc_atomic_set (head, asyh);
if (asyh->set.or ) head->func->or (head, asyh);
}
@@ -84,18 +90,20 @@ nv50_head_atomic_check_dither(struct nv50_head_atom *armh,
{
u32 mode = 0x00;
- if (asyc->dither.mode == DITHERING_MODE_AUTO) {
- if (asyh->base.depth > asyh->or.bpc * 3)
- mode = DITHERING_MODE_DYNAMIC2X2;
- } else {
- mode = asyc->dither.mode;
- }
+ if (asyc->dither.mode) {
+ if (asyc->dither.mode == DITHERING_MODE_AUTO) {
+ if (asyh->base.depth > asyh->or.bpc * 3)
+ mode = DITHERING_MODE_DYNAMIC2X2;
+ } else {
+ mode = asyc->dither.mode;
+ }
- if (asyc->dither.depth == DITHERING_DEPTH_AUTO) {
- if (asyh->or.bpc >= 8)
- mode |= DITHERING_DEPTH_8BPC;
- } else {
- mode |= asyc->dither.depth;
+ if (asyc->dither.depth == DITHERING_DEPTH_AUTO) {
+ if (asyh->or.bpc >= 8)
+ mode |= DITHERING_DEPTH_8BPC;
+ } else {
+ mode |= asyc->dither.depth;
+ }
}
asyh->dither.enable = mode;
@@ -311,7 +319,7 @@ nv50_head_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state)
struct nouveau_conn_atom *asyc = NULL;
struct drm_connector_state *conns;
struct drm_connector *conn;
- int i;
+ int i, ret;
NV_ATOMIC(drm, "%s atomic_check %d\n", crtc->name, asyh->state.active);
if (asyh->state.active) {
@@ -406,6 +414,10 @@ nv50_head_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state)
asyh->set.curs = asyh->curs.visible;
}
+ ret = nv50_crc_atomic_check_head(head, asyh, armh);
+ if (ret)
+ return ret;
+
if (asyh->clr.mask || asyh->set.mask)
nv50_atom(asyh->state.state)->lock_core = true;
return 0;
@@ -444,6 +456,7 @@ nv50_head_atomic_duplicate_state(struct drm_crtc *crtc)
asyh->ovly = armh->ovly;
asyh->dither = armh->dither;
asyh->procamp = armh->procamp;
+ asyh->crc = armh->crc;
asyh->or = armh->or;
asyh->dp = armh->dp;
asyh->clr.mask = 0;
@@ -465,10 +478,18 @@ nv50_head_reset(struct drm_crtc *crtc)
__drm_atomic_helper_crtc_reset(crtc, &asyh->state);
}
+static int
+nv50_head_late_register(struct drm_crtc *crtc)
+{
+ return nv50_head_crc_late_register(nv50_head(crtc));
+}
+
static void
nv50_head_destroy(struct drm_crtc *crtc)
{
struct nv50_head *head = nv50_head(crtc);
+
+ nvif_notify_fini(&head->base.vblank);
nv50_lut_fini(&head->olut);
drm_crtc_cleanup(crtc);
kfree(head);
@@ -486,8 +507,38 @@ nv50_head_func = {
.enable_vblank = nouveau_display_vblank_enable,
.disable_vblank = nouveau_display_vblank_disable,
.get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
+ .late_register = nv50_head_late_register,
+};
+
+static const struct drm_crtc_funcs
+nvd9_head_func = {
+ .reset = nv50_head_reset,
+ .gamma_set = drm_atomic_helper_legacy_gamma_set,
+ .destroy = nv50_head_destroy,
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .atomic_duplicate_state = nv50_head_atomic_duplicate_state,
+ .atomic_destroy_state = nv50_head_atomic_destroy_state,
+ .enable_vblank = nouveau_display_vblank_enable,
+ .disable_vblank = nouveau_display_vblank_disable,
+ .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
+ .verify_crc_source = nv50_crc_verify_source,
+ .get_crc_sources = nv50_crc_get_sources,
+ .set_crc_source = nv50_crc_set_source,
+ .late_register = nv50_head_late_register,
};
+static int nv50_head_vblank_handler(struct nvif_notify *notify)
+{
+ struct nouveau_crtc *nv_crtc =
+ container_of(notify, struct nouveau_crtc, vblank);
+
+ if (drm_crtc_handle_vblank(&nv_crtc->base))
+ nv50_crc_handle_vblank(nv50_head(&nv_crtc->base));
+
+ return NVIF_NOTIFY_KEEP;
+}
+
struct nv50_head *
nv50_head_create(struct drm_device *dev, int index)
{
@@ -495,7 +546,9 @@ nv50_head_create(struct drm_device *dev, int index)
struct nv50_disp *disp = nv50_disp(dev);
struct nv50_head *head;
struct nv50_wndw *base, *ovly, *curs;
+ struct nouveau_crtc *nv_crtc;
struct drm_crtc *crtc;
+ const struct drm_crtc_funcs *funcs;
int ret;
head = kzalloc(sizeof(*head), GFP_KERNEL);
@@ -505,6 +558,11 @@ nv50_head_create(struct drm_device *dev, int index)
head->func = disp->core->func->head;
head->base.index = index;
+ if (disp->disp->object.oclass < GF110_DISP)
+ funcs = &nv50_head_func;
+ else
+ funcs = &nvd9_head_func;
+
if (disp->disp->object.oclass < GV100_DISP) {
ret = nv50_base_new(drm, head->base.index, &base);
ret = nv50_ovly_new(drm, head->base.index, &ovly);
@@ -521,9 +579,10 @@ nv50_head_create(struct drm_device *dev, int index)
return ERR_PTR(ret);
}
- crtc = &head->base.base;
+ nv_crtc = &head->base;
+ crtc = &nv_crtc->base;
drm_crtc_init_with_planes(dev, crtc, &base->plane, &curs->plane,
- &nv50_head_func, "head-%d", head->base.index);
+ funcs, "head-%d", head->base.index);
drm_crtc_helper_add(crtc, &nv50_head_help);
/* Keep the legacy gamma size at 256 to avoid compatibility issues */
drm_mode_crtc_set_gamma_size(crtc, 256);
@@ -539,5 +598,16 @@ nv50_head_create(struct drm_device *dev, int index)
}
}
+ ret = nvif_notify_init(&disp->disp->object, nv50_head_vblank_handler,
+ false, NV04_DISP_NTFY_VBLANK,
+ &(struct nvif_notify_head_req_v0) {
+ .head = nv_crtc->index,
+ },
+ sizeof(struct nvif_notify_head_req_v0),
+ sizeof(struct nvif_notify_head_rep_v0),
+ &nv_crtc->vblank);
+ if (ret)
+ return ERR_PTR(ret);
+
return head;
}
diff --git a/drivers/gpu/drm/nouveau/dispnv50/head.h b/drivers/gpu/drm/nouveau/dispnv50/head.h
index c32b27cdaefc..30501ad1824e 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/head.h
+++ b/drivers/gpu/drm/nouveau/dispnv50/head.h
@@ -1,22 +1,28 @@
#ifndef __NV50_KMS_HEAD_H__
#define __NV50_KMS_HEAD_H__
#define nv50_head(c) container_of((c), struct nv50_head, base.base)
+#include <linux/workqueue.h>
+
#include "disp.h"
#include "atom.h"
+#include "crc.h"
#include "lut.h"
#include "nouveau_crtc.h"
+#include "nouveau_encoder.h"
struct nv50_head {
const struct nv50_head_func *func;
struct nouveau_crtc base;
+ struct nv50_crc crc;
struct nv50_lut olut;
struct nv50_msto *msto;
};
struct nv50_head *nv50_head_create(struct drm_device *, int index);
-void nv50_head_flush_set(struct nv50_head *, struct nv50_head_atom *);
-void nv50_head_flush_clr(struct nv50_head *, struct nv50_head_atom *, bool y);
+void nv50_head_flush_set(struct nv50_head *head, struct nv50_head_atom *asyh);
+void nv50_head_flush_clr(struct nv50_head *head,
+ struct nv50_head_atom *asyh, bool flush);
struct nv50_head_func {
void (*view)(struct nv50_head *, struct nv50_head_atom *);
@@ -40,6 +46,7 @@ struct nv50_head_func {
void (*dither)(struct nv50_head *, struct nv50_head_atom *);
void (*procamp)(struct nv50_head *, struct nv50_head_atom *);
void (*or)(struct nv50_head *, struct nv50_head_atom *);
+ void (*static_wndw_map)(struct nv50_head *, struct nv50_head_atom *);
};
extern const struct nv50_head_func head507d;
@@ -86,6 +93,7 @@ int headc37d_curs_format(struct nv50_head *, struct nv50_wndw_atom *,
void headc37d_curs_set(struct nv50_head *, struct nv50_head_atom *);
void headc37d_curs_clr(struct nv50_head *);
void headc37d_dither(struct nv50_head *, struct nv50_head_atom *);
+void headc37d_static_wndw_map(struct nv50_head *, struct nv50_head_atom *);
extern const struct nv50_head_func headc57d;
#endif
diff --git a/drivers/gpu/drm/nouveau/dispnv50/head907d.c b/drivers/gpu/drm/nouveau/dispnv50/head907d.c
index 3002ec23d7a6..63a0b45d96d6 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/head907d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/head907d.c
@@ -19,8 +19,15 @@
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
+#include <drm/drm_connector.h>
+#include <drm/drm_mode_config.h>
+#include <drm/drm_vblank.h>
+#include "nouveau_drv.h"
+#include "nouveau_bios.h"
+#include "nouveau_connector.h"
#include "head.h"
#include "core.h"
+#include "crc.h"
void
head907d_or(struct nv50_head *head, struct nv50_head_atom *asyh)
@@ -29,9 +36,10 @@ head907d_or(struct nv50_head *head, struct nv50_head_atom *asyh)
u32 *push;
if ((push = evo_wait(core, 3))) {
evo_mthd(push, 0x0404 + (head->base.index * 0x300), 2);
- evo_data(push, 0x00000001 | asyh->or.depth << 6 |
- asyh->or.nvsync << 4 |
- asyh->or.nhsync << 3);
+ evo_data(push, asyh->or.depth << 6 |
+ asyh->or.nvsync << 4 |
+ asyh->or.nhsync << 3 |
+ asyh->or.crc_raster);
evo_data(push, 0x31ec6000 | head->base.index << 25 |
asyh->mode.interlace);
evo_kick(push, core);
diff --git a/drivers/gpu/drm/nouveau/dispnv50/headc37d.c b/drivers/gpu/drm/nouveau/dispnv50/headc37d.c
index 4a9a32b89f74..35fcdf8825b5 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/headc37d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/headc37d.c
@@ -27,26 +27,29 @@ static void
headc37d_or(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
+ u8 depth;
u32 *push;
+
if ((push = evo_wait(core, 2))) {
/*XXX: This is a dirty hack until OR depth handling is
* improved later for deep colour etc.
*/
switch (asyh->or.depth) {
- case 6: asyh->or.depth = 5; break;
- case 5: asyh->or.depth = 4; break;
- case 2: asyh->or.depth = 1; break;
- case 0: asyh->or.depth = 4; break;
+ case 6: depth = 5; break;
+ case 5: depth = 4; break;
+ case 2: depth = 1; break;
+ case 0: depth = 4; break;
default:
+ depth = asyh->or.depth;
WARN_ON(1);
break;
}
evo_mthd(push, 0x2004 + (head->base.index * 0x400), 1);
- evo_data(push, 0x00000001 |
- asyh->or.depth << 4 |
+ evo_data(push, depth << 4 |
asyh->or.nvsync << 3 |
- asyh->or.nhsync << 2);
+ asyh->or.nhsync << 2 |
+ asyh->or.crc_raster);
evo_kick(push, core);
}
}
@@ -201,6 +204,15 @@ headc37d_view(struct nv50_head *head, struct nv50_head_atom *asyh)
}
}
+void
+headc37d_static_wndw_map(struct nv50_head *head, struct nv50_head_atom *asyh)
+{
+ int i, end;
+
+ for (i = head->base.index * 2, end = i + 2; i < end; i++)
+ asyh->wndw.owned |= BIT(i);
+}
+
const struct nv50_head_func
headc37d = {
.view = headc37d_view,
@@ -216,4 +228,5 @@ headc37d = {
.dither = headc37d_dither,
.procamp = headc37d_procamp,
.or = headc37d_or,
+ .static_wndw_map = headc37d_static_wndw_map,
};
diff --git a/drivers/gpu/drm/nouveau/dispnv50/headc57d.c b/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
index 859131a8bc3c..c7d04dd935fd 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
@@ -27,26 +27,30 @@ static void
headc57d_or(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
+ u8 depth;
u32 *push;
+
if ((push = evo_wait(core, 2))) {
/*XXX: This is a dirty hack until OR depth handling is
* improved later for deep colour etc.
*/
switch (asyh->or.depth) {
- case 6: asyh->or.depth = 5; break;
- case 5: asyh->or.depth = 4; break;
- case 2: asyh->or.depth = 1; break;
- case 0: asyh->or.depth = 4; break;
+ case 6: depth = 5; break;
+ case 5: depth = 4; break;
+ case 2: depth = 1; break;
+ case 0: depth = 4; break;
default:
+ depth = asyh->or.depth;
WARN_ON(1);
break;
}
evo_mthd(push, 0x2004 + (head->base.index * 0x400), 1);
- evo_data(push, 0xfc000001 |
- asyh->or.depth << 4 |
+ evo_data(push, 0xfc000000 |
+ depth << 4 |
asyh->or.nvsync << 3 |
- asyh->or.nhsync << 2);
+ asyh->or.nhsync << 2 |
+ asyh->or.crc_raster);
evo_kick(push, core);
}
}
@@ -208,4 +212,6 @@ headc57d = {
.dither = headc37d_dither,
.procamp = headc57d_procamp,
.or = headc57d_or,
+ /* TODO: flexible window mappings */
+ .static_wndw_map = headc37d_static_wndw_map,
};
diff --git a/drivers/gpu/drm/nouveau/dispnv50/ovly507e.c b/drivers/gpu/drm/nouveau/dispnv50/ovly507e.c
index 8ccd96113bad..4cce1078140a 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/ovly507e.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/ovly507e.c
@@ -186,7 +186,7 @@ ovly507e_new_(const struct nv50_wndw_func *func, const u32 *format,
ret = nv50_dmac_create(&drm->client.device, &disp->disp->object,
&oclass, 0, &args, sizeof(args),
- disp->sync->bo.offset, &wndw->wndw);
+ disp->sync->offset, &wndw->wndw);
if (ret) {
NV_ERROR(drm, "ovly%04x allocation failed: %d\n", oclass, ret);
return ret;
diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndw.c b/drivers/gpu/drm/nouveau/dispnv50/wndw.c
index 99b9b681736d..293ccfdba17e 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/wndw.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/wndw.c
@@ -21,6 +21,7 @@
*/
#include "wndw.h"
#include "wimm.h"
+#include "handles.h"
#include <nvif/class.h>
#include <nvif/cl0002.h>
@@ -59,7 +60,7 @@ nv50_wndw_ctxdma_new(struct nv50_wndw *wndw, struct drm_framebuffer *fb)
int ret;
nouveau_framebuffer_get_layout(fb, &unused, &kind);
- handle = 0xfb000000 | kind;
+ handle = NV50_DISP_HANDLE_WNDW_CTX(kind);
list_for_each_entry(ctxdma, &wndw->ctxdma.list, head) {
if (ctxdma->object.handle == handle)
@@ -526,7 +527,7 @@ nv50_wndw_prepare_fb(struct drm_plane *plane, struct drm_plane_state *state)
}
asyw->state.fence = dma_resv_get_excl_rcu(nvbo->bo.base.resv);
- asyw->image.offset[0] = nvbo->bo.offset;
+ asyw->image.offset[0] = nvbo->offset;
if (wndw->func->prepare) {
asyh = nv50_head_atom_get(asyw->state.state, asyw->state.crtc);
diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndwc37e.c b/drivers/gpu/drm/nouveau/dispnv50/wndwc37e.c
index b92dc3461bbd..bb84e4d54a33 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/wndwc37e.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/wndwc37e.c
@@ -298,7 +298,7 @@ wndwc37e_new_(const struct nv50_wndw_func *func, struct nouveau_drm *drm,
ret = nv50_dmac_create(&drm->client.device, &disp->disp->object,
&oclass, 0, &args, sizeof(args),
- disp->sync->bo.offset, &wndw->wndw);
+ disp->sync->offset, &wndw->wndw);
if (ret) {
NV_ERROR(drm, "qndw%04x allocation failed: %d\n", oclass, ret);
return ret;
diff --git a/drivers/gpu/drm/nouveau/nouveau_abi16.c b/drivers/gpu/drm/nouveau/nouveau_abi16.c
index e2bae1424502..5b2406950e53 100644
--- a/drivers/gpu/drm/nouveau/nouveau_abi16.c
+++ b/drivers/gpu/drm/nouveau/nouveau_abi16.c
@@ -139,7 +139,7 @@ nouveau_abi16_chan_fini(struct nouveau_abi16 *abi16,
if (chan->ntfy) {
nouveau_vma_del(&chan->ntfy_vma);
nouveau_bo_unpin(chan->ntfy);
- drm_gem_object_put_unlocked(&chan->ntfy->bo.base);
+ drm_gem_object_put(&chan->ntfy->bo.base);
}
if (chan->heap.block_size)
@@ -558,13 +558,13 @@ nouveau_abi16_ioctl_notifierobj_alloc(ABI16_IOCTL_ARGS)
if (drm->agp.bridge) {
args.target = NV_DMA_V0_TARGET_AGP;
args.access = NV_DMA_V0_ACCESS_RDWR;
- args.start += drm->agp.base + chan->ntfy->bo.offset;
- args.limit += drm->agp.base + chan->ntfy->bo.offset;
+ args.start += drm->agp.base + chan->ntfy->offset;
+ args.limit += drm->agp.base + chan->ntfy->offset;
} else {
args.target = NV_DMA_V0_TARGET_VM;
args.access = NV_DMA_V0_ACCESS_RDWR;
- args.start += chan->ntfy->bo.offset;
- args.limit += chan->ntfy->bo.offset;
+ args.start += chan->ntfy->offset;
+ args.limit += chan->ntfy->offset;
}
client->route = NVDRM_OBJECT_ABI16;
diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c
index c40f127de3d0..4ccf937df0d0 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
@@ -655,13 +655,12 @@ nouveau_bo_init_mem_type(struct ttm_bo_device *bdev, uint32_t type,
switch (type) {
case TTM_PL_SYSTEM:
- man->flags = TTM_MEMTYPE_FLAG_MAPPABLE;
+ man->flags = 0;
man->available_caching = TTM_PL_MASK_CACHING;
man->default_caching = TTM_PL_FLAG_CACHED;
break;
case TTM_PL_VRAM:
- man->flags = TTM_MEMTYPE_FLAG_FIXED |
- TTM_MEMTYPE_FLAG_MAPPABLE;
+ man->flags = TTM_MEMTYPE_FLAG_FIXED;
man->available_caching = TTM_PL_FLAG_UNCACHED |
TTM_PL_FLAG_WC;
man->default_caching = TTM_PL_FLAG_WC;
@@ -675,7 +674,6 @@ nouveau_bo_init_mem_type(struct ttm_bo_device *bdev, uint32_t type,
}
man->func = &nouveau_vram_manager;
- man->io_reserve_fastpath = false;
man->use_io_reserve_lru = true;
} else {
man->func = &ttm_bo_manager_func;
@@ -691,13 +689,12 @@ nouveau_bo_init_mem_type(struct ttm_bo_device *bdev, uint32_t type,
man->func = &ttm_bo_manager_func;
if (drm->agp.bridge) {
- man->flags = TTM_MEMTYPE_FLAG_MAPPABLE;
+ man->flags = 0;
man->available_caching = TTM_PL_FLAG_UNCACHED |
TTM_PL_FLAG_WC;
man->default_caching = TTM_PL_FLAG_WC;
} else {
- man->flags = TTM_MEMTYPE_FLAG_MAPPABLE |
- TTM_MEMTYPE_FLAG_CMA;
+ man->flags = 0;
man->available_caching = TTM_PL_MASK_CACHING;
man->default_caching = TTM_PL_FLAG_CACHED;
}
@@ -1317,6 +1314,14 @@ nouveau_bo_move_ntfy(struct ttm_buffer_object *bo, bool evict,
nouveau_vma_unmap(vma);
}
}
+
+ if (new_reg) {
+ if (new_reg->mm_node)
+ nvbo->offset = (new_reg->start << PAGE_SHIFT);
+ else
+ nvbo->offset = 0;
+ }
+
}
static int
@@ -1431,7 +1436,6 @@ nouveau_bo_verify_access(struct ttm_buffer_object *bo, struct file *filp)
static int
nouveau_ttm_io_mem_reserve(struct ttm_bo_device *bdev, struct ttm_mem_reg *reg)
{
- struct ttm_mem_type_manager *man = &bdev->man[reg->mem_type];
struct nouveau_drm *drm = nouveau_bdev(bdev);
struct nvkm_device *device = nvxx_device(&drm->client.device);
struct nouveau_mem *mem = nouveau_mem(reg);
@@ -1441,8 +1445,7 @@ nouveau_ttm_io_mem_reserve(struct ttm_bo_device *bdev, struct ttm_mem_reg *reg)
reg->bus.size = reg->num_pages << PAGE_SHIFT;
reg->bus.base = 0;
reg->bus.is_iomem = false;
- if (!(man->flags & TTM_MEMTYPE_FLAG_MAPPABLE))
- return -EINVAL;
+
switch (reg->mem_type) {
case TTM_PL_SYSTEM:
/* System memory */
@@ -1497,8 +1500,6 @@ nouveau_ttm_io_mem_reserve(struct ttm_bo_device *bdev, struct ttm_mem_reg *reg)
if (ret != 1) {
if (WARN_ON(ret == 0))
return -EINVAL;
- if (ret == -ENOSPC)
- return -EAGAIN;
return ret;
}
diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.h b/drivers/gpu/drm/nouveau/nouveau_bo.h
index 38f9d8350963..e944b4aa5547 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.h
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.h
@@ -24,6 +24,9 @@ struct nouveau_bo {
int pbbo_index;
bool validate_mapped;
+ /* GPU address space is independent of CPU word size */
+ uint64_t offset;
+
struct list_head vma_list;
unsigned contig:1;
diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.c b/drivers/gpu/drm/nouveau/nouveau_chan.c
index d9381a053169..3d71dfcb2fde 100644
--- a/drivers/gpu/drm/nouveau/nouveau_chan.c
+++ b/drivers/gpu/drm/nouveau/nouveau_chan.c
@@ -162,7 +162,7 @@ nouveau_channel_prep(struct nouveau_drm *drm, struct nvif_device *device,
* pushbuf lives in, this is because the GEM code requires that
* we be able to call out to other (indirect) push buffers
*/
- chan->push.addr = chan->push.buffer->bo.offset;
+ chan->push.addr = chan->push.buffer->offset;
if (device->info.family >= NV_DEVICE_INFO_V0_TESLA) {
ret = nouveau_vma_new(chan->push.buffer, chan->vmm,
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index 1b383ae0248f..ab2c2b2cab10 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -60,7 +60,6 @@ nouveau_conn_native_mode(struct drm_connector *connector)
int high_w = 0, high_h = 0, high_v = 0;
list_for_each_entry(mode, &connector->probed_modes, head) {
- mode->vrefresh = drm_mode_vrefresh(mode);
if (helper->mode_valid(connector, mode) != MODE_OK ||
(mode->flags & DRM_MODE_FLAG_INTERLACE))
continue;
@@ -81,12 +80,12 @@ nouveau_conn_native_mode(struct drm_connector *connector)
continue;
if (mode->hdisplay == high_w && mode->vdisplay == high_h &&
- mode->vrefresh < high_v)
+ drm_mode_vrefresh(mode) < high_v)
continue;
high_w = mode->hdisplay;
high_h = mode->vdisplay;
- high_v = mode->vrefresh;
+ high_v = drm_mode_vrefresh(mode);
largest = mode;
}
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
index 496c4621cc78..657554cf011e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.c
+++ b/drivers/gpu/drm/nouveau/nouveau_display.c
@@ -44,15 +44,7 @@
#include <nvif/class.h>
#include <nvif/cl0046.h>
#include <nvif/event.h>
-
-static int
-nouveau_display_vblank_handler(struct nvif_notify *notify)
-{
- struct nouveau_crtc *nv_crtc =
- container_of(notify, typeof(*nv_crtc), vblank);
- drm_crtc_handle_vblank(&nv_crtc->base);
- return NVIF_NOTIFY_KEEP;
-}
+#include <dispnv50/crc.h>
int
nouveau_display_vblank_enable(struct drm_crtc *crtc)
@@ -136,50 +128,6 @@ nouveau_display_scanoutpos(struct drm_crtc *crtc,
stime, etime);
}
-static void
-nouveau_display_vblank_fini(struct drm_device *dev)
-{
- struct drm_crtc *crtc;
-
- list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
- struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
- nvif_notify_fini(&nv_crtc->vblank);
- }
-}
-
-static int
-nouveau_display_vblank_init(struct drm_device *dev)
-{
- struct nouveau_display *disp = nouveau_display(dev);
- struct drm_crtc *crtc;
- int ret;
-
- list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
- struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
- ret = nvif_notify_init(&disp->disp.object,
- nouveau_display_vblank_handler, false,
- NV04_DISP_NTFY_VBLANK,
- &(struct nvif_notify_head_req_v0) {
- .head = nv_crtc->index,
- },
- sizeof(struct nvif_notify_head_req_v0),
- sizeof(struct nvif_notify_head_rep_v0),
- &nv_crtc->vblank);
- if (ret) {
- nouveau_display_vblank_fini(dev);
- return ret;
- }
- }
-
- ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
- if (ret) {
- nouveau_display_vblank_fini(dev);
- return ret;
- }
-
- return 0;
-}
-
static const struct drm_framebuffer_funcs nouveau_framebuffer_funcs = {
.destroy = drm_gem_fb_destroy,
.create_handle = drm_gem_fb_create_handle,
@@ -426,7 +374,7 @@ nouveau_user_framebuffer_create(struct drm_device *dev,
if (ret == 0)
return fb;
- drm_gem_object_put_unlocked(gem);
+ drm_gem_object_put(gem);
return ERR_PTR(ret);
}
@@ -705,9 +653,12 @@ nouveau_display_create(struct drm_device *dev)
drm_mode_config_reset(dev);
if (dev->mode_config.num_crtc) {
- ret = nouveau_display_vblank_init(dev);
+ ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
if (ret)
goto vblank_err;
+
+ if (disp->disp.object.oclass >= NV50_DISP)
+ nv50_crc_init(dev);
}
INIT_WORK(&drm->hpd_work, nouveau_display_hpd_work);
@@ -734,7 +685,6 @@ nouveau_display_destroy(struct drm_device *dev)
#ifdef CONFIG_ACPI
unregister_acpi_notifier(&nouveau_drm(dev)->acpi_nb);
#endif
- nouveau_display_vblank_fini(dev);
drm_kms_helper_poll_fini(dev);
drm_mode_config_cleanup(dev);
@@ -808,7 +758,7 @@ nouveau_display_dumb_create(struct drm_file *file_priv, struct drm_device *dev,
return ret;
ret = drm_gem_handle_create(file_priv, &bo->bo.base, &args->handle);
- drm_gem_object_put_unlocked(&bo->bo.base);
+ drm_gem_object_put(&bo->bo.base);
return ret;
}
@@ -823,7 +773,7 @@ nouveau_display_dumb_map_offset(struct drm_file *file_priv,
if (gem) {
struct nouveau_bo *bo = nouveau_gem_object(gem);
*poffset = drm_vma_node_offset_addr(&bo->bo.base.vma_node);
- drm_gem_object_put_unlocked(gem);
+ drm_gem_object_put(gem);
return 0;
}
diff --git a/drivers/gpu/drm/nouveau/nouveau_dmem.c b/drivers/gpu/drm/nouveau/nouveau_dmem.c
index cc9993837508..462ee0493651 100644
--- a/drivers/gpu/drm/nouveau/nouveau_dmem.c
+++ b/drivers/gpu/drm/nouveau/nouveau_dmem.c
@@ -100,7 +100,7 @@ unsigned long nouveau_dmem_page_addr(struct page *page)
unsigned long off = (page_to_pfn(page) << PAGE_SHIFT) -
chunk->pagemap.res.start;
- return chunk->bo->bo.offset + off;
+ return chunk->bo->offset + off;
}
static void nouveau_dmem_page_free(struct page *page)
diff --git a/drivers/gpu/drm/nouveau/nouveau_fbcon.c b/drivers/gpu/drm/nouveau/nouveau_fbcon.c
index 3d11b84d4cf9..f9f5a13fdb80 100644
--- a/drivers/gpu/drm/nouveau/nouveau_fbcon.c
+++ b/drivers/gpu/drm/nouveau/nouveau_fbcon.c
@@ -393,7 +393,7 @@ nouveau_fbcon_create(struct drm_fb_helper *helper,
/* To allow resizeing without swapping buffers */
NV_INFO(drm, "allocated %dx%d fb: 0x%llx, bo %p\n",
- fb->width, fb->height, nvbo->bo.offset, nvbo);
+ fb->width, fb->height, nvbo->offset, nvbo);
vga_switcheroo_client_fb_set(dev->pdev, info);
return 0;
diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c
index 4c3f131ad31d..63b832585390 100644
--- a/drivers/gpu/drm/nouveau/nouveau_gem.c
+++ b/drivers/gpu/drm/nouveau/nouveau_gem.c
@@ -234,7 +234,7 @@ nouveau_gem_info(struct drm_file *file_priv, struct drm_gem_object *gem,
rep->domain = NOUVEAU_GEM_DOMAIN_GART;
else
rep->domain = NOUVEAU_GEM_DOMAIN_VRAM;
- rep->offset = nvbo->bo.offset;
+ rep->offset = nvbo->offset;
if (vmm->vmm.object.oclass >= NVIF_CLASS_VMM_NV50) {
vma = nouveau_vma_find(nvbo, vmm);
if (!vma)
@@ -281,7 +281,7 @@ nouveau_gem_ioctl_new(struct drm_device *dev, void *data,
}
/* drop reference from allocate - handle holds it now */
- drm_gem_object_put_unlocked(&nvbo->bo.base);
+ drm_gem_object_put(&nvbo->bo.base);
return ret;
}
@@ -360,7 +360,7 @@ validate_fini_no_ticket(struct validate_op *op, struct nouveau_channel *chan,
list_del(&nvbo->entry);
nvbo->reserved_by = NULL;
ttm_bo_unreserve(&nvbo->bo);
- drm_gem_object_put_unlocked(&nvbo->bo.base);
+ drm_gem_object_put(&nvbo->bo.base);
}
}
@@ -407,14 +407,14 @@ retry:
nvbo = nouveau_gem_object(gem);
if (nvbo == res_bo) {
res_bo = NULL;
- drm_gem_object_put_unlocked(gem);
+ drm_gem_object_put(gem);
continue;
}
if (nvbo->reserved_by && nvbo->reserved_by == file_priv) {
NV_PRINTK(err, cli, "multiple instances of buffer %d on "
"validation list\n", b->handle);
- drm_gem_object_put_unlocked(gem);
+ drm_gem_object_put(gem);
ret = -EINVAL;
break;
}
@@ -518,7 +518,7 @@ validate_list(struct nouveau_channel *chan, struct nouveau_cli *cli,
}
if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA) {
- if (nvbo->bo.offset == b->presumed.offset &&
+ if (nvbo->offset == b->presumed.offset &&
((nvbo->bo.mem.mem_type == TTM_PL_VRAM &&
b->presumed.domain & NOUVEAU_GEM_DOMAIN_VRAM) ||
(nvbo->bo.mem.mem_type == TTM_PL_TT &&
@@ -529,7 +529,7 @@ validate_list(struct nouveau_channel *chan, struct nouveau_cli *cli,
b->presumed.domain = NOUVEAU_GEM_DOMAIN_GART;
else
b->presumed.domain = NOUVEAU_GEM_DOMAIN_VRAM;
- b->presumed.offset = nvbo->bo.offset;
+ b->presumed.offset = nvbo->offset;
b->presumed.valid = 0;
relocs++;
}
@@ -807,7 +807,7 @@ revalidate:
struct nouveau_bo *nvbo = (void *)(unsigned long)
bo[push[i].bo_index].user_priv;
- OUT_RING(chan, (nvbo->bo.offset + push[i].offset) | 2);
+ OUT_RING(chan, (nvbo->offset + push[i].offset) | 2);
OUT_RING(chan, 0);
}
} else {
@@ -842,7 +842,7 @@ revalidate:
}
OUT_RING(chan, 0x20000000 |
- (nvbo->bo.offset + push[i].offset));
+ (nvbo->offset + push[i].offset));
OUT_RING(chan, 0);
for (j = 0; j < NOUVEAU_DMA_SKIPS; j++)
OUT_RING(chan, 0);
@@ -931,7 +931,7 @@ nouveau_gem_ioctl_cpu_prep(struct drm_device *dev, void *data,
ret = lret;
nouveau_bo_sync_for_cpu(nvbo);
- drm_gem_object_put_unlocked(gem);
+ drm_gem_object_put(gem);
return ret;
}
@@ -950,7 +950,7 @@ nouveau_gem_ioctl_cpu_fini(struct drm_device *dev, void *data,
nvbo = nouveau_gem_object(gem);
nouveau_bo_sync_for_device(nvbo);
- drm_gem_object_put_unlocked(gem);
+ drm_gem_object_put(gem);
return 0;
}
@@ -967,7 +967,7 @@ nouveau_gem_ioctl_info(struct drm_device *dev, void *data,
return -ENOENT;
ret = nouveau_gem_info(file_priv, gem, req);
- drm_gem_object_put_unlocked(gem);
+ drm_gem_object_put(gem);
return ret;
}
diff --git a/drivers/gpu/drm/nouveau/nouveau_ttm.c b/drivers/gpu/drm/nouveau/nouveau_ttm.c
index 7ca0a2498532..e89ea052cf71 100644
--- a/drivers/gpu/drm/nouveau/nouveau_ttm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_ttm.c
@@ -75,10 +75,6 @@ nouveau_vram_manager_new(struct ttm_mem_type_manager *man,
ret = nouveau_mem_vram(reg, nvbo->contig, nvbo->page);
if (ret) {
nouveau_mem_del(reg);
- if (ret == -ENOSPC) {
- reg->mm_node = NULL;
- return 0;
- }
return ret;
}
@@ -139,10 +135,6 @@ nv04_gart_manager_new(struct ttm_mem_type_manager *man,
reg->num_pages << PAGE_SHIFT, &mem->vma[0]);
if (ret) {
nouveau_mem_del(reg);
- if (ret == -ENOSPC) {
- reg->mm_node = NULL;
- return 0;
- }
return ret;
}