// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2020 MediaTek Inc.
 *
 */



#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/kobject.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/clk.h>


#include "hdmirx_dolby_md.h"

struct hdmirx_dolby_md *global_dolby_md;
unsigned char hdmirx_dolby_md_on;


inline bool dolby_md_readbit(struct hdmirx_dolby_md *dolby_meta,
	unsigned short reg, unsigned int offset)
{
	return (readl(dolby_meta->dobly_md_base + reg) & offset) ? true : false;
}

inline unsigned int dolby_md_read(
	struct hdmirx_dolby_md *dolby_meta, unsigned short reg)
{
	return readl(dolby_meta->dobly_md_base + reg);
}

inline void dolby_md_write(struct hdmirx_dolby_md *dolby_meta,
	unsigned short reg, unsigned int val)
{
	writel(val, dolby_meta->dobly_md_base + reg);
}

inline void dolby_md_mask(struct hdmirx_dolby_md *dolby_meta,
	unsigned int reg, unsigned int val, unsigned int mask)
{
	unsigned int tmp;

	tmp = readl(dolby_meta->dobly_md_base + reg) & ~mask;
	tmp |= (val & mask);
	writel(tmp, dolby_meta->dobly_md_base + reg);
}

inline void mtk_rx_dolby_meta_regops_probe(
	struct hdmirx_dolby_md *dolby_meta)
{
	dolby_meta->dolby_md_readbit = dolby_md_readbit;
	dolby_meta->dolby_md_read = dolby_md_read;
	dolby_meta->dolby_md_write = dolby_md_write;
	dolby_meta->dolby_md_mask = dolby_md_mask;
}

inline void mtk_rx_dolby_meta_irq_clr_all(
	struct hdmirx_dolby_md *dolby_md)
{
	dolby_md->dolby_md_mask(dolby_md, DOLBY_INT_CTRL,
		MD_INT_ALL_CLR, MD_INT_ALL_CLR);
	dolby_md->dolby_md_mask(dolby_md, DOLBY_INT_CTRL,
		0, MD_INT_ALL_CLR);
}

inline void mtk_rx_dolby_meta_irq_mask_all(
	struct hdmirx_dolby_md *dolby_md, bool en)
{
	if (en == true)
		dolby_md->dolby_md_mask(dolby_md, DOLBY_INT_CTRL,
			MD_INT_ALL_MASK, MD_INT_ALL_MASK);
	else
		dolby_md->dolby_md_mask(dolby_md, DOLBY_INT_CTRL,
			0, MD_INT_ALL_MASK);
}

void mtk_rx_dolby_meta_clk_enable(
	struct hdmirx_dolby_md *dolby_meta, unsigned char en)
{
	static bool dolby_meta_clk_is_off = true;

	if (en && dolby_meta_clk_is_off) {
		clk_prepare_enable(dolby_meta->clock);
		dolby_meta_clk_is_off = false;
	} else if ((en == false) && (dolby_meta_clk_is_off == false)) {
		clk_disable_unprepare(dolby_meta->clock);
		dolby_meta_clk_is_off = true;
	} else
		DOLYB_MD_DEF_LOG("dolby md clk warning, en=%d,state=%d\n",
			en, dolby_meta_clk_is_off);
}

void mtk_rx_dolby_meta_clk_probe(
	struct hdmirx_dolby_md *dolby_metadata)
{
	dolby_metadata->clock = devm_clk_get(&dolby_metadata->pdev->dev,
		"hdmirx_dolby_md");
	WARN_ON(IS_ERR(dolby_metadata->clock));
	mtk_rx_dolby_meta_clk_enable(dolby_metadata, true);
}

inline void mtk_rx_dolby_meta_mon_sel(
	struct hdmirx_dolby_md *dolby_metadata, bool en)
{
/*true for Philips monitor, false for dolby metadata monitor*/
	if (en == true)
		dolby_metadata->dolby_md_mask(dolby_metadata, DOLBY_TOP_CTRL,
			REG_PHILIPS_MD_MON_SEL, REG_PHILIPS_MD_MON_SEL);
	else
		dolby_metadata->dolby_md_mask(dolby_metadata, DOLBY_TOP_CTRL,
			0, REG_PHILIPS_MD_MON_SEL);
}

inline void mtk_rx_dolby_meta_rst_md(
	struct hdmirx_dolby_md *dolby_metadata)
{
	dolby_metadata->dolby_md_mask(dolby_metadata,
		DOLBY_TOP_CTRL, RESET_MD_DATA, RESET_MD_DATA);
	dolby_metadata->dolby_md_mask(dolby_metadata,
		DOLBY_TOP_CTRL, 0, RESET_MD_DATA);
}

inline void mtk_rx_dolby_meta_wr_en(
	struct hdmirx_dolby_md *dolby_metadata, bool en)
{
	if (en == true)
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_TOP_CTRL, MD_WR_EN, MD_WR_EN);
	else
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_TOP_CTRL, 0, MD_WR_EN);
}

inline void mtk_rx_dolby_meta_rst_pclk(
	struct hdmirx_dolby_md *dolby_metadata)
{
	dolby_metadata->dolby_md_mask(dolby_metadata,
		DOLBY_TOP_CTRL, PCLK_RST, PCLK_RST);
	dolby_metadata->dolby_md_mask(dolby_metadata,
		DOLBY_TOP_CTRL, 0, PCLK_RST);
}

inline void mtk_rx_dolby_meta_pclk_en(
	struct hdmirx_dolby_md *dolby_metadata, bool en)
{
	if (en == true)
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_TOP_CTRL, PCLK_EN, PCLK_EN);
	else
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_TOP_CTRL, 0, PCLK_EN);
}

inline void mtk_rx_dolby_meta_hsync_pol(
	struct hdmirx_dolby_md *dolby_metadata, bool positive)
{
/*true for hsync Postive, false for hsync Negative*/
	if (positive == true)
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_VID_CTRL, 0, HSYNC_POL);
	else
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_VID_CTRL, HSYNC_POL, HSYNC_POL);
}

inline void mtk_rx_dolby_meta_vsync_pol(
	struct hdmirx_dolby_md *dolby_metadata, bool positive)
{
/*true for vsync Postive, false for vsync Negative*/
	if (positive == true)
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_VID_CTRL, 0, VSYNC_POL);
	else
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_VID_CTRL, VSYNC_POL, VSYNC_POL);
}

inline void mtk_rx_dolby_meta_rgb_inv(
	struct hdmirx_dolby_md *dolby_metadata, bool bgr)
{
/*true for [B,G,R], false for [R,G,B]*/
	if (bgr == true)
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_VID_CTRL, RGB_INV, RGB_INV);
	else
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_VID_CTRL, 0, RGB_INV);
}

inline void mtk_rx_dolby_meta_YUV422(
	struct hdmirx_dolby_md *dolby_metadata, bool yuv)
{
/*true for YUV422 input, false for not YUV422*/
	if (yuv == true)
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_VID_CTRL, YUV_422_IN, YUV_422_IN);
	else
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_VID_CTRL, 0, YUV_422_IN);
}

inline void mtk_rx_dolby_meta_bit_inv(
	struct hdmirx_dolby_md *dolby_metadata, bool inv)
{
/*true for data bit invert, false for not invert*/
	if (inv == true)
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_DES_CTRL, DATA_BIT_INV, DATA_BIT_INV);
	else
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_DES_CTRL, 0, DATA_BIT_INV);
}

inline void mtk_rx_dolby_meta_CRC_force_ok(
	struct hdmirx_dolby_md *dolby_metadata, bool force)
{
/*true for force ok*/
	if (force == true)
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_DES_CTRL, CRC_FORCE_OK, CRC_FORCE_OK);
	else
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_DES_CTRL, 0, CRC_FORCE_OK);
}

inline void mtk_rx_dolby_meta_force_SDR(
	struct hdmirx_dolby_md *dolby_metadata, bool force)
{
/*true for force SDR*/
	if (force == true)
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_DES_CTRL, SDR_FORCE, SDR_FORCE);
	else
		dolby_metadata->dolby_md_mask(dolby_metadata,
			DOLBY_DES_CTRL, 0, SDR_FORCE);
}

void hdmirx_dolby_meta_isrprocess(struct hdmirx_dolby_md *dolby_md)
{
	unsigned int reg;

	unsigned int int_status = dolby_md->dolby_md_read(
		dolby_md, DOLBY_INT_STA);

	DOLYB_MD_DEF_LOG("Dolby MD int status = %x\n", int_status);

	DOLYB_MD_DEF_LOG("MD_RCVD_INT_STA %c\n",
		dolby_md->dolby_md_readbit(dolby_md,
		DOLBY_INT_STA, MD_RCVD_INT_STA));
	DOLYB_MD_DEF_LOG("FIRST_BLOCK_CORRECT_STA %c\n",
		dolby_md->dolby_md_readbit(dolby_md,
		DOLBY_INT_STA, FIRST_BLOCK_CORRECT_STA));
	DOLYB_MD_DEF_LOG("SECOND_BLOCK_CORRECT_STA %c\n",
		dolby_md->dolby_md_readbit(dolby_md,
		DOLBY_INT_STA, SECOND_BLOCK_CORRECT_STA));
	DOLYB_MD_DEF_LOG("CRC_ERROR_INT_STA %c\n",
		dolby_md->dolby_md_readbit(dolby_md,
		DOLBY_INT_STA, CRC_ERROR_INT_STA));
	DOLYB_MD_DEF_LOG("MD_OK_INT_STA %c\n",
		dolby_md->dolby_md_readbit(dolby_md,
		DOLBY_INT_STA, MD_OK_INT_STA));
	DOLYB_MD_DEF_LOG("VSYNC_INT_STA %c\n",
		dolby_md->dolby_md_readbit(dolby_md,
		DOLBY_INT_STA, VSYNC_INT_STA));

	if (dolby_md->dolby_md_readbit(dolby_md,
		DOLBY_INT_STA, MD_OK_INT_STA) == true) {
		DOLYB_MD_DEF_LOG("MetaData OK !\n");

		DOLYB_MD_DEF_LOG("PKT00\n");
		pr_info("0x14017020 = %x\n0x14017024 = %x\n",
			dolby_md->dolby_md_read(dolby_md, DOLBY_PKT00_HEADER),
			dolby_md->dolby_md_read(dolby_md, DOLBY_PKT00_LENGTH));
		for (reg = DOLBY_PKT00_BODY00;
			reg <= DOLBY_PKT00_BODY29;
			reg = reg + 4)
			pr_info("0x%x = %x\n", 0x14017000 + reg,
				dolby_md->dolby_md_read(dolby_md, reg));

		DOLYB_MD_DEF_LOG("PKT01\n");
		for (reg = DOLBY_PKT01_HEADER;
			reg <= DOLBY_PKT01_BODY30;
			reg = reg + 4)
			pr_info("0x%x = %x\n", 0x14017000 + reg,
				dolby_md->dolby_md_read(dolby_md, reg));

		DOLYB_MD_DEF_LOG("PKT10\n");
		for (reg = DOLBY_PKT10_HEADER;
			reg <= DOLBY_PKT10_BODY30;
			reg = reg + 4)
			pr_info("0x%x = %x\n", 0x14017000 + reg,
				dolby_md->dolby_md_read(dolby_md, reg));

		DOLYB_MD_DEF_LOG("PKT11\n");
		for (reg = DOLBY_PKT11_HEADER;
			reg <= DOLBY_PKT11_BODY30;
			reg = reg + 4)
			pr_info("0x%x = %x\n", 0x14017000 + reg,
				dolby_md->dolby_md_read(dolby_md, reg));


		mtk_rx_dolby_meta_rst_md(dolby_md);
	}
}

static irqreturn_t hdmirx_dolby_meta_isr(int irq, void *dev_id)
{
	struct hdmirx_dolby_md *dolby_md = global_dolby_md;

	DOLYB_MD_DEF_LOG("%s()\n", __func__);

	mtk_rx_dolby_meta_irq_mask_all(dolby_md, false);

	if (hdmirx_dolby_md_on == 1)
		hdmirx_dolby_meta_isrprocess(dolby_md);

	mtk_rx_dolby_meta_irq_clr_all(dolby_md);
	mtk_rx_dolby_meta_irq_mask_all(dolby_md, true);

	return IRQ_HANDLED;
}

void mtk_rx_dolby_meta_init(struct hdmirx_dolby_md *dolby_metadata)
{
	DOLYB_MD_DEF_LOG("%s()\n", __func__);

	mtk_rx_dolby_meta_mon_sel(dolby_metadata, false);
	mtk_rx_dolby_meta_pclk_en(dolby_metadata, true);
	mtk_rx_dolby_meta_rst_pclk(dolby_metadata);
	mtk_rx_dolby_meta_wr_en(dolby_metadata, true);
	mtk_rx_dolby_meta_hsync_pol(dolby_metadata, true);
	mtk_rx_dolby_meta_vsync_pol(dolby_metadata, true);
	mtk_rx_dolby_meta_rgb_inv(dolby_metadata, false);
	mtk_rx_dolby_meta_YUV422(dolby_metadata, false);
	mtk_rx_dolby_meta_bit_inv(dolby_metadata, false);
	mtk_rx_dolby_meta_CRC_force_ok(dolby_metadata, false);
	mtk_rx_dolby_meta_force_SDR(dolby_metadata, false);

	mtk_rx_dolby_meta_rst_md(dolby_metadata);

	mtk_rx_dolby_meta_irq_clr_all(dolby_metadata);
	mtk_rx_dolby_meta_irq_mask_all(dolby_metadata, true);

	hdmirx_dolby_md_on = 1;

}

int mtk_hdmirx_dolby_meta_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device_node *of_node = dev->of_node;
	struct hdmirx_dolby_md *dolby_metadata;
	unsigned int irq_type;
	struct MTK_HDMI *hdmirx;

	dolby_metadata = devm_kzalloc(dev, sizeof(struct hdmirx_dolby_md),
		GFP_KERNEL);
	if (!dolby_metadata)
		return -ENOMEM;

	global_dolby_md = dolby_metadata;
	dolby_metadata->pdev = pdev;

	dolby_metadata->dobly_md_base = of_iomap(of_node, 8);
	if (dolby_metadata->dobly_md_base == NULL) {
		DOLYB_MD_DEF_LOG("Unable to ioremap hdmiRx dolby md reg\n");
		return -ENOMEM;
	}

	hdmirx = platform_get_drvdata(pdev);
	dolby_metadata->hdmirx = hdmirx;

	mtk_rx_dolby_meta_regops_probe(dolby_metadata);
	mtk_rx_dolby_meta_clk_probe(dolby_metadata);

	dolby_metadata->irq = irq_of_parse_and_map(of_node, 1);
	irq_type = irq_get_trigger_type(dolby_metadata->irq);
	if (request_irq(dolby_metadata->irq, hdmirx_dolby_meta_isr,
		irq_type, "hdmirx_dolby_md_irq", dolby_metadata) < 0)
		DOLYB_MD_DEF_LOG("request hdmiRx dolby md int failed.\n");
	else
		DOLYB_MD_DEF_LOG("request hdmiRx dolby md int success\n");

	mtk_rx_dolby_meta_init(dolby_metadata);

	return 0;
}
