// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
 * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
 */

#define pr_fmt(fmt)	"[drm:%s:%d] " fmt, __func__, __LINE__
#include <linux/sync_file.h>
#include <linux/dma-fence.h>
#include <linux/dma-fence-array.h>
#include "msm_drv.h"
#include "sde_kms.h"
#include "sde_fence.h"

#define TIMELINE_VAL_LENGTH		128
#define SPEC_FENCE_FLAG_FENCE_ARRAY	0x10
#define SPEC_FENCE_FLAG_ARRAY_BIND	0x11

/**
 * struct sde_fence - release/retire fence structure
 * @base: base fence structure
 * @ctx: fence context
 * @name: name of each fence- it is fence timeline + commit_count
 * @fence_list: list to associated this fence on timeline/context
 * @fd: fd attached to this fence - debugging purpose.
 * @hwfence_out_ctl: hw ctl for the output fence
 * @hwfence_index: hw fence index for this fence
 * @txq_updated_fence: flag to indicate that a fence has been updated in txq
 */
struct sde_fence {
	struct dma_fence base;
	struct sde_fence_context *ctx;
	char name[SDE_FENCE_NAME_SIZE];
	struct list_head fence_list;
	int fd;
	struct sde_hw_ctl *hwfence_out_ctl;
	u64 hwfence_index;
	bool txq_updated_fence;
};

/**
 * enum sde_hw_fence_clients - sde clients for the hw-fence feature
 *
 * Do not modify the order of this struct and/or add more elements
 * without modify/add fields in the 'hw_fence_data' structs.
 */
enum sde_hw_fence_clients {
	SDE_HW_FENCE_CLIENT_CTL_0,
	SDE_HW_FENCE_CLIENT_CTL_1,
	SDE_HW_FENCE_CLIENT_CTL_2,
	SDE_HW_FENCE_CLIENT_CTL_3,
	SDE_HW_FENCE_CLIENT_CTL_4,
	SDE_HW_FENCE_CLIENT_CTL_5,
	SDE_HW_FENCE_CLIENT_MAX,
};

/**
 * hw_fence_data_dpu_client - this table maps the dpu ipcc input and output signals for each display
 *              clients to communicate with the fence controller.
 * This struct must match the order of the 'sde_hw_fence_clients' enum,
 * the output signal must match with the signals that FenceCTL expects for each display client.
 * This 'hw_fence_data_dpu_client' must be used for HW that does not support dpu-signal.
 */
struct sde_hw_fence_data hw_fence_data_no_dpu[SDE_HW_FENCE_CLIENT_MAX] = {
	{SDE_HW_FENCE_CLIENT_CTL_0, HW_FENCE_CLIENT_ID_CTL0, NULL, {0}, 8, 14, {2, 3},   0, 8, 8},
	{SDE_HW_FENCE_CLIENT_CTL_1, HW_FENCE_CLIENT_ID_CTL1, NULL, {0}, 8, 15, {4, 5},   0, 8, 8},
	{SDE_HW_FENCE_CLIENT_CTL_2, HW_FENCE_CLIENT_ID_CTL2, NULL, {0}, 8, 16, {6, 7},   0, 8, 8},
	{SDE_HW_FENCE_CLIENT_CTL_3, HW_FENCE_CLIENT_ID_CTL3, NULL, {0}, 8, 17, {8, 9},   0, 8, 8},
	{SDE_HW_FENCE_CLIENT_CTL_4, HW_FENCE_CLIENT_ID_CTL4, NULL, {0}, 8, 18, {10, 11}, 0, 8, 8},
	{SDE_HW_FENCE_CLIENT_CTL_5, HW_FENCE_CLIENT_ID_CTL5, NULL, {0}, 8, 19, {12, 13}, 0, 8, 8}
};

/**
 * hw_fence_data_dpu_client - this table maps the dpu ipcc input and output signals for each display
 *              clients to communicate with the fence controller.
 * This struct must match the order of the 'sde_hw_fence_clients' enum,
 * the output signal must match with the signals that FenceCTL expects for each display client.
 * This 'hw_fence_data_dpu_client' must be used for HW that supports dpu-signal
 */
struct sde_hw_fence_data hw_fence_data_dpu_client[SDE_HW_FENCE_CLIENT_MAX] = {
	{SDE_HW_FENCE_CLIENT_CTL_0, HW_FENCE_CLIENT_ID_CTL0, NULL, {0}, 8, 0, {0, 6},  0, 8, 25},
	{SDE_HW_FENCE_CLIENT_CTL_1, HW_FENCE_CLIENT_ID_CTL1, NULL, {0}, 8, 1, {1, 7},  0, 8, 25},
	{SDE_HW_FENCE_CLIENT_CTL_2, HW_FENCE_CLIENT_ID_CTL2, NULL, {0}, 8, 2, {2, 8},  0, 8, 25},
	{SDE_HW_FENCE_CLIENT_CTL_3, HW_FENCE_CLIENT_ID_CTL3, NULL, {0}, 8, 3, {3, 9},  0, 8, 25},
	{SDE_HW_FENCE_CLIENT_CTL_4, HW_FENCE_CLIENT_ID_CTL4, NULL, {0}, 8, 4, {4, 10}, 0, 8, 25},
	{SDE_HW_FENCE_CLIENT_CTL_5, HW_FENCE_CLIENT_ID_CTL5, NULL, {0}, 8, 5, {5, 11}, 0, 8, 25}
};

int sde_hw_fence_init(struct sde_hw_ctl *hw_ctl, bool use_dpu_ipcc)
{
	struct sde_hw_fence_data *sde_hw_fence_data;
	struct sde_hw_fence_data *hwfence_data;
	int ctl_id;

	if (!hw_ctl)
		return -EINVAL;

	ctl_id = hw_ctl->idx - CTL_0;
	if (ctl_id >= SDE_HW_FENCE_CLIENT_MAX || ctl_id < 0) {
		SDE_ERROR("unexpected ctl_id:%d\n", ctl_id);
		return -EINVAL;
	}

	hwfence_data = &hw_ctl->hwfence_data;
	sde_hw_fence_data = use_dpu_ipcc ? hw_fence_data_dpu_client : hw_fence_data_no_dpu;

	if (sde_hw_fence_data[ctl_id].client_id != ctl_id) {
		SDE_ERROR("Unexpected client_id:%d for ctl_id:%d\n",
			sde_hw_fence_data[ctl_id].client_id, ctl_id);
		return -EINVAL;
	}

	/* init the default fence-data for this client */
	memcpy(hwfence_data, &sde_hw_fence_data[ctl_id], sizeof(struct sde_hw_fence_data));

	SDE_DEBUG("hwfence register ctl:%d client:%d\n", ctl_id, hwfence_data->hw_fence_client_id);
	hwfence_data->hw_fence_handle = msm_hw_fence_register(hwfence_data->hw_fence_client_id,
		&hwfence_data->mem_descriptor);
	if (IS_ERR_OR_NULL(hwfence_data->hw_fence_handle)) {

		hwfence_data->hw_fence_handle = NULL;

		SDE_ERROR("error cannot register ctl_id:%d hw-fence client:%d\n", ctl_id,
			hwfence_data->hw_fence_client_id);
		return -EINVAL;
	}

	SDE_DEBUG("hwfence registered ctl_id:%d hw_fence_client_id:%d handle:0x%p\n",
		ctl_id, hwfence_data->hw_fence_client_id, hwfence_data->hw_fence_handle);

	return 0;
}

void sde_hw_fence_deinit(struct sde_hw_ctl *hw_ctl)
{
	struct sde_hw_fence_data *hwfence_data;

	if (!hw_ctl)
		return;

	hwfence_data = &hw_ctl->hwfence_data;

	/* client was not registered */
	if (IS_ERR_OR_NULL(hwfence_data->hw_fence_handle))
		return;

	SDE_DEBUG("hwfence deregister ctl_id:%d hw_fence_client_id:%d\n",
		hw_ctl->idx - CTL_0, hwfence_data->hw_fence_client_id);

	msm_hw_fence_deregister(hwfence_data->hw_fence_handle);

	hwfence_data->hw_fence_handle = NULL;
}

static int sde_fence_create_hw_fence(struct sde_hw_ctl *hw_ctl, struct sde_fence *sde_fence)
{
	struct sde_hw_fence_data *data;
	struct msm_hw_fence_create_params params;
	int ctl_id;
	u64 hwfence_index;
	int ret;

	if (!hw_ctl)
		return -EINVAL;

	ctl_id = hw_ctl->idx - CTL_0;
	data = &hw_ctl->hwfence_data;

	if (IS_ERR_OR_NULL(data->hw_fence_handle)) {
		SDE_ERROR("unexpected handle for ctl_id:%d\n", ctl_id);
		return -EINVAL;
	}
	params.fence = &sde_fence->base;
	params.handle = &hwfence_index;

	/* Create the HW fence */
	ret = msm_hw_fence_create(data->hw_fence_handle, &params);
	if (ret) {
		SDE_ERROR("failed to create hw_fence for client:%d ctx:%llu seqno:%llu\n", ctl_id,
			sde_fence->base.context, sde_fence->base.seqno);
	} else {
		/* store ctl and index for this fence */
		sde_fence->hwfence_out_ctl = hw_ctl;
		sde_fence->hwfence_index = hwfence_index;

		SDE_DEBUG("create hfence index:%llu ctl:%d ctx:%llu seqno:%llu name:%s\n",
			sde_fence->hwfence_index, ctl_id, sde_fence->base.context,
			sde_fence->base.seqno, sde_fence->name);
	}

	return ret;
}

static inline char *_get_client_id_name(int hw_fence_client_id)
{
	switch (hw_fence_client_id) {
	case HW_FENCE_CLIENT_ID_CTX0:
		return "HW_FENCE_CLIENT_ID_CTX0";
	case HW_FENCE_CLIENT_ID_CTL0:
		return "HW_FENCE_CLIENT_ID_CTL0";
	case HW_FENCE_CLIENT_ID_CTL1:
		return "HW_FENCE_CLIENT_ID_CTL1";
	case HW_FENCE_CLIENT_ID_CTL2:
		return "HW_FENCE_CLIENT_ID_CTL2";
	case HW_FENCE_CLIENT_ID_CTL3:
		return "HW_FENCE_CLIENT_ID_CTL3";
	case HW_FENCE_CLIENT_ID_CTL4:
		return "HW_FENCE_CLIENT_ID_CTL4";
	case HW_FENCE_CLIENT_ID_CTL5:
		return "HW_FENCE_CLIENT_ID_CTL15";
	default:
		return "Unknown";
	}

	return "unknown";
}

int sde_fence_register_hw_fences_wait(struct sde_hw_ctl *hw_ctl, struct dma_fence **fences,
	u32 num_fences)
{
	struct sde_hw_fence_data *data;
	int i, ret;
	int ctl_id;

	if (!hw_ctl) {
		SDE_ERROR("wrong ctl\n");
		return -EINVAL;
	}

	ctl_id = hw_ctl->idx - CTL_0;
	data = &hw_ctl->hwfence_data;
	if (IS_ERR_OR_NULL(data->hw_fence_handle)) {
		SDE_ERROR("unexpected handle for ctl_id:%d\n", ctl_id);
		return -EINVAL;
	}

	SDE_DEBUG("register for wait fences:%d ctl_id:%d hw_fence_client:%s\n",
		num_fences, ctl_id, _get_client_id_name(data->hw_fence_client_id));

	for (i = 0; i < num_fences; i++) {
		SDE_DEBUG("registering fence: ctx:%llu seqno:%llu\n",
			(fences[i])->context, (fences[i])->seqno);
	}

	/* register for wait */
	ret = msm_hw_fence_wait_update(data->hw_fence_handle, fences, num_fences, true);
	if (ret)
		SDE_ERROR("failed to register wait fences for ctl_id:%d ret:%d\n", ctl_id, ret);
	SDE_EVT32_VERBOSE(ctl_id, num_fences, ret);

	return ret;
}

static int _arm_output_hw_fence(struct sde_hw_ctl *hw_ctl, u32 line_count, u32 debugfs_hw_fence)
{
	struct sde_hw_fence_data *data;
	u32 ipcc_out_signal;
	int ctl_id;

	if (!hw_ctl || !hw_ctl->ops.hw_fence_trigger_output_fence ||
			!hw_ctl->ops.hw_fence_update_output_fence) {
		SDE_ERROR("missing ctl/trigger or update fence %d\n", !hw_ctl);
		return -EINVAL;
	}

	ctl_id = hw_ctl->idx - CTL_0;
	data = &hw_ctl->hwfence_data;
	if (data->ipcc_out_signal_pp_idx >= MAX_SDE_HFENCE_OUT_SIGNAL_PING_PONG) {
		/* This should not have happened!, review the ping pong calculation */
		SDE_ERROR("Wrong pp_idx:%d, max:%d\n", data->ipcc_out_signal_pp_idx,
			MAX_SDE_HFENCE_OUT_SIGNAL_PING_PONG);
		return -EINVAL;
	}

	ipcc_out_signal = data->ipcc_out_signal_pp[data->ipcc_out_signal_pp_idx];
	data->ipcc_out_signal_pp_idx = (++data->ipcc_out_signal_pp_idx %
		MAX_SDE_HFENCE_OUT_SIGNAL_PING_PONG);

	SDE_DEBUG("out-fence ctl_id:%d out_signal:%d hw_fence_client:%s\n",
		ctl_id, ipcc_out_signal, _get_client_id_name(data->hw_fence_client_id));

	if ((debugfs_hw_fence & SDE_OUTPUT_HW_FENCE_TIMESTAMP) &&
			hw_ctl->ops.hw_fence_output_timestamp_ctrl)
		hw_ctl->ops.hw_fence_output_timestamp_ctrl(hw_ctl, true, false);

	/* update client/signal output fence */
	hw_ctl->ops.hw_fence_update_output_fence(hw_ctl, data->ipcc_out_client, ipcc_out_signal);
	SDE_EVT32_VERBOSE(ctl_id, ipcc_out_signal);

	/* arm dpu to trigger output fence signal once ready */
	if (line_count)
		hw_ctl->ops.hw_fence_trigger_output_fence(hw_ctl,
			HW_FENCE_TRIGGER_SEL_PROG_LINE_COUNT);
	else
		hw_ctl->ops.hw_fence_trigger_output_fence(hw_ctl, HW_FENCE_TRIGGER_SEL_CTRL_DONE);

	return 0;
}

static int _sde_fence_arm_output_hw_fence(struct sde_fence_context *ctx, u32 line_count,
		u32 debugfs_hw_fence)
{
	struct sde_hw_ctl *hw_ctl = NULL;
	struct sde_fence *fc, *next;

	spin_lock(&ctx->list_lock);
	if (list_empty(&ctx->fence_list_head)) {
		spin_unlock(&ctx->list_lock);
		return 0;
	}

	list_for_each_entry_safe(fc, next, &ctx->fence_list_head, fence_list) {
		struct dma_fence *fence = &fc->base;

		/* this is not hw-fence, or already processed */
		if (!test_bit(MSM_HW_FENCE_FLAG_ENABLED_BIT, &fence->flags))
			continue;

		hw_ctl = fc->hwfence_out_ctl;
		if (!hw_ctl) {
			/*
			 * We flaged an output dma-fence as hw-fence but the hw ctl to handle
			 * it is not available, this should not have happened, but if it does,
			 * this can translate to a fence-timeout!
			 */
			SDE_ERROR("invalid hw ctl, this can cause a fence-timeout!\n");
			SDE_EVT32(SDE_EVTLOG_ERROR, SDE_EVTLOG_FUNC_CASE1, fence->flags,
				fence->context, fence->seqno);

			spin_unlock(&ctx->list_lock);
			return -EINVAL;
		}
	}
	spin_unlock(&ctx->list_lock);

	/* arm dpu to trigger output hw-fence ipcc signal upon completion */
	if (hw_ctl)
		_arm_output_hw_fence(hw_ctl, line_count, debugfs_hw_fence);

	return 0;
}

/* update output hw_fences txq */
int sde_fence_update_hw_fences_txq(struct sde_fence_context *ctx, bool vid_mode, u32 line_count,
		u32 debugfs_hw_fence)
{
	int ret = 0;
	struct sde_hw_fence_data *data;
	struct sde_fence *fc, *next;
	struct sde_hw_ctl *hw_ctl = NULL;
	int ctl_id;
	bool txq_updated = false;

	spin_lock(&ctx->list_lock);
	if (list_empty(&ctx->fence_list_head)) {
		spin_unlock(&ctx->list_lock);
		return 0;
	}

	list_for_each_entry_safe(fc, next, &ctx->fence_list_head, fence_list) {
		struct dma_fence *fence = &fc->base;

		/* this is not hw-fence, or already processed */
		if (!test_bit(MSM_HW_FENCE_FLAG_ENABLED_BIT, &fence->flags) ||
				fc->txq_updated_fence)
			continue;

		hw_ctl = fc->hwfence_out_ctl;
		if (!hw_ctl) {
			/* We flaged an output dma-fence as hw-fence but the hw ctl to handle
			 * it is not available, this should not have happened, but if it does,
			 * this can translate to a fence-timeout!
			 */
			SDE_ERROR("invalid hw ctl, this can cause a fence-timeout!\n");
			SDE_EVT32(SDE_EVTLOG_FUNC_CASE1, fence->flags, fence->context,
				fence->seqno, SDE_EVTLOG_ERROR);
			ret = -EINVAL;
			goto exit;
		}

		ctl_id = hw_ctl->idx - CTL_0;
		data = &hw_ctl->hwfence_data;
		if (IS_ERR_OR_NULL(data->hw_fence_handle)) {
			SDE_ERROR("unexpected handle for ctl_id:%d, this can fence-timeout\n",
				ctl_id);
			SDE_EVT32(SDE_EVTLOG_FUNC_CASE2, fence->flags, fence->context,
				fence->seqno, ctl_id, SDE_EVTLOG_ERROR);
			ret = -EINVAL;
			goto exit;
		}

		/* update hw-fence tx queue */
		ret = msm_hw_fence_update_txq(data->hw_fence_handle, fc->hwfence_index, 0, 0);
		if (ret) {
			SDE_ERROR("fail txq update index:%llu fctx:%llu seqno:%llu client:%d\n",
				fc->hwfence_index, fence->context, fence->seqno,
				data->hw_fence_client_id);
			SDE_EVT32(SDE_EVTLOG_FUNC_CASE3, fence->flags, fence->context,
				fence->seqno, ctl_id, SDE_EVTLOG_ERROR);
			goto exit;
		}
		/* avoid updating txq more than once and avoid repeating the same fence twice */
		txq_updated = fc->txq_updated_fence = true;

		SDE_DEBUG("update txq fence:0x%pK ctx:%llu seqno:%llu f:0x%llx ctl:%d vid:%d\n",
			fence, fence->context, fence->seqno, fence->flags, ctl_id, vid_mode);

		/* We will update TxQ one time per frame */
		if (txq_updated)
			break;
	}

exit:
	spin_unlock(&ctx->list_lock);

	/* arm dpu to trigger output hw-fence ipcc signal upon completion in vid-mode */
	if ((txq_updated && hw_ctl) || line_count)
		_sde_fence_arm_output_hw_fence(ctx, line_count, debugfs_hw_fence);

	return ret;
}

static void _sde_hw_fence_release(struct sde_fence *f)
{
	struct sde_hw_fence_data *data;
	struct sde_hw_ctl *hw_ctl = f->hwfence_out_ctl;
	int ctl_id;
	int ret;

	if (!hw_ctl) {
		SDE_ERROR("invalid hw_ctl\n");
		return;
	}

	ctl_id = hw_ctl->idx - CTL_0;
	data = &hw_ctl->hwfence_data;
	if (IS_ERR_OR_NULL(data->hw_fence_handle)) {
		SDE_ERROR("unexpected handle for ctl_id:%d\n", ctl_id);
		return;
	}

	SDE_DEBUG("destroy hw fence ctl_id:%d ctx:%llu seqno:%llu name:%s\n",
		ctl_id, f->base.context, f->base.seqno, f->name);

	/* Delete the HW fence */
	ret = msm_hw_fence_destroy(data->hw_fence_handle, &f->base);
	if (ret)
		SDE_ERROR("failed to destroy hw_fence for ctl_id:%d ctx:%llu seqno:%llu\n", ctl_id,
			f->base.context, f->base.seqno);
}

static int _reset_hw_fence_timeline(struct sde_hw_ctl *hw_ctl, u32 flags)
{
	struct sde_hw_fence_data *data;
	int ret = 0;

	data = &hw_ctl->hwfence_data;

	if (!IS_ERR_OR_NULL(data->hw_fence_handle)) {
		SDE_EVT32(data->hw_fence_client_id);
		ret = msm_hw_fence_reset_client(data->hw_fence_handle, flags);
		if (ret) {
			pr_err("failed to reset client %d\n", data->hw_fence_client_id);
			return -EINVAL;
		}
	}

	return ret;
}

int sde_fence_update_input_hw_fence_signal(struct sde_hw_ctl *hw_ctl, u32 debugfs_hw_fence,
		struct sde_hw_mdp *hw_mdp, bool disable)
{
	struct sde_hw_fence_data *data;
	u32 ipcc_signal_id;
	u32 ipcc_client_id;
	int ctl_id;

	/* we must support sw_override as well, so check both functions */
	if (!hw_mdp || !hw_ctl || !hw_ctl->ops.hw_fence_update_input_fence ||
			!hw_ctl->ops.hw_fence_trigger_sw_override) {
		SDE_ERROR("missing ctl/override/update fence %d\n", !hw_ctl);
		return -EINVAL;
	}

	ctl_id = hw_ctl->idx - CTL_0;
	data = &hw_ctl->hwfence_data;

	if (disable) {
		hw_ctl->ops.hw_fence_ctrl(hw_ctl, false, false, 0);
		return -EPERM;
	}

	if ((debugfs_hw_fence & SDE_INPUT_HW_FENCE_TIMESTAMP)
			&& hw_mdp->ops.hw_fence_input_timestamp_ctrl)
		hw_mdp->ops.hw_fence_input_timestamp_ctrl(hw_mdp, true, false);

	ipcc_signal_id = data->ipcc_in_signal;
	ipcc_client_id = data->ipcc_in_client;

	SDE_DEBUG("configure input signal:%d out client:%d ctl_id:%d\n", ipcc_signal_id,
		ipcc_client_id, ctl_id);
	SDE_EVT32(ctl_id, ipcc_signal_id, ipcc_client_id);

	/* configure dpu hw for the client/signal pair signaling input-fence */
	hw_ctl->ops.hw_fence_update_input_fence(hw_ctl, ipcc_client_id, ipcc_signal_id);

	/* Enable hw-fence for this ctrl-path */
	hw_ctl->ops.hw_fence_ctrl(hw_ctl, true, true, 1);

	return 0;
}

void *sde_sync_get(uint64_t fd)
{
	/* force signed compare, fdget accepts an int argument */
	return (signed int)fd >= 0 ? sync_file_get_fence(fd) : NULL;
}

void sde_sync_put(void *fence)
{
	if (fence)
		dma_fence_put(fence);
}

void sde_fence_dump(struct dma_fence *fence)
{
	char timeline_str[TIMELINE_VAL_LENGTH];

	if (fence->ops->timeline_value_str)
		fence->ops->timeline_value_str(fence, timeline_str, TIMELINE_VAL_LENGTH);

	SDE_ERROR(
		"fence drv name:%s timeline name:%s seqno:0x%llx timeline:%s signaled:0x%x status:%d flags:0x%x\n",
		fence->ops->get_driver_name(fence),
		fence->ops->get_timeline_name(fence),
		fence->seqno, timeline_str,
		fence->ops->signaled ?
		fence->ops->signaled(fence) : 0xffffffff,
		dma_fence_get_status(fence), fence->flags);
}

static void sde_fence_dump_user_fds_info(struct dma_fence *base_fence)
{
	struct dma_fence_array *array;
	struct dma_fence *user_fence;
	int i;

	array = container_of(base_fence, struct dma_fence_array, base);
	if (test_bit(SPEC_FENCE_FLAG_FENCE_ARRAY, &base_fence->flags) &&
		test_bit(SPEC_FENCE_FLAG_ARRAY_BIND, &base_fence->flags)) {
		for (i = 0; i < array->num_fences; i++) {
			user_fence = array->fences[i];
			if (user_fence) {
				dma_fence_get(user_fence);
				sde_fence_dump(user_fence);
				dma_fence_put(user_fence);
			}
		}
	}
}

signed long sde_sync_wait(void *fnc, long timeout_ms)
{
	struct dma_fence *fence = fnc;
	int rc, status = 0;

	if (!fence)
		return -EINVAL;
	else if (dma_fence_is_signaled(fence))
		return timeout_ms ? msecs_to_jiffies(timeout_ms) : 1;

	rc = dma_fence_wait_timeout(fence, true,
				msecs_to_jiffies(timeout_ms));
	if (!rc || (rc == -EINVAL) || fence->error) {
		status = dma_fence_get_status(fence);
		if (test_bit(SPEC_FENCE_FLAG_FENCE_ARRAY, &fence->flags)) {
			if (status == -EINVAL) {
				SDE_INFO("spec fence bind failure status:%d\n", status);
				rc = -EBADF;
			} else if (fence->ops->signaled && fence->ops->signaled(fence)) {
				SDE_INFO("spec fence status:%d\n", status);
			} else {
				sde_fence_dump(fence);
				sde_fence_dump_user_fds_info(fence);
			}
		} else {
			sde_fence_dump(fence);
		}
	}

	return rc;
}

uint32_t sde_sync_get_name_prefix(void *fence)
{
	const char *name;
	uint32_t i, prefix;
	struct dma_fence *f = fence;

	if (!fence)
		return 0;

	name = f->ops->get_driver_name(f);
	if (!name)
		return 0;

	prefix = 0x0;
	for (i = 0; i < sizeof(uint32_t) && name[i]; ++i)
		prefix = (prefix << CHAR_BIT) | name[i];

	return prefix;
}

static void sde_fence_destroy(struct kref *kref)
{
	struct sde_fence_context *ctx;

	if (!kref) {
		SDE_ERROR("received invalid kref\n");
		return;
	}

	ctx = container_of(kref, struct sde_fence_context, kref);
	kfree(ctx);
}

static inline struct sde_fence *to_sde_fence(struct dma_fence *fence)
{
	return container_of(fence, struct sde_fence, base);
}

static const char *sde_fence_get_driver_name(struct dma_fence *fence)
{
	struct sde_fence *f = to_sde_fence(fence);

	return f->name;
}

static const char *sde_fence_get_timeline_name(struct dma_fence *fence)
{
	struct sde_fence *f = to_sde_fence(fence);

	return f->ctx->name;
}

static bool sde_fence_enable_signaling(struct dma_fence *fence)
{
	return true;
}

static bool sde_fence_signaled(struct dma_fence *fence)
{
	struct sde_fence *f = to_sde_fence(fence);
	bool status;

	status = ((int)(fence->seqno - f->ctx->done_count) <= 0);
	SDE_DEBUG("status:%d fence seq:%llu and timeline:%u\n",
			status, fence->seqno, f->ctx->done_count);
	return status;
}

static void sde_fence_release(struct dma_fence *fence)
{
	struct sde_fence *f;

	if (fence) {
		f = to_sde_fence(fence);

		/* Delete the HW fence */
		if (test_bit(MSM_HW_FENCE_FLAG_ENABLED_BIT, &fence->flags))
			_sde_hw_fence_release(f);

		kref_put(&f->ctx->kref, sde_fence_destroy);
		kfree(f);
	}
}

static void sde_fence_value_str(struct dma_fence *fence, char *str, int size)
{
	if (!fence || !str)
		return;

	snprintf(str, size, "%llu", fence->seqno);
}

static void sde_fence_timeline_value_str(struct dma_fence *fence, char *str,
		int size)
{
	struct sde_fence *f = to_sde_fence(fence);

	if (!fence || !f->ctx || !str)
		return;

	snprintf(str, size, "%d", f->ctx->done_count);
}

static struct dma_fence_ops sde_fence_ops = {
	.get_driver_name = sde_fence_get_driver_name,
	.get_timeline_name = sde_fence_get_timeline_name,
	.enable_signaling = sde_fence_enable_signaling,
	.signaled = sde_fence_signaled,
	.wait = dma_fence_default_wait,
	.release = sde_fence_release,
	.fence_value_str = sde_fence_value_str,
	.timeline_value_str = sde_fence_timeline_value_str,
};

/**
 * _sde_fence_create_fd - create fence object and return an fd for it
 * This function is NOT thread-safe.
 * @timeline: Timeline to associate with fence
 * @val: Timeline value at which to signal the fence
 * Return: File descriptor on success, or error code on error
 */
static int _sde_fence_create_fd(void *fence_ctx, uint32_t val, struct sde_hw_ctl *hw_ctl)
{
	struct sde_fence *sde_fence;
	struct sync_file *sync_file;
	signed int fd = -EINVAL;
	struct sde_fence_context *ctx = fence_ctx;

	if (!ctx) {
		SDE_ERROR("invalid context\n");
		goto exit;
	}

	sde_fence = kzalloc(sizeof(*sde_fence), GFP_KERNEL);
	if (!sde_fence)
		return -ENOMEM;

	sde_fence->ctx = fence_ctx;
	snprintf(sde_fence->name, SDE_FENCE_NAME_SIZE, "sde_fence:%s:%u",
						sde_fence->ctx->name, val);
	dma_fence_init(&sde_fence->base, &sde_fence_ops, &ctx->lock,
		ctx->context, val);
	kref_get(&ctx->kref);

	/* create fd */
	fd = get_unused_fd_flags(0);
	if (fd < 0) {
		SDE_ERROR("failed to get_unused_fd_flags(), %s\n",
							sde_fence->name);
		dma_fence_put(&sde_fence->base);
		goto exit;
	}

	/* create fence */
	sync_file = sync_file_create(&sde_fence->base);
	if (sync_file == NULL) {
		put_unused_fd(fd);
		fd = -EINVAL;
		SDE_ERROR("couldn't create fence, %s\n", sde_fence->name);
		dma_fence_put(&sde_fence->base);
		goto exit;
	}

	/* If ctl_id is valid, try to create a hw-fence */
	if (hw_ctl)
		sde_fence_create_hw_fence(hw_ctl, sde_fence);

	fd_install(fd, sync_file->file);
	sde_fence->fd = fd;

	spin_lock(&ctx->list_lock);
	list_add_tail(&sde_fence->fence_list, &ctx->fence_list_head);
	spin_unlock(&ctx->list_lock);

exit:
	return fd;
}

struct sde_fence_context *sde_fence_init(const char *name, uint32_t drm_id)
{
	struct sde_fence_context *ctx;

	if (!name) {
		SDE_ERROR("invalid argument(s)\n");
		return ERR_PTR(-EINVAL);
	}

	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);

	if (!ctx) {
		SDE_ERROR("failed to alloc fence ctx\n");
		return ERR_PTR(-ENOMEM);
	}

	strlcpy(ctx->name, name, ARRAY_SIZE(ctx->name));
	ctx->drm_id = drm_id;
	kref_init(&ctx->kref);
	ctx->context = dma_fence_context_alloc(1);

	spin_lock_init(&ctx->lock);
	spin_lock_init(&ctx->list_lock);
	INIT_LIST_HEAD(&ctx->fence_list_head);

	return ctx;
}

void sde_fence_deinit(struct sde_fence_context *ctx)
{
	if (!ctx) {
		SDE_ERROR("invalid fence\n");
		return;
	}

	kref_put(&ctx->kref, sde_fence_destroy);
}

void sde_fence_prepare(struct sde_fence_context *ctx)
{
	unsigned long flags;

	if (!ctx) {
		SDE_ERROR("invalid argument(s), fence %pK\n", ctx);
	} else {
		spin_lock_irqsave(&ctx->lock, flags);
		++ctx->commit_count;
		spin_unlock_irqrestore(&ctx->lock, flags);
	}
}

static void _sde_fence_trigger(struct sde_fence_context *ctx, bool error, ktime_t ts)
{
	unsigned long flags;
	struct sde_fence *fc, *next;
	bool is_signaled = false;

	kref_get(&ctx->kref);

	spin_lock(&ctx->list_lock);
	if (list_empty(&ctx->fence_list_head)) {
		SDE_DEBUG("nothing to trigger!\n");
		goto end;
	}

	list_for_each_entry_safe(fc, next, &ctx->fence_list_head, fence_list) {
		spin_lock_irqsave(&ctx->lock, flags);
		if (error)
			dma_fence_set_error(&fc->base, -EBUSY);
		is_signaled = sde_fence_signaled(&fc->base);
		if (is_signaled)
			dma_fence_signal_timestamp_locked(&fc->base, ts);
		spin_unlock_irqrestore(&ctx->lock, flags);

		if (is_signaled) {
			list_del_init(&fc->fence_list);
			dma_fence_put(&fc->base);
		}
	}
end:
	spin_unlock(&ctx->list_lock);
	kref_put(&ctx->kref, sde_fence_destroy);
}

int sde_fence_create(struct sde_fence_context *ctx, uint64_t *val,
				uint32_t offset, struct sde_hw_ctl *hw_ctl)
{
	uint32_t trigger_value;
	int fd, rc = -EINVAL;
	unsigned long flags;

	if (!ctx || !val) {
		SDE_ERROR("invalid argument(s), fence %d, pval %d\n",
				ctx != NULL, val != NULL);
		return rc;
	}

	/*
	 * Allow created fences to have a constant offset with respect
	 * to the timeline. This allows us to delay the fence signalling
	 * w.r.t. the commit completion (e.g., an offset of +1 would
	 * cause fences returned during a particular commit to signal
	 * after an additional delay of one commit, rather than at the
	 * end of the current one.
	 */
	spin_lock_irqsave(&ctx->lock, flags);
	trigger_value = ctx->commit_count + offset;
	spin_unlock_irqrestore(&ctx->lock, flags);

	fd = _sde_fence_create_fd(ctx, trigger_value, hw_ctl);
	*val = fd;
	SDE_DEBUG("fd:%d trigger:%d commit:%d offset:%d\n",
			fd, trigger_value, ctx->commit_count, offset);

	SDE_EVT32(ctx->drm_id, trigger_value, fd, hw_ctl ? hw_ctl->idx : 0);
	rc = (fd >= 0) ? 0 : fd;

	return rc;
}

void sde_fence_signal(struct sde_fence_context *ctx, ktime_t ts,
		enum sde_fence_event fence_event, struct sde_hw_ctl *hw_ctl)
{
	unsigned long flags;

	if (!ctx) {
		SDE_ERROR("invalid ctx, %pK\n", ctx);
		return;
	}

	spin_lock_irqsave(&ctx->lock, flags);
	if (fence_event == SDE_FENCE_RESET_TIMELINE) {
		/* reset hw-fences without error */
		if (hw_ctl)
			_reset_hw_fence_timeline(hw_ctl, MSM_HW_FENCE_RESET_WITHOUT_ERROR |
				MSM_HW_FENCE_RESET_WITHOUT_DESTROY);

		if ((int)(ctx->done_count - ctx->commit_count) < 0) {
			SDE_DEBUG(
			  "timeline reset attempt! ctx:0x%x done count:%d commit:%d\n",
				ctx->drm_id, ctx->done_count, ctx->commit_count);
			ctx->done_count = ctx->commit_count;
			SDE_EVT32(ctx->drm_id, ctx->done_count,
				ctx->commit_count, ktime_to_us(ts),
				fence_event, SDE_EVTLOG_FUNC_CASE1);
		} else {
			spin_unlock_irqrestore(&ctx->lock, flags);
			return;
		}
	} else if ((int)(ctx->done_count - ctx->commit_count) < 0) {
		++ctx->done_count;
		SDE_DEBUG("fence_signal:done count:%d commit count:%d\n",
					ctx->done_count, ctx->commit_count);
	} else {
		SDE_ERROR("extra signal attempt! done count:%d commit:%d\n",
					ctx->done_count, ctx->commit_count);
		SDE_EVT32(ctx->drm_id, ctx->done_count, ctx->commit_count,
			ktime_to_us(ts), fence_event, SDE_EVTLOG_FATAL);
		spin_unlock_irqrestore(&ctx->lock, flags);
		return;
	}
	spin_unlock_irqrestore(&ctx->lock, flags);

	SDE_EVT32(ctx->drm_id, ctx->done_count, ctx->commit_count,
			ktime_to_us(ts));

	_sde_fence_trigger(ctx, (fence_event == SDE_FENCE_SIGNAL_ERROR), ts);
}

void sde_fence_timeline_status(struct sde_fence_context *ctx,
					struct drm_mode_object *drm_obj)
{
	char *obj_name;

	if (!ctx || !drm_obj) {
		SDE_ERROR("invalid input params\n");
		return;
	}

	switch (drm_obj->type) {
	case DRM_MODE_OBJECT_CRTC:
		obj_name = "crtc";
		break;
	case DRM_MODE_OBJECT_CONNECTOR:
		obj_name = "connector";
		break;
	default:
		obj_name = "unknown";
		break;
	}

	SDE_ERROR("drm obj:%s id:%d type:0x%x done_count:%d commit_count:%d\n",
		obj_name, drm_obj->id, drm_obj->type, ctx->done_count,
		ctx->commit_count);
}

void sde_fence_list_dump(struct dma_fence *fence, struct seq_file **s)
{
	char timeline_str[TIMELINE_VAL_LENGTH];

	if (fence->ops->timeline_value_str)
		fence->ops->timeline_value_str(fence,
		timeline_str, TIMELINE_VAL_LENGTH);

	seq_printf(*s, "fence name:%s timeline name:%s seqno:0x%llx timeline:%s signaled:0x%x\n",
		fence->ops->get_driver_name(fence),
		fence->ops->get_timeline_name(fence),
		fence->seqno, timeline_str,
		fence->ops->signaled ?
		fence->ops->signaled(fence) : 0xffffffff);
}

void sde_debugfs_timeline_dump(struct sde_fence_context *ctx,
		struct drm_mode_object *drm_obj, struct seq_file **s)
{
	char *obj_name;
	struct sde_fence *fc, *next;
	struct dma_fence *fence;

	if (!ctx || !drm_obj) {
		SDE_ERROR("invalid input params\n");
		return;
	}

	switch (drm_obj->type) {
	case DRM_MODE_OBJECT_CRTC:
		obj_name = "crtc";
		break;
	case DRM_MODE_OBJECT_CONNECTOR:
		obj_name = "connector";
		break;
	default:
		obj_name = "unknown";
		break;
	}

	seq_printf(*s, "drm obj:%s id:%d type:0x%x done_count:%d commit_count:%d\n",
		obj_name, drm_obj->id, drm_obj->type, ctx->done_count,
		ctx->commit_count);

	spin_lock(&ctx->list_lock);
	list_for_each_entry_safe(fc, next, &ctx->fence_list_head, fence_list) {
		fence = &fc->base;
		sde_fence_list_dump(fence, s);
	}
	spin_unlock(&ctx->list_lock);
}
