123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745 |
- // 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 <nvhw/class/cl907d.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 drm_device *dev = crtc->dev;
- struct nv50_disp *disp = nv50_disp(dev);
- const uint64_t start_vbl = drm_crtc_vblank_count(crtc);
- uint64_t end_vbl;
- 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_dbg_kms(dev, "Lock contended, delaying CRC ctx flip for %s\n", crtc->name);
- drm_vblank_work_schedule(work, start_vbl + 1, true);
- return;
- }
- drm_dbg_kms(dev, "Flipping notifier ctx for %s (%d -> %d)\n",
- crtc->name, crc->ctx_idx, new_idx);
- nv50_crc_program_ctx(head, NULL);
- nv50_crc_program_ctx(head, &crc->ctx[new_idx]);
- mutex_unlock(&disp->mutex);
- end_vbl = drm_crtc_vblank_count(crtc);
- if (unlikely(end_vbl != start_vbl))
- NV_ERROR(nouveau_drm(dev),
- "Failed to flip CRC context on %s on time (%llu > %llu)\n",
- crtc->name, end_vbl, start_vbl);
- 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_dbg_kms(head->base.base.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);
- 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;
- }
- if (drm_atomic_crtc_needs_modeset(&asyh->state) || changed) {
- 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;
- struct drm_encoder *encoder, *enc;
- enc = nv50_head_atom_get_encoder(armh);
- if (!enc)
- continue;
- outp = nv50_real_outp(enc);
- if (!outp)
- continue;
- 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;
- struct drm_encoder *encoder;
- encoder = nv50_head_atom_get_encoder(asyh);
- if (!encoder)
- return;
- outp = nv50_real_outp(encoder);
- if (!outp)
- return;
- func->set_src(head, outp->or, nv50_crc_source_type(outp, asyh->crc.src),
- &crc->ctx[crc->ctx_idx]);
- }
- 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);
- }
- 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 NV907D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_CRC_MODE_ACTIVE_RASTER;
- case NV50_CRC_SOURCE_OUTP_COMPLETE:
- return NV907D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_CRC_MODE_COMPLETE_RASTER;
- case NV50_CRC_SOURCE_OUTP_INACTIVE:
- return NV907D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_CRC_MODE_NON_ACTIVE_RASTER;
- }
- 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_ctor_map(mmu, "kmsCrcNtfy", NVIF_MEM_VRAM, len, &ctx->mem);
- if (ret)
- return ret;
- ret = nvif_object_ctor(&core->chan.base.user, "kmsCrcNtfyCtxDma",
- 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_dtor(&ctx->mem);
- return ret;
- }
- static inline void
- nv50_crc_ctx_fini(struct nv50_crc_notifier_ctx *ctx)
- {
- nvif_object_dtor(&ctx->ntfy);
- nvif_mem_dtor(&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,
- .release = single_release,
- };
- 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));
- }
|