// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2023 MediaTek Inc.
 *
 */

#include <linux/clk.h>
#include <linux/io.h>
#include <linux/time64.h>
#include <linux/of_platform.h>
#include <linux/device.h>
#include <linux/io.h>
#include "emi_plt.h"

extern unsigned int emi_pmu_poll_period_us;
static u32 emi_clock;
/*
 * dramc_dcm_enable - enable/disable dramc.
 */
void dramc_dcm_enable(bool enable)
{
	if (!enable) {
		//disable DCM -> 0x10205060[31:24] = 0xff
		writel((readl(REG_EMI_CONM) | DCM_BITMASK), REG_EMI_CONM);
		writel((readl(REG_EMI_CONN) | DCM_BITMASK), REG_EMI_CONN);
	} else {
		//enable DCM -> 0x10205060[31:24] = 0x00
		writel((readl(REG_EMI_CONM) & ~DCM_BITMASK), REG_EMI_CONM);
		writel((readl(REG_EMI_CONN) & ~DCM_BITMASK), REG_EMI_CONN);
	}
	wmb(); /* make sure settings are written */
}

/*
 * emi_counter_enable - enable/disable emi counter.
 */
void emi_counter_enable(bool enable)
{
	unsigned int value, value_set;

	value = readl(REG_EMI_BMEN);
	if (!enable) {  /* disable monitor circuit */
		//  bit3 =1     bit0 = 0-> clear
		value_set = (value) | (BUS_MON_IDLE);
		writel(value_set, REG_EMI_BMEN);

		value_set = ((value) | (BUS_MON_IDLE)) & ~(BUS_MON_EN);
		writel(value_set, REG_EMI_BMEN);

		value_set = ((value) & ~(BUS_MON_IDLE)) & ~(BUS_MON_EN);
		writel(value_set, REG_EMI_BMEN);
	} else {	/* enable monitor circuit */
		/* clear BM */
		//  bit3 =0     &   bit0=1
		value_set = (value & ~(BUS_MON_IDLE));
		writel(value_set, REG_EMI_BMEN);

		/* enable BM */
		value_set = (value & ~(BUS_MON_IDLE)) | (BUS_MON_EN);
		writel(value_set, REG_EMI_BMEN);
	}
	wmb(); /* make sure settings are written */
}

/*
 * emi_counter_pause - pause/continue emi counter.
 */
void emi_counter_pause(bool enable)
{
	unsigned int value = readl(REG_EMI_BMEN);

	if (enable)   //Pause Counter
		writel(value | BUS_MON_PAUSE, REG_EMI_BMEN);
	else	//Continue Counter
		writel(value & (~BUS_MON_PAUSE), REG_EMI_BMEN);
}

/*
 * emi_port_mon_select - select monitor group ports and rw_mon_type for emi counter.
 */
void emi_port_mon_select(int cfg)
{
	switch (cfg) {
	case EMIBM_TOTAL:
		break;
	case EMIBM_0:
		bw_setbit(DBW0_SEL_MASTER_M0, true, REG_EMI_DBWA);
		bw_setbit(DBW0_SEL_MASTER_M1, true, REG_EMI_DBWA);
		break;
	case EMIBM_1:
		bw_setbit(DBW0_SEL_MASTER_M7, true, REG_EMI_DBWB);
		break;
	case EMIBM_2:
		bw_setbit(DBW0_SEL_MASTER_M2, true, REG_EMI_DBWC);
		bw_setbit(DBW0_SEL_MASTER_M3, true, REG_EMI_DBWC);
		bw_setbit(DBW0_SEL_MASTER_M5, true, REG_EMI_DBWC);
		break;
	case EMIBM_3:
		bw_setbit(DBW0_SEL_MASTER_M4, true, REG_EMI_DBWD);
		break;
	}
}

/*
 * emi_pmu_read_counter - Read the emi counter of each monitor group ports.
 */
u32 emi_pmu_read_counter(int cfg)
{
	u32 value;

	switch (cfg) {
	case EMIBM_TOTAL:
		value = DW2BYTES(readl(REG_EMI_WACT));
		break;
	case EMIBM_0:
		value = DW2BYTES(readl(REG_EMI_WSCT));
		break;
	case EMIBM_1:
		value = DW2BYTES(readl(REG_EMI_WSCT2));
		break;
	case EMIBM_2:
		value = DW2BYTES(readl(REG_EMI_WSCT3));
		break;
	case EMIBM_3:
		value = DW2BYTES(readl(REG_EMI_WSCT4));
		break;
	case EMIBM_4:
		value = readl(REG_EMI_TTYPE_TRANS(0)) + readl(REG_EMI_TTYPE_TRANS(1));
		break;
	case EMIBM_5:
		value = bw_div(((readl(REG_EMI_TTYPE_LAT(0)) + readl(REG_EMI_TTYPE_LAT(1)))),
							emi_clock) * NSEC_PER_USEC;
		break;
	default:
		return WARN_ONCE(1,
			"invalid configuration %d for emi counter", cfg);
	}

	return value;
}

void emi_mon_restart(void) /*use in the polling stage*/
{
	//Disable DCM
	dramc_dcm_enable(false);
	//Disable BM
	emi_counter_enable(false);
	//Start BM
	emi_counter_enable(true);
	//Enable DCM
	dramc_dcm_enable(false);
}

void emi_mon_start(void) /*use in the polling stage*/
{
	emi_counter_enable(true);
}

void emi_mon_clear(void)
{
	/* Clear counter */
	emi_counter_enable(false);

	/* clear monitor setting register */
	writel(0, REG_EMI_BMEN);
	writel(0, REG_EMI_BSTP);
	writel(0, REG_EMI_MSEL);
	writel(0, REG_EMI_MSEL2);
	writel(0, REG_EMI_BMEN2);
	writel(0, REG_EMI_BMID0);
	writel(0, REG_EMI_DBWA);
	writel(0, REG_EMI_DBWB);
	writel(0, REG_EMI_DBWC);
	writel(0, REG_EMI_DBWD);
	writel(0, REG_EMI_DBWE);
	writel(0, REG_EMI_DBWF);
	writel(0, REG_EMI_BMRW0);

	wmb(); /* make sure settings are written */
}

static void emi_bw_measure_init_monitor(void __iomem *base_dbwx,
					void __iomem *base_dbwx_2nd)
{
	u32 reg_temp;

	/* setup monitor setting */
	bw_setbit(DBW0_RCMD_GNT_SEL, true, base_dbwx);
	bw_setbit(DBW0_WCMD_GNT_SEL, true, base_dbwx);

	/* setup monitor all priority transaction */
	bw_setbit(DBW0_HPRI_DIS, true, base_dbwx);

	readl(REG_EMI_CONM);
//	enable_broadcast_mode(false);
	reg_temp = readl(base_dbwx_2nd);
	reg_temp |= (BIT(HPRI_SEL_HIGH)|
		     BIT(HPRI_SEL_FLUSH)|
		     BIT(HPRI_SEL_PREULTRA)|
		     BIT(HPRI_SEL_ULTRA));
//	enable_broadcast_mode(true);

	writel(reg_temp, base_dbwx_2nd);

	wmb(); /* make sure settings are written */
}

bool emi_get_emi_config(void)
{
	struct device_node *np = NULL;

	np = of_find_node_by_name(NULL, "dram_config");
	if (!np)
		return -1;

	if (of_property_read_u32(np, "emi_clock", &emi_clock) < 0)
		return -1;

	return 0;
}

void emi_mon_init(void)
{
	u32 emi_cycle;

	if (emi_get_emi_config())
		pr_err("%s: fail to get emi config!\n", __func__);

	emi_mon_clear();

	emi_bw_measure_init_monitor(REG_EMI_DBWA, REG_EMI_DBWA_2ND);
	emi_bw_measure_init_monitor(REG_EMI_DBWB, REG_EMI_DBWB_2ND);
	emi_bw_measure_init_monitor(REG_EMI_DBWC, REG_EMI_DBWC_2ND);
	emi_bw_measure_init_monitor(REG_EMI_DBWD, REG_EMI_DBWD_2ND);
	emi_bw_measure_init_monitor(REG_EMI_DBWE, REG_EMI_DBWE_2ND);
	emi_bw_measure_init_monitor(REG_EMI_DBWF, REG_EMI_DBWF_2ND);

	/* monitor read and write transaction for latency */
	writel(LOW_32BIT, REG_EMI_BMRW0);

	/* enable latency monitor */
	bw_setbit(BMEN_LAT_CNT_SEL, true, REG_EMI_BMEN2);

	/* disable DCM */
	dramc_dcm_enable(false);

	/* count emi cycle */
	emi_cycle = emi_pmu_poll_period_us * emi_clock;

	/* setup monitor setting */
	writel(emi_cycle, REG_EMI_BSTP);

	wmb(); /* make sure settings are written */
}

