// SPDX-License-Identifier: GPL-2.0
/**
 *
 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
 *        http://www.samsung.com
 *
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2  of
 * the License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

//#include <lk/reg.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/io.h>
#include "phy-samsung-usb-cal.h"

#include "exynos-usb-blkcon-sfr.h"

static void ready_rewa(struct exynos_usbphy_info *cal_info);

void exynos_usbcon_init_link(struct exynos_usbphy_info *cal_info)
{
	void __iomem *regs_base;
	u32 reg;

	regs_base = cal_info->regs_base;

	/* LINKCTRL
	 * 1. Disable q-channel
	 * 2. Bypass debounce filter for vbus, bvalid and id
	 * 3. sel_sof for SOF tick when LPM L1
	 */
	reg = readl(regs_base + USBCON_REG_LINKCTRL);
	((USBCON_REG_LINKCTRL_p) (&reg))->b.dis_id0_qact = 1;
	((USBCON_REG_LINKCTRL_p) (&reg))->b.dis_bvalid_qact = 1;
	((USBCON_REG_LINKCTRL_p) (&reg))->b.dis_vbusvalid_qact = 1;
	((USBCON_REG_LINKCTRL_p) (&reg))->b.dis_linkgate_qact = 1;
	((USBCON_REG_LINKCTRL_p) (&reg))->b.force_qact = 0;
	udelay(500);
	writel(reg, regs_base + USBCON_REG_LINKCTRL);
	udelay(500);
	((USBCON_REG_LINKCTRL_p) (&reg))->b.force_qact = 1;
	((USBCON_REG_LINKCTRL_p) (&reg))->b.bus_filter_bypass = 0xf;
	((USBCON_REG_LINKCTRL_p) (&reg))->b.sel_sof = 0x1;
	writel(reg, regs_base + USBCON_REG_LINKCTRL);

	pr_info("%s %d USBCON_REG_LINKCTRL: 0x %08x\n", __func__, __LINE__, readl(regs_base + USBCON_REG_LINKCTRL));

	/* Reset Link */
	reg = readl(regs_base + USBCON_REG_LINK_CLKRST);
	((USBCON_REG_LINK_CLKRST_p) (&reg))->b.link_sw_rst = 1;
	writel(reg, regs_base + USBCON_REG_LINK_CLKRST);
	udelay(10);
	((USBCON_REG_LINK_CLKRST_p) (&reg))->b.link_sw_rst = 0;
	writel(reg, regs_base + USBCON_REG_LINK_CLKRST);

	/* UTMI_CTRL
	 * 1. Set high bvalid and vbus valid  */
	reg = readl(regs_base + USBCON_REG_UTMI_CTRL);
	((USBCON_REG_UTMI_CTRL_p) (&reg))->b.force_bvalid = 1;
	((USBCON_REG_UTMI_CTRL_p) (&reg))->b.force_vbusvalid = 1;
	writel(reg, regs_base + USBCON_REG_UTMI_CTRL);

	/* Initilze UTMI ReWA */
	if (cal_info->hs_rewa)
		ready_rewa(cal_info);

}

void exynos_usbcon_detach_pipe3_phy(struct exynos_usbphy_info *cal_info)
{
	void __iomem *regs_base;
	u32 reg;

	regs_base = cal_info->regs_base;

	/* force pipe3 signal for link */
	reg = readl(regs_base + USBCON_REG_LINKCTRL);
	((USBCON_REG_LINKCTRL_p) (&reg))->b.force_pipe_en = 1;
	((USBCON_REG_LINKCTRL_p) (&reg))->b.force_phystatus = 0;
	((USBCON_REG_LINKCTRL_p) (&reg))->b.force_rxelecidle = 1;
	writel(reg, regs_base + USBCON_REG_LINKCTRL);
	pr_info("%s %d USBCON_REG_LINKCTRL: 0x %08x\n", __func__, __LINE__, readl(regs_base + USBCON_REG_LINKCTRL));

	/* pclk to suspend clock */
	reg = readl(regs_base + USBCON_REG_LINK_CLKRST);
	((USBCON_REG_LINK_CLKRST_p) (&reg))->b.link_pclk_sel = 0;
	writel(reg, regs_base + USBCON_REG_LINK_CLKRST);
}

void exynos_usbcon_disable_pipe3_phy(struct exynos_usbphy_info *cal_info)
{
	void __iomem *regs_base;
	u32 reg;

	exynos_usbcon_detach_pipe3_phy(cal_info);

	regs_base = cal_info->regs_base;

	/* calibrate only eUSB Phy */
	reg = readl(regs_base + USBCON_REG_HSP_MISC);
	((USBCON_REG_HSP_MISC_p) (&reg))->b.sel_res_tune_mux = 2;
	((USBCON_REG_HSP_MISC_p) (&reg))->b.set_req_in2 = 1;
	((USBCON_REG_HSP_MISC_p) (&reg))->b.set_ack_in2 = 0;
	((USBCON_REG_HSP_MISC_p) (&reg))->b.set_req_in1 = 0;
	((USBCON_REG_HSP_MISC_p) (&reg))->b.set_ack_in1 = 0;
	writel(reg, regs_base + USBCON_REG_HSP_MISC);
}

void exynos_usbcon_ready_to_pipe3_phy(struct exynos_usbphy_info *cal_info)
{
	void __iomem *regs_base;
	u32 reg;

	regs_base = cal_info->regs_base;

	/* Disable forcing pipe interface */
	reg = readl(regs_base + USBCON_REG_LINKCTRL);
	((USBCON_REG_LINKCTRL_p) (&reg))->b.force_pipe_en = 0;
	writel(reg, regs_base + USBCON_REG_LINKCTRL);

	/* calibrate Sequence: Dual Phy */
	reg = readl(regs_base + USBCON_REG_HSP_MISC);
	((USBCON_REG_HSP_MISC_p) (&reg))->b.sel_res_tune_mux = 1;
	((USBCON_REG_HSP_MISC_p) (&reg))->b.set_req_in2 = 0;
	((USBCON_REG_HSP_MISC_p) (&reg))->b.set_ack_in2 = 0;
	((USBCON_REG_HSP_MISC_p) (&reg))->b.set_req_in1 = 0;
	((USBCON_REG_HSP_MISC_p) (&reg))->b.set_ack_in1 = 0;
	writel(reg, regs_base + USBCON_REG_HSP_MISC);

	/* Pclk to pipe_clk */
	reg = readl(regs_base + USBCON_REG_LINK_CLKRST);
	((USBCON_REG_LINK_CLKRST_p) (&reg))->b.link_pclk_sel = 1;
	writel(reg, regs_base + USBCON_REG_LINK_CLKRST);
}

u64 exynos_usbcon_get_logic_trace(struct exynos_usbphy_info *cal_info)
{
	void __iomem *regs_base;
	u64 ret;

	regs_base = cal_info->regs_base;

	ret = readl(regs_base + USBCON_REG_LINK_DEBUG_L);
	ret |= ((u64) readl(regs_base + USBCON_REG_LINK_DEBUG_H)) << 32;

	return ret;
}

void exynos_usbcon_set_fsv_out_en(struct exynos_usbphy_info *cal_info, u32 en)
{
	void __iomem *regs_base = cal_info->regs_base;
	u32 reg;

	reg = readl(regs_base + USBCON_REG_HSP_MISC);
	if (en) {
		((USBCON_REG_HSP_MISC_p) (&reg))->b.fsvp_out_en = 1;
		((USBCON_REG_HSP_MISC_p) (&reg))->b.fsvm_out_en = 1;
	} else {
		((USBCON_REG_HSP_MISC_p) (&reg))->b.fsvp_out_en = 0;
		((USBCON_REG_HSP_MISC_p) (&reg))->b.fsvm_out_en = 0;
	}
	writel(reg, regs_base + USBCON_REG_HSP_MISC);
}

u8 exynos_usbcon_get_fs_vplus_vminus(struct exynos_usbphy_info *cal_info)
{
	void __iomem *regs_base = cal_info->regs_base;
	USBCON_REG_HSP_MISC_o reg;

	reg.data = readl(regs_base + USBCON_REG_HSP_MISC);

	return ((reg.b.fsvminus & 0x1) << 1) | (reg.b.fsvplus & 0x1);
}

static void ready_rewa(struct exynos_usbphy_info *cal_info)
{
	u32 reg;
	void __iomem *regs_base = cal_info->regs_base;

	/* Disable ReWA */
	reg = readl(regs_base + USBCON_REG_REWA_CTL);
	((USBCON_REG_REWA_CTL_p) (&reg))->b.hsrewa_en = 0;
	writel(reg, regs_base + USBCON_REG_REWA_CTL);

	/* Config ReWA Operation */
	reg = readl(regs_base + USBCON_REG_HSREWA_CTL);
	/* Select line state check circuit
	 * 0 : FSVPLUS/FSMINUS
	 * 1 : LINE STATE
	 * */
	((USBCON_REG_HSREWA_CTL_p) (&reg))->b.dpdm_mon_sel = 1;
	/* Select Drive K circuit
	 * 0 : Auto Resume in the PHY
	 * 1 : BYPASS mode by ReWA
	 * */
	((USBCON_REG_HSREWA_CTL_p) (&reg))->b.dig_bypass_con_en = 0;
	writel(reg, regs_base + USBCON_REG_HSREWA_CTL);

	/* Set host K timeout from host K drive. */
	reg = 0xFF00; // value 0xFF00 means 200ms (Nominal host K drive time is 20ms)
	writel(reg, regs_base + USBCON_REG_HSREWA_REFTO);

	/* Set host K delay from device K*/
	reg = 0x1; // value 1 means 30.5us
	writel(reg, regs_base + USBCON_REG_HSREWA_HSTK);

	/* Mask wakeup_req and all inetrrupts */
	reg = readl(regs_base + USBCON_REG_HSREWA_INTR);
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.wakeup_req_mask = 1;
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.timeout_intr_mask = 1;
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.event_intr_mask = 1;
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.wakeup_intr_mask = 1;
	writel(reg, regs_base + USBCON_REG_HSREWA_INTR);

	/* Mask all INT events  */
	reg = readl(regs_base + USBCON_REG_HSREWA_INT1_EVNT_MSK);
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.err_sus_mask = 1;
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.err_dev_k_mask = 1;
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.discon_mask = 1;
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.bypass_dis_mask = 1;
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.ret_dis_mask = 1;
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.ret_en_mask = 1;
	writel(reg, regs_base + USBCON_REG_HSREWA_INT1_EVNT_MSK);
}

int exynos_usbcon_enable_rewa(struct exynos_usbphy_info *cal_info)
{
	void __iomem *regs_base = cal_info->regs_base;
	u32 reg;
	int cnt;

	/* Set the wakeup source mask */
	reg = readl(regs_base + USBCON_REG_HSREWA_INTR);
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.wakeup_req_mask = 1;
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.timeout_intr_mask = 0;
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.event_intr_mask = 0;
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.wakeup_intr_mask = 0;
	writel(reg, regs_base + USBCON_REG_HSREWA_INTR);

	/* Clear the link_ready and sys_valid flag */
	reg = readl(regs_base + USBCON_REG_HSREWA_CTL);
	((USBCON_REG_HSREWA_CTL_p) (&reg))->b.hs_link_ready = 0;
	((USBCON_REG_HSREWA_CTL_p) (&reg))->b.hs_sys_valid = 0;
	writel(reg, regs_base + USBCON_REG_HSREWA_CTL);

	/* Enable ReWA */
	reg = readl(regs_base + USBCON_REG_REWA_CTL);
	((USBCON_REG_REWA_CTL_p) (&reg))->b.hsrewa_en = 1;
	writel(reg, regs_base + USBCON_REG_REWA_CTL);

	/* Check Status : Wait ReWA Status is retention enabled */
	for (cnt = 10000; cnt != 0; cnt--) {
		reg = readl(regs_base + USBCON_REG_HSREWA_INT1_EVNT);
		/* non suspend status*/
		if (((USBCON_REG_HSREWA_INT1_EVNT_p) (&reg))->b.err_sus) {
			pr_info("enable_rewa-not_suspend\n");
			return HS_REWA_EN_STS_NOT_SUSPEND;
		}

		/* Disconnect Status */
		if (((USBCON_REG_HSREWA_INT1_EVNT_p) (&reg))->b.discon) {
			pr_info("enable_rewa-discon\n");
			return HS_REWA_EN_STS_DISCONNECT;
		}
		/* Success ReWA Enable */
		if (((USBCON_REG_HSREWA_INT1_EVNT_p) (&reg))->b.ret_en)
			break;
		udelay(30);
	}
	if (cnt == 0) {
		pr_info("enable_rewa-timeout\n");
		return HS_REWA_EN_STS_NOT_SUSPEND;
	}

	/* Set the INT1 for detect K and Disconnect */
	reg = readl(regs_base + USBCON_REG_HSREWA_INT1_EVNT_MSK);
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.err_sus_mask = 1;
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.err_dev_k_mask = 0;
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.discon_mask = 0;
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.bypass_dis_mask = 1;
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.ret_dis_mask = 1;
	((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (&reg))->b.ret_en_mask = 1;
	writel(reg, regs_base + USBCON_REG_HSREWA_INT1_EVNT_MSK);

	/* Clear the wakeup source mask */
	reg = readl(regs_base + USBCON_REG_HSREWA_INTR);
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.wakeup_req_mask = 0;
	writel(reg, regs_base + USBCON_REG_HSREWA_INTR);

	udelay(100);
	pr_info("enable_rewa-done\n");

	return HS_REWA_EN_STS_ENALBED;
}

int exynos_usbcon_rewa_req_sys_valid(struct exynos_usbphy_info *cal_info)
{
	void __iomem *regs_base = cal_info->regs_base;
	u32 reg;
	int cnt;

	/* Set interrupt mask for prevent addtional interrupt */
	reg = readl(regs_base + USBCON_REG_HSREWA_INTR);
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.wakeup_req_mask = 1;
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.timeout_intr_mask = 1;
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.event_intr_mask = 1;
	((USBCON_REG_HSREWA_INTR_p) (&reg))->b.wakeup_intr_mask = 1;
	writel(reg, regs_base + USBCON_REG_HSREWA_INTR);

	/* Set the system valid flag after all System clock resumed */
	reg = readl(regs_base + USBCON_REG_HSREWA_CTL);
	((USBCON_REG_HSREWA_CTL_p) (&reg))->b.hs_sys_valid = 1;
	writel(reg, regs_base + USBCON_REG_HSREWA_CTL);

	/* Check Status : Wait ReWA Status is retention disabled */
	for (cnt = 10000; cnt != 0; cnt--) {

		reg = readl(regs_base + USBCON_REG_HSREWA_INT1_EVNT);

		/* Disconnect Status */
		if (((USBCON_REG_HSREWA_INT1_EVNT_p) (&reg))->b.discon) {
			pr_info("sysvalid-discon\n");
			return HS_REWA_EN_STS_DISCONNECT;
		}

		/* Success ReWA Enable */
		if (((USBCON_REG_HSREWA_INT1_EVNT_p) (&reg))->b.ret_dis)
			break;
		udelay(30);
	}

	pr_info("sysvalid-done\n");
	return HS_REWA_EN_STS_DISABLED;
}

int exynos_usbcon_rewa_disable(struct exynos_usbphy_info *cal_info)
{
	void __iomem *regs_base = cal_info->regs_base;
	u32 reg;
	int cnt;

	/* Check ReWA already disable
	 * If ReWA was disabled states, disabled sequence is already done */
	reg = readl(regs_base + USBCON_REG_REWA_CTL);
	if (!(((USBCON_REG_REWA_CTL_p) (&reg))->b.hsrewa_en)) {
		pr_info("disable_rewa-already\n");
		return 0;
	}

	/* Set the USB link_ready flag */
	reg = readl(regs_base + USBCON_REG_HSREWA_CTL);
	((USBCON_REG_HSREWA_CTL_p) (&reg))->b.hs_link_ready = 1;
	writel(reg, regs_base + USBCON_REG_HSREWA_CTL);

	/* Wait for Digital bypass control signals is disabled event */
	for (cnt = 10000; cnt != 0; cnt--) {
		reg = readl(regs_base + USBCON_REG_HSREWA_INT1_EVNT);
		if (((USBCON_REG_HSREWA_INT1_EVNT_p) (&reg))->b.bypass_dis)
			break;
		udelay(30);
	}

	if (!cnt) {
		pr_info("disable_rewa-timeout_bypass_dis\n");
		return -1;
	}

	/* Wait for HS-ReWA done */
	for (cnt = 1000; cnt != 0; cnt--) {
		reg = readl(regs_base + USBCON_REG_HSREWA_CTL);
		if (((USBCON_REG_HSREWA_CTL_p) (&reg))->b.hs_rewa_done)
			break;
		udelay(30);
	}

	if (!cnt) {
		pr_info("disable_rewa-timeout_hs_rewa_done\n");
		return -1;
	}

	reg = readl(regs_base + USBCON_REG_REWA_CTL);
	((USBCON_REG_REWA_CTL_p) (&reg))->b.hsrewa_en = 0;
	writel(reg, regs_base + USBCON_REG_REWA_CTL);
	pr_info("disable_rewa-done\n");

	return 0;
}

int exynos_usbcon_rewa_cancel(struct exynos_usbphy_info *cal_info)
{
	void __iomem *regs_base = cal_info->regs_base;
	u32 reg;

	/* Check ReWA already disable
	 * If ReWA was disabled states, disabled sequence is already done */
	reg = readl(regs_base + USBCON_REG_REWA_CTL);
	if (!(((USBCON_REG_REWA_CTL_p) (&reg))->b.hsrewa_en)) {
		pr_info("cancel_rewa-already\n");
		return 0;
	}

	reg = readl(regs_base + USBCON_REG_REWA_CTL);
	((USBCON_REG_REWA_CTL_p) (&reg))->b.hsrewa_en = 0;
	writel(reg, regs_base + USBCON_REG_REWA_CTL);
	pr_info("cancel_rewa-done\n");

	return 0;
}

void exynos_usbcon_u3_rewa_enable(struct exynos_usbphy_info *cal_info, int lfps_overlap_mode)
{
	void __iomem *regs_base = cal_info->regs_base;
	u32 reg;
	u32 lfpsresp_limit_cnt = 0x39219;   // 9ms

	reg = readl(regs_base + USBCON_REG_U3REWA_CTRL);
	((USBCON_REG_U3REWA_CTRL_p) (&reg))->b.check_u3 = 0;
	((USBCON_REG_U3REWA_CTRL_p) (&reg))->b.u3rewa_blk_en = 1;

	if (!lfps_overlap_mode) {
		/* Disable overlap_lfps */
		((USBCON_REG_U3REWA_CTRL_p) (&reg))->b.overlap_lfps = 0;
	} else {
		/* Enable overlap_lfps */
		((USBCON_REG_U3REWA_CTRL_p) (&reg))->b.overlap_lfps = 1;

		/* Set lfpsresp_limit_cnt */
		writel(lfpsresp_limit_cnt, regs_base + USBCON_REG_U3REWA_LMT_CNT);
	}

	writel(reg, regs_base + USBCON_REG_U3REWA_CTRL);
	pr_info("enable_u3-rewa-done\n");
}

void exynos_usbcon_u3_rewa_disable(struct exynos_usbphy_info *cal_info)
{
	void __iomem *regs_base = cal_info->regs_base;
	u32 reg;

	reg = readl(regs_base + USBCON_REG_U3REWA_CTRL);
	((USBCON_REG_U3REWA_CTRL_p) (&reg))->b.u3rewa_blk_en = 0;
	writel(reg, regs_base + USBCON_REG_U3REWA_CTRL);
	pr_info("disable_u3-rewa-done\n");
}