18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: MIT
28c2ecf20Sopenharmony_ci#include <linux/string.h>
38c2ecf20Sopenharmony_ci#include <drm/drm_crtc.h>
48c2ecf20Sopenharmony_ci#include <drm/drm_atomic_helper.h>
58c2ecf20Sopenharmony_ci#include <drm/drm_vblank.h>
68c2ecf20Sopenharmony_ci#include <drm/drm_vblank_work.h>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <nvif/class.h>
98c2ecf20Sopenharmony_ci#include <nvif/cl0002.h>
108c2ecf20Sopenharmony_ci#include <nvif/timer.h>
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <nvhw/class/cl907d.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include "nouveau_drv.h"
158c2ecf20Sopenharmony_ci#include "core.h"
168c2ecf20Sopenharmony_ci#include "head.h"
178c2ecf20Sopenharmony_ci#include "wndw.h"
188c2ecf20Sopenharmony_ci#include "handles.h"
198c2ecf20Sopenharmony_ci#include "crc.h"
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistatic const char * const nv50_crc_sources[] = {
228c2ecf20Sopenharmony_ci	[NV50_CRC_SOURCE_NONE] = "none",
238c2ecf20Sopenharmony_ci	[NV50_CRC_SOURCE_AUTO] = "auto",
248c2ecf20Sopenharmony_ci	[NV50_CRC_SOURCE_RG] = "rg",
258c2ecf20Sopenharmony_ci	[NV50_CRC_SOURCE_OUTP_ACTIVE] = "outp-active",
268c2ecf20Sopenharmony_ci	[NV50_CRC_SOURCE_OUTP_COMPLETE] = "outp-complete",
278c2ecf20Sopenharmony_ci	[NV50_CRC_SOURCE_OUTP_INACTIVE] = "outp-inactive",
288c2ecf20Sopenharmony_ci};
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistatic int nv50_crc_parse_source(const char *buf, enum nv50_crc_source *s)
318c2ecf20Sopenharmony_ci{
328c2ecf20Sopenharmony_ci	int i;
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	if (!buf) {
358c2ecf20Sopenharmony_ci		*s = NV50_CRC_SOURCE_NONE;
368c2ecf20Sopenharmony_ci		return 0;
378c2ecf20Sopenharmony_ci	}
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	i = match_string(nv50_crc_sources, ARRAY_SIZE(nv50_crc_sources), buf);
408c2ecf20Sopenharmony_ci	if (i < 0)
418c2ecf20Sopenharmony_ci		return i;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	*s = i;
448c2ecf20Sopenharmony_ci	return 0;
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ciint
488c2ecf20Sopenharmony_cinv50_crc_verify_source(struct drm_crtc *crtc, const char *source_name,
498c2ecf20Sopenharmony_ci		       size_t *values_cnt)
508c2ecf20Sopenharmony_ci{
518c2ecf20Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(crtc->dev);
528c2ecf20Sopenharmony_ci	enum nv50_crc_source source;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	if (nv50_crc_parse_source(source_name, &source) < 0) {
558c2ecf20Sopenharmony_ci		NV_DEBUG(drm, "unknown source %s\n", source_name);
568c2ecf20Sopenharmony_ci		return -EINVAL;
578c2ecf20Sopenharmony_ci	}
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	*values_cnt = 1;
608c2ecf20Sopenharmony_ci	return 0;
618c2ecf20Sopenharmony_ci}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ciconst char *const *nv50_crc_get_sources(struct drm_crtc *crtc, size_t *count)
648c2ecf20Sopenharmony_ci{
658c2ecf20Sopenharmony_ci	*count = ARRAY_SIZE(nv50_crc_sources);
668c2ecf20Sopenharmony_ci	return nv50_crc_sources;
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic void
708c2ecf20Sopenharmony_cinv50_crc_program_ctx(struct nv50_head *head,
718c2ecf20Sopenharmony_ci		     struct nv50_crc_notifier_ctx *ctx)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	struct nv50_disp *disp = nv50_disp(head->base.base.dev);
748c2ecf20Sopenharmony_ci	struct nv50_core *core = disp->core;
758c2ecf20Sopenharmony_ci	u32 interlock[NV50_DISP_INTERLOCK__SIZE] = { 0 };
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	core->func->crc->set_ctx(head, ctx);
788c2ecf20Sopenharmony_ci	core->func->update(core, interlock, false);
798c2ecf20Sopenharmony_ci}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic void nv50_crc_ctx_flip_work(struct kthread_work *base)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	struct drm_vblank_work *work = to_drm_vblank_work(base);
848c2ecf20Sopenharmony_ci	struct nv50_crc *crc = container_of(work, struct nv50_crc, flip_work);
858c2ecf20Sopenharmony_ci	struct nv50_head *head = container_of(crc, struct nv50_head, crc);
868c2ecf20Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
878c2ecf20Sopenharmony_ci	struct nv50_disp *disp = nv50_disp(crtc->dev);
888c2ecf20Sopenharmony_ci	u8 new_idx = crc->ctx_idx ^ 1;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	/*
918c2ecf20Sopenharmony_ci	 * We don't want to accidentally wait for longer then the vblank, so
928c2ecf20Sopenharmony_ci	 * try again for the next vblank if we don't grab the lock
938c2ecf20Sopenharmony_ci	 */
948c2ecf20Sopenharmony_ci	if (!mutex_trylock(&disp->mutex)) {
958c2ecf20Sopenharmony_ci		DRM_DEV_DEBUG_KMS(crtc->dev->dev,
968c2ecf20Sopenharmony_ci				  "Lock contended, delaying CRC ctx flip for head-%d\n",
978c2ecf20Sopenharmony_ci				  head->base.index);
988c2ecf20Sopenharmony_ci		drm_vblank_work_schedule(work,
998c2ecf20Sopenharmony_ci					 drm_crtc_vblank_count(crtc) + 1,
1008c2ecf20Sopenharmony_ci					 true);
1018c2ecf20Sopenharmony_ci		return;
1028c2ecf20Sopenharmony_ci	}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	DRM_DEV_DEBUG_KMS(crtc->dev->dev,
1058c2ecf20Sopenharmony_ci			  "Flipping notifier ctx for head %d (%d -> %d)\n",
1068c2ecf20Sopenharmony_ci			  drm_crtc_index(crtc), crc->ctx_idx, new_idx);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	nv50_crc_program_ctx(head, NULL);
1098c2ecf20Sopenharmony_ci	nv50_crc_program_ctx(head, &crc->ctx[new_idx]);
1108c2ecf20Sopenharmony_ci	mutex_unlock(&disp->mutex);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	spin_lock_irq(&crc->lock);
1138c2ecf20Sopenharmony_ci	crc->ctx_changed = true;
1148c2ecf20Sopenharmony_ci	spin_unlock_irq(&crc->lock);
1158c2ecf20Sopenharmony_ci}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_cistatic inline void nv50_crc_reset_ctx(struct nv50_crc_notifier_ctx *ctx)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	memset_io(ctx->mem.object.map.ptr, 0, ctx->mem.object.map.size);
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_cistatic void
1238c2ecf20Sopenharmony_cinv50_crc_get_entries(struct nv50_head *head,
1248c2ecf20Sopenharmony_ci		     const struct nv50_crc_func *func,
1258c2ecf20Sopenharmony_ci		     enum nv50_crc_source source)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
1288c2ecf20Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
1298c2ecf20Sopenharmony_ci	u32 output_crc;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	while (crc->entry_idx < func->num_entries) {
1328c2ecf20Sopenharmony_ci		/*
1338c2ecf20Sopenharmony_ci		 * While Nvidia's documentation says CRCs are written on each
1348c2ecf20Sopenharmony_ci		 * subsequent vblank after being enabled, in practice they
1358c2ecf20Sopenharmony_ci		 * aren't written immediately.
1368c2ecf20Sopenharmony_ci		 */
1378c2ecf20Sopenharmony_ci		output_crc = func->get_entry(head, &crc->ctx[crc->ctx_idx],
1388c2ecf20Sopenharmony_ci					     source, crc->entry_idx);
1398c2ecf20Sopenharmony_ci		if (!output_crc)
1408c2ecf20Sopenharmony_ci			return;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci		drm_crtc_add_crc_entry(crtc, true, crc->frame, &output_crc);
1438c2ecf20Sopenharmony_ci		crc->frame++;
1448c2ecf20Sopenharmony_ci		crc->entry_idx++;
1458c2ecf20Sopenharmony_ci	}
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_civoid nv50_crc_handle_vblank(struct nv50_head *head)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
1518c2ecf20Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
1528c2ecf20Sopenharmony_ci	const struct nv50_crc_func *func =
1538c2ecf20Sopenharmony_ci		nv50_disp(head->base.base.dev)->core->func->crc;
1548c2ecf20Sopenharmony_ci	struct nv50_crc_notifier_ctx *ctx;
1558c2ecf20Sopenharmony_ci	bool need_reschedule = false;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	if (!func)
1588c2ecf20Sopenharmony_ci		return;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	/*
1618c2ecf20Sopenharmony_ci	 * We don't lose events if we aren't able to report CRCs until the
1628c2ecf20Sopenharmony_ci	 * next vblank, so only report CRCs if the locks we need aren't
1638c2ecf20Sopenharmony_ci	 * contended to prevent missing an actual vblank event
1648c2ecf20Sopenharmony_ci	 */
1658c2ecf20Sopenharmony_ci	if (!spin_trylock(&crc->lock))
1668c2ecf20Sopenharmony_ci		return;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	if (!crc->src)
1698c2ecf20Sopenharmony_ci		goto out;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	ctx = &crc->ctx[crc->ctx_idx];
1728c2ecf20Sopenharmony_ci	if (crc->ctx_changed && func->ctx_finished(head, ctx)) {
1738c2ecf20Sopenharmony_ci		nv50_crc_get_entries(head, func, crc->src);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci		crc->ctx_idx ^= 1;
1768c2ecf20Sopenharmony_ci		crc->entry_idx = 0;
1778c2ecf20Sopenharmony_ci		crc->ctx_changed = false;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci		/*
1808c2ecf20Sopenharmony_ci		 * Unfortunately when notifier contexts are changed during CRC
1818c2ecf20Sopenharmony_ci		 * capture, we will inevitably lose the CRC entry for the
1828c2ecf20Sopenharmony_ci		 * frame where the hardware actually latched onto the first
1838c2ecf20Sopenharmony_ci		 * UPDATE. According to Nvidia's hardware engineers, there's
1848c2ecf20Sopenharmony_ci		 * no workaround for this.
1858c2ecf20Sopenharmony_ci		 *
1868c2ecf20Sopenharmony_ci		 * Now, we could try to be smart here and calculate the number
1878c2ecf20Sopenharmony_ci		 * of missed CRCs based on audit timestamps, but those were
1888c2ecf20Sopenharmony_ci		 * removed starting with volta. Since we always flush our
1898c2ecf20Sopenharmony_ci		 * updates back-to-back without waiting, we'll just be
1908c2ecf20Sopenharmony_ci		 * optimistic and assume we always miss exactly one frame.
1918c2ecf20Sopenharmony_ci		 */
1928c2ecf20Sopenharmony_ci		DRM_DEV_DEBUG_KMS(head->base.base.dev->dev,
1938c2ecf20Sopenharmony_ci				  "Notifier ctx flip for head-%d finished, lost CRC for frame %llu\n",
1948c2ecf20Sopenharmony_ci				  head->base.index, crc->frame);
1958c2ecf20Sopenharmony_ci		crc->frame++;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci		nv50_crc_reset_ctx(ctx);
1988c2ecf20Sopenharmony_ci		need_reschedule = true;
1998c2ecf20Sopenharmony_ci	}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	nv50_crc_get_entries(head, func, crc->src);
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	if (need_reschedule)
2048c2ecf20Sopenharmony_ci		drm_vblank_work_schedule(&crc->flip_work,
2058c2ecf20Sopenharmony_ci					 drm_crtc_vblank_count(crtc)
2068c2ecf20Sopenharmony_ci					 + crc->flip_threshold
2078c2ecf20Sopenharmony_ci					 - crc->entry_idx,
2088c2ecf20Sopenharmony_ci					 true);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ciout:
2118c2ecf20Sopenharmony_ci	spin_unlock(&crc->lock);
2128c2ecf20Sopenharmony_ci}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_cistatic void nv50_crc_wait_ctx_finished(struct nv50_head *head,
2158c2ecf20Sopenharmony_ci				       const struct nv50_crc_func *func,
2168c2ecf20Sopenharmony_ci				       struct nv50_crc_notifier_ctx *ctx)
2178c2ecf20Sopenharmony_ci{
2188c2ecf20Sopenharmony_ci	struct drm_device *dev = head->base.base.dev;
2198c2ecf20Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(dev);
2208c2ecf20Sopenharmony_ci	s64 ret;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	ret = nvif_msec(&drm->client.device, 50,
2238c2ecf20Sopenharmony_ci			if (func->ctx_finished(head, ctx)) break;);
2248c2ecf20Sopenharmony_ci	if (ret == -ETIMEDOUT)
2258c2ecf20Sopenharmony_ci		NV_ERROR(drm,
2268c2ecf20Sopenharmony_ci			 "CRC notifier ctx for head %d not finished after 50ms\n",
2278c2ecf20Sopenharmony_ci			 head->base.index);
2288c2ecf20Sopenharmony_ci	else if (ret)
2298c2ecf20Sopenharmony_ci		NV_ATOMIC(drm,
2308c2ecf20Sopenharmony_ci			  "CRC notifier ctx for head-%d finished after %lldns\n",
2318c2ecf20Sopenharmony_ci			  head->base.index, ret);
2328c2ecf20Sopenharmony_ci}
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_civoid nv50_crc_atomic_stop_reporting(struct drm_atomic_state *state)
2358c2ecf20Sopenharmony_ci{
2368c2ecf20Sopenharmony_ci	struct drm_crtc_state *crtc_state;
2378c2ecf20Sopenharmony_ci	struct drm_crtc *crtc;
2388c2ecf20Sopenharmony_ci	int i;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
2418c2ecf20Sopenharmony_ci		struct nv50_head *head = nv50_head(crtc);
2428c2ecf20Sopenharmony_ci		struct nv50_head_atom *asyh = nv50_head_atom(crtc_state);
2438c2ecf20Sopenharmony_ci		struct nv50_crc *crc = &head->crc;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci		if (!asyh->clr.crc)
2468c2ecf20Sopenharmony_ci			continue;
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci		spin_lock_irq(&crc->lock);
2498c2ecf20Sopenharmony_ci		crc->src = NV50_CRC_SOURCE_NONE;
2508c2ecf20Sopenharmony_ci		spin_unlock_irq(&crc->lock);
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci		drm_crtc_vblank_put(crtc);
2538c2ecf20Sopenharmony_ci		drm_vblank_work_cancel_sync(&crc->flip_work);
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci		NV_ATOMIC(nouveau_drm(crtc->dev),
2568c2ecf20Sopenharmony_ci			  "CRC reporting on vblank for head-%d disabled\n",
2578c2ecf20Sopenharmony_ci			  head->base.index);
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci		/* CRC generation is still enabled in hw, we'll just report
2608c2ecf20Sopenharmony_ci		 * any remaining CRC entries ourselves after it gets disabled
2618c2ecf20Sopenharmony_ci		 * in hardware
2628c2ecf20Sopenharmony_ci		 */
2638c2ecf20Sopenharmony_ci	}
2648c2ecf20Sopenharmony_ci}
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_civoid nv50_crc_atomic_init_notifier_contexts(struct drm_atomic_state *state)
2678c2ecf20Sopenharmony_ci{
2688c2ecf20Sopenharmony_ci	struct drm_crtc_state *new_crtc_state;
2698c2ecf20Sopenharmony_ci	struct drm_crtc *crtc;
2708c2ecf20Sopenharmony_ci	int i;
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
2738c2ecf20Sopenharmony_ci		struct nv50_head *head = nv50_head(crtc);
2748c2ecf20Sopenharmony_ci		struct nv50_head_atom *asyh = nv50_head_atom(new_crtc_state);
2758c2ecf20Sopenharmony_ci		struct nv50_crc *crc = &head->crc;
2768c2ecf20Sopenharmony_ci		int i;
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci		if (!asyh->set.crc)
2798c2ecf20Sopenharmony_ci			continue;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci		crc->entry_idx = 0;
2828c2ecf20Sopenharmony_ci		crc->ctx_changed = false;
2838c2ecf20Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(crc->ctx); i++)
2848c2ecf20Sopenharmony_ci			nv50_crc_reset_ctx(&crc->ctx[i]);
2858c2ecf20Sopenharmony_ci	}
2868c2ecf20Sopenharmony_ci}
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_civoid nv50_crc_atomic_release_notifier_contexts(struct drm_atomic_state *state)
2898c2ecf20Sopenharmony_ci{
2908c2ecf20Sopenharmony_ci	const struct nv50_crc_func *func =
2918c2ecf20Sopenharmony_ci		nv50_disp(state->dev)->core->func->crc;
2928c2ecf20Sopenharmony_ci	struct drm_crtc_state *new_crtc_state;
2938c2ecf20Sopenharmony_ci	struct drm_crtc *crtc;
2948c2ecf20Sopenharmony_ci	int i;
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
2978c2ecf20Sopenharmony_ci		struct nv50_head *head = nv50_head(crtc);
2988c2ecf20Sopenharmony_ci		struct nv50_head_atom *asyh = nv50_head_atom(new_crtc_state);
2998c2ecf20Sopenharmony_ci		struct nv50_crc *crc = &head->crc;
3008c2ecf20Sopenharmony_ci		struct nv50_crc_notifier_ctx *ctx = &crc->ctx[crc->ctx_idx];
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci		if (!asyh->clr.crc)
3038c2ecf20Sopenharmony_ci			continue;
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci		if (crc->ctx_changed) {
3068c2ecf20Sopenharmony_ci			nv50_crc_wait_ctx_finished(head, func, ctx);
3078c2ecf20Sopenharmony_ci			ctx = &crc->ctx[crc->ctx_idx ^ 1];
3088c2ecf20Sopenharmony_ci		}
3098c2ecf20Sopenharmony_ci		nv50_crc_wait_ctx_finished(head, func, ctx);
3108c2ecf20Sopenharmony_ci	}
3118c2ecf20Sopenharmony_ci}
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_civoid nv50_crc_atomic_start_reporting(struct drm_atomic_state *state)
3148c2ecf20Sopenharmony_ci{
3158c2ecf20Sopenharmony_ci	struct drm_crtc_state *crtc_state;
3168c2ecf20Sopenharmony_ci	struct drm_crtc *crtc;
3178c2ecf20Sopenharmony_ci	int i;
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
3208c2ecf20Sopenharmony_ci		struct nv50_head *head = nv50_head(crtc);
3218c2ecf20Sopenharmony_ci		struct nv50_head_atom *asyh = nv50_head_atom(crtc_state);
3228c2ecf20Sopenharmony_ci		struct nv50_crc *crc = &head->crc;
3238c2ecf20Sopenharmony_ci		u64 vbl_count;
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci		if (!asyh->set.crc)
3268c2ecf20Sopenharmony_ci			continue;
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci		drm_crtc_vblank_get(crtc);
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci		spin_lock_irq(&crc->lock);
3318c2ecf20Sopenharmony_ci		vbl_count = drm_crtc_vblank_count(crtc);
3328c2ecf20Sopenharmony_ci		crc->frame = vbl_count;
3338c2ecf20Sopenharmony_ci		crc->src = asyh->crc.src;
3348c2ecf20Sopenharmony_ci		drm_vblank_work_schedule(&crc->flip_work,
3358c2ecf20Sopenharmony_ci					 vbl_count + crc->flip_threshold,
3368c2ecf20Sopenharmony_ci					 true);
3378c2ecf20Sopenharmony_ci		spin_unlock_irq(&crc->lock);
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci		NV_ATOMIC(nouveau_drm(crtc->dev),
3408c2ecf20Sopenharmony_ci			  "CRC reporting on vblank for head-%d enabled\n",
3418c2ecf20Sopenharmony_ci			  head->base.index);
3428c2ecf20Sopenharmony_ci	}
3438c2ecf20Sopenharmony_ci}
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ciint nv50_crc_atomic_check_head(struct nv50_head *head,
3468c2ecf20Sopenharmony_ci			       struct nv50_head_atom *asyh,
3478c2ecf20Sopenharmony_ci			       struct nv50_head_atom *armh)
3488c2ecf20Sopenharmony_ci{
3498c2ecf20Sopenharmony_ci	struct nv50_atom *atom = nv50_atom(asyh->state.state);
3508c2ecf20Sopenharmony_ci	struct drm_device *dev = head->base.base.dev;
3518c2ecf20Sopenharmony_ci	struct nv50_disp *disp = nv50_disp(dev);
3528c2ecf20Sopenharmony_ci	bool changed = armh->crc.src != asyh->crc.src;
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	if (!armh->crc.src && !asyh->crc.src) {
3558c2ecf20Sopenharmony_ci		asyh->set.crc = false;
3568c2ecf20Sopenharmony_ci		asyh->clr.crc = false;
3578c2ecf20Sopenharmony_ci		return 0;
3588c2ecf20Sopenharmony_ci	}
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	/* While we don't care about entry tags, Volta+ hw always needs the
3618c2ecf20Sopenharmony_ci	 * controlling wndw channel programmed to a wndw that's owned by our
3628c2ecf20Sopenharmony_ci	 * head
3638c2ecf20Sopenharmony_ci	 */
3648c2ecf20Sopenharmony_ci	if (asyh->crc.src && disp->disp->object.oclass >= GV100_DISP &&
3658c2ecf20Sopenharmony_ci	    !(BIT(asyh->crc.wndw) & asyh->wndw.owned)) {
3668c2ecf20Sopenharmony_ci		if (!asyh->wndw.owned) {
3678c2ecf20Sopenharmony_ci			/* TODO: once we support flexible channel ownership,
3688c2ecf20Sopenharmony_ci			 * we should write some code here to handle attempting
3698c2ecf20Sopenharmony_ci			 * to "steal" a plane: e.g. take a plane that is
3708c2ecf20Sopenharmony_ci			 * currently not-visible and owned by another head,
3718c2ecf20Sopenharmony_ci			 * and reassign it to this head. If we fail to do so,
3728c2ecf20Sopenharmony_ci			 * we shuld reject the mode outright as CRC capture
3738c2ecf20Sopenharmony_ci			 * then becomes impossible.
3748c2ecf20Sopenharmony_ci			 */
3758c2ecf20Sopenharmony_ci			NV_ATOMIC(nouveau_drm(dev),
3768c2ecf20Sopenharmony_ci				  "No available wndws for CRC readback\n");
3778c2ecf20Sopenharmony_ci			return -EINVAL;
3788c2ecf20Sopenharmony_ci		}
3798c2ecf20Sopenharmony_ci		asyh->crc.wndw = ffs(asyh->wndw.owned) - 1;
3808c2ecf20Sopenharmony_ci	}
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	if (drm_atomic_crtc_needs_modeset(&asyh->state) || changed ||
3838c2ecf20Sopenharmony_ci	    armh->crc.wndw != asyh->crc.wndw) {
3848c2ecf20Sopenharmony_ci		asyh->clr.crc = armh->crc.src && armh->state.active;
3858c2ecf20Sopenharmony_ci		asyh->set.crc = asyh->crc.src && asyh->state.active;
3868c2ecf20Sopenharmony_ci		if (changed)
3878c2ecf20Sopenharmony_ci			asyh->set.or |= armh->or.crc_raster !=
3888c2ecf20Sopenharmony_ci					asyh->or.crc_raster;
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci		if (asyh->clr.crc && asyh->set.crc)
3918c2ecf20Sopenharmony_ci			atom->flush_disable = true;
3928c2ecf20Sopenharmony_ci	} else {
3938c2ecf20Sopenharmony_ci		asyh->set.crc = false;
3948c2ecf20Sopenharmony_ci		asyh->clr.crc = false;
3958c2ecf20Sopenharmony_ci	}
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci	return 0;
3988c2ecf20Sopenharmony_ci}
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_civoid nv50_crc_atomic_check_outp(struct nv50_atom *atom)
4018c2ecf20Sopenharmony_ci{
4028c2ecf20Sopenharmony_ci	struct drm_crtc *crtc;
4038c2ecf20Sopenharmony_ci	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
4048c2ecf20Sopenharmony_ci	int i;
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_ci	if (atom->flush_disable)
4078c2ecf20Sopenharmony_ci		return;
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	for_each_oldnew_crtc_in_state(&atom->state, crtc, old_crtc_state,
4108c2ecf20Sopenharmony_ci				      new_crtc_state, i) {
4118c2ecf20Sopenharmony_ci		struct nv50_head_atom *armh = nv50_head_atom(old_crtc_state);
4128c2ecf20Sopenharmony_ci		struct nv50_head_atom *asyh = nv50_head_atom(new_crtc_state);
4138c2ecf20Sopenharmony_ci		struct nv50_outp_atom *outp_atom;
4148c2ecf20Sopenharmony_ci		struct nouveau_encoder *outp;
4158c2ecf20Sopenharmony_ci		struct drm_encoder *encoder, *enc;
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci		enc = nv50_head_atom_get_encoder(armh);
4188c2ecf20Sopenharmony_ci		if (!enc)
4198c2ecf20Sopenharmony_ci			continue;
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci		outp = nv50_real_outp(enc);
4228c2ecf20Sopenharmony_ci		if (!outp)
4238c2ecf20Sopenharmony_ci			continue;
4248c2ecf20Sopenharmony_ci
4258c2ecf20Sopenharmony_ci		encoder = &outp->base.base;
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci		if (!asyh->clr.crc)
4288c2ecf20Sopenharmony_ci			continue;
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_ci		/*
4318c2ecf20Sopenharmony_ci		 * Re-programming ORs can't be done in the same flush as
4328c2ecf20Sopenharmony_ci		 * disabling CRCs
4338c2ecf20Sopenharmony_ci		 */
4348c2ecf20Sopenharmony_ci		list_for_each_entry(outp_atom, &atom->outp, head) {
4358c2ecf20Sopenharmony_ci			if (outp_atom->encoder == encoder) {
4368c2ecf20Sopenharmony_ci				if (outp_atom->set.mask) {
4378c2ecf20Sopenharmony_ci					atom->flush_disable = true;
4388c2ecf20Sopenharmony_ci					return;
4398c2ecf20Sopenharmony_ci				} else {
4408c2ecf20Sopenharmony_ci					break;
4418c2ecf20Sopenharmony_ci				}
4428c2ecf20Sopenharmony_ci			}
4438c2ecf20Sopenharmony_ci		}
4448c2ecf20Sopenharmony_ci	}
4458c2ecf20Sopenharmony_ci}
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_cistatic enum nv50_crc_source_type
4488c2ecf20Sopenharmony_cinv50_crc_source_type(struct nouveau_encoder *outp,
4498c2ecf20Sopenharmony_ci		     enum nv50_crc_source source)
4508c2ecf20Sopenharmony_ci{
4518c2ecf20Sopenharmony_ci	struct dcb_output *dcbe = outp->dcb;
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_ci	switch (source) {
4548c2ecf20Sopenharmony_ci	case NV50_CRC_SOURCE_NONE: return NV50_CRC_SOURCE_TYPE_NONE;
4558c2ecf20Sopenharmony_ci	case NV50_CRC_SOURCE_RG:   return NV50_CRC_SOURCE_TYPE_RG;
4568c2ecf20Sopenharmony_ci	default:		   break;
4578c2ecf20Sopenharmony_ci	}
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci	if (dcbe->location != DCB_LOC_ON_CHIP)
4608c2ecf20Sopenharmony_ci		return NV50_CRC_SOURCE_TYPE_PIOR;
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ci	switch (dcbe->type) {
4638c2ecf20Sopenharmony_ci	case DCB_OUTPUT_DP:	return NV50_CRC_SOURCE_TYPE_SF;
4648c2ecf20Sopenharmony_ci	case DCB_OUTPUT_ANALOG:	return NV50_CRC_SOURCE_TYPE_DAC;
4658c2ecf20Sopenharmony_ci	default:		return NV50_CRC_SOURCE_TYPE_SOR;
4668c2ecf20Sopenharmony_ci	}
4678c2ecf20Sopenharmony_ci}
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_civoid nv50_crc_atomic_set(struct nv50_head *head,
4708c2ecf20Sopenharmony_ci			 struct nv50_head_atom *asyh)
4718c2ecf20Sopenharmony_ci{
4728c2ecf20Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
4738c2ecf20Sopenharmony_ci	struct drm_device *dev = crtc->dev;
4748c2ecf20Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
4758c2ecf20Sopenharmony_ci	const struct nv50_crc_func *func = nv50_disp(dev)->core->func->crc;
4768c2ecf20Sopenharmony_ci	struct nouveau_encoder *outp;
4778c2ecf20Sopenharmony_ci	struct drm_encoder *encoder;
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci	encoder = nv50_head_atom_get_encoder(asyh);
4808c2ecf20Sopenharmony_ci	if (!encoder)
4818c2ecf20Sopenharmony_ci		return;
4828c2ecf20Sopenharmony_ci
4838c2ecf20Sopenharmony_ci	outp = nv50_real_outp(encoder);
4848c2ecf20Sopenharmony_ci	if (!outp)
4858c2ecf20Sopenharmony_ci		return;
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ci	func->set_src(head, outp->or,
4888c2ecf20Sopenharmony_ci		      nv50_crc_source_type(outp, asyh->crc.src),
4898c2ecf20Sopenharmony_ci		      &crc->ctx[crc->ctx_idx], asyh->crc.wndw);
4908c2ecf20Sopenharmony_ci}
4918c2ecf20Sopenharmony_ci
4928c2ecf20Sopenharmony_civoid nv50_crc_atomic_clr(struct nv50_head *head)
4938c2ecf20Sopenharmony_ci{
4948c2ecf20Sopenharmony_ci	const struct nv50_crc_func *func =
4958c2ecf20Sopenharmony_ci		nv50_disp(head->base.base.dev)->core->func->crc;
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_ci	func->set_src(head, 0, NV50_CRC_SOURCE_TYPE_NONE, NULL, 0);
4988c2ecf20Sopenharmony_ci}
4998c2ecf20Sopenharmony_ci
5008c2ecf20Sopenharmony_cistatic inline int
5018c2ecf20Sopenharmony_cinv50_crc_raster_type(enum nv50_crc_source source)
5028c2ecf20Sopenharmony_ci{
5038c2ecf20Sopenharmony_ci	switch (source) {
5048c2ecf20Sopenharmony_ci	case NV50_CRC_SOURCE_NONE:
5058c2ecf20Sopenharmony_ci	case NV50_CRC_SOURCE_AUTO:
5068c2ecf20Sopenharmony_ci	case NV50_CRC_SOURCE_RG:
5078c2ecf20Sopenharmony_ci	case NV50_CRC_SOURCE_OUTP_ACTIVE:
5088c2ecf20Sopenharmony_ci		return NV907D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_CRC_MODE_ACTIVE_RASTER;
5098c2ecf20Sopenharmony_ci	case NV50_CRC_SOURCE_OUTP_COMPLETE:
5108c2ecf20Sopenharmony_ci		return NV907D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_CRC_MODE_COMPLETE_RASTER;
5118c2ecf20Sopenharmony_ci	case NV50_CRC_SOURCE_OUTP_INACTIVE:
5128c2ecf20Sopenharmony_ci		return NV907D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_CRC_MODE_NON_ACTIVE_RASTER;
5138c2ecf20Sopenharmony_ci	}
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci	return 0;
5168c2ecf20Sopenharmony_ci}
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci/* We handle mapping the memory for CRC notifiers ourselves, since each
5198c2ecf20Sopenharmony_ci * notifier needs it's own handle
5208c2ecf20Sopenharmony_ci */
5218c2ecf20Sopenharmony_cistatic inline int
5228c2ecf20Sopenharmony_cinv50_crc_ctx_init(struct nv50_head *head, struct nvif_mmu *mmu,
5238c2ecf20Sopenharmony_ci		  struct nv50_crc_notifier_ctx *ctx, size_t len, int idx)
5248c2ecf20Sopenharmony_ci{
5258c2ecf20Sopenharmony_ci	struct nv50_core *core = nv50_disp(head->base.base.dev)->core;
5268c2ecf20Sopenharmony_ci	int ret;
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_ci	ret = nvif_mem_ctor_map(mmu, "kmsCrcNtfy", NVIF_MEM_VRAM, len, &ctx->mem);
5298c2ecf20Sopenharmony_ci	if (ret)
5308c2ecf20Sopenharmony_ci		return ret;
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_ci	ret = nvif_object_ctor(&core->chan.base.user, "kmsCrcNtfyCtxDma",
5338c2ecf20Sopenharmony_ci			       NV50_DISP_HANDLE_CRC_CTX(head, idx),
5348c2ecf20Sopenharmony_ci			       NV_DMA_IN_MEMORY,
5358c2ecf20Sopenharmony_ci			       &(struct nv_dma_v0) {
5368c2ecf20Sopenharmony_ci					.target = NV_DMA_V0_TARGET_VRAM,
5378c2ecf20Sopenharmony_ci					.access = NV_DMA_V0_ACCESS_RDWR,
5388c2ecf20Sopenharmony_ci					.start = ctx->mem.addr,
5398c2ecf20Sopenharmony_ci					.limit =  ctx->mem.addr
5408c2ecf20Sopenharmony_ci						+ ctx->mem.size - 1,
5418c2ecf20Sopenharmony_ci			       }, sizeof(struct nv_dma_v0),
5428c2ecf20Sopenharmony_ci			       &ctx->ntfy);
5438c2ecf20Sopenharmony_ci	if (ret)
5448c2ecf20Sopenharmony_ci		goto fail_fini;
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ci	return 0;
5478c2ecf20Sopenharmony_ci
5488c2ecf20Sopenharmony_cifail_fini:
5498c2ecf20Sopenharmony_ci	nvif_mem_dtor(&ctx->mem);
5508c2ecf20Sopenharmony_ci	return ret;
5518c2ecf20Sopenharmony_ci}
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_cistatic inline void
5548c2ecf20Sopenharmony_cinv50_crc_ctx_fini(struct nv50_crc_notifier_ctx *ctx)
5558c2ecf20Sopenharmony_ci{
5568c2ecf20Sopenharmony_ci	nvif_object_dtor(&ctx->ntfy);
5578c2ecf20Sopenharmony_ci	nvif_mem_dtor(&ctx->mem);
5588c2ecf20Sopenharmony_ci}
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ciint nv50_crc_set_source(struct drm_crtc *crtc, const char *source_str)
5618c2ecf20Sopenharmony_ci{
5628c2ecf20Sopenharmony_ci	struct drm_device *dev = crtc->dev;
5638c2ecf20Sopenharmony_ci	struct drm_atomic_state *state;
5648c2ecf20Sopenharmony_ci	struct drm_modeset_acquire_ctx ctx;
5658c2ecf20Sopenharmony_ci	struct nv50_head *head = nv50_head(crtc);
5668c2ecf20Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
5678c2ecf20Sopenharmony_ci	const struct nv50_crc_func *func = nv50_disp(dev)->core->func->crc;
5688c2ecf20Sopenharmony_ci	struct nvif_mmu *mmu = &nouveau_drm(dev)->client.mmu;
5698c2ecf20Sopenharmony_ci	struct nv50_head_atom *asyh;
5708c2ecf20Sopenharmony_ci	struct drm_crtc_state *crtc_state;
5718c2ecf20Sopenharmony_ci	enum nv50_crc_source source;
5728c2ecf20Sopenharmony_ci	int ret = 0, ctx_flags = 0, i;
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_ci	ret = nv50_crc_parse_source(source_str, &source);
5758c2ecf20Sopenharmony_ci	if (ret)
5768c2ecf20Sopenharmony_ci		return ret;
5778c2ecf20Sopenharmony_ci
5788c2ecf20Sopenharmony_ci	/*
5798c2ecf20Sopenharmony_ci	 * Since we don't want the user to accidentally interrupt us as we're
5808c2ecf20Sopenharmony_ci	 * disabling CRCs
5818c2ecf20Sopenharmony_ci	 */
5828c2ecf20Sopenharmony_ci	if (source)
5838c2ecf20Sopenharmony_ci		ctx_flags |= DRM_MODESET_ACQUIRE_INTERRUPTIBLE;
5848c2ecf20Sopenharmony_ci	drm_modeset_acquire_init(&ctx, ctx_flags);
5858c2ecf20Sopenharmony_ci
5868c2ecf20Sopenharmony_ci	state = drm_atomic_state_alloc(dev);
5878c2ecf20Sopenharmony_ci	if (!state) {
5888c2ecf20Sopenharmony_ci		ret = -ENOMEM;
5898c2ecf20Sopenharmony_ci		goto out_acquire_fini;
5908c2ecf20Sopenharmony_ci	}
5918c2ecf20Sopenharmony_ci	state->acquire_ctx = &ctx;
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_ci	if (source) {
5948c2ecf20Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(head->crc.ctx); i++) {
5958c2ecf20Sopenharmony_ci			ret = nv50_crc_ctx_init(head, mmu, &crc->ctx[i],
5968c2ecf20Sopenharmony_ci						func->notifier_len, i);
5978c2ecf20Sopenharmony_ci			if (ret)
5988c2ecf20Sopenharmony_ci				goto out_ctx_fini;
5998c2ecf20Sopenharmony_ci		}
6008c2ecf20Sopenharmony_ci	}
6018c2ecf20Sopenharmony_ci
6028c2ecf20Sopenharmony_ciretry:
6038c2ecf20Sopenharmony_ci	crtc_state = drm_atomic_get_crtc_state(state, &head->base.base);
6048c2ecf20Sopenharmony_ci	if (IS_ERR(crtc_state)) {
6058c2ecf20Sopenharmony_ci		ret = PTR_ERR(crtc_state);
6068c2ecf20Sopenharmony_ci		if (ret == -EDEADLK)
6078c2ecf20Sopenharmony_ci			goto deadlock;
6088c2ecf20Sopenharmony_ci		else if (ret)
6098c2ecf20Sopenharmony_ci			goto out_drop_locks;
6108c2ecf20Sopenharmony_ci	}
6118c2ecf20Sopenharmony_ci	asyh = nv50_head_atom(crtc_state);
6128c2ecf20Sopenharmony_ci	asyh->crc.src = source;
6138c2ecf20Sopenharmony_ci	asyh->or.crc_raster = nv50_crc_raster_type(source);
6148c2ecf20Sopenharmony_ci
6158c2ecf20Sopenharmony_ci	ret = drm_atomic_commit(state);
6168c2ecf20Sopenharmony_ci	if (ret == -EDEADLK)
6178c2ecf20Sopenharmony_ci		goto deadlock;
6188c2ecf20Sopenharmony_ci	else if (ret)
6198c2ecf20Sopenharmony_ci		goto out_drop_locks;
6208c2ecf20Sopenharmony_ci
6218c2ecf20Sopenharmony_ci	if (!source) {
6228c2ecf20Sopenharmony_ci		/*
6238c2ecf20Sopenharmony_ci		 * If the user specified a custom flip threshold through
6248c2ecf20Sopenharmony_ci		 * debugfs, reset it
6258c2ecf20Sopenharmony_ci		 */
6268c2ecf20Sopenharmony_ci		crc->flip_threshold = func->flip_threshold;
6278c2ecf20Sopenharmony_ci	}
6288c2ecf20Sopenharmony_ci
6298c2ecf20Sopenharmony_ciout_drop_locks:
6308c2ecf20Sopenharmony_ci	drm_modeset_drop_locks(&ctx);
6318c2ecf20Sopenharmony_ciout_ctx_fini:
6328c2ecf20Sopenharmony_ci	if (!source || ret) {
6338c2ecf20Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(crc->ctx); i++)
6348c2ecf20Sopenharmony_ci			nv50_crc_ctx_fini(&crc->ctx[i]);
6358c2ecf20Sopenharmony_ci	}
6368c2ecf20Sopenharmony_ci	drm_atomic_state_put(state);
6378c2ecf20Sopenharmony_ciout_acquire_fini:
6388c2ecf20Sopenharmony_ci	drm_modeset_acquire_fini(&ctx);
6398c2ecf20Sopenharmony_ci	return ret;
6408c2ecf20Sopenharmony_ci
6418c2ecf20Sopenharmony_cideadlock:
6428c2ecf20Sopenharmony_ci	drm_atomic_state_clear(state);
6438c2ecf20Sopenharmony_ci	drm_modeset_backoff(&ctx);
6448c2ecf20Sopenharmony_ci	goto retry;
6458c2ecf20Sopenharmony_ci}
6468c2ecf20Sopenharmony_ci
6478c2ecf20Sopenharmony_cistatic int
6488c2ecf20Sopenharmony_cinv50_crc_debugfs_flip_threshold_get(struct seq_file *m, void *data)
6498c2ecf20Sopenharmony_ci{
6508c2ecf20Sopenharmony_ci	struct nv50_head *head = m->private;
6518c2ecf20Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
6528c2ecf20Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
6538c2ecf20Sopenharmony_ci	int ret;
6548c2ecf20Sopenharmony_ci
6558c2ecf20Sopenharmony_ci	ret = drm_modeset_lock_single_interruptible(&crtc->mutex);
6568c2ecf20Sopenharmony_ci	if (ret)
6578c2ecf20Sopenharmony_ci		return ret;
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_ci	seq_printf(m, "%d\n", crc->flip_threshold);
6608c2ecf20Sopenharmony_ci
6618c2ecf20Sopenharmony_ci	drm_modeset_unlock(&crtc->mutex);
6628c2ecf20Sopenharmony_ci	return ret;
6638c2ecf20Sopenharmony_ci}
6648c2ecf20Sopenharmony_ci
6658c2ecf20Sopenharmony_cistatic int
6668c2ecf20Sopenharmony_cinv50_crc_debugfs_flip_threshold_open(struct inode *inode, struct file *file)
6678c2ecf20Sopenharmony_ci{
6688c2ecf20Sopenharmony_ci	return single_open(file, nv50_crc_debugfs_flip_threshold_get,
6698c2ecf20Sopenharmony_ci			   inode->i_private);
6708c2ecf20Sopenharmony_ci}
6718c2ecf20Sopenharmony_ci
6728c2ecf20Sopenharmony_cistatic ssize_t
6738c2ecf20Sopenharmony_cinv50_crc_debugfs_flip_threshold_set(struct file *file,
6748c2ecf20Sopenharmony_ci				    const char __user *ubuf, size_t len,
6758c2ecf20Sopenharmony_ci				    loff_t *offp)
6768c2ecf20Sopenharmony_ci{
6778c2ecf20Sopenharmony_ci	struct seq_file *m = file->private_data;
6788c2ecf20Sopenharmony_ci	struct nv50_head *head = m->private;
6798c2ecf20Sopenharmony_ci	struct nv50_head_atom *armh;
6808c2ecf20Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
6818c2ecf20Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(crtc->dev);
6828c2ecf20Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
6838c2ecf20Sopenharmony_ci	const struct nv50_crc_func *func =
6848c2ecf20Sopenharmony_ci		nv50_disp(crtc->dev)->core->func->crc;
6858c2ecf20Sopenharmony_ci	int value, ret;
6868c2ecf20Sopenharmony_ci
6878c2ecf20Sopenharmony_ci	ret = kstrtoint_from_user(ubuf, len, 10, &value);
6888c2ecf20Sopenharmony_ci	if (ret)
6898c2ecf20Sopenharmony_ci		return ret;
6908c2ecf20Sopenharmony_ci
6918c2ecf20Sopenharmony_ci	if (value > func->flip_threshold)
6928c2ecf20Sopenharmony_ci		return -EINVAL;
6938c2ecf20Sopenharmony_ci	else if (value == -1)
6948c2ecf20Sopenharmony_ci		value = func->flip_threshold;
6958c2ecf20Sopenharmony_ci	else if (value < -1)
6968c2ecf20Sopenharmony_ci		return -EINVAL;
6978c2ecf20Sopenharmony_ci
6988c2ecf20Sopenharmony_ci	ret = drm_modeset_lock_single_interruptible(&crtc->mutex);
6998c2ecf20Sopenharmony_ci	if (ret)
7008c2ecf20Sopenharmony_ci		return ret;
7018c2ecf20Sopenharmony_ci
7028c2ecf20Sopenharmony_ci	armh = nv50_head_atom(crtc->state);
7038c2ecf20Sopenharmony_ci	if (armh->crc.src) {
7048c2ecf20Sopenharmony_ci		ret = -EBUSY;
7058c2ecf20Sopenharmony_ci		goto out;
7068c2ecf20Sopenharmony_ci	}
7078c2ecf20Sopenharmony_ci
7088c2ecf20Sopenharmony_ci	NV_DEBUG(drm,
7098c2ecf20Sopenharmony_ci		 "Changing CRC flip threshold for next capture on head-%d to %d\n",
7108c2ecf20Sopenharmony_ci		 head->base.index, value);
7118c2ecf20Sopenharmony_ci	crc->flip_threshold = value;
7128c2ecf20Sopenharmony_ci	ret = len;
7138c2ecf20Sopenharmony_ci
7148c2ecf20Sopenharmony_ciout:
7158c2ecf20Sopenharmony_ci	drm_modeset_unlock(&crtc->mutex);
7168c2ecf20Sopenharmony_ci	return ret;
7178c2ecf20Sopenharmony_ci}
7188c2ecf20Sopenharmony_ci
7198c2ecf20Sopenharmony_cistatic const struct file_operations nv50_crc_flip_threshold_fops = {
7208c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
7218c2ecf20Sopenharmony_ci	.open = nv50_crc_debugfs_flip_threshold_open,
7228c2ecf20Sopenharmony_ci	.read = seq_read,
7238c2ecf20Sopenharmony_ci	.write = nv50_crc_debugfs_flip_threshold_set,
7248c2ecf20Sopenharmony_ci	.release = single_release,
7258c2ecf20Sopenharmony_ci};
7268c2ecf20Sopenharmony_ci
7278c2ecf20Sopenharmony_ciint nv50_head_crc_late_register(struct nv50_head *head)
7288c2ecf20Sopenharmony_ci{
7298c2ecf20Sopenharmony_ci	struct drm_crtc *crtc = &head->base.base;
7308c2ecf20Sopenharmony_ci	const struct nv50_crc_func *func =
7318c2ecf20Sopenharmony_ci		nv50_disp(crtc->dev)->core->func->crc;
7328c2ecf20Sopenharmony_ci	struct dentry *root;
7338c2ecf20Sopenharmony_ci
7348c2ecf20Sopenharmony_ci	if (!func || !crtc->debugfs_entry)
7358c2ecf20Sopenharmony_ci		return 0;
7368c2ecf20Sopenharmony_ci
7378c2ecf20Sopenharmony_ci	root = debugfs_create_dir("nv_crc", crtc->debugfs_entry);
7388c2ecf20Sopenharmony_ci	debugfs_create_file("flip_threshold", 0644, root, head,
7398c2ecf20Sopenharmony_ci			    &nv50_crc_flip_threshold_fops);
7408c2ecf20Sopenharmony_ci
7418c2ecf20Sopenharmony_ci	return 0;
7428c2ecf20Sopenharmony_ci}
7438c2ecf20Sopenharmony_ci
7448c2ecf20Sopenharmony_cistatic inline void
7458c2ecf20Sopenharmony_cinv50_crc_init_head(struct nv50_disp *disp, const struct nv50_crc_func *func,
7468c2ecf20Sopenharmony_ci		   struct nv50_head *head)
7478c2ecf20Sopenharmony_ci{
7488c2ecf20Sopenharmony_ci	struct nv50_crc *crc = &head->crc;
7498c2ecf20Sopenharmony_ci
7508c2ecf20Sopenharmony_ci	crc->flip_threshold = func->flip_threshold;
7518c2ecf20Sopenharmony_ci	spin_lock_init(&crc->lock);
7528c2ecf20Sopenharmony_ci	drm_vblank_work_init(&crc->flip_work, &head->base.base,
7538c2ecf20Sopenharmony_ci			     nv50_crc_ctx_flip_work);
7548c2ecf20Sopenharmony_ci}
7558c2ecf20Sopenharmony_ci
7568c2ecf20Sopenharmony_civoid nv50_crc_init(struct drm_device *dev)
7578c2ecf20Sopenharmony_ci{
7588c2ecf20Sopenharmony_ci	struct nv50_disp *disp = nv50_disp(dev);
7598c2ecf20Sopenharmony_ci	struct drm_crtc *crtc;
7608c2ecf20Sopenharmony_ci	const struct nv50_crc_func *func = disp->core->func->crc;
7618c2ecf20Sopenharmony_ci
7628c2ecf20Sopenharmony_ci	if (!func)
7638c2ecf20Sopenharmony_ci		return;
7648c2ecf20Sopenharmony_ci
7658c2ecf20Sopenharmony_ci	drm_for_each_crtc(crtc, dev)
7668c2ecf20Sopenharmony_ci		nv50_crc_init_head(disp, func, nv50_head(crtc));
7678c2ecf20Sopenharmony_ci}
768