/*
 * SAMSUNG EXYNOS USB HOST EHCI Controller
 *
 * Copyright (C) 2012 Samsung Electronics Co.Ltd
 *	Vivek Gautam <gautam.vivek@samsung.com>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <dm.h>
#include <fdtdec.h>
#include <malloc.h>
#include <usb.h>
#include <asm/io.h>
#include "ehci.h"
#include <asm/arch/IOAddress.h>
#include <asm/arch/hardware.h>
#include <asm/arch/efuse_protected.h>

/* Declare global data pointer */
DECLARE_GLOBAL_DATA_PTR;
#define U2PHY_SETREG(ofs,value)         writel((value), (volatile void __iomem *)(IOADDR_USB_REG_BASE + 0x1000 + ((ofs)<<2)))
#define U2PHY_GETREG(ofs)               readl((volatile void __iomem *)(IOADDR_USB_REG_BASE + 0x1000 + ((ofs)<<2)))

#ifdef CONFIG_DM_USB
struct uboot_ehci_nvt_priv_data{
	struct ehci_ctrl ctrl; /* Needed by EHCI */
	struct ehci_hccr *hccr;
	struct ehci_hcor *hcor;
};

static int ehci_usb_ofdata_to_platdata(struct udevice *dev)
{
	return 0;
}
#endif

static int nvt_get_port_speed(struct ehci_ctrl *ctrl, uint32_t reg)
{
	unsigned tmp = ehci_readl(((void *)ctrl->hcor) + 0x70);

	switch ((tmp >> 22) & 3) {
		case 0:
			return PORTSC_PSPD_FS;
		case 1:
			return PORTSC_PSPD_LS;
		case 2:
		default:
			return PORTSC_PSPD_HS;
	}
}

const struct ehci_ops nvt_ehci_ops = {
	.set_usb_mode		= NULL,
	.get_port_speed		= nvt_get_port_speed,
	.powerup_fixup		= NULL,
	.get_portsc_register	= NULL,
};

#ifndef CONFIG_DM_USB
static void nvtim_init_usbhc(void)
{
	u32 usbbase;
	u32 tmpval = 0;

	/* Enable Reset*/
	tmpval = readl((volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));
	tmpval &= ~(0x1<<19);
	writel(tmpval, (volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));

	udelay(10);

	/* Disable Reset*/
	tmpval = readl((volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));
	tmpval |= 0x1<<19;
	writel(tmpval, (volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));

	/* Release sram shutdown*/
	tmpval = readl((volatile unsigned long *)(IOADDR_TOP_REG_BASE+0x1000));
	tmpval &= ~(0x1<<19);
	writel(tmpval, (volatile unsigned long *)(IOADDR_TOP_REG_BASE+0x1000));

	mdelay(10);

	if (nvt_get_chip_id() == CHIP_NA51055)
		usbbase = IOADDR_USB_REG_BASE;
	else
		usbbase = IOADDR_USB528_REG_BASE;

	/* Set USB ID & VBUSI */
	if (nvt_get_chip_id() == CHIP_NA51055)
		writel((u32)0x1, (volatile unsigned long *)(usbbase+0x310));
	else {
		tmpval = readl((volatile unsigned long *)(usbbase+0x400));
		tmpval |= 0x1 << 20;
		writel(tmpval, (volatile unsigned long *)(usbbase+0x400));
	}

	/* Clear FORCE_FS[9] and handle HALF_SPEED[1] */
	tmpval = readl((volatile unsigned long *)(usbbase+0x100));
	tmpval &= ~(0x1<<9);
	//#ifdef CONFIG_FPGA_EMULATION
	//tmpval |=  (0x1<<1);
	//#endif
	writel(tmpval, (volatile unsigned long *)(usbbase+0x100));

	/* Clear DEVPHY_SUSPEND[5] */
	tmpval = readl((volatile unsigned long *)(usbbase+0x1C8));
	tmpval &= ~(0x1<<5);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x1C8));

	/* Clear HOSTPHY_SUSPEND[6] */
	tmpval = readl((volatile unsigned long *)(usbbase+0x40));
	tmpval &= ~(0x1<<6);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x40));

	/* USB_ACCESS_SELECT[2] to 0 (DRAM only) */
	tmpval = readl((volatile unsigned long *)(usbbase+0x1C4));
	tmpval &= ~(0x1<<2);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x1C4));

	/* Host EOF_BEHAVE[31] = 0 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x80));
	tmpval &= ~(0x1<<31);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x80));

	/* Clear EOF1=3[3:2] EOF2[5:4]=0 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x40));
	tmpval &= ~(0x3<<4);
	tmpval |=  (0x3<<2);
	tmpval &= ~(0x3F<<8);
	tmpval |=  (0x22<<8);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x40));

	/* A_BUS_DROP[5] = 0 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x80));
	tmpval &= ~(0x1<<5);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x80));

	mdelay(2);

	/* A_BUS_REQ[4] = 1 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x80));
	tmpval |= (0x1<<4);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x80));

	/* Configure PHY related settings below */
	{
		u32 temp;
		u8 u2_trim_swctrl=4, u2_trim_sqsel=4, u2_trim_resint=8;

#if CONFIG_TARGET_NA51089 || CONFIG_TARGET_NA51055
		int code;
		bool is_found;
		u32 trim;

		// get trim value of usb column
		code = otp_key_manager(1);
		printf("code is %d\n", code);
		if(code == -33) {
			//!!!Please apply default value here!!!
			printf("Read USB trim error\r\n");
		} else {
			// check if usb trim value is valid
			is_found = extract_trim_valid(code, (u32 *)&trim);
			if(is_found) {
				printf("is found trim is 0x%04x\n", (int)trim);
				u2_trim_resint = (trim>>6)&0x1f;
				printf("is found [%d][USB] = 0x%04x\r\n", is_found, (int)trim);
				printf("        => 12K_resistor[0x%04x]\r\n", (int)u2_trim_resint);
			} else {
				//!!!Please apply default value here!!!
				printf("is found [%d][ USB] = 0x%08x\r\n", is_found, (int)code);
			}
		}
#endif
		U2PHY_SETREG(0x51, 0x20);
		U2PHY_SETREG(0x50, 0x30);

		temp = U2PHY_GETREG(0x06);
		temp &= ~(0x7<<1);
		temp |= (u2_trim_swctrl<<1);
		U2PHY_SETREG(0x06, temp);

		temp = U2PHY_GETREG(0x05);
		temp &= ~(0x7<<2);
		temp |= (u2_trim_sqsel<<2);
		U2PHY_SETREG(0x05, temp);

		U2PHY_SETREG(0x52, 0x60+u2_trim_resint);
		U2PHY_SETREG(0x51, 0x00);
	}
}
/*
 * Create the appropriate control structures to manage
 * a new EHCI host controller.
 */
int ehci_hcd_init(int index, enum usb_init_type init,
		struct ehci_hccr **hccr, struct ehci_hcor **hcor)
{
	nvtim_init_usbhc();

	if (nvt_get_chip_id() == CHIP_NA51055)
		*hccr = (struct ehci_hccr *)IOADDR_USB_REG_BASE;
	else
		*hccr = (struct ehci_hccr *)IOADDR_USB528_REG_BASE;

	*hcor = (struct ehci_hcor *)((uint32_t)*hccr +
			HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase)));

	ehci_set_controller_priv(index, NULL, &nvt_ehci_ops);

	return 0;
}

/*
 * Destroy the appropriate control structures corresponding
 * the the EHCI host controller.
 */
int ehci_hcd_stop(int index)
{
	u32 usbbase;
	u32 tmpval = 0;

	if (nvt_get_chip_id() == CHIP_NA51055)
		usbbase = IOADDR_USB_REG_BASE;
	else
		usbbase = IOADDR_USB528_REG_BASE;

	/* Host Reset */
	writel((readl((volatile unsigned long *)(usbbase+0x10)) | 0x2), (volatile unsigned long *)(usbbase+0x10));

	/* A_BUS_REQ[4] = 0 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x80));
	tmpval &= ~(0x1<<4);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x80));

	/* A_BUS_DROP[5] = 1 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x80));
	tmpval |= (0x1<<5);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x80));


	/* Enable Reset*/
	tmpval = readl((volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));
	tmpval &= ~(0x1<<19);
	writel(tmpval, (volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));

	udelay(10);

	/* Disable Reset*/
	tmpval = readl((volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));
	tmpval |= 0x1<<19;
	writel(tmpval, (volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));

	return 0;
}

#else
static void nvtim_init_usbhc_dm(struct ehci_hccr **hccr, struct ehci_hcor **hcor)
{
	u32 usbbase;
	u32 tmpval = 0;

	/* Enable Reset*/
	tmpval = readl((volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));
	tmpval &= ~(0x1<<19);
	writel(tmpval, (volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));

	udelay(10);

	/* Disable Reset*/
	tmpval = readl((volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));
	tmpval |= 0x1<<19;
	writel(tmpval, (volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));

	/* Release sram shutdown*/
	tmpval = readl((volatile unsigned long *)(IOADDR_TOP_REG_BASE+0x1000));
	tmpval &= ~(0x1<<19);
	writel(tmpval, (volatile unsigned long *)(IOADDR_TOP_REG_BASE+0x1000));

	mdelay(10);

	if (nvt_get_chip_id() == CHIP_NA51055)
		usbbase = IOADDR_USB_REG_BASE;
	else
		usbbase = IOADDR_USB528_REG_BASE;

	/* Set USB ID & VBUSI */
	if (nvt_get_chip_id() == CHIP_NA51055)
		writel((u32)0x1, (volatile unsigned long *)(usbbase+0x310));
	else {
		tmpval = readl((volatile unsigned long *)(usbbase+0x400));
		tmpval |= 0x1 << 20;
		writel(tmpval, (volatile unsigned long *)(usbbase+0x400));
	}


	/* Clear FORCE_FS[9] and handle HALF_SPEED[1] */
	tmpval = readl((volatile unsigned long *)(usbbase+0x100));
	tmpval &= ~(0x1<<9);
	//#ifdef CONFIG_FPGA_EMULATION
	//tmpval |=  (0x1<<1);
	//#endif
	writel(tmpval, (volatile unsigned long *)(usbbase+0x100));

	/* Clear DEVPHY_SUSPEND[5] */
	tmpval = readl((volatile unsigned long *)(usbbase+0x1C8));
	if (nvt_get_chip_id() == CHIP_NA51055)
		tmpval &= ~(0x1<<5);
	else
		tmpval &= ~(0x1<<31);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x1C8));

	/* Clear HOSTPHY_SUSPEND[6] */
	tmpval = readl((volatile unsigned long *)(usbbase+0x40));
	tmpval &= ~(0x1<<6);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x40));

	/* USB_ACCESS_SELECT[2] to 0 (DRAM only) */
	tmpval = readl((volatile unsigned long *)(usbbase+0x1C4));
	tmpval &= ~(0x1<<2);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x1C4));

	/* Host EOF_BEHAVE[31] = 0 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x80));
	tmpval &= ~(0x1<<31);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x80));

	/* Clear EOF1=3[3:2] EOF2[5:4]=0 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x40));
	tmpval &= ~(0x3<<4);
	tmpval |=  (0x3<<2);
	if (nvt_get_chip_id() == CHIP_NA51055) {
		tmpval &= ~(0x3F<<8);
		tmpval |=  (0x22<<8);
	} else {
		tmpval &= ~(0x3F<<16);
		tmpval |=  (0x22<<16);
	}
	writel(tmpval, (volatile unsigned long *)(usbbase+0x40));

	/* A_BUS_DROP[5] = 0 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x80));
	tmpval &= ~(0x1<<5);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x80));

	mdelay(2);

	/* A_BUS_REQ[4] = 1 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x80));
	tmpval |= (0x1<<4);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x80));

	/* Configure PHY related settings below */
	{
		u32 temp;
		u8 u2_trim_swctrl=4, u2_trim_sqsel=4, u2_trim_resint=8;
#if CONFIG_TARGET_NA51089 || CONFIG_TARGET_NA51055
		int code;
                bool is_found;
		u32 trim;

		// get trim value of usb column
		code = otp_key_manager(1);
		printf("code is %d\n", code);
		if(code == -33) {
			//!!!Please apply default value here!!!
			printf("Read USB trim error\r\n");
		} else {
			// check if usb trim value is valid
			is_found = extract_trim_valid(code, (u32 *)&trim);
			if(is_found) {
				printf("is found trim is 0x%04x\n", (int)trim);
				u2_trim_resint = (trim>>6)&0x1f;
				printf("is found [%d][USB] = 0x%04x\r\n", is_found, (int)trim);
				printf("        => 12K_resistor[0x%04x]\r\n", (int)u2_trim_resint);
			} else {
				//!!!Please apply default value here!!!
				printf("is found [%d][ USB] = 0x%08x\r\n", is_found, (int)code);
			}
		}
#endif
		U2PHY_SETREG(0x51, 0x20);
		U2PHY_SETREG(0x50, 0x30);

		temp = U2PHY_GETREG(0x06);
		temp &= ~(0x7<<1);
		temp |= (u2_trim_swctrl<<1);
		U2PHY_SETREG(0x06, temp);

		temp = U2PHY_GETREG(0x05);
		temp &= ~(0x7<<2);
		temp |= (u2_trim_sqsel<<2);
		U2PHY_SETREG(0x05, temp);

		U2PHY_SETREG(0x52, 0x60+u2_trim_resint);
		U2PHY_SETREG(0x51, 0x00);
	}

	*hccr = (struct ehci_hccr *)usbbase;
	*hcor = (struct ehci_hcor *)((uint32_t)*hccr +
			HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase)));
}

static int ehci_usb_probe(struct udevice *dev)
{
	struct uboot_ehci_nvt_priv_data *ctx = dev_get_priv(dev);

	nvtim_init_usbhc_dm(&ctx->hccr , &ctx->hcor);

	return ehci_register(dev, ctx->hccr, ctx->hcor, &nvt_ehci_ops, 0, USB_INIT_HOST);
}

static int ehci_usb_remove(struct udevice *dev)
{
	int ret;
	u32 usbbase;
	u32 tmpval = 0;

	if (nvt_get_chip_id() == CHIP_NA51055)
		usbbase = IOADDR_USB_REG_BASE;
	else
		usbbase = IOADDR_USB528_REG_BASE;

	/* Host Reset */
	writel((readl((volatile unsigned long *)(usbbase+0x10)) | 0x2), (volatile unsigned long *)(usbbase+0x10));

	/* A_BUS_REQ[4] = 0 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x80));
	tmpval &= ~(0x1<<4);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x80));

	/* A_BUS_DROP[5] = 1 */
	tmpval = readl((volatile unsigned long *)(usbbase+0x80));
	tmpval |= (0x1<<5);
	writel(tmpval, (volatile unsigned long *)(usbbase+0x80));


	/* Enable Reset*/
	tmpval = readl((volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));
	tmpval &= ~(0x1<<19);
	writel(tmpval, (volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));

	udelay(10);

	/* Disable Reset*/
	tmpval = readl((volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));
	tmpval |= 0x1<<19;
	writel(tmpval, (volatile unsigned long *)(IOADDR_CG_REG_BASE+0x84));


	ret = ehci_deregister(dev);
	if (ret)
		return ret;

	return 0;
}

static const struct udevice_id ehci_usb_ids[] = {
	{ .compatible = "nvt,ehci-nvtivot" },
	{ }
};

U_BOOT_DRIVER(usb_ehci) = {
	.name	= "ehci_nvt",
	.id	= UCLASS_USB,
	.of_match = ehci_usb_ids,
	.ofdata_to_platdata = ehci_usb_ofdata_to_platdata,
	.probe = ehci_usb_probe,
	.remove = ehci_usb_remove,
	.ops	= &ehci_usb_ops,
	.priv_auto_alloc_size = sizeof(struct uboot_ehci_nvt_priv_data),
//	.platdata_auto_alloc_size = sizeof(struct exynos_ehci_platdata),
	.flags	= DM_FLAG_ALLOC_PRIV_DMA,
};
#endif
