#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>
#include <linux/ioport.h>
#include <linux/timer.h>
#include <asm/io.h>

static long cp3_ctrl_switch = 0;
static long cp3_mode = 0x04030201;

mm_segment_t oldfs;
struct file *open_file(char *path, int flag, int mode)
{
	struct file *fp;
	fp = filp_open(path, flag, 0);
	if (fp)
		return fp;
	else
		return NULL;
}

int read_file(struct file *fp, char *buf, int readlen)
{
	return vfs_read(fp, (void __user *)buf, readlen, &fp->f_pos);
}

int write_file(struct file *fp, char *buf, int writelen)
{
	return vfs_write(fp, (__force const char __user *)buf,
			 writelen, &fp->f_pos);
}

int close_file(struct file *fp)
{
	filp_close(fp,NULL);
	return 0;
}

void init_kernel_envent(void)
{
	oldfs = get_fs();
	set_fs(KERNEL_DS);
}

static void start_cp3(void)
{
	unsigned long cp0_sts, cp0_tmp = 0x80000000;
	unsigned long mode2set;

	if (cp3_ctrl_switch) {
		mode2set = cp3_mode;
		__asm__ __volatile__ (
		"	.set push			\n"
		"	.set noreorder			\n"
		"					\n"
		"	mfc0	%[sts],	$12		\n"
		"	or	%[sts],	%[tmp]		\n"
		"	mtc0	%[sts],	$12		\n"
		"	ctc3	$0,	$0		\n"
		"	mtc3	$0,	$8		\n"
		"	mtc3	$0,	$9		\n"
		"	mtc3	$0,	$10		\n"
		"	mtc3	$0,	$11		\n"
		"	mtc3	$0,	$12		\n"
		"	mtc3	$0,	$13		\n"
		"	mtc3	$0,	$14		\n"
		"	mtc3	$0,	$15		\n"
		"	ctc3	%[mode],$0		\n"
		"	.set pop			\n"
		: [sts] "=&r" (cp0_sts)
		: [tmp]  "r" (cp0_tmp),
		  [mode] "r" (mode2set));
	}
}

static void cp3_stop(void)
{
	unsigned long cp0_sts, cp0_tmp = 0x80000000;
	unsigned long cp3_cnt0lo, cp3_cnt1lo, cp3_cnt2lo, cp3_cnt3lo;
	unsigned long cp3_cnt0hi, cp3_cnt1hi, cp3_cnt2hi, cp3_cnt3hi;
	char buf[200];
	struct file *fp;
	int ret;

	if (cp3_ctrl_switch) {
		__asm__ __volatile__ (
		"	.set push			\n"
		"	.set noreorder			\n"
		"	mfc0	%[sts],	$12		\n"
		"	or	%[sts],	%[tmp]		\n"
		"	mtc0	%[sts],	$12		\n"
		"					\n"
		"	ctc3	$0,	$0		\n"
		"	mfc3	%[cnt0lo],$8		\n"
		"	mfc3	%[cnt0hi],$9		\n"
		"	mfc3	%[cnt1lo],$10		\n"
		"	mfc3	%[cnt1hi],$11		\n"
		"	mfc3	%[cnt2lo],$12		\n"
		"	mfc3	%[cnt2hi],$13		\n"
		"	mfc3	%[cnt3lo],$14		\n"
		"	mfc3	%[cnt3hi],$15		\n"
		"	.set pop			\n"
		: [sts] "=&r" (cp0_sts),
		  [cnt0lo] "=&r" (cp3_cnt0lo),
		  [cnt0hi] "=&r" (cp3_cnt0hi),
		  [cnt1lo] "=&r" (cp3_cnt1lo),
		  [cnt1hi] "=&r" (cp3_cnt1hi),
		  [cnt2lo] "=&r" (cp3_cnt2lo),
		  [cnt2hi] "=&r" (cp3_cnt2hi),
		  [cnt3lo] "=&r" (cp3_cnt3lo),
		  [cnt3hi] "=&r" (cp3_cnt3hi)
		: [tmp] "r" (cp0_tmp));

		memset(buf, 0, 200);
		sprintf(buf, "<%8lx> [0]%8lx%08lx,[1]%8lx%08lx,[2]%8lx%08lx,[3]%8lx%08lx\n",
			jiffies,
			cp3_cnt0hi,
			cp3_cnt0lo,
			cp3_cnt1hi,
			cp3_cnt1lo,
			cp3_cnt2hi,
			cp3_cnt2lo,
			cp3_cnt3hi,
			cp3_cnt3lo);

		init_kernel_envent();
		fp = open_file("/tmp/cp3.log", O_APPEND|O_RDWR, 0);
		if (fp != NULL) {
			if ((ret = write_file(fp, buf, strlen(buf))) <= 0)
				printk("CP3: write file error %d\n", ret);
			close_file(fp);

		}
		set_fs(oldfs);
	}
}

static int cp3_ctrl_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%ld\n", cp3_ctrl_switch);
	return 0;
}

static int cp3_ctrl_write(struct file *file, const char __user *buffer,
				size_t count, loff_t *pos)
{
	char mode;
	char buf[200];
	struct file *fp;
	int ret;

	if (count > 0) {
		if (get_user(mode, buffer))
			return -EFAULT;
		if (mode == '0') {
			if (cp3_ctrl_switch) {
				printk("CP3: stop measurement\n");
				cp3_stop();
				cp3_ctrl_switch = 0;
			}
		}
		if (mode == '1') {
			if (!cp3_ctrl_switch) {
				printk("CP3: start measurement\n");
				cp3_ctrl_switch = 1;

				memset(buf, 0, 200);
				sprintf(buf, "<%8lx> cp3 test start=%lx\n", jiffies, cp3_mode);

				init_kernel_envent();
				fp = open_file("/tmp/cp3.log", O_APPEND|O_CREAT|O_RDWR, 0);
				if (fp != NULL)
				{
					if ((ret = write_file(fp, buf, strlen(buf))) <= 0)
						printk("CP3: write file error %d\n", ret);
					close_file(fp);
				}
				set_fs(oldfs);
				start_cp3();
			}
		}
	}
	return count;
}

static int cp3_mode_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%lx\n", cp3_mode);
	return 0;
}

static int cp3_mode_write(struct file *file, const char __user *buffer,
				size_t count, loff_t *pos)

{
	char buf[100];
	unsigned long modeval;

	memset(buf , 0, 100);
	if (count > 0) {
		if (copy_from_user (buf, buffer, count))
			return -EFAULT;

		if (kstrtoul(buf, 0, &modeval)) {
			printk("CP3: error mode\n");
			return -EFAULT;
		}
		else {
			cp3_mode = modeval;
			printk("CP3: new mode:0x%lx\n", cp3_mode);
		}
	}
	return count;
}

static int btb_ctrl_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%lx\n", cp3_mode);
	return 0;
}

static int btb_ctrl_write(struct file *file, const char __user *buffer,
				size_t count, loff_t *pos)
{
	char buf[100];
	unsigned long oldctrl;
	unsigned long ctrl = 0x30000;
	unsigned long arg;

	memset(buf , 0, 100);
	if (count > 0) {
		if (copy_from_user (buf, buffer, count))
			return -EFAULT;

		if (kstrtoul(buf, 0, &arg)) {
			printk("BTB CTRL: error mode\n");
			return -EFAULT;
		}
		else {
			__asm__ __volatile__ (
			"	.set push			\n"
			"	.set noreorder			\n"
			"					\n"
			"	mflxc0	%[old],	$4		\n"
			"	or	%[ctl],	%[old]		\n"
			"	mtlxc0	%[ctl],	$4		\n"
			"	.set pop			\n"
			: [ctl]  "=&r" (ctrl),
			  [old]  "=&r" (oldctrl)
			: );

			printk("BTB CTRL: new mode:0x%lx, old 0x%lx\n", ctrl, oldctrl);
		}
	}
	return count;
}

static int os_ctrl_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%lx\n", cp3_mode);
	return 0;
}

static int os_ctrl_write(struct file *file, const char __user *buffer,
				size_t count, loff_t *pos)
{
	char buf[100];
	unsigned long arg;
	unsigned long oldctrl;
	unsigned long ctrl = 0x03000f03;

	memset(buf , 0, 100);
	if (count > 0) {
		if (copy_from_user (buf, buffer, count))
			return -EFAULT;

		if (kstrtoul(buf, 0, &arg)) {
			printk("Outstanding CTRL: error mode\n");
			return -EFAULT;
		}
		else {
			__asm__ __volatile__ (
			"	.set push			\n"
			"	.set noreorder			\n"
			"					\n"
			"	mfc0	%[old],	$20,	2	\n"
			"	nop				\n"
			"	mtc0	%[ctl],	$20,	2	\n"
			"	.set pop			\n"
			: [old]  "=&r" (oldctrl)
			: [ctl]  "r" (ctrl));

			printk("Outstanding CTRL: new mode:0x%lx, old 0x%lx\n", ctrl, oldctrl);
		}
	}
	return count;
}

static int cp3_ctrl_open(struct inode *inode, struct file *file)
{
	return single_open(file, cp3_ctrl_show, NULL);
}

static int cp3_mode_open(struct inode *inode, struct file *file)
{
	return single_open(file, cp3_mode_show, NULL);
}

static int os_ctrl_open(struct inode *inode, struct file *file)
{
	return single_open(file, os_ctrl_show, NULL);
}

static int btb_ctrl_open(struct inode *inode, struct file *file)
{
	return single_open(file, btb_ctrl_show, NULL);
}

static const struct file_operations cp3_ctrl_fops = {
	.open = cp3_ctrl_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
	.write = cp3_ctrl_write,
};

static const struct file_operations cp3_mode_fops = {
	.open = cp3_mode_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
	.write = cp3_mode_write,
};

static const struct file_operations os_ctrl_fops = {
	.open = os_ctrl_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
	.write = os_ctrl_write,
};

static const struct file_operations btb_ctrl_fops = {
	.open = btb_ctrl_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
	.write = btb_ctrl_write,
};

static int __init cp3_access_init(void)
{
	printk("cp3access driver demo\n");
	proc_create("cp3ctrl", 0755, NULL, &cp3_ctrl_fops);

	proc_create("cp3mode", 0755, NULL, &cp3_mode_fops);

	proc_create("osctrl", 0755, NULL, &os_ctrl_fops);

	proc_create("btbctrl", 0755, NULL, &btb_ctrl_fops);

	return 0;
}

static void __exit cp3_access_exit(void)
{
	remove_proc_entry("cp3ctrl", NULL);
	remove_proc_entry("cp3mode", NULL);
	printk("cp3access driver remove successfully\n");
}
module_init(cp3_access_init);
module_exit(cp3_access_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("cp3ctrl module");
MODULE_AUTHOR("Viller Hsiao<villerhsiao@realtek.com>");
MODULE_ALIAS("cp3ctrl module");
