/*
 *
 * (C) COPYRIGHT 2012-2015 ARM Limited. All rights reserved.
 *
 * This program is free software and is provided to you under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation, and any use by you of this program is subject to the terms
 * of such GNU licence.
 *
 * A copy of the licence is included with the program, and can also be obtained
 * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 */



#ifdef CONFIG_SYNC

#include <linux/seq_file.h>
#include "sync.h"
#include <mali_kbase.h>
#include <mali_kbase_sync.h>

struct mali_sync_timeline {
	struct sync_timeline timeline;
	atomic_t counter;
	atomic_t signalled;
};

struct mali_sync_pt {
	struct sync_pt pt;
	int order;
	int result;
};

static struct mali_sync_timeline *to_mali_sync_timeline(struct sync_timeline *timeline)
{
	return container_of(timeline, struct mali_sync_timeline, timeline);
}

static struct mali_sync_pt *to_mali_sync_pt(struct sync_pt *pt)
{
	return container_of(pt, struct mali_sync_pt, pt);
}

static struct sync_pt *timeline_dup(struct sync_pt *pt)
{
	struct mali_sync_pt *mpt = to_mali_sync_pt(pt);
	struct mali_sync_pt *new_mpt;
	struct sync_pt *new_pt = sync_pt_create(sync_pt_parent(pt), sizeof(struct mali_sync_pt));

	if (!new_pt)
		return NULL;

	new_mpt = to_mali_sync_pt(new_pt);
	new_mpt->order = mpt->order;
	new_mpt->result = mpt->result;

	return new_pt;
}

static int timeline_has_signaled(struct sync_pt *pt)
{
	struct mali_sync_pt *mpt = to_mali_sync_pt(pt);
	struct mali_sync_timeline *mtl = to_mali_sync_timeline(sync_pt_parent(pt));
	int result = mpt->result;

	int diff = atomic_read(&mtl->signalled) - mpt->order;

	if (diff >= 0)
		return (result < 0) ? result : 1;

	return 0;
}

static int timeline_compare(struct sync_pt *a, struct sync_pt *b)
{
	struct mali_sync_pt *ma = container_of(a, struct mali_sync_pt, pt);
	struct mali_sync_pt *mb = container_of(b, struct mali_sync_pt, pt);

	int diff = ma->order - mb->order;

	if (diff == 0)
		return 0;

	return (diff < 0) ? -1 : 1;
}

static void timeline_value_str(struct sync_timeline *timeline, char *str,
			       int size)
{
	struct mali_sync_timeline *mtl = to_mali_sync_timeline(timeline);

	snprintf(str, size, "%d", atomic_read(&mtl->signalled));
}

static void pt_value_str(struct sync_pt *pt, char *str, int size)
{
	struct mali_sync_pt *mpt = to_mali_sync_pt(pt);

	snprintf(str, size, "%d(%d)", mpt->order, mpt->result);
}

static struct sync_timeline_ops mali_timeline_ops = {
	.driver_name = "Mali",
	.dup = timeline_dup,
	.has_signaled = timeline_has_signaled,
	.compare = timeline_compare,
	.timeline_value_str = timeline_value_str,
	.pt_value_str       = pt_value_str,
};

int kbase_sync_timeline_is_ours(struct sync_timeline *timeline)
{
	return timeline->ops == &mali_timeline_ops;
}

struct sync_timeline *kbase_sync_timeline_alloc(const char *name)
{
	struct sync_timeline *tl;
	struct mali_sync_timeline *mtl;

	tl = sync_timeline_create(&mali_timeline_ops, sizeof(struct mali_sync_timeline), name);
	if (!tl)
		return NULL;

	/* Set the counter in our private struct */
	mtl = to_mali_sync_timeline(tl);
	atomic_set(&mtl->counter, 0);
	atomic_set(&mtl->signalled, 0);

	return tl;
}

struct sync_pt *kbase_sync_pt_alloc(struct sync_timeline *parent)
{
	struct sync_pt *pt = sync_pt_create(parent, sizeof(struct mali_sync_pt));
	struct mali_sync_timeline *mtl = to_mali_sync_timeline(parent);
	struct mali_sync_pt *mpt;

	if (!pt)
		return NULL;

	mpt = to_mali_sync_pt(pt);
	mpt->order = atomic_inc_return(&mtl->counter);
	mpt->result = 0;

	return pt;
}

void kbase_sync_signal_pt(struct sync_pt *pt, int result)
{
	struct mali_sync_pt *mpt = to_mali_sync_pt(pt);
	struct mali_sync_timeline *mtl = to_mali_sync_timeline(sync_pt_parent(pt));
	int signalled;
	int diff;

	mpt->result = result;

	do {
		signalled = atomic_read(&mtl->signalled);

		diff = signalled - mpt->order;

		if (diff > 0) {
			/* The timeline is already at or ahead of this point.
			 * This should not happen unless userspace has been
			 * signalling fences out of order, so warn but don't
			 * violate the sync_pt API.
			 * The warning is only in debug builds to prevent
			 * a malicious user being able to spam dmesg.
			 */
#ifdef CONFIG_MALI_DEBUG
			pr_err("Fences were triggered in a different order to allocation!");
#endif				/* CONFIG_MALI_DEBUG */
			return;
		}
	} while (atomic_cmpxchg(&mtl->signalled, signalled, mpt->order) != signalled);
}

#endif				/* CONFIG_SYNC */
