162306a36Sopenharmony_ci// SPDX-License-Identifier: MIT
262306a36Sopenharmony_ci#include <linux/string.h>
362306a36Sopenharmony_ci#include <drm/drm_crtc.h>
462306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h>
562306a36Sopenharmony_ci#include <drm/drm_vblank.h>
662306a36Sopenharmony_ci#include <drm/drm_vblank_work.h>
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <nvif/class.h>
962306a36Sopenharmony_ci#include <nvif/cl0002.h>
1062306a36Sopenharmony_ci#include <nvif/timer.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <nvhw/class/cl907d.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include "nouveau_drv.h"
1562306a36Sopenharmony_ci#include "core.h"
1662306a36Sopenharmony_ci#include "head.h"
1762306a36Sopenharmony_ci#include "wndw.h"
1862306a36Sopenharmony_ci#include "handles.h"
1962306a36Sopenharmony_ci#include "crc.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic const char * const nv50_crc_sources[] = {
2262306a36Sopenharmony_ci	[NV50_CRC_SOURCE_NONE] = "none",
2362306a36Sopenharmony_ci	[NV50_CRC_SOURCE_AUTO] = "auto",
2462306a36Sopenharmony_ci	[NV50_CRC_SOURCE_RG] = "rg",
2562306a36Sopenharmony_ci	[NV50_CRC_SOURCE_OUTP_ACTIVE] = "outp-active",
2662306a36Sopenharmony_ci	[NV50_CRC_SOURCE_OUTP_COMPLETE] = "outp-complete",
2762306a36Sopenharmony_ci	[NV50_CRC_SOURCE_OUTP_INACTIVE] = "outp-inactive",
2862306a36Sopenharmony_ci};
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic int nv50_crc_parse_source(const char *buf, enum nv50_crc_source *s)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	int i;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	if (!buf) {
3562306a36Sopenharmony_ci		*s = NV50_CRC_SOURCE_NONE;
3662306a36Sopenharmony_ci		return 0;
3762306a36Sopenharmony_ci	}
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	i = match_string(nv50_crc_sources, ARRAY_SIZE(nv50_crc_sources), buf);
4062306a36Sopenharmony_ci	if (i < 0)
4162306a36Sopenharmony_ci		return i;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	*s = i;
4462306a36Sopenharmony_ci	return 0;
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ciint
4862306a36Sopenharmony_cinv50_crc_verify_source(struct drm_crtc *crtc, const char *source_name,
4962306a36Sopenharmony_ci		       size_t *values_cnt)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(crtc->dev);
5262306a36Sopenharmony_ci	enum nv50_crc_source source;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	if (nv50_crc_parse_source(source_name, &source) < 0) {
5562306a36Sopenharmony_ci		NV_DEBUG(drm, "unknown source %s\n", source_name);
5662306a36Sopenharmony_ci		return -EINVAL;
5762306a36Sopenharmony_ci	}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	*values_cnt = 1;
6062306a36Sopenharmony_ci	return 0;
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ciconst char *const *nv50_crc_get_sources(struct drm_crtc *crtc, size_t *count)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	*count = ARRAY_SIZE(nv50_crc_sources);
6662306a36Sopenharmony_ci	return nv50_crc_sources;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic void
7062306a36Sopenharmony_cinv50_crc_program_ctx(struct nv50_head *head,
7162306a36Sopenharmony_ci		     struct nv50_crc_notifier_ctx *ctx)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct nv50_disp *disp = nv50_disp(head->base.base.dev);
7462306a36Sopenharmony_ci	struct nv50_core *core = disp->core;
7562306a36Sopenharmony_ci	u32 interlock[NV50_DISP_INTERLOCK__SIZE] = { 0 };
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	core->func->crc->set_ctx(head, ctx);
7862306a36Sopenharmony_ci	core->func->update(core, interlock, false);
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic void nv50_crc_ctx_flip_work(struct kthread_work *base)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	struct drm_vblank_work *work = to_drm_vblank_work(base);
8462306a36Sopenharmony_ci	struct nv50_crc *crc = container_of(work, struct nv50_crc, flip_work);
8562306a36Sopenharmony_ci	struct nv50_head *head = container_of(crc, struct nv50_head, crc);
8662306a36Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
8762306a36Sopenharmony_ci	struct drm_device *dev = crtc->dev;
8862306a36Sopenharmony_ci	struct nv50_disp *disp = nv50_disp(dev);
8962306a36Sopenharmony_ci	const uint64_t start_vbl = drm_crtc_vblank_count(crtc);
9062306a36Sopenharmony_ci	uint64_t end_vbl;
9162306a36Sopenharmony_ci	u8 new_idx = crc->ctx_idx ^ 1;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	/*
9462306a36Sopenharmony_ci	 * We don't want to accidentally wait for longer then the vblank, so
9562306a36Sopenharmony_ci	 * try again for the next vblank if we don't grab the lock
9662306a36Sopenharmony_ci	 */
9762306a36Sopenharmony_ci	if (!mutex_trylock(&disp->mutex)) {
9862306a36Sopenharmony_ci		drm_dbg_kms(dev, "Lock contended, delaying CRC ctx flip for %s\n", crtc->name);
9962306a36Sopenharmony_ci		drm_vblank_work_schedule(work, start_vbl + 1, true);
10062306a36Sopenharmony_ci		return;
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	drm_dbg_kms(dev, "Flipping notifier ctx for %s (%d -> %d)\n",
10462306a36Sopenharmony_ci		    crtc->name, crc->ctx_idx, new_idx);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	nv50_crc_program_ctx(head, NULL);
10762306a36Sopenharmony_ci	nv50_crc_program_ctx(head, &crc->ctx[new_idx]);
10862306a36Sopenharmony_ci	mutex_unlock(&disp->mutex);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	end_vbl = drm_crtc_vblank_count(crtc);
11162306a36Sopenharmony_ci	if (unlikely(end_vbl != start_vbl))
11262306a36Sopenharmony_ci		NV_ERROR(nouveau_drm(dev),
11362306a36Sopenharmony_ci			 "Failed to flip CRC context on %s on time (%llu > %llu)\n",
11462306a36Sopenharmony_ci			 crtc->name, end_vbl, start_vbl);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	spin_lock_irq(&crc->lock);
11762306a36Sopenharmony_ci	crc->ctx_changed = true;
11862306a36Sopenharmony_ci	spin_unlock_irq(&crc->lock);
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic inline void nv50_crc_reset_ctx(struct nv50_crc_notifier_ctx *ctx)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	memset_io(ctx->mem.object.map.ptr, 0, ctx->mem.object.map.size);
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic void
12762306a36Sopenharmony_cinv50_crc_get_entries(struct nv50_head *head,
12862306a36Sopenharmony_ci		     const struct nv50_crc_func *func,
12962306a36Sopenharmony_ci		     enum nv50_crc_source source)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
13262306a36Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
13362306a36Sopenharmony_ci	u32 output_crc;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	while (crc->entry_idx < func->num_entries) {
13662306a36Sopenharmony_ci		/*
13762306a36Sopenharmony_ci		 * While Nvidia's documentation says CRCs are written on each
13862306a36Sopenharmony_ci		 * subsequent vblank after being enabled, in practice they
13962306a36Sopenharmony_ci		 * aren't written immediately.
14062306a36Sopenharmony_ci		 */
14162306a36Sopenharmony_ci		output_crc = func->get_entry(head, &crc->ctx[crc->ctx_idx],
14262306a36Sopenharmony_ci					     source, crc->entry_idx);
14362306a36Sopenharmony_ci		if (!output_crc)
14462306a36Sopenharmony_ci			return;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci		drm_crtc_add_crc_entry(crtc, true, crc->frame, &output_crc);
14762306a36Sopenharmony_ci		crc->frame++;
14862306a36Sopenharmony_ci		crc->entry_idx++;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_civoid nv50_crc_handle_vblank(struct nv50_head *head)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
15562306a36Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
15662306a36Sopenharmony_ci	const struct nv50_crc_func *func =
15762306a36Sopenharmony_ci		nv50_disp(head->base.base.dev)->core->func->crc;
15862306a36Sopenharmony_ci	struct nv50_crc_notifier_ctx *ctx;
15962306a36Sopenharmony_ci	bool need_reschedule = false;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	if (!func)
16262306a36Sopenharmony_ci		return;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/*
16562306a36Sopenharmony_ci	 * We don't lose events if we aren't able to report CRCs until the
16662306a36Sopenharmony_ci	 * next vblank, so only report CRCs if the locks we need aren't
16762306a36Sopenharmony_ci	 * contended to prevent missing an actual vblank event
16862306a36Sopenharmony_ci	 */
16962306a36Sopenharmony_ci	if (!spin_trylock(&crc->lock))
17062306a36Sopenharmony_ci		return;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	if (!crc->src)
17362306a36Sopenharmony_ci		goto out;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	ctx = &crc->ctx[crc->ctx_idx];
17662306a36Sopenharmony_ci	if (crc->ctx_changed && func->ctx_finished(head, ctx)) {
17762306a36Sopenharmony_ci		nv50_crc_get_entries(head, func, crc->src);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci		crc->ctx_idx ^= 1;
18062306a36Sopenharmony_ci		crc->entry_idx = 0;
18162306a36Sopenharmony_ci		crc->ctx_changed = false;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci		/*
18462306a36Sopenharmony_ci		 * Unfortunately when notifier contexts are changed during CRC
18562306a36Sopenharmony_ci		 * capture, we will inevitably lose the CRC entry for the
18662306a36Sopenharmony_ci		 * frame where the hardware actually latched onto the first
18762306a36Sopenharmony_ci		 * UPDATE. According to Nvidia's hardware engineers, there's
18862306a36Sopenharmony_ci		 * no workaround for this.
18962306a36Sopenharmony_ci		 *
19062306a36Sopenharmony_ci		 * Now, we could try to be smart here and calculate the number
19162306a36Sopenharmony_ci		 * of missed CRCs based on audit timestamps, but those were
19262306a36Sopenharmony_ci		 * removed starting with volta. Since we always flush our
19362306a36Sopenharmony_ci		 * updates back-to-back without waiting, we'll just be
19462306a36Sopenharmony_ci		 * optimistic and assume we always miss exactly one frame.
19562306a36Sopenharmony_ci		 */
19662306a36Sopenharmony_ci		drm_dbg_kms(head->base.base.dev,
19762306a36Sopenharmony_ci			    "Notifier ctx flip for head-%d finished, lost CRC for frame %llu\n",
19862306a36Sopenharmony_ci			    head->base.index, crc->frame);
19962306a36Sopenharmony_ci		crc->frame++;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci		nv50_crc_reset_ctx(ctx);
20262306a36Sopenharmony_ci		need_reschedule = true;
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	nv50_crc_get_entries(head, func, crc->src);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	if (need_reschedule)
20862306a36Sopenharmony_ci		drm_vblank_work_schedule(&crc->flip_work,
20962306a36Sopenharmony_ci					 drm_crtc_vblank_count(crtc)
21062306a36Sopenharmony_ci					 + crc->flip_threshold
21162306a36Sopenharmony_ci					 - crc->entry_idx,
21262306a36Sopenharmony_ci					 true);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ciout:
21562306a36Sopenharmony_ci	spin_unlock(&crc->lock);
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic void nv50_crc_wait_ctx_finished(struct nv50_head *head,
21962306a36Sopenharmony_ci				       const struct nv50_crc_func *func,
22062306a36Sopenharmony_ci				       struct nv50_crc_notifier_ctx *ctx)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	struct drm_device *dev = head->base.base.dev;
22362306a36Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(dev);
22462306a36Sopenharmony_ci	s64 ret;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	ret = nvif_msec(&drm->client.device, 50,
22762306a36Sopenharmony_ci			if (func->ctx_finished(head, ctx)) break;);
22862306a36Sopenharmony_ci	if (ret == -ETIMEDOUT)
22962306a36Sopenharmony_ci		NV_ERROR(drm,
23062306a36Sopenharmony_ci			 "CRC notifier ctx for head %d not finished after 50ms\n",
23162306a36Sopenharmony_ci			 head->base.index);
23262306a36Sopenharmony_ci	else if (ret)
23362306a36Sopenharmony_ci		NV_ATOMIC(drm,
23462306a36Sopenharmony_ci			  "CRC notifier ctx for head-%d finished after %lldns\n",
23562306a36Sopenharmony_ci			  head->base.index, ret);
23662306a36Sopenharmony_ci}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_civoid nv50_crc_atomic_stop_reporting(struct drm_atomic_state *state)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	struct drm_crtc_state *crtc_state;
24162306a36Sopenharmony_ci	struct drm_crtc *crtc;
24262306a36Sopenharmony_ci	int i;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
24562306a36Sopenharmony_ci		struct nv50_head *head = nv50_head(crtc);
24662306a36Sopenharmony_ci		struct nv50_head_atom *asyh = nv50_head_atom(crtc_state);
24762306a36Sopenharmony_ci		struct nv50_crc *crc = &head->crc;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci		if (!asyh->clr.crc)
25062306a36Sopenharmony_ci			continue;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci		spin_lock_irq(&crc->lock);
25362306a36Sopenharmony_ci		crc->src = NV50_CRC_SOURCE_NONE;
25462306a36Sopenharmony_ci		spin_unlock_irq(&crc->lock);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci		drm_crtc_vblank_put(crtc);
25762306a36Sopenharmony_ci		drm_vblank_work_cancel_sync(&crc->flip_work);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci		NV_ATOMIC(nouveau_drm(crtc->dev),
26062306a36Sopenharmony_ci			  "CRC reporting on vblank for head-%d disabled\n",
26162306a36Sopenharmony_ci			  head->base.index);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci		/* CRC generation is still enabled in hw, we'll just report
26462306a36Sopenharmony_ci		 * any remaining CRC entries ourselves after it gets disabled
26562306a36Sopenharmony_ci		 * in hardware
26662306a36Sopenharmony_ci		 */
26762306a36Sopenharmony_ci	}
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_civoid nv50_crc_atomic_init_notifier_contexts(struct drm_atomic_state *state)
27162306a36Sopenharmony_ci{
27262306a36Sopenharmony_ci	struct drm_crtc_state *new_crtc_state;
27362306a36Sopenharmony_ci	struct drm_crtc *crtc;
27462306a36Sopenharmony_ci	int i;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
27762306a36Sopenharmony_ci		struct nv50_head *head = nv50_head(crtc);
27862306a36Sopenharmony_ci		struct nv50_head_atom *asyh = nv50_head_atom(new_crtc_state);
27962306a36Sopenharmony_ci		struct nv50_crc *crc = &head->crc;
28062306a36Sopenharmony_ci		int i;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci		if (!asyh->set.crc)
28362306a36Sopenharmony_ci			continue;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci		crc->entry_idx = 0;
28662306a36Sopenharmony_ci		crc->ctx_changed = false;
28762306a36Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(crc->ctx); i++)
28862306a36Sopenharmony_ci			nv50_crc_reset_ctx(&crc->ctx[i]);
28962306a36Sopenharmony_ci	}
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_civoid nv50_crc_atomic_release_notifier_contexts(struct drm_atomic_state *state)
29362306a36Sopenharmony_ci{
29462306a36Sopenharmony_ci	const struct nv50_crc_func *func =
29562306a36Sopenharmony_ci		nv50_disp(state->dev)->core->func->crc;
29662306a36Sopenharmony_ci	struct drm_crtc_state *new_crtc_state;
29762306a36Sopenharmony_ci	struct drm_crtc *crtc;
29862306a36Sopenharmony_ci	int i;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
30162306a36Sopenharmony_ci		struct nv50_head *head = nv50_head(crtc);
30262306a36Sopenharmony_ci		struct nv50_head_atom *asyh = nv50_head_atom(new_crtc_state);
30362306a36Sopenharmony_ci		struct nv50_crc *crc = &head->crc;
30462306a36Sopenharmony_ci		struct nv50_crc_notifier_ctx *ctx = &crc->ctx[crc->ctx_idx];
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci		if (!asyh->clr.crc)
30762306a36Sopenharmony_ci			continue;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci		if (crc->ctx_changed) {
31062306a36Sopenharmony_ci			nv50_crc_wait_ctx_finished(head, func, ctx);
31162306a36Sopenharmony_ci			ctx = &crc->ctx[crc->ctx_idx ^ 1];
31262306a36Sopenharmony_ci		}
31362306a36Sopenharmony_ci		nv50_crc_wait_ctx_finished(head, func, ctx);
31462306a36Sopenharmony_ci	}
31562306a36Sopenharmony_ci}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_civoid nv50_crc_atomic_start_reporting(struct drm_atomic_state *state)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	struct drm_crtc_state *crtc_state;
32062306a36Sopenharmony_ci	struct drm_crtc *crtc;
32162306a36Sopenharmony_ci	int i;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
32462306a36Sopenharmony_ci		struct nv50_head *head = nv50_head(crtc);
32562306a36Sopenharmony_ci		struct nv50_head_atom *asyh = nv50_head_atom(crtc_state);
32662306a36Sopenharmony_ci		struct nv50_crc *crc = &head->crc;
32762306a36Sopenharmony_ci		u64 vbl_count;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci		if (!asyh->set.crc)
33062306a36Sopenharmony_ci			continue;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci		drm_crtc_vblank_get(crtc);
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci		spin_lock_irq(&crc->lock);
33562306a36Sopenharmony_ci		vbl_count = drm_crtc_vblank_count(crtc);
33662306a36Sopenharmony_ci		crc->frame = vbl_count;
33762306a36Sopenharmony_ci		crc->src = asyh->crc.src;
33862306a36Sopenharmony_ci		drm_vblank_work_schedule(&crc->flip_work,
33962306a36Sopenharmony_ci					 vbl_count + crc->flip_threshold,
34062306a36Sopenharmony_ci					 true);
34162306a36Sopenharmony_ci		spin_unlock_irq(&crc->lock);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci		NV_ATOMIC(nouveau_drm(crtc->dev),
34462306a36Sopenharmony_ci			  "CRC reporting on vblank for head-%d enabled\n",
34562306a36Sopenharmony_ci			  head->base.index);
34662306a36Sopenharmony_ci	}
34762306a36Sopenharmony_ci}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ciint nv50_crc_atomic_check_head(struct nv50_head *head,
35062306a36Sopenharmony_ci			       struct nv50_head_atom *asyh,
35162306a36Sopenharmony_ci			       struct nv50_head_atom *armh)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	struct nv50_atom *atom = nv50_atom(asyh->state.state);
35462306a36Sopenharmony_ci	bool changed = armh->crc.src != asyh->crc.src;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	if (!armh->crc.src && !asyh->crc.src) {
35762306a36Sopenharmony_ci		asyh->set.crc = false;
35862306a36Sopenharmony_ci		asyh->clr.crc = false;
35962306a36Sopenharmony_ci		return 0;
36062306a36Sopenharmony_ci	}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	if (drm_atomic_crtc_needs_modeset(&asyh->state) || changed) {
36362306a36Sopenharmony_ci		asyh->clr.crc = armh->crc.src && armh->state.active;
36462306a36Sopenharmony_ci		asyh->set.crc = asyh->crc.src && asyh->state.active;
36562306a36Sopenharmony_ci		if (changed)
36662306a36Sopenharmony_ci			asyh->set.or |= armh->or.crc_raster !=
36762306a36Sopenharmony_ci					asyh->or.crc_raster;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci		if (asyh->clr.crc && asyh->set.crc)
37062306a36Sopenharmony_ci			atom->flush_disable = true;
37162306a36Sopenharmony_ci	} else {
37262306a36Sopenharmony_ci		asyh->set.crc = false;
37362306a36Sopenharmony_ci		asyh->clr.crc = false;
37462306a36Sopenharmony_ci	}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	return 0;
37762306a36Sopenharmony_ci}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_civoid nv50_crc_atomic_check_outp(struct nv50_atom *atom)
38062306a36Sopenharmony_ci{
38162306a36Sopenharmony_ci	struct drm_crtc *crtc;
38262306a36Sopenharmony_ci	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
38362306a36Sopenharmony_ci	int i;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	if (atom->flush_disable)
38662306a36Sopenharmony_ci		return;
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	for_each_oldnew_crtc_in_state(&atom->state, crtc, old_crtc_state,
38962306a36Sopenharmony_ci				      new_crtc_state, i) {
39062306a36Sopenharmony_ci		struct nv50_head_atom *armh = nv50_head_atom(old_crtc_state);
39162306a36Sopenharmony_ci		struct nv50_head_atom *asyh = nv50_head_atom(new_crtc_state);
39262306a36Sopenharmony_ci		struct nv50_outp_atom *outp_atom;
39362306a36Sopenharmony_ci		struct nouveau_encoder *outp;
39462306a36Sopenharmony_ci		struct drm_encoder *encoder, *enc;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci		enc = nv50_head_atom_get_encoder(armh);
39762306a36Sopenharmony_ci		if (!enc)
39862306a36Sopenharmony_ci			continue;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci		outp = nv50_real_outp(enc);
40162306a36Sopenharmony_ci		if (!outp)
40262306a36Sopenharmony_ci			continue;
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci		encoder = &outp->base.base;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci		if (!asyh->clr.crc)
40762306a36Sopenharmony_ci			continue;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci		/*
41062306a36Sopenharmony_ci		 * Re-programming ORs can't be done in the same flush as
41162306a36Sopenharmony_ci		 * disabling CRCs
41262306a36Sopenharmony_ci		 */
41362306a36Sopenharmony_ci		list_for_each_entry(outp_atom, &atom->outp, head) {
41462306a36Sopenharmony_ci			if (outp_atom->encoder == encoder) {
41562306a36Sopenharmony_ci				if (outp_atom->set.mask) {
41662306a36Sopenharmony_ci					atom->flush_disable = true;
41762306a36Sopenharmony_ci					return;
41862306a36Sopenharmony_ci				} else {
41962306a36Sopenharmony_ci					break;
42062306a36Sopenharmony_ci				}
42162306a36Sopenharmony_ci			}
42262306a36Sopenharmony_ci		}
42362306a36Sopenharmony_ci	}
42462306a36Sopenharmony_ci}
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_cistatic enum nv50_crc_source_type
42762306a36Sopenharmony_cinv50_crc_source_type(struct nouveau_encoder *outp,
42862306a36Sopenharmony_ci		     enum nv50_crc_source source)
42962306a36Sopenharmony_ci{
43062306a36Sopenharmony_ci	struct dcb_output *dcbe = outp->dcb;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	switch (source) {
43362306a36Sopenharmony_ci	case NV50_CRC_SOURCE_NONE: return NV50_CRC_SOURCE_TYPE_NONE;
43462306a36Sopenharmony_ci	case NV50_CRC_SOURCE_RG:   return NV50_CRC_SOURCE_TYPE_RG;
43562306a36Sopenharmony_ci	default:		   break;
43662306a36Sopenharmony_ci	}
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	if (dcbe->location != DCB_LOC_ON_CHIP)
43962306a36Sopenharmony_ci		return NV50_CRC_SOURCE_TYPE_PIOR;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	switch (dcbe->type) {
44262306a36Sopenharmony_ci	case DCB_OUTPUT_DP:	return NV50_CRC_SOURCE_TYPE_SF;
44362306a36Sopenharmony_ci	case DCB_OUTPUT_ANALOG:	return NV50_CRC_SOURCE_TYPE_DAC;
44462306a36Sopenharmony_ci	default:		return NV50_CRC_SOURCE_TYPE_SOR;
44562306a36Sopenharmony_ci	}
44662306a36Sopenharmony_ci}
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_civoid nv50_crc_atomic_set(struct nv50_head *head,
44962306a36Sopenharmony_ci			 struct nv50_head_atom *asyh)
45062306a36Sopenharmony_ci{
45162306a36Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
45262306a36Sopenharmony_ci	struct drm_device *dev = crtc->dev;
45362306a36Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
45462306a36Sopenharmony_ci	const struct nv50_crc_func *func = nv50_disp(dev)->core->func->crc;
45562306a36Sopenharmony_ci	struct nouveau_encoder *outp;
45662306a36Sopenharmony_ci	struct drm_encoder *encoder;
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	encoder = nv50_head_atom_get_encoder(asyh);
45962306a36Sopenharmony_ci	if (!encoder)
46062306a36Sopenharmony_ci		return;
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	outp = nv50_real_outp(encoder);
46362306a36Sopenharmony_ci	if (!outp)
46462306a36Sopenharmony_ci		return;
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_ci	func->set_src(head, outp->outp.or.id, nv50_crc_source_type(outp, asyh->crc.src),
46762306a36Sopenharmony_ci		      &crc->ctx[crc->ctx_idx]);
46862306a36Sopenharmony_ci}
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_civoid nv50_crc_atomic_clr(struct nv50_head *head)
47162306a36Sopenharmony_ci{
47262306a36Sopenharmony_ci	const struct nv50_crc_func *func =
47362306a36Sopenharmony_ci		nv50_disp(head->base.base.dev)->core->func->crc;
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	func->set_src(head, 0, NV50_CRC_SOURCE_TYPE_NONE, NULL);
47662306a36Sopenharmony_ci}
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_cistatic inline int
47962306a36Sopenharmony_cinv50_crc_raster_type(enum nv50_crc_source source)
48062306a36Sopenharmony_ci{
48162306a36Sopenharmony_ci	switch (source) {
48262306a36Sopenharmony_ci	case NV50_CRC_SOURCE_NONE:
48362306a36Sopenharmony_ci	case NV50_CRC_SOURCE_AUTO:
48462306a36Sopenharmony_ci	case NV50_CRC_SOURCE_RG:
48562306a36Sopenharmony_ci	case NV50_CRC_SOURCE_OUTP_ACTIVE:
48662306a36Sopenharmony_ci		return NV907D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_CRC_MODE_ACTIVE_RASTER;
48762306a36Sopenharmony_ci	case NV50_CRC_SOURCE_OUTP_COMPLETE:
48862306a36Sopenharmony_ci		return NV907D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_CRC_MODE_COMPLETE_RASTER;
48962306a36Sopenharmony_ci	case NV50_CRC_SOURCE_OUTP_INACTIVE:
49062306a36Sopenharmony_ci		return NV907D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_CRC_MODE_NON_ACTIVE_RASTER;
49162306a36Sopenharmony_ci	}
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	return 0;
49462306a36Sopenharmony_ci}
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci/* We handle mapping the memory for CRC notifiers ourselves, since each
49762306a36Sopenharmony_ci * notifier needs it's own handle
49862306a36Sopenharmony_ci */
49962306a36Sopenharmony_cistatic inline int
50062306a36Sopenharmony_cinv50_crc_ctx_init(struct nv50_head *head, struct nvif_mmu *mmu,
50162306a36Sopenharmony_ci		  struct nv50_crc_notifier_ctx *ctx, size_t len, int idx)
50262306a36Sopenharmony_ci{
50362306a36Sopenharmony_ci	struct nv50_core *core = nv50_disp(head->base.base.dev)->core;
50462306a36Sopenharmony_ci	int ret;
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	ret = nvif_mem_ctor_map(mmu, "kmsCrcNtfy", NVIF_MEM_VRAM, len, &ctx->mem);
50762306a36Sopenharmony_ci	if (ret)
50862306a36Sopenharmony_ci		return ret;
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	ret = nvif_object_ctor(&core->chan.base.user, "kmsCrcNtfyCtxDma",
51162306a36Sopenharmony_ci			       NV50_DISP_HANDLE_CRC_CTX(head, idx),
51262306a36Sopenharmony_ci			       NV_DMA_IN_MEMORY,
51362306a36Sopenharmony_ci			       &(struct nv_dma_v0) {
51462306a36Sopenharmony_ci					.target = NV_DMA_V0_TARGET_VRAM,
51562306a36Sopenharmony_ci					.access = NV_DMA_V0_ACCESS_RDWR,
51662306a36Sopenharmony_ci					.start = ctx->mem.addr,
51762306a36Sopenharmony_ci					.limit =  ctx->mem.addr
51862306a36Sopenharmony_ci						+ ctx->mem.size - 1,
51962306a36Sopenharmony_ci			       }, sizeof(struct nv_dma_v0),
52062306a36Sopenharmony_ci			       &ctx->ntfy);
52162306a36Sopenharmony_ci	if (ret)
52262306a36Sopenharmony_ci		goto fail_fini;
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	return 0;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_cifail_fini:
52762306a36Sopenharmony_ci	nvif_mem_dtor(&ctx->mem);
52862306a36Sopenharmony_ci	return ret;
52962306a36Sopenharmony_ci}
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_cistatic inline void
53262306a36Sopenharmony_cinv50_crc_ctx_fini(struct nv50_crc_notifier_ctx *ctx)
53362306a36Sopenharmony_ci{
53462306a36Sopenharmony_ci	nvif_object_dtor(&ctx->ntfy);
53562306a36Sopenharmony_ci	nvif_mem_dtor(&ctx->mem);
53662306a36Sopenharmony_ci}
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ciint nv50_crc_set_source(struct drm_crtc *crtc, const char *source_str)
53962306a36Sopenharmony_ci{
54062306a36Sopenharmony_ci	struct drm_device *dev = crtc->dev;
54162306a36Sopenharmony_ci	struct drm_atomic_state *state;
54262306a36Sopenharmony_ci	struct drm_modeset_acquire_ctx ctx;
54362306a36Sopenharmony_ci	struct nv50_head *head = nv50_head(crtc);
54462306a36Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
54562306a36Sopenharmony_ci	const struct nv50_crc_func *func = nv50_disp(dev)->core->func->crc;
54662306a36Sopenharmony_ci	struct nvif_mmu *mmu = &nouveau_drm(dev)->client.mmu;
54762306a36Sopenharmony_ci	struct nv50_head_atom *asyh;
54862306a36Sopenharmony_ci	struct drm_crtc_state *crtc_state;
54962306a36Sopenharmony_ci	enum nv50_crc_source source;
55062306a36Sopenharmony_ci	int ret = 0, ctx_flags = 0, i;
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	ret = nv50_crc_parse_source(source_str, &source);
55362306a36Sopenharmony_ci	if (ret)
55462306a36Sopenharmony_ci		return ret;
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	/*
55762306a36Sopenharmony_ci	 * Since we don't want the user to accidentally interrupt us as we're
55862306a36Sopenharmony_ci	 * disabling CRCs
55962306a36Sopenharmony_ci	 */
56062306a36Sopenharmony_ci	if (source)
56162306a36Sopenharmony_ci		ctx_flags |= DRM_MODESET_ACQUIRE_INTERRUPTIBLE;
56262306a36Sopenharmony_ci	drm_modeset_acquire_init(&ctx, ctx_flags);
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	state = drm_atomic_state_alloc(dev);
56562306a36Sopenharmony_ci	if (!state) {
56662306a36Sopenharmony_ci		ret = -ENOMEM;
56762306a36Sopenharmony_ci		goto out_acquire_fini;
56862306a36Sopenharmony_ci	}
56962306a36Sopenharmony_ci	state->acquire_ctx = &ctx;
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	if (source) {
57262306a36Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(head->crc.ctx); i++) {
57362306a36Sopenharmony_ci			ret = nv50_crc_ctx_init(head, mmu, &crc->ctx[i],
57462306a36Sopenharmony_ci						func->notifier_len, i);
57562306a36Sopenharmony_ci			if (ret)
57662306a36Sopenharmony_ci				goto out_ctx_fini;
57762306a36Sopenharmony_ci		}
57862306a36Sopenharmony_ci	}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ciretry:
58162306a36Sopenharmony_ci	crtc_state = drm_atomic_get_crtc_state(state, &head->base.base);
58262306a36Sopenharmony_ci	if (IS_ERR(crtc_state)) {
58362306a36Sopenharmony_ci		ret = PTR_ERR(crtc_state);
58462306a36Sopenharmony_ci		if (ret == -EDEADLK)
58562306a36Sopenharmony_ci			goto deadlock;
58662306a36Sopenharmony_ci		else if (ret)
58762306a36Sopenharmony_ci			goto out_drop_locks;
58862306a36Sopenharmony_ci	}
58962306a36Sopenharmony_ci	asyh = nv50_head_atom(crtc_state);
59062306a36Sopenharmony_ci	asyh->crc.src = source;
59162306a36Sopenharmony_ci	asyh->or.crc_raster = nv50_crc_raster_type(source);
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	ret = drm_atomic_commit(state);
59462306a36Sopenharmony_ci	if (ret == -EDEADLK)
59562306a36Sopenharmony_ci		goto deadlock;
59662306a36Sopenharmony_ci	else if (ret)
59762306a36Sopenharmony_ci		goto out_drop_locks;
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci	if (!source) {
60062306a36Sopenharmony_ci		/*
60162306a36Sopenharmony_ci		 * If the user specified a custom flip threshold through
60262306a36Sopenharmony_ci		 * debugfs, reset it
60362306a36Sopenharmony_ci		 */
60462306a36Sopenharmony_ci		crc->flip_threshold = func->flip_threshold;
60562306a36Sopenharmony_ci	}
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ciout_drop_locks:
60862306a36Sopenharmony_ci	drm_modeset_drop_locks(&ctx);
60962306a36Sopenharmony_ciout_ctx_fini:
61062306a36Sopenharmony_ci	if (!source || ret) {
61162306a36Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(crc->ctx); i++)
61262306a36Sopenharmony_ci			nv50_crc_ctx_fini(&crc->ctx[i]);
61362306a36Sopenharmony_ci	}
61462306a36Sopenharmony_ci	drm_atomic_state_put(state);
61562306a36Sopenharmony_ciout_acquire_fini:
61662306a36Sopenharmony_ci	drm_modeset_acquire_fini(&ctx);
61762306a36Sopenharmony_ci	return ret;
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_cideadlock:
62062306a36Sopenharmony_ci	drm_atomic_state_clear(state);
62162306a36Sopenharmony_ci	drm_modeset_backoff(&ctx);
62262306a36Sopenharmony_ci	goto retry;
62362306a36Sopenharmony_ci}
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_cistatic int
62662306a36Sopenharmony_cinv50_crc_debugfs_flip_threshold_get(struct seq_file *m, void *data)
62762306a36Sopenharmony_ci{
62862306a36Sopenharmony_ci	struct nv50_head *head = m->private;
62962306a36Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
63062306a36Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
63162306a36Sopenharmony_ci	int ret;
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci	ret = drm_modeset_lock_single_interruptible(&crtc->mutex);
63462306a36Sopenharmony_ci	if (ret)
63562306a36Sopenharmony_ci		return ret;
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	seq_printf(m, "%d\n", crc->flip_threshold);
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	drm_modeset_unlock(&crtc->mutex);
64062306a36Sopenharmony_ci	return ret;
64162306a36Sopenharmony_ci}
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_cistatic int
64462306a36Sopenharmony_cinv50_crc_debugfs_flip_threshold_open(struct inode *inode, struct file *file)
64562306a36Sopenharmony_ci{
64662306a36Sopenharmony_ci	return single_open(file, nv50_crc_debugfs_flip_threshold_get,
64762306a36Sopenharmony_ci			   inode->i_private);
64862306a36Sopenharmony_ci}
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_cistatic ssize_t
65162306a36Sopenharmony_cinv50_crc_debugfs_flip_threshold_set(struct file *file,
65262306a36Sopenharmony_ci				    const char __user *ubuf, size_t len,
65362306a36Sopenharmony_ci				    loff_t *offp)
65462306a36Sopenharmony_ci{
65562306a36Sopenharmony_ci	struct seq_file *m = file->private_data;
65662306a36Sopenharmony_ci	struct nv50_head *head = m->private;
65762306a36Sopenharmony_ci	struct nv50_head_atom *armh;
65862306a36Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
65962306a36Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(crtc->dev);
66062306a36Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
66162306a36Sopenharmony_ci	const struct nv50_crc_func *func =
66262306a36Sopenharmony_ci		nv50_disp(crtc->dev)->core->func->crc;
66362306a36Sopenharmony_ci	int value, ret;
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_ci	ret = kstrtoint_from_user(ubuf, len, 10, &value);
66662306a36Sopenharmony_ci	if (ret)
66762306a36Sopenharmony_ci		return ret;
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_ci	if (value > func->flip_threshold)
67062306a36Sopenharmony_ci		return -EINVAL;
67162306a36Sopenharmony_ci	else if (value == -1)
67262306a36Sopenharmony_ci		value = func->flip_threshold;
67362306a36Sopenharmony_ci	else if (value < -1)
67462306a36Sopenharmony_ci		return -EINVAL;
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	ret = drm_modeset_lock_single_interruptible(&crtc->mutex);
67762306a36Sopenharmony_ci	if (ret)
67862306a36Sopenharmony_ci		return ret;
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_ci	armh = nv50_head_atom(crtc->state);
68162306a36Sopenharmony_ci	if (armh->crc.src) {
68262306a36Sopenharmony_ci		ret = -EBUSY;
68362306a36Sopenharmony_ci		goto out;
68462306a36Sopenharmony_ci	}
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ci	NV_DEBUG(drm,
68762306a36Sopenharmony_ci		 "Changing CRC flip threshold for next capture on head-%d to %d\n",
68862306a36Sopenharmony_ci		 head->base.index, value);
68962306a36Sopenharmony_ci	crc->flip_threshold = value;
69062306a36Sopenharmony_ci	ret = len;
69162306a36Sopenharmony_ci
69262306a36Sopenharmony_ciout:
69362306a36Sopenharmony_ci	drm_modeset_unlock(&crtc->mutex);
69462306a36Sopenharmony_ci	return ret;
69562306a36Sopenharmony_ci}
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_cistatic const struct file_operations nv50_crc_flip_threshold_fops = {
69862306a36Sopenharmony_ci	.owner = THIS_MODULE,
69962306a36Sopenharmony_ci	.open = nv50_crc_debugfs_flip_threshold_open,
70062306a36Sopenharmony_ci	.read = seq_read,
70162306a36Sopenharmony_ci	.write = nv50_crc_debugfs_flip_threshold_set,
70262306a36Sopenharmony_ci	.release = single_release,
70362306a36Sopenharmony_ci};
70462306a36Sopenharmony_ci
70562306a36Sopenharmony_ciint nv50_head_crc_late_register(struct nv50_head *head)
70662306a36Sopenharmony_ci{
70762306a36Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
70862306a36Sopenharmony_ci	const struct nv50_crc_func *func =
70962306a36Sopenharmony_ci		nv50_disp(crtc->dev)->core->func->crc;
71062306a36Sopenharmony_ci	struct dentry *root;
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci	if (!func || !crtc->debugfs_entry)
71362306a36Sopenharmony_ci		return 0;
71462306a36Sopenharmony_ci
71562306a36Sopenharmony_ci	root = debugfs_create_dir("nv_crc", crtc->debugfs_entry);
71662306a36Sopenharmony_ci	debugfs_create_file("flip_threshold", 0644, root, head,
71762306a36Sopenharmony_ci			    &nv50_crc_flip_threshold_fops);
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_ci	return 0;
72062306a36Sopenharmony_ci}
72162306a36Sopenharmony_ci
72262306a36Sopenharmony_cistatic inline void
72362306a36Sopenharmony_cinv50_crc_init_head(struct nv50_disp *disp, const struct nv50_crc_func *func,
72462306a36Sopenharmony_ci		   struct nv50_head *head)
72562306a36Sopenharmony_ci{
72662306a36Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci	crc->flip_threshold = func->flip_threshold;
72962306a36Sopenharmony_ci	spin_lock_init(&crc->lock);
73062306a36Sopenharmony_ci	drm_vblank_work_init(&crc->flip_work, &head->base.base,
73162306a36Sopenharmony_ci			     nv50_crc_ctx_flip_work);
73262306a36Sopenharmony_ci}
73362306a36Sopenharmony_ci
73462306a36Sopenharmony_civoid nv50_crc_init(struct drm_device *dev)
73562306a36Sopenharmony_ci{
73662306a36Sopenharmony_ci	struct nv50_disp *disp = nv50_disp(dev);
73762306a36Sopenharmony_ci	struct drm_crtc *crtc;
73862306a36Sopenharmony_ci	const struct nv50_crc_func *func = disp->core->func->crc;
73962306a36Sopenharmony_ci
74062306a36Sopenharmony_ci	if (!func)
74162306a36Sopenharmony_ci		return;
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci	drm_for_each_crtc(crtc, dev)
74462306a36Sopenharmony_ci		nv50_crc_init_head(disp, func, nv50_head(crtc));
74562306a36Sopenharmony_ci}
746