/* drivers/soc/qcom/smp2p_debug.c
 *
 * Copyright (c) 2013-2014,2016 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/ctype.h>
#include <linux/list.h>
#include <linux/debugfs.h>
#include <linux/io.h>
#include "smp2p_private.h"

#if defined(CONFIG_DEBUG_FS)

/**
 * Dump interrupt statistics.
 *
 * @s:   pointer to output file
 */
static void smp2p_int_stats(struct seq_file *s)
{
	struct smp2p_interrupt_config *int_cfg;
	int pid;

	int_cfg = smp2p_get_interrupt_config();
	if (!int_cfg)
		return;

	seq_puts(s, "| Processor | Incoming Id | Incoming # |");
	seq_puts(s, " Outgoing # | Base Ptr |   Mask   |\n");

	for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) {
		if (!int_cfg[pid].is_configured &&
				pid != SMP2P_REMOTE_MOCK_PROC)
			continue;

		seq_printf(s, "| %5s (%d) | %11u | %10u | %10u | %pK | %08x |\n",
			int_cfg[pid].name,
			pid, int_cfg[pid].in_int_id,
			int_cfg[pid].in_interrupt_count,
			int_cfg[pid].out_interrupt_count,
			int_cfg[pid].out_int_ptr,
			int_cfg[pid].out_int_mask);
	}
}

/**
 * Dump item header line 1.
 *
 * @buf:      output buffer
 * @max:      length of output buffer
 * @item_ptr: SMEM item pointer
 * @state:    item state
 * @returns: Number of bytes written to output buffer
 */
static int smp2p_item_header1(char *buf, int max, struct smp2p_smem *item_ptr,
	enum msm_smp2p_edge_state state)
{
	int i = 0;
	const char *state_text;

	if (!item_ptr) {
		i += scnprintf(buf + i, max - i, "None");
		return i;
	}

	switch (state) {
	case SMP2P_EDGE_STATE_CLOSED:
		state_text = "State: Closed";
		break;
	case SMP2P_EDGE_STATE_OPENING:
		state_text = "State: Opening";
		break;
	case SMP2P_EDGE_STATE_OPENED:
		state_text = "State: Opened";
		break;
	default:
		state_text = "";
		break;
	}

	i += scnprintf(buf + i, max - i,
		"%-14s LPID %d RPID %d",
		state_text,
		SMP2P_GET_LOCAL_PID(item_ptr->rem_loc_proc_id),
		SMP2P_GET_REMOTE_PID(item_ptr->rem_loc_proc_id)
		);

	return i;
}

/**
 * Dump item header line 2.
 *
 * @buf:      output buffer
 * @max:      length of output buffer
 * @item_ptr: SMEM item pointer
 * @returns: Number of bytes written to output buffer
 */
static int smp2p_item_header2(char *buf, int max, struct smp2p_smem *item_ptr)
{
	int i = 0;

	if (!item_ptr) {
		i += scnprintf(buf + i, max - i, "None");
		return i;
	}

	i += scnprintf(buf + i, max - i,
		"Version: %08x Features: %08x",
		SMP2P_GET_VERSION(item_ptr->feature_version),
		SMP2P_GET_FEATURES(item_ptr->feature_version)
		);

	return i;
}

/**
 * Dump item header line 3.
 *
 * @buf:      output buffer
 * @max:      length of output buffer
 * @item_ptr: SMEM item pointer
 * @state:    item state
 * @returns: Number of bytes written to output buffer
 */
static int smp2p_item_header3(char *buf, int max, struct smp2p_smem *item_ptr)
{
	int i = 0;

	if (!item_ptr) {
		i += scnprintf(buf + i, max - i, "None");
		return i;
	}

	i += scnprintf(buf + i, max - i,
		"Entries #/Max: %d/%d Flags: %c%c",
		SMP2P_GET_ENT_VALID(item_ptr->valid_total_ent),
		SMP2P_GET_ENT_TOTAL(item_ptr->valid_total_ent),
		item_ptr->flags & SMP2P_FLAGS_RESTART_ACK_MASK ? 'A' : 'a',
		item_ptr->flags & SMP2P_FLAGS_RESTART_DONE_MASK ? 'D' : 'd'
		);

	return i;
}

/**
 * Dump individual input/output item pair.
 *
 * @s:   pointer to output file
 */
static void smp2p_item(struct seq_file *s, int remote_pid)
{
	struct smp2p_smem *out_ptr;
	struct smp2p_smem *in_ptr;
	struct smp2p_interrupt_config *int_cfg;
	char tmp_buff[64];
	int state;
	int entry;
	struct smp2p_entry_v1 *out_entries = NULL;
	struct smp2p_entry_v1 *in_entries = NULL;
	int out_valid = 0;
	int in_valid = 0;
	char entry_name[SMP2P_MAX_ENTRY_NAME];

	int_cfg = smp2p_get_interrupt_config();
	if (!int_cfg)
		return;
	if (!int_cfg[remote_pid].is_configured &&
			remote_pid != SMP2P_REMOTE_MOCK_PROC)
		return;

	out_ptr = smp2p_get_out_item(remote_pid, &state);
	in_ptr = smp2p_get_in_item(remote_pid);

	if (!out_ptr && !in_ptr)
		return;

	/* print item headers */
	seq_printf(s, "%s%s\n",
		" ====================================== ",
		"======================================");
	scnprintf(tmp_buff, sizeof(tmp_buff),
		"Apps(%d)->%s(%d)",
		SMP2P_APPS_PROC, int_cfg[remote_pid].name, remote_pid);
	seq_printf(s, "| %-37s", tmp_buff);

	scnprintf(tmp_buff, sizeof(tmp_buff),
		"%s(%d)->Apps(%d)",
		int_cfg[remote_pid].name, remote_pid, SMP2P_APPS_PROC);
	seq_printf(s, "| %-37s|\n", tmp_buff);
	seq_printf(s, "%s%s\n",
		" ====================================== ",
		"======================================");

	smp2p_item_header1(tmp_buff, sizeof(tmp_buff), out_ptr, state);
	seq_printf(s, "| %-37s", tmp_buff);
	smp2p_item_header1(tmp_buff, sizeof(tmp_buff), in_ptr, -1);
	seq_printf(s, "| %-37s|\n", tmp_buff);

	smp2p_item_header2(tmp_buff, sizeof(tmp_buff), out_ptr);
	seq_printf(s, "| %-37s", tmp_buff);
	smp2p_item_header2(tmp_buff, sizeof(tmp_buff), in_ptr);
	seq_printf(s, "| %-37s|\n", tmp_buff);

	smp2p_item_header3(tmp_buff, sizeof(tmp_buff), out_ptr);
	seq_printf(s, "| %-37s", tmp_buff);
	smp2p_item_header3(tmp_buff, sizeof(tmp_buff), in_ptr);
	seq_printf(s, "| %-37s|\n", tmp_buff);

	seq_printf(s, " %s%s\n",
		"-------------------------------------- ",
		"--------------------------------------");
	seq_printf(s, "| %-37s",
		"Entry Name       Value");
	seq_printf(s, "| %-37s|\n",
		"Entry Name       Value");
	seq_printf(s, " %s%s\n",
		"-------------------------------------- ",
		"--------------------------------------");

	/* print entries */
	if (out_ptr) {
		out_entries = (struct smp2p_entry_v1 *)((void *)out_ptr +
				sizeof(struct smp2p_smem));
		out_valid = SMP2P_GET_ENT_VALID(out_ptr->valid_total_ent);
	}

	if (in_ptr) {
		in_entries = (struct smp2p_entry_v1 *)((void *)in_ptr +
				sizeof(struct smp2p_smem));
		in_valid = SMP2P_GET_ENT_VALID(in_ptr->valid_total_ent);
	}

	for (entry = 0; out_entries || in_entries; ++entry) {
		if (out_entries && entry < out_valid) {
			memcpy_fromio(entry_name, out_entries->name,
							SMP2P_MAX_ENTRY_NAME);
			scnprintf(tmp_buff, sizeof(tmp_buff),
					"%-16s 0x%08x",
					entry_name,
					out_entries->entry);
			++out_entries;
		} else {
			out_entries = NULL;
			scnprintf(tmp_buff, sizeof(tmp_buff), "None");
		}
		seq_printf(s, "| %-37s", tmp_buff);

		if (in_entries && entry < in_valid) {
			memcpy_fromio(entry_name, in_entries->name,
							SMP2P_MAX_ENTRY_NAME);
			scnprintf(tmp_buff, sizeof(tmp_buff),
					"%-16s 0x%08x",
					entry_name,
					in_entries->entry);
			++in_entries;
		} else {
			in_entries = NULL;
			scnprintf(tmp_buff, sizeof(tmp_buff), "None");
		}
		seq_printf(s, "| %-37s|\n", tmp_buff);
	}
	seq_printf(s, " %s%s\n\n",
		"-------------------------------------- ",
		"--------------------------------------");
}

/**
 * Dump item state.
 *
 * @s:   pointer to output file
 */
static void smp2p_items(struct seq_file *s)
{
	int pid;

	for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid)
		smp2p_item(s, pid);
}

static struct dentry *dent;

static int debugfs_show(struct seq_file *s, void *data)
{
	void (*show)(struct seq_file *) = s->private;

	show(s);

	return 0;
}

static int debug_open(struct inode *inode, struct file *file)
{
	return single_open(file, debugfs_show, inode->i_private);
}

static const struct file_operations debug_ops = {
	.open = debug_open,
	.release = single_release,
	.read = seq_read,
	.llseek = seq_lseek,
};

void debug_create(const char *name,
			 void (*show)(struct seq_file *))
{
	struct dentry *file;

	file = debugfs_create_file(name, 0444, dent, show, &debug_ops);
	if (!file)
		pr_err("%s: unable to create file '%s'\n", __func__, name);
}

static int __init smp2p_debugfs_init(void)
{
	dent = debugfs_create_dir("smp2p", 0);
	if (IS_ERR(dent))
		return PTR_ERR(dent);

	debug_create("int_stats", smp2p_int_stats);
	debug_create("items", smp2p_items);

	return 0;
}

late_initcall(smp2p_debugfs_init);
#endif /* CONFIG_DEBUG_FS */
