/* Copyright (c) 2002,2008-2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/export.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#include "kgsl.h"
#include "adreno.h"
#include "kgsl_cffdump.h"
#include "kgsl_sync.h"

static int _isdb_set(void *data, u64 val)
{
	struct kgsl_device *device = data;
	struct adreno_device *adreno_dev = ADRENO_DEVICE(device);

	/* Once ISDB goes enabled it stays enabled */
	if (test_bit(ADRENO_DEVICE_ISDB_ENABLED, &adreno_dev->priv))
		return 0;

	mutex_lock(&device->mutex);

	/*
	 * Bring down the GPU so we can bring it back up with the correct power
	 * and clock settings
	 */
	kgsl_pwrctrl_change_state(device, KGSL_STATE_SUSPEND);
	set_bit(ADRENO_DEVICE_ISDB_ENABLED, &adreno_dev->priv);
	kgsl_pwrctrl_change_state(device, KGSL_STATE_SLUMBER);

	mutex_unlock(&device->mutex);

	return 0;
}

static int _isdb_get(void *data, u64 *val)
{
	struct kgsl_device *device = data;
	struct adreno_device *adreno_dev = ADRENO_DEVICE(device);

	*val = (u64) test_bit(ADRENO_DEVICE_ISDB_ENABLED, &adreno_dev->priv);
	return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(_isdb_fops, _isdb_get, _isdb_set, "%llu\n");

static int _lm_limit_set(void *data, u64 val)
{
	struct kgsl_device *device = data;
	struct adreno_device *adreno_dev = ADRENO_DEVICE(device);

	if (!ADRENO_FEATURE(adreno_dev, ADRENO_LM))
		return 0;

	/* assure value is between 3A and 10A */
	if (val > 10000)
		val = 10000;
	else if (val < 3000)
		val = 3000;

	adreno_dev->lm_limit = val;

	if (test_bit(ADRENO_LM_CTRL, &adreno_dev->pwrctrl_flag)) {
		mutex_lock(&device->mutex);
		kgsl_pwrctrl_change_state(device, KGSL_STATE_SUSPEND);
		kgsl_pwrctrl_change_state(device, KGSL_STATE_SLUMBER);
		mutex_unlock(&device->mutex);
	}

	return 0;
}

static int _lm_limit_get(void *data, u64 *val)
{
	struct kgsl_device *device = data;
	struct adreno_device *adreno_dev = ADRENO_DEVICE(device);

	if (!ADRENO_FEATURE(adreno_dev, ADRENO_LM))
		*val = 0;

	*val = (u64) adreno_dev->lm_limit;
	return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(_lm_limit_fops, _lm_limit_get, _lm_limit_set, "%llu\n");

static int _lm_threshold_count_get(void *data, u64 *val)
{
	struct kgsl_device *device = data;
	struct adreno_device *adreno_dev = ADRENO_DEVICE(device);

	if (!ADRENO_FEATURE(adreno_dev, ADRENO_LM))
		*val = 0;
	else
		*val = (u64) adreno_dev->lm_threshold_cross;
	return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(_lm_threshold_fops, _lm_threshold_count_get,
	NULL, "%llu\n");

static int _active_count_get(void *data, u64 *val)
{
	struct kgsl_device *device = data;
	unsigned int i = atomic_read(&device->active_cnt);

	*val = (u64) i;
	return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(_active_count_fops, _active_count_get, NULL, "%llu\n");

typedef void (*reg_read_init_t)(struct kgsl_device *device);
typedef void (*reg_read_fill_t)(struct kgsl_device *device, int i,
	unsigned int *vals, int linec);


static void sync_event_print(struct seq_file *s,
		struct kgsl_cmdbatch_sync_event *sync_event)
{
	unsigned long flags;

	switch (sync_event->type) {
	case KGSL_CMD_SYNCPOINT_TYPE_TIMESTAMP: {
		seq_printf(s, "sync: ctx: %d ts: %d",
				sync_event->context->id, sync_event->timestamp);
		break;
	}
	case KGSL_CMD_SYNCPOINT_TYPE_FENCE:
		spin_lock_irqsave(&sync_event->handle_lock, flags);

		seq_printf(s, "sync: [%pk] %s", sync_event->handle,
		(sync_event->handle && sync_event->handle->fence)
				? sync_event->handle->fence->name : "NULL");

		spin_unlock_irqrestore(&sync_event->handle_lock, flags);
		break;
	default:
		seq_printf(s, "sync: type: %d", sync_event->type);
		break;
	}
}

struct flag_entry {
	unsigned long mask;
	const char *str;
};

static const struct flag_entry cmdbatch_flags[] = {KGSL_CMDBATCH_FLAGS};

static const struct flag_entry cmdbatch_priv[] = {
	{ CMDBATCH_FLAG_SKIP, "skip"},
	{ CMDBATCH_FLAG_FORCE_PREAMBLE, "force_preamble"},
	{ CMDBATCH_FLAG_WFI, "wait_for_idle" },
};

static const struct flag_entry context_flags[] = {KGSL_CONTEXT_FLAGS};

/*
 * Note that the ADRENO_CONTEXT_* flags start at
 * KGSL_CONTEXT_PRIV_DEVICE_SPECIFIC so it is ok to cross the streams here.
 */
static const struct flag_entry context_priv[] = {
	{ KGSL_CONTEXT_PRIV_SUBMITTED, "submitted"},
	{ KGSL_CONTEXT_PRIV_DETACHED, "detached"},
	{ KGSL_CONTEXT_PRIV_INVALID, "invalid"},
	{ KGSL_CONTEXT_PRIV_PAGEFAULT, "pagefault"},
	{ ADRENO_CONTEXT_FAULT, "fault"},
	{ ADRENO_CONTEXT_GPU_HANG, "gpu_hang"},
	{ ADRENO_CONTEXT_GPU_HANG_FT, "gpu_hang_ft"},
	{ ADRENO_CONTEXT_SKIP_EOF, "skip_end_of_frame" },
	{ ADRENO_CONTEXT_FORCE_PREAMBLE, "force_preamble"},
};

static void print_flags(struct seq_file *s, const struct flag_entry *table,
			size_t table_size, unsigned long flags)
{
	int i;
	int first = 1;

	for (i = 0; i < table_size; i++) {
		if (flags & table[i].mask) {
			seq_printf(s, "%c%s", first ? '\0' : '|', table[i].str);
			flags &= ~(table[i].mask);
			first = 0;
		}
	}
	if (flags) {
		seq_printf(s, "%c0x%lx", first ? '\0' : '|', flags);
		first = 0;
	}
	if (first)
		seq_puts(s, "None");
}

static void cmdbatch_print(struct seq_file *s, struct kgsl_cmdbatch *cmdbatch)
{
	struct kgsl_cmdbatch_sync_event *event;
	unsigned int i;

	/* print fences first, since they block this cmdbatch */

	for (i = 0; i < cmdbatch->numsyncs; i++) {
		event = &cmdbatch->synclist[i];

		if (!kgsl_cmdbatch_event_pending(cmdbatch, i))
			continue;

		/*
		 * Timestamp is 0 for KGSL_CONTEXT_SYNC, but print it anyways
		 * so that it is clear if the fence was a separate submit
		 * or part of an IB submit.
		 */
		seq_printf(s, "\t%d ", cmdbatch->timestamp);
		sync_event_print(s, event);
		seq_puts(s, "\n");
	}

	/* if this flag is set, there won't be an IB */
	if (cmdbatch->flags & KGSL_CONTEXT_SYNC)
		return;

	seq_printf(s, "\t%d: ", cmdbatch->timestamp);

	seq_puts(s, " flags: ");
	print_flags(s, cmdbatch_flags, ARRAY_SIZE(cmdbatch_flags),
		    cmdbatch->flags);

	seq_puts(s, " priv: ");
	print_flags(s, cmdbatch_priv, ARRAY_SIZE(cmdbatch_priv),
		    cmdbatch->priv);

	seq_puts(s, "\n");
}

static const char *ctx_type_str(unsigned int type)
{
	int i;
	struct flag_entry table[] = {KGSL_CONTEXT_TYPES};

	for (i = 0; i < ARRAY_SIZE(table); i++)
		if (type == table[i].mask)
			return table[i].str;
	return "UNKNOWN";
}

static int ctx_print(struct seq_file *s, void *unused)
{
	struct adreno_context *drawctxt = s->private;
	unsigned int i;
	struct kgsl_event *event;
	unsigned int queued = 0, consumed = 0, retired = 0;

	seq_printf(s, "id: %d type: %s priority: %d process: %s (%d) tid: %d\n",
		   drawctxt->base.id,
		   ctx_type_str(drawctxt->type),
		   drawctxt->base.priority,
		   drawctxt->base.proc_priv->comm,
		   drawctxt->base.proc_priv->pid,
		   drawctxt->base.tid);

	seq_puts(s, "flags: ");
	print_flags(s, context_flags, ARRAY_SIZE(context_flags),
		    drawctxt->base.flags & ~(KGSL_CONTEXT_PRIORITY_MASK
						| KGSL_CONTEXT_TYPE_MASK));
	seq_puts(s, " priv: ");
	print_flags(s, context_priv, ARRAY_SIZE(context_priv),
			drawctxt->base.priv);
	seq_puts(s, "\n");

	seq_puts(s, "timestamps: ");
	kgsl_readtimestamp(drawctxt->base.device, &drawctxt->base,
				KGSL_TIMESTAMP_QUEUED, &queued);
	kgsl_readtimestamp(drawctxt->base.device, &drawctxt->base,
				KGSL_TIMESTAMP_CONSUMED, &consumed);
	kgsl_readtimestamp(drawctxt->base.device, &drawctxt->base,
				KGSL_TIMESTAMP_RETIRED, &retired);
	seq_printf(s, "queued: %u consumed: %u retired: %u global:%u\n",
		   queued, consumed, retired,
		   drawctxt->internal_timestamp);

	seq_puts(s, "cmdqueue:\n");

	spin_lock(&drawctxt->lock);
	for (i = drawctxt->cmdqueue_head;
		i != drawctxt->cmdqueue_tail;
		i = CMDQUEUE_NEXT(i, ADRENO_CONTEXT_CMDQUEUE_SIZE))
		cmdbatch_print(s, drawctxt->cmdqueue[i]);
	spin_unlock(&drawctxt->lock);

	seq_puts(s, "events:\n");
	spin_lock(&drawctxt->base.events.lock);
	list_for_each_entry(event, &drawctxt->base.events.events, node)
		seq_printf(s, "\t%d: %pF created: %u\n", event->timestamp,
				event->func, event->created);
	spin_unlock(&drawctxt->base.events.lock);

	return 0;
}

static int ctx_open(struct inode *inode, struct file *file)
{
	int ret;
	unsigned int id = (unsigned int)(unsigned long)inode->i_private;
	struct kgsl_context *context;

	context = kgsl_context_get(kgsl_get_device(KGSL_DEVICE_3D0), id);
	if (context == NULL)
		return -ENODEV;

	ret = single_open(file, ctx_print, context);
	if (ret)
		kgsl_context_put(context);
	return ret;
}

static int ctx_release(struct inode *inode, struct file *file)
{
	struct kgsl_context *context;

	context = ((struct seq_file *)file->private_data)->private;

	kgsl_context_put(context);

	return single_release(inode, file);
}

static const struct file_operations ctx_fops = {
	.open = ctx_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = ctx_release,
};


void
adreno_context_debugfs_init(struct adreno_device *adreno_dev,
			    struct adreno_context *ctx)
{
	unsigned char name[16];

	snprintf(name, sizeof(name), "%d", ctx->base.id);

	ctx->debug_root = debugfs_create_file(name, 0444,
				adreno_dev->ctx_d_debugfs,
				(void *)(unsigned long)ctx->base.id, &ctx_fops);
}

void adreno_debugfs_init(struct adreno_device *adreno_dev)
{
	struct kgsl_device *device = KGSL_DEVICE(adreno_dev);

	if (!device->d_debugfs || IS_ERR(device->d_debugfs))
		return;

	kgsl_cffdump_debugfs_create(device);

	debugfs_create_file("active_cnt", 0444, device->d_debugfs, device,
			    &_active_count_fops);
	adreno_dev->ctx_d_debugfs = debugfs_create_dir("ctx",
							device->d_debugfs);

	if (ADRENO_FEATURE(adreno_dev, ADRENO_LM)) {
		debugfs_create_file("lm_limit", 0644, device->d_debugfs, device,
			&_lm_limit_fops);
		debugfs_create_file("lm_threshold_count", 0444,
			device->d_debugfs, device, &_lm_threshold_fops);
	}

	if (adreno_is_a5xx(adreno_dev))
		debugfs_create_file("isdb", 0644, device->d_debugfs,
			device, &_isdb_fops);
}
