// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/module.h>
#include <linux/pwm.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include "hdmi_aocec_api.h"
#include "hdmi_ao_cec.h"

#define DEVICE_NAME "aocec"

static const char * const cec_reg_name1[] = {
	"CEC_TX_MSG_LENGTH",
	"CEC_TX_MSG_CMD",
	"CEC_TX_WRITE_BUF",
	"CEC_TX_CLEAR_BUF",
	"CEC_RX_MSG_CMD",
	"CEC_RX_CLEAR_BUF",
	"CEC_LOGICAL_ADDR0",
	"CEC_LOGICAL_ADDR1",
	"CEC_LOGICAL_ADDR2",
	"CEC_LOGICAL_ADDR3",
	"CEC_LOGICAL_ADDR4",
	"CEC_CLOCK_DIV_H",
	"CEC_CLOCK_DIV_L"
};

static const char * const cec_reg_name2[] = {
	"CEC_RX_MSG_LENGTH",
	"CEC_RX_MSG_STATUS",
	"CEC_RX_NUM_MSG",
	"CEC_TX_MSG_STATUS",
	"CEC_TX_NUM_MSG"
};

static const char * const ceca_reg_name3[] = {
	"STAT_0_0",
	"STAT_0_1",
	"STAT_0_2",
	"STAT_0_3",
	"STAT_1_0",
	"STAT_1_1",
	"STAT_1_2"
};

static int dump_cec_reg_show(struct seq_file *s, void *p)
{
	unsigned int reg32;
	int i = 0;
	unsigned char reg;

	seq_puts(s, "\n--------CEC registers--------\n");

#ifndef CONFIG_AMLOGIC_ZAPPER_CUT
	if (cec_dev->plat_data->chip_id == CEC_CHIP_A1) {
		seq_printf(s, "[%2x]:%2x\n", 0xe4, read_periphs(0xe4));
		seq_printf(s, "[%2x]:%2x\n", 0xe8, read_periphs(0xe8));
		seq_printf(s, "[%2x]:%2x\n", 0xec, read_periphs(0xec));
		seq_printf(s, "[%2x]:%2x\n", 0xf0, read_periphs(0xf0));
	} else
#endif
	if (cec_dev->plat_data->chip_id == CEC_CHIP_SC2 ||
		   cec_dev->plat_data->chip_id == CEC_CHIP_S4) {
		seq_printf(s, "[%2x]:%2x\n", 0x22, read_clock(0x22));
		seq_printf(s, "[%2x]:%2x\n", 0x23, read_clock(0x23));
		seq_printf(s, "[%2x]:%2x\n", 0x24, read_clock(0x24));
		seq_printf(s, "[%2x]:%2x\n", 0x25, read_clock(0x25));
	}

	if (ee_cec == CEC_B) {
		reg32 = read_ao(AO_CECB_CLK_CNTL_REG0);
		seq_printf(s, "AO_CECB_CLK_CNTL_REG0:0x%08x\n", reg32);
		reg32 = read_ao(AO_CECB_CLK_CNTL_REG1);
		seq_printf(s, "AO_CECB_CLK_CNTL_REG1:0x%08x\n", reg32);
		reg32 = read_ao(AO_CECB_GEN_CNTL);
		seq_printf(s, "AO_CECB_GEN_CNTL:0x%08x\n", reg32);
		reg32 = read_ao(AO_CECB_RW_REG);
		seq_printf(s, "AO_CECB_RW_REG: 0x%08x\n", reg32);
		reg32 = read_ao(AO_CECB_INTR_MASKN);
		seq_printf(s, "AO_CECB_INTR_MASKN:0x%08x\n", reg32);
		reg32 = read_ao(AO_CECB_INTR_STAT);
		seq_printf(s, "AO_CECB_INTR_STAT: 0x%08x\n", reg32);

		seq_puts(s, "CEC MODULE REGS:\n");
		seq_printf(s, "CEC_CTRL = 0x%02x\n",
			hdmirx_cec_read(DWC_CEC_CTRL));
		if (cec_dev->plat_data->cecb_ver >= CECB_VER_2)
			seq_printf(s, "CEC_CTRL2 = 0x%02x\n",
				hdmirx_cec_read(DWC_CEC_CTRL2));
		seq_printf(s, "CEC_MASK = 0x%02x\n",
			hdmirx_cec_read(DWC_CEC_MASK));
		seq_printf(s, "CEC_ADDR_L = 0x%02x\n",
			hdmirx_cec_read(DWC_CEC_ADDR_L));
		seq_printf(s, "CEC_ADDR_H = 0x%02x\n",
			hdmirx_cec_read(DWC_CEC_ADDR_H));
		seq_printf(s, "CEC_TX_CNT = 0x%02x\n",
			hdmirx_cec_read(DWC_CEC_TX_CNT));
		seq_printf(s, "CEC_RX_CNT = 0x%02x\n",
			hdmirx_cec_read(DWC_CEC_RX_CNT));
		if (cec_dev->plat_data->cecb_ver >= CECB_VER_2)
			seq_printf(s, "CEC_STAT0 = 0x%02x\n",
				hdmirx_cec_read(DWC_CEC_STAT0));
		seq_printf(s, "CEC_LOCK	= 0x%02x\n",
			hdmirx_cec_read(DWC_CEC_LOCK));
		seq_printf(s, "CEC_WKUPCTRL = 0x%02x\n",
			hdmirx_cec_read(DWC_CEC_WKUPCTRL));

		seq_printf(s, "%s", "RX buffer:");
		for (i = 0; i < 16; i++) {
			reg = hdmirx_cec_read(DWC_CEC_RX_DATA0 + i * 4) & 0xff;
			seq_printf(s, " %02x", reg);
		}
		seq_puts(s, "\n");

		seq_printf(s, "%s", "TX buffer:");
		for (i = 0; i < 16; i++) {
			reg = hdmirx_cec_read(DWC_CEC_TX_DATA0 + i * 4) & 0xff;
			seq_printf(s, " %02x", reg);
		}
		seq_puts(s, "\n");
	} else {
		reg32 = read_ao(AO_CEC_CLK_CNTL_REG0);
		seq_printf(s, "AO_CEC_CLK_CNTL_REG0: 0x%08x\n", reg32);
		reg32 = read_ao(AO_CEC_CLK_CNTL_REG1);
		seq_printf(s, "AO_CEC_CLK_CNTL_REG1:0x%08x\n", reg32);
		reg32 = read_ao(AO_CEC_GEN_CNTL);
		seq_printf(s, "AO_CEC_GEN_CNTL:	0x%08x\n", reg32);
		reg32 = read_ao(AO_CEC_RW_REG);
		seq_printf(s, "AO_CEC_RW_REG:	0x%08x\n", reg32);
		reg32 = read_ao(AO_CEC_INTR_MASKN);
		seq_printf(s, "AO_CEC_INTR_MASKN:0x%08x\n",	reg32);
		reg32 = read_ao(AO_CEC_INTR_STAT);
		seq_printf(s, "AO_CEC_INTR_STAT: 0x%08x\n", reg32);
		seq_puts(s, "CEC MODULE REGS:\n");
		for (i = 0; i < ARRAY_SIZE(cec_reg_name1); i++) {
			seq_printf(s, "%s:%2x\n",
				cec_reg_name1[i], aocec_rd_reg(i + 0x10));
		}

		for (i = 0; i < ARRAY_SIZE(cec_reg_name2); i++) {
			seq_printf(s, "%s:%2x\n",
				cec_reg_name2[i], aocec_rd_reg(i + 0x90));
		}

		if (cec_dev->plat_data->ceca_sts_reg) {
			for (i = 0; i < ARRAY_SIZE(ceca_reg_name3); i++) {
				seq_printf(s, "%s:%2x\n",
				 ceca_reg_name3[i], aocec_rd_reg(i + 0xA0));
			}
		}
	}

	seq_puts(s, "\n");
	return 0;
}

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

static const struct file_operations dump_reg_fops = {
	.open		= dump_reg_open,
	.read		= seq_read,
	.release	= single_release,
};

static int dump_cec_status_show(struct seq_file *s, void *p)
{
	struct hdmi_port_info *port;
	unsigned char i = 0;
	unsigned int tmp = 0;

	seq_puts(s, "\n--------CEC status--------\n");
	seq_printf(s, "driver date:%s\n", CEC_DRIVER_VERSION);
	seq_printf(s, "ee_cec:%d\n", ee_cec);
	seq_printf(s, "cec_num:%d\n", cec_dev->cec_num);
	seq_printf(s, "dev_type:%d\n", (unsigned int)cec_dev->dev_type);
	seq_printf(s, "wk_logic_addr:0x%x\n", cec_dev->wakeup_data.wk_logic_addr);
	seq_printf(s, "wk_phy_addr:0x%x\n", cec_dev->wakeup_data.wk_phy_addr);
	seq_printf(s, "wk_port_id:0x%x\n", cec_dev->wakeup_data.wk_port_id);
	seq_printf(s, "wakeup_reason:0x%x\n", cec_dev->wakeup_reason);
	seq_printf(s, "phy_addr:0x%x\n", cec_dev->phy_addr);
	seq_printf(s, "cec_version:0x%x\n", cec_dev->cec_info.cec_version);
	seq_printf(s, "hal_ctl:0x%x\n", cec_dev->cec_info.hal_ctl);
	seq_printf(s, "menu_lang:0x%x\n", cec_dev->cec_info.menu_lang);
	seq_printf(s, "menu_status:0x%x\n", cec_dev->cec_info.menu_status);
	seq_printf(s, "open_count:%d\n", cec_dev->cec_info.open_count.counter);
	seq_printf(s, "vendor_id:0x%x\n", cec_dev->cec_info.vendor_id);
	seq_printf(s, "port_num:0x%x\n", cec_dev->port_num);
	seq_printf(s, "output:0x%x\n", cec_dev->output);
	seq_printf(s, "arc_port:0x%x\n", cec_dev->arc_port);
	seq_printf(s, "hal_flag:0x%x\n", cec_dev->hal_flag);
	seq_printf(s, "hpd_state:0x%x\n", get_hpd_state());
	seq_printf(s, "cec_config:0x%x\n", cec_config(0, 0));
	seq_printf(s, "log_addr:0x%x\n", cec_dev->cec_info.log_addr);

	seq_printf(s, "id:0x%x\n", cec_dev->plat_data->chip_id);
	seq_printf(s, "ceca_ver:0x%x\n", cec_dev->plat_data->ceca_ver);
	seq_printf(s, "cecb_ver:0x%x\n", cec_dev->plat_data->cecb_ver);
	seq_printf(s, "share_io:0x%x\n", cec_dev->plat_data->share_io);
	seq_printf(s, "line_bit:0x%x\n", cec_dev->plat_data->line_bit);
	seq_printf(s, "ee_to_ao:0x%x\n", cec_dev->plat_data->ee_to_ao);
	seq_printf(s, "irq_ceca:0x%x\n", cec_dev->irq_ceca);
	seq_printf(s, "irq_cecb:0x%x\n", cec_dev->irq_cecb);
	seq_printf(s, "framework_on:0x%x\n", cec_dev->framework_on);
	seq_printf(s, "store msg_num:0x%x\n", cec_dev->msg_num);
	seq_printf(s, "store msg_idx:0x%x\n", cec_dev->msg_idx);

	seq_printf(s, "cec_msg_dbg_en: %d\n", cec_msg_dbg_en);
	seq_printf(s, "port_seq: %x\n", cec_dev->port_seq);
	seq_printf(s, "cec_log_en: %x\n", cec_dev->cec_log_en);

	port = kcalloc(cec_dev->port_num, sizeof(*port), GFP_KERNEL);
	if (port) {
		init_cec_port_info(port, cec_dev);
		for (i = 0; i < cec_dev->port_num; i++) {
			/* port_id: 1/2/3 means HDMIRX1/2/3, 0 means HDMITX port */
			seq_printf(s, "port_id: %d, ",
					port[i].port_id);
			seq_printf(s, "port_type: %s, ",
					port[i].type == HDMI_INPUT ?
					"hdmirx" : "hdmitx");
			seq_printf(s, "physical_address: %x, ",
					port[i].physical_address);
			seq_printf(s, "cec_supported: %s, ",
					port[i].cec_supported ? "true" : "false");
			seq_printf(s, "arc_supported: %s\n",
					port[i].arc_supported ? "true" : "false");
		}
		kfree(port);
	}

#if (defined(CONFIG_AMLOGIC_HDMITX) || defined(CONFIG_AMLOGIC_HDMITX21))
	tmp |= (get_hpd_state() << 4);
#endif
#ifdef CONFIG_AMLOGIC_MEDIA_TVIN_HDMI
	tmp |= (hdmirx_get_connect_info() & 0xF);
#endif
	seq_printf(s, "hdmitx/rx connect status: 0x%x\n", tmp);

	if (cec_dev->cec_num > ENABLE_ONE_CEC) {
		seq_printf(s, "B addrL 0x%x\n", hdmirx_cec_read(DWC_CEC_ADDR_L));
		seq_printf(s, "B addrH 0x%x\n", hdmirx_cec_read(DWC_CEC_ADDR_H));

		seq_printf(s, "A addr0 0x%x\n", aocec_rd_reg(CEC_LOGICAL_ADDR0));
		seq_printf(s, "A addr1 0x%x\n", aocec_rd_reg(CEC_LOGICAL_ADDR1));
		/*seq_printf(s, "addr2 0x%x\n", aocec_rd_reg(CEC_LOGICAL_ADDR2));*/
		/*seq_printf(s, "addr3 0x%x\n", aocec_rd_reg(CEC_LOGICAL_ADDR3));*/
		/*seq_printf(s, "addr4 0x%x\n", aocec_rd_reg(CEC_LOGICAL_ADDR4));*/
	} else {
		if (ee_cec == CEC_B) {
			seq_printf(s, "addrL 0x%x\n",
				hdmirx_cec_read(DWC_CEC_ADDR_L));
			seq_printf(s, "addrH 0x%x\n",
				hdmirx_cec_read(DWC_CEC_ADDR_H));
		} else {
			seq_printf(s, "addr0 0x%x\n",
				aocec_rd_reg(CEC_LOGICAL_ADDR0));
			seq_printf(s, "addr1 0x%x\n",
				aocec_rd_reg(CEC_LOGICAL_ADDR1));
			seq_printf(s, "addr2 0x%x\n",
				aocec_rd_reg(CEC_LOGICAL_ADDR2));
			seq_printf(s, "addr3 0x%x\n",
				aocec_rd_reg(CEC_LOGICAL_ADDR3));
			seq_printf(s, "addr4 0x%x\n",
				aocec_rd_reg(CEC_LOGICAL_ADDR4));
		}
	}
	seq_printf(s, "addr_enable:0x%x\n", cec_dev->cec_info.addr_enable);
	seq_printf(s, "chk_sig_free_time: %d\n", cec_dev->chk_sig_free_time);
	seq_printf(s, "sw_chk_bus: %d\n", cec_dev->sw_chk_bus);

	seq_puts(s, "\n");
	return 0;
}

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

static const struct file_operations dump_status_fops = {
	.open		= dump_status_open,
	.read		= seq_read,
	.release	= single_release,
};

struct cec_dbg_files_s {
	const char *name;
	const umode_t mode;
	const struct file_operations *fops;
};

static struct cec_dbg_files_s cec_dbg_files[] = {
	{"cec_reg", S_IFREG | 0444, &dump_reg_fops},
	{"cec_status", S_IFREG | 0444, &dump_status_fops},
};

static struct dentry *cec_dbg_fs;
void cec_debug_fs_init(void)
{
	struct dentry *entry;
	int i;

	if (cec_dbg_fs)
		return;

	cec_dbg_fs = debugfs_create_dir(DEVICE_NAME, NULL);
	if (!cec_dbg_fs) {
		pr_err("can't create %s debugfs dir\n", DEVICE_NAME);
		return;
	}

	for (i = 0; i < ARRAY_SIZE(cec_dbg_files); i++) {
		entry = debugfs_create_file(cec_dbg_files[i].name,
			cec_dbg_files[i].mode,
			cec_dbg_fs, NULL,
			cec_dbg_files[i].fops);
		if (!entry)
			pr_err("debugfs create file %s failed\n",
				cec_dbg_files[i].name);
	}
}

