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

#include <linux/version.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/mm.h>
#include <linux/major.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/arm-smccc.h>

#include "common.h"

static int hdmi_dbg;

struct reg_map reg21_maps[REG_IDX_END] = {0};
int hdmitx21_init_reg_map(struct platform_device *pdev)
{
	int i = 0;
	struct resource res;
	struct device_node *np = pdev->dev.of_node;

	for (i = 0; i < REG_IDX_END; i++) {
		if (of_address_to_resource(np, i, &res)) {
			HDMITX_ERROR("not get regbase index %d\n", i);
			return 0;
		}

		reg21_maps[i].phy_addr = res.start;
		reg21_maps[i].size = resource_size(&res);
		reg21_maps[i].p = devm_ioremap(&pdev->dev, res.start,
					     resource_size(&res));
		if (IS_ERR(reg21_maps[i].p))
			return -ENOMEM;

		pr_debug("Mapped PHY: 0x%x\n", reg21_maps[i].phy_addr);
	}

	return 0;
}

u32 get21_hdcp22_base(void)
{
	return 0;
}

inline bool cor_reg_addr_mask(u32 addr)
{
	struct hdmitx_dev *hdev = get_hdmitx21_device();

	addr = addr & 0xffff;
	if (hdev->tx_hw.chip_data->chip_type == MESON_CPU_ID_S1A)
		if ((addr >= 0x0330 && addr <= 0x03ff) ||
			(addr >= 0x0800 && addr <= 0x08ff) ||
			(addr >= 0x0940 && addr <= 0x09ff) ||
			//(addr >= 0x0b00 && addr <= 0x0dff) ||
			(addr >= 0x0f00 && addr <= 0x0fff))
			return 1;

	return 0;
}

static void sec_wr(u32 addr, u32 data)
{
	struct arm_smccc_res res;

	if (hdmi_dbg)
		HDMITX_INFO("sec_wr32[0x%08x] 0x%08x\n", addr, data);
	arm_smccc_smc(0x82000019, (unsigned long)addr, data, 32, 0, 0, 0, 0, &res);
}

static void sec_wr8(u32 addr, u8 data)
{
	struct arm_smccc_res res;

	if (hdmi_dbg)
		HDMITX_INFO("%s[0x%08x] 0x%02x\n", __func__, addr, data);
	arm_smccc_smc(0x82000019, (unsigned long)addr, data & 0xff, 8, 0, 0, 0, 0, &res);
}

static u32 sec_rd(u32 addr)
{
	u32 data;
	struct arm_smccc_res res;

	if (hdmi_dbg)
		HDMITX_INFO("sec_rd32[0x%08x]\n", addr);
	arm_smccc_smc(0x82000018, (unsigned long)addr, 32, 0, 0, 0, 0, 0, &res);
	data = (unsigned int)((res.a0) & 0xffffffff);

	if (hdmi_dbg)
		HDMITX_INFO("[0x%08x] 0x%08x\n", addr, data);
	return data;
}

static u8 sec_rd8(u32 addr)
{
	u32 data;
	struct arm_smccc_res res;

	if (hdmi_dbg)
		HDMITX_INFO("%s[0x%08x]\n", __func__, addr);
	arm_smccc_smc(0x82000018, (unsigned long)addr, 8, 0, 0, 0, 0, 0, &res);
	data = (unsigned int)((res.a0) & 0xffffffff);

	if (hdmi_dbg)
		HDMITX_INFO("[0x%08x] 0x%02x\n", addr, data);
	return data;
}

u32 TO21_PHY_ADDR(u32 addr)
{
	u32 index;
	u32 offset;

	index = addr >> BASE_REG_OFFSET;
	offset = addr & (((1 << BASE_REG_OFFSET) - 1));

	return (reg21_maps[index].phy_addr + offset);
}

static void __iomem *TO_PMAP_ADDR(u32 addr)
{
	u32 index;
	u32 offset;

	index = addr >> BASE_REG_OFFSET;
	offset = addr & (((1 << BASE_REG_OFFSET) - 1));

	return (void __iomem *)(reg21_maps[index].p + offset);
}

static u32 get_enc_paddr(unsigned int addr)
{
	struct hdmitx_dev *hdev = get_hdmitx21_device();
	unsigned int idx = addr >> BASE_REG_OFFSET;
	unsigned int offset = (addr & 0xffff) >> 2;

	if (hdev->tx_comm.enc_idx == 2 && idx == VPUCTRL_REG_IDX) {
		if (offset >= 0x1b00 && offset < 0x1d00)
			return addr + (0x800 << 2);
	}
	return addr;
}

u32 hd21_read_reg(u32 vaddr)
{
	u32 val;
	u32 paddr = get_enc_paddr(vaddr);

	val = readl(TO_PMAP_ADDR(paddr));
	if (hdmi_dbg)
		HDMITX_INFO("Rd32[0x%08x] 0x%08x\n", TO21_PHY_ADDR(paddr), val);
	return val;
}
EXPORT_SYMBOL(hd21_read_reg);

void hd21_write_reg(u32 vaddr, u32 val)
{
	u32 rval;
	u32 paddr = get_enc_paddr(vaddr);

	writel(val, TO_PMAP_ADDR(paddr));
	rval = readl(TO_PMAP_ADDR(paddr));
	if (!hdmi_dbg)
		return;
	if (val != rval)
		HDMITX_INFO("Wr32[0x%08x] 0x%08x != Rd32 0x%08x\n",
			TO21_PHY_ADDR(paddr), val, rval);
	else
		HDMITX_INFO("Wr32[0x%08x] 0x%08x\n",
			TO21_PHY_ADDR(paddr), val);
}
EXPORT_SYMBOL(hd21_write_reg);

void hd21_set_reg_bits(u32 addr, u32 value,
		     u32 offset, u32 len)
{
	u32 data32 = 0;

	data32 = hd21_read_reg(addr);
	data32 &= ~(((1 << len) - 1) << offset);
	data32 |= (value & ((1 << len) - 1)) << offset;
	hd21_write_reg(addr, data32);
}
EXPORT_SYMBOL(hd21_set_reg_bits);

static u32 hdmitx_rd_top(u32 addr)
{
	u32 base_offset;
	u32 data;

	base_offset = reg21_maps[2].phy_addr;

	data = sec_rd(base_offset + addr);
	return data;
} /* hdmitx_rd_top */

static u8 hdmitx_rd_cor(u32 addr)
{
	u32 base_offset;
	u8 data;

	if (cor_reg_addr_mask(addr))
		return 0;

	base_offset = reg21_maps[1].phy_addr;
	data = sec_rd8(base_offset + addr);
	return data;
} /* hdmitx_rd_cor */

static void hdmitx_wr_top(u32 addr, u32 data)
{
	u32 base_offset;

	base_offset = reg21_maps[2].phy_addr;
	sec_wr(base_offset + addr, data);
} /* hdmitx_wr_top */

static void hdmitx_wr_cor(u32 addr, u8 data)
{
	u32 base_offset;

	if (cor_reg_addr_mask(addr))
		return;

	base_offset = reg21_maps[1].phy_addr;
	sec_wr8(base_offset + addr, data);
} /* hdmitx_wr_cor */

static DEFINE_SPINLOCK(reg_lock);

u32 hdmitx21_rd_reg(u32 addr)
{
	u32 offset;
	u32 data;
	unsigned long flags;

	spin_lock_irqsave(&reg_lock, flags);
	offset = (addr & TOP_OFFSET_MASK) >> 24;

	addr = addr & 0xffff;
	if (offset)
		data = hdmitx_rd_top(addr);
	else
		data = hdmitx_rd_cor(addr);
	spin_unlock_irqrestore(&reg_lock, flags);

	return data;
}

void hdmitx21_wr_reg(u32 addr, u32 val)
{
	unsigned long flags;
	u32 offset;

	spin_lock_irqsave(&reg_lock, flags);
	offset = (addr & TOP_OFFSET_MASK) >> 24;

	addr = addr & 0xffff;
	if (offset) {
		hdmitx_wr_top(addr, val);
	} else {
		/* don't clear hdcp2.2 interrupt if hdcp22 core is under reset */
		if ((addr != CP2TX_INTR0_IVCTX &&
			addr != CP2TX_INTR1_IVCTX &&
			addr != CP2TX_INTR2_IVCTX &&
			addr != CP2TX_INTR3_IVCTX) ||
			!(hdmitx_rd_cor(HDCP2X_TX_SRST_IVCTX) & 0x30))
			hdmitx_wr_cor(addr, val);
	}
	spin_unlock_irqrestore(&reg_lock, flags);
}

bool hdmitx21_get_bit(u32 addr, u32 bit_nr)
{
	return (hdmitx21_rd_reg(addr) & (1 << bit_nr)) == (1 << bit_nr);
}

void hdmitx21_set_reg_bits(u32 addr, u32 value,
			 u32 offset, u32 len)
{
	u32 data32 = 0;

	data32 = hdmitx21_rd_reg(addr);
	data32 &= ~(((1 << len) - 1) << offset);
	data32 |= (value & ((1 << len) - 1)) << offset;
	hdmitx21_wr_reg(addr, data32);
}
EXPORT_SYMBOL(hdmitx21_set_reg_bits);

void hdmitx21_set_bit(u32 addr, u32 bit_val, bool st)
{
	u32 data32 = 0;

	data32 = hdmitx21_rd_reg(addr);
	data32 = st ? (data32 | bit_val) : (data32 & ~bit_val);
	hdmitx21_wr_reg(addr, data32);
}
EXPORT_SYMBOL(hdmitx21_set_bit);

void hdmitx21_poll_reg(u32 addr, u8 exp_data, u8 mask, ulong timeout)
{
	u8 rd_data;
	u8 done = 0;
	ulong time = 0;

	time = jiffies;

	rd_data = hdmitx21_rd_reg(addr);
	while (time_before(jiffies, time + timeout)) {
		if ((rd_data | mask) == (exp_data | mask)) {
			done = 1;
			break;
		}
		rd_data = hdmitx21_rd_reg(addr);
		usleep_range(10, 20);
	}
	if (done == 0)
		HDMITX_INFO("%s 0x%x poll time-out!\n", __func__, addr);
} /* hdmitx21_poll_reg */
EXPORT_SYMBOL(hdmitx21_poll_reg);

void hdmitx21_seq_rd_reg(u16 offset, u8 *buf, u16 cnt)
{
	int i = 0;

	while (cnt--) {
		buf[i] = hdmitx21_rd_reg(offset + i);
		i++;
	}
}
EXPORT_SYMBOL(hdmitx21_seq_rd_reg);

void hdmitx21_fifo_read(u16 offset, u8 *buf, u16 cnt)
{
	int i = 0;

	if (!buf)
		return;
	for (i = 0; i < cnt; i++)
		buf[i] = hdmitx21_rd_reg(offset);
}
EXPORT_SYMBOL(hdmitx21_fifo_read);

MODULE_PARM_DESC(hdmi_dbg, "\n hdmi_dbg\n");
module_param(hdmi_dbg, int, 0644);
