/* drivers/soc/qcom/smd_debug.c
 *
 * Copyright (C) 2007 Google, Inc.
 * Copyright (c) 2009-2015, The Linux Foundation. All rights reserved.
 * Author: Brian Swetland <swetland@google.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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/debugfs.h>
#include <linux/list.h>
#include <linux/ctype.h>
#include <linux/jiffies.h>
#include <linux/err.h>

#include <soc/qcom/smem.h>

#include "smd_private.h"

#if defined(CONFIG_DEBUG_FS)

static char *chstate(unsigned n)
{
	switch (n) {
	case SMD_SS_CLOSED:
		return "CLOSED";
	case SMD_SS_OPENING:
		return "OPENING";
	case SMD_SS_OPENED:
		return "OPENED";
	case SMD_SS_FLUSHING:
		return "FLUSHING";
	case SMD_SS_CLOSING:
		return "CLOSING";
	case SMD_SS_RESET:
		return "RESET";
	case SMD_SS_RESET_OPENING:
		return "ROPENING";
	default:
		return "UNKNOWN";
	}
}

static void debug_int_stats(struct seq_file *s)
{
	int subsys;
	struct interrupt_stat *stats = interrupt_stats;
	const char *subsys_name;

	seq_puts(s,
		"   Subsystem    | Interrupt ID |    In     |    Out    |\n");

	for (subsys = 0; subsys < NUM_SMD_SUBSYSTEMS; ++subsys) {
		subsys_name = smd_pid_to_subsystem(subsys);
		if (!IS_ERR_OR_NULL(subsys_name)) {
			seq_printf(s, "%-10s %4s |    %9d | %9u | %9u |\n",
				smd_pid_to_subsystem(subsys), "smd",
				stats->smd_interrupt_id,
				stats->smd_in_count,
				stats->smd_out_count);

			seq_printf(s, "%-10s %4s |    %9d | %9u | %9u |\n",
				smd_pid_to_subsystem(subsys), "smsm",
				stats->smsm_interrupt_id,
				stats->smsm_in_count,
				stats->smsm_out_count);
		}
		++stats;
	}
}

static void debug_int_stats_reset(struct seq_file *s)
{
	int subsys;
	struct interrupt_stat *stats = interrupt_stats;

	seq_puts(s, "Resetting interrupt stats.\n");

	for (subsys = 0; subsys < NUM_SMD_SUBSYSTEMS; ++subsys) {
		stats->smd_in_count = 0;
		stats->smd_out_count = 0;
		stats->smsm_in_count = 0;
		stats->smsm_out_count = 0;
		++stats;
	}
}

/* NNV: revist, it may not be smd version */
static void debug_read_smd_version(struct seq_file *s)
{
	uint32_t *smd_ver;
	uint32_t n, version;

	smd_ver = smem_find(SMEM_VERSION_SMD, 32 * sizeof(uint32_t),
							0, SMEM_ANY_HOST_FLAG);

	if (smd_ver)
		for (n = 0; n < 32; n++) {
			version = smd_ver[n];
			seq_printf(s, "entry %d: %d.%d\n", n,
				       version >> 16,
				       version & 0xffff);
		}
}

/**
 * pid_to_str - Convert a numeric processor id value into a human readable
 *		string value.
 *
 * @pid: the processor id to convert
 * @returns: a string representation of @pid
 */
static char *pid_to_str(int pid)
{
	switch (pid) {
	case SMD_APPS:
		return "APPS";
	case SMD_MODEM:
		return "MDMSW";
	case SMD_Q6:
		return "ADSP";
	case SMD_TZ:
		return "TZ";
	case SMD_WCNSS:
		return "WCNSS";
	case SMD_MODEM_Q6_FW:
		return "MDMFW";
	case SMD_RPM:
		return "RPM";
	default:
		return "???";
	}
}

/**
 * print_half_ch_state - Print the state of half of a SMD channel in a human
 *			readable format.
 *
 * @s: the sequential file to print to
 * @half_ch: half of a SMD channel that should have its state printed
 * @half_ch_funcs: the relevant channel access functions for @half_ch
 * @size: size of the fifo in bytes associated with @half_ch
 * @proc: the processor id that owns the part of the SMD channel associated with
 *		@half_ch
 * @is_restricted: true if memory access is restricted
 */
static void print_half_ch_state(struct seq_file *s,
				void *half_ch,
				struct smd_half_channel_access *half_ch_funcs,
				unsigned size,
				int proc,
				bool is_restricted)
{
	seq_printf(s, "%-5s|", pid_to_str(proc));

	if (!is_restricted) {
		seq_printf(s, "%-7s|0x%05X|0x%05X|0x%05X",
			chstate(half_ch_funcs->get_state(half_ch)),
			size,
			half_ch_funcs->get_tail(half_ch),
			half_ch_funcs->get_head(half_ch));
		seq_printf(s, "|%c%c%c%c%c%c%c%c|0x%05X",
			half_ch_funcs->get_fDSR(half_ch) ? 'D' : 'd',
			half_ch_funcs->get_fCTS(half_ch) ? 'C' : 'c',
			half_ch_funcs->get_fCD(half_ch) ? 'C' : 'c',
			half_ch_funcs->get_fRI(half_ch) ? 'I' : 'i',
			half_ch_funcs->get_fHEAD(half_ch) ? 'W' : 'w',
			half_ch_funcs->get_fTAIL(half_ch) ? 'R' : 'r',
			half_ch_funcs->get_fSTATE(half_ch) ? 'S' : 's',
			half_ch_funcs->get_fBLOCKREADINTR(half_ch) ? 'B' : 'b',
			(half_ch_funcs->get_head(half_ch) -
				half_ch_funcs->get_tail(half_ch)) & (size - 1));
	} else {
		seq_puts(s, " Access Restricted");
	}
}

/**
 * smd_xfer_type_to_str - Convert a numeric transfer type value into a human
 *		readable string value.
 *
 * @xfer_type: the processor id to convert
 * @returns: a string representation of @xfer_type
 */
static char *smd_xfer_type_to_str(uint32_t xfer_type)
{
	if (xfer_type == 1)
		return "S"; /* streaming type */
	else if (xfer_type == 2)
		return "P"; /* packet type */
	else
		return "L"; /* legacy type */
}

/**
 * print_smd_ch_table - Print the current state of every valid SMD channel in a
 *			specific SMD channel allocation table to a human
 *			readable formatted output.
 *
 * @s: the sequential file to print to
 * @tbl: a valid pointer to the channel allocation table to print from
 * @num_tbl_entries: total number of entries in the table referenced by @tbl
 * @ch_base_id: the SMEM item id corresponding to the array of channel
 *		structures for the channels found in @tbl
 * @fifo_base_id: the SMEM item id corresponding to the array of channel fifos
 *		for the channels found in @tbl
 * @pid: processor id to use for any SMEM operations
 * @flags: flags to use for any SMEM operations
 */
static void print_smd_ch_table(struct seq_file *s,
				struct smd_alloc_elm *tbl,
				unsigned num_tbl_entries,
				unsigned ch_base_id,
				unsigned fifo_base_id,
				unsigned pid,
				unsigned flags)
{
	void *half_ch;
	unsigned half_ch_size;
	uint32_t ch_type;
	void *buffer;
	unsigned buffer_size;
	int n;
	bool is_restricted;

/*
 * formatted, human readable channel state output, ie:
ID|CHANNEL NAME       |T|PROC |STATE  |FIFO SZ|RDPTR  |WRPTR  |FLAGS   |DATAPEN
-------------------------------------------------------------------------------
00|DS                 |S|APPS |CLOSED |0x02000|0x00000|0x00000|dcCiwrsb|0x00000
  |                   | |MDMSW|OPENING|0x02000|0x00000|0x00000|dcCiwrsb|0x00000
-------------------------------------------------------------------------------
 */

	seq_printf(s, "%2s|%-19s|%1s|%-5s|%-7s|%-7s|%-7s|%-7s|%-8s|%-7s\n",
								"ID",
								"CHANNEL NAME",
								"T",
								"PROC",
								"STATE",
								"FIFO SZ",
								"RDPTR",
								"WRPTR",
								"FLAGS",
								"DATAPEN");
	seq_puts(s,
		"-------------------------------------------------------------------------------\n");
	for (n = 0; n < num_tbl_entries; ++n) {
		if (strlen(tbl[n].name) == 0)
			continue;

		seq_printf(s, "%2u|%-19s|%s|", tbl[n].cid, tbl[n].name,
			smd_xfer_type_to_str(SMD_XFER_TYPE(tbl[n].type)));
		ch_type = SMD_CHANNEL_TYPE(tbl[n].type);


		if (smd_edge_to_remote_pid(ch_type) == SMD_RPM &&
		   smd_edge_to_local_pid(ch_type) != SMD_APPS)
			is_restricted = true;
		else
			is_restricted = false;

		if (is_word_access_ch(ch_type))
			half_ch_size =
				sizeof(struct smd_half_channel_word_access);
		else
			half_ch_size = sizeof(struct smd_half_channel);

		half_ch = smem_find(ch_base_id + n, 2 * half_ch_size,
								pid, flags);
		buffer = smem_get_entry(fifo_base_id + n, &buffer_size,
								pid, flags);
		if (half_ch && buffer)
			print_half_ch_state(s,
					half_ch,
					get_half_ch_funcs(ch_type),
					buffer_size / 2,
					smd_edge_to_local_pid(ch_type),
					is_restricted);

		seq_puts(s, "\n");
		seq_printf(s, "%2s|%-19s|%1s|", "", "", "");

		if (half_ch && buffer)
			print_half_ch_state(s,
					half_ch + half_ch_size,
					get_half_ch_funcs(ch_type),
					buffer_size / 2,
					smd_edge_to_remote_pid(ch_type),
					is_restricted);

		seq_puts(s, "\n");
		seq_puts(s,
			"-------------------------------------------------------------------------------\n");
	}
}

/**
 * debug_ch - Print the current state of every valid SMD channel in a human
 *		readable formatted table.
 *
 * @s: the sequential file to print to
 */
static void debug_ch(struct seq_file *s)
{
	struct smd_alloc_elm *tbl;
	struct smd_alloc_elm *default_pri_tbl;
	struct smd_alloc_elm *default_sec_tbl;
	unsigned tbl_size;
	int i;

	tbl = smem_get_entry(ID_CH_ALLOC_TBL, &tbl_size, 0, SMEM_ANY_HOST_FLAG);
	default_pri_tbl = tbl;

	if (!tbl) {
		seq_puts(s, "Channel allocation table not found\n");
		return;
	}

	if (IS_ERR(tbl) && PTR_ERR(tbl) == -EPROBE_DEFER) {
		seq_puts(s, "SMEM is not initialized\n");
		return;
	}

	seq_puts(s, "Primary allocation table:\n");
	print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl), ID_SMD_CHANNELS,
							SMEM_SMD_FIFO_BASE_ID,
							0,
							SMEM_ANY_HOST_FLAG);

	tbl = smem_get_entry(SMEM_CHANNEL_ALLOC_TBL_2, &tbl_size, 0,
							SMEM_ANY_HOST_FLAG);
	default_sec_tbl = tbl;
	if (tbl) {
		seq_puts(s, "\n\nSecondary allocation table:\n");
		print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl),
						SMEM_SMD_BASE_ID_2,
						SMEM_SMD_FIFO_BASE_ID_2,
						0,
						SMEM_ANY_HOST_FLAG);
	}

	for (i = 1; i < NUM_SMD_SUBSYSTEMS; ++i) {
		tbl = smem_get_entry(ID_CH_ALLOC_TBL, &tbl_size, i, 0);
		if (tbl && tbl != default_pri_tbl) {
			seq_puts(s, "\n\n");
			seq_printf(s, "%s <-> %s Primary allocation table:\n",
							pid_to_str(SMD_APPS),
							pid_to_str(i));
			print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl),
							ID_SMD_CHANNELS,
							SMEM_SMD_FIFO_BASE_ID,
							i,
							0);
		}

		tbl = smem_get_entry(SMEM_CHANNEL_ALLOC_TBL_2, &tbl_size, i, 0);
		if (tbl && tbl != default_sec_tbl) {
			seq_puts(s, "\n\n");
			seq_printf(s, "%s <-> %s Secondary allocation table:\n",
							pid_to_str(SMD_APPS),
							pid_to_str(i));
			print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl),
						SMEM_SMD_BASE_ID_2,
						SMEM_SMD_FIFO_BASE_ID_2,
						i,
						0);
		}
	}
}

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,
};

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

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

static int __init smd_debugfs_init(void)
{
	struct dentry *dent;

	dent = debugfs_create_dir("smd", 0);
	if (IS_ERR(dent))
		return PTR_ERR(dent);

	debug_create("ch", 0444, dent, debug_ch);
	debug_create("version", 0444, dent, debug_read_smd_version);
	debug_create("int_stats", 0444, dent, debug_int_stats);
	debug_create("int_stats_reset", 0444, dent, debug_int_stats_reset);

	return 0;
}

late_initcall(smd_debugfs_init);
#endif
