/*
 *amlogic USB HOST XHCI Controller
 *
 *	Yue Wang <yue.wang@amlogic.com>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

/*
 * This file is a conglomeration for DWC3-init sequence and further
 * exynos5 specific PHY-init sequence.
 */

#include <common.h>
//#include <fdtdec.h>
#include <libfdt.h>
#include <malloc.h>
#include <usb.h>
#include <watchdog.h>
#include <asm/arch/cpu.h>
//#include <asm/arch/power.h>
//#include <asm/gpio.h>
#include <asm-generic/errno.h>
#include <linux/compat.h>
#include <linux/usb/dwc3.h>
#include <asm/arch/usb.h>

#include "xhci.h"

/* Declare global data pointer */
DECLARE_GLOBAL_DATA_PTR;

/**
 * Contains pointers to register base addresses
 * for the usb controller.
 */
struct amlogic_xhci {
	struct amlogic_usb2_phy *usb2_phy;	
	struct amlogic_usb3_phy *usb3_phy;
	struct xhci_hccr *hcd;
	struct dwc3 *dwc3_reg;
};
amlogic_usb3_phy *usb_aml_reg;

static struct amlogic_xhci amlogic;


static void amlogic_usb2_phy_init(struct amlogic_usb2_phy *phy)
{
	int time_dly = 500;
	int i;

	amlogic_usb2_phy *u2p_aml_reg;
	u2p_r0_t reg0;

	for(i=0; i<4; i++)
	{
		u2p_aml_reg = (amlogic_usb2_phy *)((unsigned int)phy+i*PHY_REGISTER_SIZE);

		reg0.d32 = u2p_aml_reg->u2p_r0;
		reg0.b.por = 1;
		reg0.b.dmpulldown = 1;
		reg0.b.dppulldown =1;
		u2p_aml_reg->u2p_r0 = reg0.d32;

		udelay(time_dly);

		reg0.d32 = u2p_aml_reg->u2p_r0;
		reg0.b.por = 0;
		u2p_aml_reg->u2p_r0 = reg0.d32;		
	}

	return;

}

void cr_bus_addr (u32 addr)
{
	usb_r2_t usb_r2 = {.d32 = 0};
	usb_r6_t usb_r6 = {.d32 = 0};

	// prepare addr
	usb_r2.b.p30_cr_data_in = addr;
	usb_aml_reg->usb_r2 = usb_r2.d32;

	// cap addr rising edge
	usb_r2.b.p30_cr_cap_addr = 0;
	usb_aml_reg->usb_r2 = usb_r2.d32;
	usb_r2.b.p30_cr_cap_addr = 1;
	usb_aml_reg->usb_r2 = usb_r2.d32;

	// wait ack 1
	do {
		usb_r6.d32 = usb_aml_reg->usb_r6;
	} while (usb_r6.b.p30_cr_ack == 0);

	// clear cap addr
	usb_r2.b.p30_cr_cap_addr = 0;
	usb_aml_reg->usb_r2 = usb_r2.d32;

	// wait ack 0
	do {
		usb_r6.d32 = usb_aml_reg->usb_r6;
	} while (usb_r6.b.p30_cr_ack == 1);
}

int cr_bus_read (u32 addr)
{
	int data;
	usb_r2_t usb_r2 = {.d32 = 0};
	usb_r6_t usb_r6 = {.d32 = 0};

	cr_bus_addr ( addr );

	// read rising edge
	usb_r2.b.p30_cr_read = 0;
	usb_aml_reg->usb_r2 = usb_r2.d32;
	usb_r2.b.p30_cr_read = 1;
	usb_aml_reg->usb_r2 = usb_r2.d32;

	// wait ack 1
	do {
		usb_r6.d32 = usb_aml_reg->usb_r6;
	} while (usb_r6.b.p30_cr_ack == 0);

	// save data
	data = usb_r6.b.p30_cr_data_out;

	// clear read
	usb_r2.b.p30_cr_read = 0;
	usb_aml_reg->usb_r2 = usb_r2.d32;

	// wait ack 0
	do {
		usb_r6.d32 = usb_aml_reg->usb_r6;
	} while (usb_r6.b.p30_cr_ack == 1);

	return data;
}

void cr_bus_write (u32 addr, u32 data)
{
	usb_r2_t usb_r2 = {.d32 = 0};
	usb_r6_t usb_r6 = {.d32 = 0};

	cr_bus_addr ( addr );

	// prepare data
	usb_r2.b.p30_cr_data_in = data;
	usb_aml_reg->usb_r2 = usb_r2.d32;

	// cap data rising edge
	usb_r2.b.p30_cr_cap_data = 0;
	usb_aml_reg->usb_r2 = usb_r2.d32;
	usb_r2.b.p30_cr_cap_data = 1;
	usb_aml_reg->usb_r2 = usb_r2.d32;

	// wait ack 1
	do {
		usb_r6.d32 = usb_aml_reg->usb_r6;
	} while (usb_r6.b.p30_cr_ack == 0);

	// clear cap data
	usb_r2.b.p30_cr_cap_data = 0;
	usb_aml_reg->usb_r2 = usb_r2.d32;

	// wait ack 0
	do {
		usb_r6.d32 = usb_aml_reg->usb_r6;
	} while (usb_r6.b.p30_cr_ack == 1);

	// write rising edge
	usb_r2.b.p30_cr_write = 0;
	usb_aml_reg->usb_r2 = usb_r2.d32;
	usb_r2.b.p30_cr_write = 1;
	usb_aml_reg->usb_r2 = usb_r2.d32;

	// wait ack 1
	do {
		usb_r6.d32 = usb_aml_reg->usb_r6;
	} while (usb_r6.b.p30_cr_ack == 0);

	// clear write
	usb_r2.b.p30_cr_write = 0;
	usb_aml_reg->usb_r2 = usb_r2.d32;

	// wait ack 0
	do {
		usb_r6.d32 = usb_aml_reg->usb_r6;
	} while (usb_r6.b.p30_cr_ack == 1);
}


static void amlogic_usb3_phy_init(struct amlogic_usb3_phy *phy)
{ 	
	usb_r0_t r0 = {.d32 = 0};
	usb_r1_t r1 = {.d32 = 0};
	usb_r2_t r2 = {.d32 = 0};
	usb_r3_t r3 = {.d32 = 0};
	int i;
	u32 data = 0;
	
	for(i=0;i<1;i++)
	{
		usb_aml_reg = (amlogic_usb3_phy *)((unsigned int)phy+i*PHY_REGISTER_SIZE);

		usb_aml_reg->usb_r3 = (1<<13) | (0x68<<24);
		r0.d32 = usb_aml_reg->usb_r0;
		r0.b.p30_phy_reset = 1;
		usb_aml_reg->usb_r0 = r0.d32;
		udelay(2);
		r0.b.p30_phy_reset = 0;
		usb_aml_reg->usb_r0 = r0.d32;
	

		/*
		* WORKAROUND: There is SSPHY suspend bug due to which USB enumerates
		* in HS mode instead of SS mode. Workaround it by asserting
		* LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus mode
		*/
		data = cr_bus_read(0x102d);
		data |= (1 << 7);
		cr_bus_write(0x102D, data);

		data = cr_bus_read(0x1010);
		data &= ~0xff0;
		data |= 0x20;
		cr_bus_write(0x1010, data);

		/*
		* Fix RX Equalization setting as follows
		* LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0
		* LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1
		* LANE0.RX_OVRD_IN_HI.RX_EQ set to 3
		* LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1
		*/
		data = cr_bus_read(0x1006);
		data &= ~(1 << 6);
		data |= (1 << 7);
		data &= ~(0x7 << 8);
		data |= (0x3 << 8);
		data |= (0x1 << 11);
		cr_bus_write(0x1006, data);

		/*
		* Set EQ and TX launch amplitudes as follows
		* LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22
		* LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127
		 * LANE0.TX_OVRD_DRV_LO.EN set to 1.
		*/
		data = cr_bus_read(0x1002);
		data &= ~0x3f80;
		data |= (0x16 << 7);
		data &= ~0x7f;
		data |= (0x7f | (1 << 14));
		cr_bus_write(0x1002, data);

		/*
		* TX_FULL_SWING  to 127
		*/
		r1.d32 = usb_aml_reg->usb_r1;
		r1.b.p30_pcs_tx_swing_full = 127;
		r1.b.u3h_fladj_30mhz_reg = 0x20;
		usb_aml_reg->usb_r1 = r1.d32;

		/*
		* TX_DEEMPH_3_5DB  to 22
		*/
		r2.d32 = usb_aml_reg->usb_r2;
		r2.b.p30_pcs_tx_deemph_3p5db = 22;
		usb_aml_reg->usb_r2 = r2.d32;

		/*
		* LOS_BIAS	to 0x5
		* LOS_LEVEL to 0x9
		*/
		r3.d32 = usb_aml_reg->usb_r3;
		r3.b.p30_los_bias = 0x5;
		r3.b.p30_los_level = 0x9;
		usb_aml_reg->usb_r3 = r3.d32;
	}

}

static void amlogic_usb2_phy_exit(struct amlogic_usb2_phy *phy)
{
	return;
}

static void amlogic_usb3_phy_exit(struct amlogic_usb3_phy *phy)
{
	return;
}
void dwc3_set_mode(struct dwc3 *dwc3_reg, u32 mode)
{
	u32 reg;
	
	reg = xhci_readl(&dwc3_reg->g_ctl);
	reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
	reg |= DWC3_GCTL_PRTCAPDIR(mode);
	xhci_writel(&dwc3_reg->g_ctl, reg);
}

static void dwc3_core_soft_reset(struct dwc3 *dwc3_reg)
{
	u32		reg;

	/* Before Resetting PHY, put Core in Reset */
	reg = xhci_readl(&dwc3_reg->g_ctl);
	reg |= DWC3_GCTL_CORESOFTRESET;
	xhci_writel(&dwc3_reg->g_ctl, reg);

	/* Assert USB3 PHY reset */
	reg = xhci_readl(&dwc3_reg->g_usb3pipectl[0]);
	reg |= DWC3_GUSB3PIPECTL_PHYSOFTRST;
	xhci_writel(&dwc3_reg->g_usb3pipectl[0], reg);

	/* Assert USB2 PHY reset */
	reg = xhci_readl(&dwc3_reg->g_usb2phycfg[0]);
	reg |= DWC3_GUSB2PHYCFG_PHYSOFTRST;
	xhci_writel(&dwc3_reg->g_usb2phycfg[0], reg);

	reg = xhci_readl(&dwc3_reg->g_usb2phycfg[1]);
	reg |= DWC3_GUSB2PHYCFG_PHYSOFTRST;
	xhci_writel(&dwc3_reg->g_usb2phycfg[1], reg);

	reg = xhci_readl(&dwc3_reg->g_usb2phycfg[2]);
	reg |= DWC3_GUSB2PHYCFG_PHYSOFTRST;
	xhci_writel(&dwc3_reg->g_usb2phycfg[2], reg);

	reg = xhci_readl(&dwc3_reg->g_usb2phycfg[3]);
	reg |= DWC3_GUSB2PHYCFG_PHYSOFTRST;
	xhci_writel(&dwc3_reg->g_usb2phycfg[3], reg);

	amlogic_usb2_phy_init(amlogic.usb2_phy);
	amlogic_usb3_phy_init(amlogic.usb3_phy);
	wait_ms(100);

	/* Clear USB3 PHY reset */
	reg = xhci_readl(&dwc3_reg->g_usb3pipectl[0]);
	reg &= ~DWC3_GUSB3PIPECTL_PHYSOFTRST;
	xhci_writel(&dwc3_reg->g_usb3pipectl[0], reg);

	/* Clear USB2 PHY reset */
	reg = xhci_readl(&dwc3_reg->g_usb2phycfg[0]);
	reg &= ~DWC3_GUSB2PHYCFG_PHYSOFTRST;
	reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
	xhci_writel(&dwc3_reg->g_usb2phycfg[0], reg);

	/* Clear USB2 PHY reset */
	reg = xhci_readl(&dwc3_reg->g_usb2phycfg[1]);
	reg &= ~DWC3_GUSB2PHYCFG_PHYSOFTRST;
	xhci_writel(&dwc3_reg->g_usb2phycfg[1], reg);

	/* Clear USB2 PHY reset */
	reg = xhci_readl(&dwc3_reg->g_usb2phycfg[2]);
	reg &= ~DWC3_GUSB2PHYCFG_PHYSOFTRST;
	xhci_writel(&dwc3_reg->g_usb2phycfg[2], reg);

	/* Clear USB2 PHY reset */
	reg = xhci_readl(&dwc3_reg->g_usb2phycfg[3]);
	reg &= ~DWC3_GUSB2PHYCFG_PHYSOFTRST;
	xhci_writel(&dwc3_reg->g_usb2phycfg[3], reg);

	wait_ms(100);

	/* After PHYs are stable we can take Core out of reset state */
	reg = xhci_readl(&dwc3_reg->g_ctl);
	reg &= ~DWC3_GCTL_CORESOFTRESET;
	xhci_writel(&dwc3_reg->g_ctl, reg);
}

static int dwc3_core_init(struct dwc3 *dwc3_reg)
{
	u32 reg;
	u32 revision;
	unsigned int dwc3_hwparams1;	
	unsigned long		timeout;

	revision = xhci_readl(&dwc3_reg->g_snpsid);
	/* This should read as U3 followed by revision number */
	if ((revision & DWC3_GSNPSID_MASK) != 0x55330000) {
		printf("this is not a DesignWare USB3 DRD Core\n");
		return -EINVAL;
	}

	/* issue device SoftReset too */
	timeout = 500;
	xhci_writel(&dwc3_reg->d_ctl, DWC3_DCTL_CSFTRST);
	do {
		reg = xhci_readl(&dwc3_reg->d_ctl);
		if (!(reg & DWC3_DCTL_CSFTRST))
			break;

		timeout--;		
		wait_ms(1);
	} while (timeout);

	if (!timeout) {
		printf("device SoftReset fail!\n");
		return -EINVAL;
	}

	dwc3_core_soft_reset(dwc3_reg);

	dwc3_hwparams1 = xhci_readl(&dwc3_reg->g_hwparams1);

	reg = xhci_readl(&dwc3_reg->g_ctl);
	reg &= ~DWC3_GCTL_SCALEDOWN_MASK;
	reg &= ~DWC3_GCTL_DISSCRAMBLE;
	switch (DWC3_GHWPARAMS1_EN_PWROPT(dwc3_hwparams1)) {
	case DWC3_GHWPARAMS1_EN_PWROPT_CLK:
		reg &= ~DWC3_GCTL_DSBLCLKGTNG;
		break;
	default:
		debug("No power optimization available\n");
	}

	/*
	 * WORKAROUND: DWC3 revisions <1.90a have a bug
	 * where the device can fail to connect at SuperSpeed
	 * and falls back to high-speed mode which causes
	 * the device to enter a Connect/Disconnect loop
	 */
	if ((revision & DWC3_REVISION_MASK) < 0x190a)
		reg |= DWC3_GCTL_U2RSTECN;

	xhci_writel(&dwc3_reg->g_ctl, reg);

	return 0;
}

static int amlogic_xhci_core_init(struct amlogic_xhci *amlogic)
{
	int ret;

	ret = dwc3_core_init(amlogic->dwc3_reg);
	if (ret) {
		debug("failed to initialize core\n");
		return -EINVAL;
	}

	/* We are hard-coding DWC3 core to Host Mode */
	dwc3_set_mode(amlogic->dwc3_reg, DWC3_GCTL_PRTCAP_HOST);

	return 0;
}

static void amlogic_xhci_core_exit(struct amlogic_xhci *amlogic)
{
	amlogic_usb2_phy_exit(amlogic->usb2_phy);

	amlogic_usb3_phy_exit(amlogic->usb3_phy);
}

int xhci_hcd_init(int index, struct xhci_hccr **hccr, struct xhci_hcor **hcor)
{
	struct amlogic_xhci *ctx = &amlogic;
	amlogic_usb_config_t * usb_config;	
	int ret;

    usb_config = board_usb_start(BOARD_USB_MODE_HOST,index);

	ctx->hcd = (struct xhci_hccr *)(usb_config->base_addr);
	ctx->dwc3_reg = (struct dwc3 *)((char *)(ctx->hcd) + DWC3_REG_OFFSET);
	ctx->usb3_phy = (struct amlogic_usb3_phy *)(usb_config->usb_phy3_base_addr);
	ctx->usb2_phy = (struct amlogic_usb2_phy *)(usb_config->usb_phy2_base_addr);

	ret = amlogic_xhci_core_init(ctx);
	if (ret) {
		puts("XHCI: failed to initialize controller\n");
		return -EINVAL;
	}

	*hccr = (ctx->hcd);
	*hcor = (struct xhci_hcor *)((uint32_t) *hccr
				+ HC_LENGTH(xhci_readl(&(*hccr)->cr_capbase)));

	debug("amlogic-xhci: init hccr %x and hcor %x hc_length %d\n",
		(uint32_t)*hccr, (uint32_t)*hcor,
		(uint32_t)HC_LENGTH(xhci_readl(&(*hccr)->cr_capbase)));

	return 0;
}

void xhci_hcd_stop(int index)
{
	struct amlogic_xhci *ctx = &amlogic;

	amlogic_xhci_core_exit(ctx);
}

