/*
 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
 *              http://www.samsung.com
 *
 * Author: Sung-Hyun Na <sunghyun.na@samsung.com>
 * Author: Minho Lee <minho55.lee@samsung.com>
 *
 * Chip Abstraction Layer for USB PHY
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 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.
 */

#ifdef __KERNEL__

#ifndef __EXCITE__
#include <linux/delay.h>
#include <linux/io.h>
#endif

#include <linux/platform_device.h>

#else

#include "types.h"
#include "customfunctions.h"
#include "mct.h"

#endif

#include "phy-samsung-usb-cal.h"
#include "phy-samsung-usb3-cal.h"

#define USB_SS_TX_TUNE_PCS

//#define USB_MUX_UTMI_ENABLE

enum exynos_usbcon_cr {
	USBCON_CR_ADDR = 0,
	USBCON_CR_DATA = 1,
	USBCON_CR_READ = 18,
	USBCON_CR_WRITE = 19,
};

static u16 samsung_exynos_cal_cr_access(struct exynos_usbphy_info *usbphy_info,
		enum exynos_usbcon_cr cr_bit, u16 data)
{
	void __iomem *base;
	u32 phyreg0;
	u32 phyreg1;
	u32 loop;
	u32 loop_cnt;

	if (usbphy_info->used_phy_port != -1) {
		if (usbphy_info->used_phy_port == 0)
			base = usbphy_info->regs_base;
		else
			base = usbphy_info->regs_base_2nd;

	} else
		base = usbphy_info->regs_base;

	/* Clear CR port register */
	phyreg0 = readl(base + EXYNOS_USBCON_PHYREG0);
	phyreg0 &= ~(0xfffff);
	writel(phyreg0, base + EXYNOS_USBCON_PHYREG0);

	/* Set Data for cr port */
	phyreg0 &= ~PHYREG0_CR_DATA_MASK;
	phyreg0 |= PHYREG0_CR_DATA_IN(data);
	writel(phyreg0, base + EXYNOS_USBCON_PHYREG0);

	if (cr_bit == USBCON_CR_ADDR)
		loop = 1;
	else
		loop = 2;

	for (loop_cnt = 0; loop_cnt < loop; loop_cnt++) {
		u32 trigger_bit = 0;
		u32 handshake_cnt = 2;
		/* Trigger cr port */
		if (cr_bit == USBCON_CR_ADDR)
			trigger_bit = PHYREG0_CR_CR_CAP_ADDR;
		else {
			if (loop_cnt == 0)
				trigger_bit = PHYREG0_CR_CR_CAP_DATA;
			else {
				if (cr_bit == USBCON_CR_READ)
					trigger_bit = PHYREG0_CR_READ;
				else
					trigger_bit = PHYREG0_CR_WRITE;
			}
		}
		/* Handshake Procedure */
		do {
			u32 usec = 100;
			if (handshake_cnt == 2)
				phyreg0 |= trigger_bit;
			else
				phyreg0 &= ~trigger_bit;
			writel(phyreg0, base + EXYNOS_USBCON_PHYREG0);

			/* Handshake */
			do {
				phyreg1 = readl(base + EXYNOS_USBCON_PHYREG1);
				if ((handshake_cnt == 2)
						&& (phyreg1 & PHYREG1_CR_ACK))
					break;
				else if ((handshake_cnt == 1)
						&& !(phyreg1 & PHYREG1_CR_ACK))
					break;

				udelay(1);
			} while (usec-- > 0);

			if (!usec)
				pr_err("CRPORT handshake timeout1 (0x%08x)\n",
						phyreg0);

			udelay(5);
			handshake_cnt--;
		} while (handshake_cnt != 0);
		udelay(50);
	}
	return (u16) ((phyreg1 & PHYREG1_CR_DATA_OUT_MASK) >> 1);
}

void samsung_exynos_cal_cr_write(struct exynos_usbphy_info *usbphy_info,
		u16 addr, u16 data)
{
	samsung_exynos_cal_cr_access(usbphy_info, USBCON_CR_ADDR, addr);
	samsung_exynos_cal_cr_access(usbphy_info, USBCON_CR_WRITE, data);
}

u16 samsung_exynos_cal_cr_read(struct exynos_usbphy_info *usbphy_info, u16 addr)
{
	samsung_exynos_cal_cr_access(usbphy_info, USBCON_CR_ADDR, addr);
	return samsung_exynos_cal_cr_access(usbphy_info, USBCON_CR_READ, 0);
}

void samsung_exynos_cal_usb3phy_tune_fix_rxeq(
	struct exynos_usbphy_info *usbphy_info)
{
	u16 reg;
	struct exynos_usbphy_ss_tune *tune = usbphy_info->ss_tune;

	if (!tune)
		return;

	reg = samsung_exynos_cal_cr_read(usbphy_info, 0x1006);
	reg &= ~(1 << 6);
	samsung_exynos_cal_cr_write(usbphy_info, 0x1006, reg);

	udelay(10);

	reg |= (1 << 7);
	samsung_exynos_cal_cr_write(usbphy_info, 0x1006, reg);

	udelay(10);

	reg &= ~(0x7 << 0x8);
	reg |= (tune->fix_rxeq_value << 8);
	samsung_exynos_cal_cr_write(usbphy_info, 0x1006, reg);

	udelay(10);

	reg |= (1 << 11);
	samsung_exynos_cal_cr_write(usbphy_info, 0x1006, reg);

	printk("Reg RX_OVRD_IN_HI : 0x%x\n",
		samsung_exynos_cal_cr_read(usbphy_info, 0x1006));

	printk("Reg RX_CDR_CDR_FSM_DEBUG : 0x%x\n",
		samsung_exynos_cal_cr_read(usbphy_info, 0x101c));
}

void samsung_exynos_cal_usb3phy_tune_adaptive_eq(
	struct exynos_usbphy_info *usbphy_info, u8 eq_fix)
{
	u16 reg;

	reg = samsung_exynos_cal_cr_read(usbphy_info, 0x1006);
	if (eq_fix) {
		reg |= (1 << 6);
		reg &= ~(1 << 7);
	} else {
		reg &= ~(1 << 6);
		reg |= (1 << 7);
	}
	samsung_exynos_cal_cr_write(usbphy_info, 0x1006, reg);
}

void samsung_exynos_cal_usb3phy_tune_chg_rxeq(
	struct exynos_usbphy_info *usbphy_info, u8 eq_val)
{
	u16 reg;

	reg = samsung_exynos_cal_cr_read(usbphy_info, 0x1006);
	reg &= ~(0x7 << 0x8);
	reg |= ((eq_val & 0x7) << 8);
	reg |= (1 << 11);
	samsung_exynos_cal_cr_write(usbphy_info, 0x1006, reg);
}

void samsung_exynos_cal_usb3phy_dp_altmode_set_phy_enable(
		struct exynos_usbphy_info *usbphy_info, int dp_phy_port)
{
	void __iomem *base;
	u32 reg;
	u32 powerdown_ssp;

	if (dp_phy_port != -1) {
		if (dp_phy_port == 0)
			base = usbphy_info->regs_base;
		else
			base = usbphy_info->regs_base_2nd;

	} else
		base = usbphy_info->regs_base;

	/* powerdown_ssp -> disable */
	reg = readl(base + EXYNOS_USBCON_PHYTEST);
	powerdown_ssp = PHYTEST_POWERDOWN_SSP_EXT(reg);

	/* when reverse connection as DP Alt mode, usb hs of phy0 should not be reset. */
	if (dp_phy_port == 0 && powerdown_ssp == 0)
		return;

	reg &= ~PHYTEST_POWERDOWN_SSP;
	writel(reg, base + EXYNOS_USBCON_PHYTEST);

	/* PHY_RESET_SEL -> 1 */
	reg = readl(base + EXYNOS_USBCON_PHYPARAM1);
	reg |= PHYPARAM1_PHY_RESET_SEL;
	writel(reg, base + EXYNOS_USBCON_PHYPARAM1);

	/* phy_sw_rst -> 1 */
	reg = readl(base + EXYNOS_USBCON_LINKSYSTEM);
	reg |= LINKSYSTEM_PHY_SW_RESET;
	writel(reg, base + EXYNOS_USBCON_LINKSYSTEM);

	udelay(10);

	/* phy_sw_rst -> 0 */
	reg = readl(base + EXYNOS_USBCON_LINKSYSTEM);
	reg &= ~LINKSYSTEM_PHY_SW_RESET;
	writel(reg, base + EXYNOS_USBCON_LINKSYSTEM);

	udelay(10);

	/* PHY_RESET_SEL -> 0 */
	reg = readl(base + EXYNOS_USBCON_PHYPARAM1);
	reg &= ~PHYPARAM1_PHY_RESET_SEL;
	writel(reg, base + EXYNOS_USBCON_PHYPARAM1);

	udelay(75);
}

void samsung_exynos_cal_usb3phy_dp_altmode_clear_phy_enable(
		struct exynos_usbphy_info *usbphy_info, int dp_phy_port)
{
	void __iomem *base;
	u32 reg;

	if (dp_phy_port != -1) {
		if (dp_phy_port == 0)
			base = usbphy_info->regs_base;
		else
			base = usbphy_info->regs_base_2nd;

	} else
		base = usbphy_info->regs_base;

	/* powerdown_ssp -> enable */
	reg = readl(base + EXYNOS_USBCON_PHYTEST);
	reg |= PHYTEST_POWERDOWN_SSP;
	writel(reg, base + EXYNOS_USBCON_PHYTEST);

	/* PHY_RESET_SEL -> 1 */
	reg = readl(base + EXYNOS_USBCON_PHYPARAM1);
	reg |= PHYPARAM1_PHY_RESET_SEL;
	writel(reg, base + EXYNOS_USBCON_PHYPARAM1);

	/* phy_sw_rst -> 1 */
	reg = readl(base + EXYNOS_USBCON_LINKSYSTEM);
	reg |= LINKSYSTEM_PHY_SW_RESET;
	writel(reg, base + EXYNOS_USBCON_LINKSYSTEM);

	udelay(10);

	/* phy_sw_rst -> 0 */
	reg = readl(base + EXYNOS_USBCON_LINKSYSTEM);
	reg &= ~LINKSYSTEM_PHY_SW_RESET;
	writel(reg, base + EXYNOS_USBCON_LINKSYSTEM);

	udelay(10);

	/* PHY_RESET_SEL -> 0 */
	reg = readl(base + EXYNOS_USBCON_PHYPARAM1);
	reg &= ~PHYPARAM1_PHY_RESET_SEL;
	writel(reg, base + EXYNOS_USBCON_PHYPARAM1);

	udelay(75);
}

void samsung_exynos_cal_usb3phy_dp_altmode_set_ss_disable(
		struct exynos_usbphy_info *usbphy_info, int dp_phy_port)
{
	u32 addr, reg;

	/* backup used_phy_port */
	int used_phy_port_org = usbphy_info->used_phy_port;
	usbphy_info->used_phy_port = dp_phy_port;

	/* SUP.MPLL_OVRD_IN_LO    16��h11
	   [1] MPLL_EN_OVRD = 1
	   [0] MPLL_EN = 0 */
	addr = 0x0011;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg =  (reg & ~(0x3<<0)) | 0x2<<0;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.TX_OVRD_IN_LO    16'h1N00
	   [9] TX_CM_EN_OVRD = 1
	   [8] TX_CM_EN = 0 */
	addr = 0x1000;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0x3<<8)) | 0x2<<8;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.RX_OVRD_IN_LO    16'h1N05
	   [13] RX_LOS_EN_OVRD = 1
	   [12] RX_LOS_EN = 1 */
	addr = 0x1005;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0x3<<12)) | 0x3<<12;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.TX_OVRD_IN_LO    16'h1N00
	   [7] TX_EN_OVRD = 1
	   [6] TX_EN = 0
	   [5] TX_DATA_EN_OVRD = 1
	   [4] TX_DATA_EN = 0 */
	addr = 0x1000;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0xf<<4)) | 0xa<<4;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.RX_OVRD_IN_LO    16'h1N05
	   [5] RX_DATA_EN_OVRD = 1
	   [4] RX_DATA_EN = 0
	   [3] RX_PLL_EN_OVRD = 1
	   [2] RX_PLL_EN = 0*/
	addr = 0x1005;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0xf<<2)) | 0xa<<2;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.RX_OVRD_IN_LO    16'h1N05
	   [11] RX_TERM_EN_OVRD = 1
	   [10] RX_TERM_EN = 0*/
	addr = 0x1005;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0x2<<10)) | 0x2<<10;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.RX_OVRD_IN_HI    16'h1N06
	   [13] RX_RESET_OVRD = 1
	   [12] RX_RESET = 1*/
	addr = 0x1006;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0x3<<12)) | 0x3<<12;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* restore used_phy_port */
	usbphy_info->used_phy_port = used_phy_port_org;
}

void samsung_exynos_cal_usb3phy_dp_altmode_clear_ss_disable(
		struct exynos_usbphy_info *usbphy_info, int dp_phy_port)
{
	u32 addr, reg;

	/* backup used_phy_port */
	int used_phy_port_org = usbphy_info->used_phy_port;
	usbphy_info->used_phy_port = dp_phy_port;

	/* SUP.MPLL_OVRD_IN_LO    16��h11
	   [1] MPLL_EN_OVRD = 0
	   [0] MPLL_EN = 0*/
	addr = 0x0011;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg =  (reg & ~(0x3<<0)) | 0x0<<0;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.TX_OVRD_IN_LO    16'h1N00
	   [9] TX_CM_EN_OVRD = 0
	   [8] TX_CM_EN = 0 */
	addr = 0x1000;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0x3<<8)) | 0x0<<8;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.RX_OVRD_IN_LO    16'h1N05
	   [13] RX_LOS_EN_OVRD = 0
	   [12] RX_LOS_EN = 0 */
	addr = 0x1005;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0x3<<12)) | 0x0<<12;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.TX_OVRD_IN_LO    16'h1N00
	   [7] TX_EN_OVRD = 0
	   [6] TX_EN = 0
	   [5] TX_DATA_EN_OVRD = 0
	   [4] TX_DATA_EN = 0 */
	addr = 0x1000;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0xf<<4)) | 0x0<<4;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.RX_OVRD_IN_LO    16'h1N05
	   [5] RX_DATA_EN_OVRD = 0
	   [4] RX_DATA_EN = 0
	   [3] RX_PLL_EN_OVRD = 0
	   [2] RX_PLL_EN = 0 */
	addr = 0x1005;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0xf<<2)) | 0x0<<2;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.RX_OVRD_IN_LO    16'h1N05
	   [11] RX_TERM_EN_OVRD = 0
	   [10] RX_TERM_EN = 0*/
	addr = 0x1005;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0x2<<10)) | 0x0<<10;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* LANEN.RX_OVRD_IN_HI    16'h1N06
	   [13] RX_RESET_OVRD = 0
	   [12] RX_RESET = 0*/
	addr = 0x1006;
	reg = samsung_exynos_cal_cr_read(usbphy_info, addr);
	reg = (reg & ~(0x3<<12)) | 0x0<<12;
	samsung_exynos_cal_cr_write(usbphy_info, addr, reg);

	/* restore used_phy_port */
	usbphy_info->used_phy_port = used_phy_port_org;
}

static void exynos_cal_ss_enable(struct exynos_usbphy_info *info,
	u32 *arg_phyclkrst, u8 port_num)
{
	u32 phyclkrst = *arg_phyclkrst;
	u32 phypipe;
	u32 phyparam0;
	u32 phyreg0;
	void *reg_base;

	if (port_num == 0)
		reg_base = info->regs_base;
	else
		reg_base = info->regs_base_2nd;

	if (info->common_block_disable) {
		/* Disable Common Block in suspend/sleep mode */
		if (info->version < EXYNOS_USBCON_VER_01_0_1)
			phyclkrst &= ~PHYCLKRST_COMMONONN;
		else
			phyclkrst |= PHYCLKRST_COMMONONN;
	} else {
		/* Enable Common Block in suspend/sleep mode */
		if (info->version < EXYNOS_USBCON_VER_01_0_1)
			phyclkrst |= PHYCLKRST_COMMONONN;
		else
			phyclkrst &= ~PHYCLKRST_COMMONONN;
	}
	/* Disable Common block control by link */
	phyclkrst &= ~PHYCLKRST_EN_UTMISUSPEND;

	/* Digital Supply Mode : normal operating mode */
	phyclkrst |= PHYCLKRST_RETENABLEN;

	/* ref. clock enable for ss function */
	phyclkrst |= PHYCLKRST_REF_SSP_EN;

	phyparam0 = readl(info->regs_base + EXYNOS_USBCON_PHYPARAM0);
	if (info->refsel == USBPHY_REFSEL_DIFF_PAD)
		phyparam0 |= PHYPARAM0_REF_USE_PAD;
	else
		phyparam0 &= ~PHYPARAM0_REF_USE_PAD;
	writel(phyparam0, reg_base + EXYNOS_USBCON_PHYPARAM0);

	if (info->version >= EXYNOS_USBCON_VER_01_0_1)
		phyreg0 = readl(reg_base + EXYNOS_USBCON_PHYREG0);

	switch (info->refclk) {
	case USBPHY_REFCLK_DIFF_100MHZ:
		phyclkrst &= ~PHYCLKRST_MPLL_MULTIPLIER_MASK;
		phyclkrst |= PHYCLKRST_MPLL_MULTIPLIER(0x00);
		phyclkrst &= ~PHYCLKRST_REF_CLKDIV2;
		if (info->version == EXYNOS_USBCON_VER_01_0_1) {
			phyreg0 &= ~PHYREG0_SSC_REFCLKSEL_MASK;
			phyreg0 |= PHYREG0_SSC_REFCLKSEL(0x00);
		} else {
			phyclkrst &= ~PHYCLKRST_SSC_REFCLKSEL_MASK;
			phyclkrst |= PHYCLKRST_SSC_REFCLKSEL(0x00);
		}
		break;
	case USBPHY_REFCLK_DIFF_26MHZ:
	case USBPHY_REFCLK_DIFF_52MHZ:
		phyclkrst &= ~PHYCLKRST_MPLL_MULTIPLIER_MASK;
		phyclkrst |= PHYCLKRST_MPLL_MULTIPLIER(0x60);
		if (info->refclk & 0x40)
			phyclkrst |= PHYCLKRST_REF_CLKDIV2;
		else
			phyclkrst &= ~PHYCLKRST_REF_CLKDIV2;
		if (info->version == EXYNOS_USBCON_VER_01_0_1) {
			phyreg0 &= ~PHYREG0_SSC_REFCLKSEL_MASK;
			phyreg0 |= PHYREG0_SSC_REFCLKSEL(0x108);
		} else {
			phyclkrst &= ~PHYCLKRST_SSC_REFCLKSEL_MASK;
			phyclkrst |= PHYCLKRST_SSC_REFCLKSEL(0x108);
		}
		break;
	case USBPHY_REFCLK_DIFF_24MHZ:
	case USBPHY_REFCLK_EXT_24MHZ:
		phyclkrst &= ~PHYCLKRST_MPLL_MULTIPLIER_MASK;
		phyclkrst |= PHYCLKRST_MPLL_MULTIPLIER(0x00);
		phyclkrst &= ~PHYCLKRST_REF_CLKDIV2;
		if (info->version != EXYNOS_USBCON_VER_01_0_1) {
			phyclkrst &= ~PHYCLKRST_SSC_REFCLKSEL_MASK;
			phyclkrst |= PHYCLKRST_SSC_REFCLKSEL(0x88);
		} else {
			phyreg0 &= ~PHYREG0_SSC_REFCLKSEL_MASK;
			phyreg0 |= PHYREG0_SSC_REFCLKSEL(0x88);
		}
		break;
	case USBPHY_REFCLK_DIFF_20MHZ:
	case USBPHY_REFCLK_DIFF_19_2MHZ:
		phyclkrst &= ~PHYCLKRST_MPLL_MULTIPLIER_MASK;
		phyclkrst &= ~PHYCLKRST_REF_CLKDIV2;
		if (info->version == EXYNOS_USBCON_VER_01_0_1)
			phyreg0 &= ~PHYREG0_SSC_REFCLKSEL_MASK;
		else
			phyclkrst &= ~PHYCLKRST_SSC_REFCLKSEL_MASK;
		break;
	default:
		break;
	}
	/* SSC Enable */
	if (info->ss_tune) {
		u32 range = info->ss_tune->ssc_range;

		if (info->ss_tune->enable_ssc)
			phyclkrst |= PHYCLKRST_SSC_EN;
		else
			phyclkrst &= ~PHYCLKRST_SSC_EN;

		if (info->version != EXYNOS_USBCON_VER_01_0_1) {
			phyclkrst &= ~PHYCLKRST_SSC_RANGE_MASK;
			phyclkrst |= PHYCLKRST_SSC_RANGE(range);
		} else {
			phyreg0 &= ~PHYREG0_SSC_RANGE_MASK;
			phyreg0 |= PHYREG0_SSC_RAMGE(range);
		}
	} else {
		/* Default Value : SSC Enable */
		phyclkrst |= PHYCLKRST_SSC_EN;
		if (info->version != EXYNOS_USBCON_VER_01_0_1) {
			phyclkrst &= ~PHYCLKRST_SSC_RANGE_MASK;
			phyclkrst |= PHYCLKRST_SSC_RANGE(0x0);
		} else {
			phyreg0 &= ~PHYREG0_SSC_RANGE_MASK;
			phyreg0 |= PHYREG0_SSC_RAMGE(0x0);
		}
	}

	/* Select UTMI CLOCK 0 : PHY CLOCK, 1 : FREE CLOCK */
	phypipe = readl(reg_base + EXYNOS_USBCON_PHYPIPE);
	phypipe |= PHYPIPE_PHY_CLOCK_SEL;
	writel(phypipe, reg_base + EXYNOS_USBCON_PHYPIPE);

	if (info->version >= EXYNOS_USBCON_VER_01_0_1)
		writel(phyreg0, reg_base + EXYNOS_USBCON_PHYREG0);

	*arg_phyclkrst = phyclkrst;
}

static void exynos_cal_ss_power_down(struct exynos_usbphy_info *info, bool en)
{
	u32 phytest;
	u32 phyutmi;

	phytest = readl(info->regs_base + EXYNOS_USBCON_PHYTEST);
	if (en == 1) {
		phytest &= ~PHYTEST_POWERDOWN_HSP;
		phytest &= ~PHYTEST_POWERDOWN_SSP;
		writel(phytest, info->regs_base + EXYNOS_USBCON_PHYTEST);
		if (info->used_phy_port == 1) {
			void *reg_base = info->regs_base_2nd;
#if 0		/* when reverse connection as DP Alt mode, early set powerdown_ssp of phy0 to 0. */
			phytest |= PHYTEST_POWERDOWN_SSP;
			writel(phytest, info->regs_base + EXYNOS_USBCON_PHYTEST);
#endif

			phytest = readl(reg_base + EXYNOS_USBCON_PHYTEST);
			phytest &= ~PHYTEST_POWERDOWN_SSP;
			writel(phytest, reg_base + EXYNOS_USBCON_PHYTEST);

			/* force suspend phy1 hs */
			phyutmi = readl(reg_base + EXYNOS_USBCON_PHYUTMI);
			phyutmi |= PHYUTMI_FORCESLEEP;
			phyutmi |= PHYUTMI_FORCESUSPEND;
			writel(phyutmi, reg_base + EXYNOS_USBCON_PHYUTMI);
		}
	} else {
		phytest |= PHYTEST_POWERDOWN_HSP;
		phytest |= PHYTEST_POWERDOWN_SSP;
		writel(phytest, info->regs_base + EXYNOS_USBCON_PHYTEST);
		if (info->used_phy_port == 1) {
			void *reg_base = info->regs_base_2nd;

			phytest = readl(reg_base + EXYNOS_USBCON_PHYTEST);
			phytest |= PHYTEST_POWERDOWN_HSP;
			phytest |= PHYTEST_POWERDOWN_SSP;
			writel(phytest, reg_base + EXYNOS_USBCON_PHYTEST);
		}
	}
}

static void exynos_cal_hs_enable(struct exynos_usbphy_info *info,
	u32 *arg_phyclkrst)
{
	u32 phyclkrst = *arg_phyclkrst;
	u32 hsphyplltune;

	if(info->version == EXYNOS_USBCON_VER_02_1_2)
		hsphyplltune = readl(info->regs_base + EXYNOS_USBCON_HSPHYPLLTUNE_K1);
	else
		hsphyplltune = readl(info->regs_base + EXYNOS_USBCON_HSPHYPLLTUNE);

	if ((info->version & 0xf0) >= 0x10) {
		/* Disable Common block control by link */
		phyclkrst |= PHYCLKRST_EN_UTMISUSPEND;
		phyclkrst |= PHYCLKRST_COMMONONN;
	} else {
		phyclkrst |= PHYCLKRST_EN_UTMISUSPEND;
		phyclkrst |= PHYCLKRST_COMMONONN;
	}

	/* Change PHY PLL Tune value */
	if (info->refclk == USBPHY_REFCLK_EXT_24MHZ)
		hsphyplltune |= HSPHYPLLTUNE_PLL_B_TUNE;
	else
		hsphyplltune &= ~HSPHYPLLTUNE_PLL_B_TUNE;
	hsphyplltune |= HSPHYPLLTUNE_PLL_P_TUNE(0xe);

	if(info->version == EXYNOS_USBCON_VER_02_1_2)
		writel(hsphyplltune, info->regs_base + EXYNOS_USBCON_HSPHYPLLTUNE_K1);
	else
		writel(hsphyplltune, info->regs_base + EXYNOS_USBCON_HSPHYPLLTUNE);

	*arg_phyclkrst = phyclkrst;
}

static void exynos_cal_hs_power_down(struct exynos_usbphy_info *info, bool en)
{
	u32 hsphyctrl;

	hsphyctrl = readl(info->regs_base + EXYNOS_USBCON_HSPHYCTRL);
	if (en) {
		if(info->version == EXYNOS_USBCON_VER_02_1_2)
			hsphyctrl &= ~HSPHYCTRL_SIDDQ_K1;
		else
			hsphyctrl &= ~HSPHYCTRL_SIDDQ;
		hsphyctrl &= ~HSPHYCTRL_PHYSWRST;
		hsphyctrl &= ~HSPHYCTRL_PHYSWRSTALL;
	} else {
		if(info->version == EXYNOS_USBCON_VER_02_1_2)
			hsphyctrl |= HSPHYCTRL_SIDDQ_K1;
		else
			hsphyctrl |= HSPHYCTRL_SIDDQ;
	}
	writel(hsphyctrl, info->regs_base + EXYNOS_USBCON_HSPHYCTRL);
}

static void exynos_cal_usbphy_q_ch(void *regs_base, u8 enable)
{
	u32 phy_resume;

	if (enable) {
		/* WA for Q-channel: disable all q-act from usb */
		phy_resume = readl(regs_base + EXYNOS_USBCON_PHYRESUME);
		phy_resume |= PHYRESUME_DIS_ID0_QACT;
		phy_resume |= PHYRESUME_DIS_VBUSVALID_QACT;
		phy_resume |= PHYRESUME_DIS_BVALID_QACT;
		phy_resume |= PHYRESUME_DIS_LINKGATE_QACT;
		//phy_resume |= PHYRESUME_DIS_BUSPEND_QACT;
		phy_resume &= ~PHYRESUME_FORCE_QACT;
		udelay(500);
		writel(phy_resume, regs_base + EXYNOS_USBCON_PHYRESUME);
		udelay(500);
		phy_resume = readl(regs_base + EXYNOS_USBCON_PHYRESUME);
		phy_resume |= PHYRESUME_FORCE_QACT;
		udelay(500);
		writel(phy_resume, regs_base + EXYNOS_USBCON_PHYRESUME);
	} else {
		phy_resume = readl(regs_base + EXYNOS_USBCON_PHYRESUME);
		phy_resume &= ~PHYRESUME_FORCE_QACT;
		phy_resume |= PHYRESUME_DIS_ID0_QACT;
		phy_resume |= PHYRESUME_DIS_VBUSVALID_QACT;
		phy_resume |= PHYRESUME_DIS_BVALID_QACT;
		phy_resume |= PHYRESUME_DIS_LINKGATE_QACT;
		//phy_resume |= PHYRESUME_DIS_BUSPEND_QACT;
		writel(phy_resume, regs_base + EXYNOS_USBCON_PHYRESUME);
	}
}

void samsung_exynos_cal_usb3phy_enable(struct exynos_usbphy_info *usbphy_info)
{
	void __iomem *regs_base = usbphy_info->regs_base;
	u32 version = usbphy_info->version;
	enum exynos_usbphy_refclk refclkfreq = usbphy_info->refclk;
	u32 phyutmi;
	u32 phyclkrst;
	u32 linkport;

	/* check phycon version */
	if (!usbphy_info->hw_version) {
		u32 phyversion = readl(regs_base);

		if (!phyversion) {
			usbphy_info->hw_version = -1;
			usbphy_info->used_phy_port = -1;
		} else {
			usbphy_info->hw_version = phyversion;
			usbphy_info->regs_base += 4;
			regs_base = usbphy_info->regs_base;

			if (usbphy_info->regs_base_2nd)
				usbphy_info->regs_base_2nd += 4;
		}
	}

	/* Set force q-channel */
	if ((version & 0xf) >= 0x01)
		exynos_cal_usbphy_q_ch(regs_base, 1);

	/* Select PHY MUX */
	if (usbphy_info->used_phy_port != -1) {
		u32 physel;

		physel = readl(regs_base + EXYNOS_USBCON_PHYSELECTION);
		if (usbphy_info->used_phy_port == 0) {
			physel &= ~PHYSEL_PIPE;
			physel &= ~PHYSEL_PIPE_CLK;
#if defined(USB_MUX_UTMI_ENABLE)
			/* UE_TASK: for utmi 2nd port test : will be removed */
			physel &= ~PHYSEL_UTMI_CLK;
			physel &= ~PHYSEL_UTMI;
			physel &= ~PHYSEL_SIDEBAND;
#endif
		} else {
			physel |= PHYSEL_PIPE;
			physel |= PHYSEL_PIPE_CLK;
#if defined(USB_MUX_UTMI_ENABLE)
			/* UE_TASK: for utmi 2nd port test : will be removed */
			physel |= PHYSEL_UTMI_CLK;
			physel |= PHYSEL_UTMI;
			physel |= PHYSEL_SIDEBAND;
#endif
		}
		writel(physel, regs_base + EXYNOS_USBCON_PHYSELECTION);
	}

	/* set phy clock & control HS phy */
	phyclkrst = readl(regs_base + EXYNOS_USBCON_PHYCLKRST);

	/* assert port_reset */
	phyclkrst |= PHYCLKRST_PORTRESET;

	/* Select Reference clock source path */
	phyclkrst &= ~PHYCLKRST_REFCLKSEL_MASK;
	phyclkrst |= PHYCLKRST_REFCLKSEL(usbphy_info->refsel);

	/* Select ref clk */
	phyclkrst &= ~PHYCLKRST_FSEL_MASK;
	phyclkrst |= PHYCLKRST_FSEL(refclkfreq & 0x3f);

	/* Additional control for 3.0 PHY */
	if ((EXYNOS_USBCON_VER_01_0_0 <= version)
			&& (version <= EXYNOS_USBCON_VER_01_MAX)) {
		exynos_cal_ss_enable(usbphy_info, &phyclkrst, 0);
		/* select used phy port */
		if (usbphy_info->used_phy_port == 1) {
			void *reg_2nd = usbphy_info->regs_base_2nd;

			exynos_cal_ss_enable(usbphy_info, &phyclkrst, 1);

			writel(phyclkrst,
			       reg_2nd + EXYNOS_USBCON_PHYCLKRST);
			udelay(10);

			phyclkrst &= ~PHYCLKRST_PORTRESET;
			writel(phyclkrst,
			       reg_2nd + EXYNOS_USBCON_PHYCLKRST);

			phyclkrst |= PHYCLKRST_PORTRESET;
		}
	} else if ((EXYNOS_USBCON_VER_02_0_0 <= version)
			&& (version <= EXYNOS_USBCON_VER_02_MAX))
		exynos_cal_hs_enable(usbphy_info, &phyclkrst);

	writel(phyclkrst, regs_base + EXYNOS_USBCON_PHYCLKRST);
	udelay(10);

	phyclkrst &= ~PHYCLKRST_PORTRESET;
	writel(phyclkrst, regs_base + EXYNOS_USBCON_PHYCLKRST);

	if ((EXYNOS_USBCON_VER_01_0_0 <= version)
			&& (version <= EXYNOS_USBCON_VER_01_MAX)) {
		exynos_cal_ss_power_down(usbphy_info, 1);
	} else if (version >= EXYNOS_USBCON_VER_02_0_0
			&& version <= EXYNOS_USBCON_VER_02_MAX) {
		exynos_cal_hs_power_down(usbphy_info, 1);
	}
	udelay(500);

	/* release force_sleep & force_suspend */
	phyutmi = readl(regs_base + EXYNOS_USBCON_PHYUTMI);
	phyutmi &= ~PHYUTMI_FORCESLEEP;
	phyutmi &= ~PHYUTMI_FORCESUSPEND;

	/* DP/DM Pull Down Control */
	phyutmi &= ~PHYUTMI_DMPULLDOWN;
	phyutmi &= ~PHYUTMI_DPPULLDOWN;

	/* Set VBUSVALID signal if VBUS pad is not used */
	if (usbphy_info->not_used_vbus_pad) {
		u32 linksystem;

		linksystem = readl(regs_base + EXYNOS_USBCON_LINKSYSTEM);
		linksystem |= LINKSYSTEM_FORCE_BVALID;
		linksystem |= LINKSYSTEM_FORCE_VBUSVALID;
		writel(linksystem, regs_base + EXYNOS_USBCON_LINKSYSTEM);
#if defined(USB_MUX_UTMI_ENABLE)
		/* UE_TASK: for utmi 2nd port test : will be removed */
		if (usbphy_info->regs_base_2nd && usbphy_info->used_phy_port == 1) {
			linksystem = readl(usbphy_info->regs_base_2nd + EXYNOS_USBCON_LINKSYSTEM);
			linksystem |= LINKSYSTEM_FORCE_BVALID;
			linksystem |= LINKSYSTEM_FORCE_VBUSVALID;
			writel(linksystem, usbphy_info->regs_base_2nd + EXYNOS_USBCON_LINKSYSTEM);
		}
#endif
		phyutmi |= PHYUTMI_VBUSVLDEXTSEL;
		phyutmi |= PHYUTMI_VBUSVLDEXT;
	}

	/* disable OTG block and VBUS valid comparator */
	phyutmi &= ~PHYUTMI_DRVVBUS;
	phyutmi |= PHYUTMI_OTGDISABLE;
	writel(phyutmi, regs_base + EXYNOS_USBCON_PHYUTMI);

#if defined(USB_MUX_UTMI_ENABLE)
	/* UE_TASK: for utmi 2nd port test : will be removed */
	if (usbphy_info->regs_base_2nd && usbphy_info->used_phy_port == 1)
		writel(phyutmi, usbphy_info->regs_base_2nd + EXYNOS_USBCON_PHYUTMI);
#endif

	/* OVC io usage */
	linkport = readl(regs_base + EXYNOS_USBCON_LINKPORT);
	if (usbphy_info->use_io_for_ovc) {
		linkport &= ~LINKPORT_HOST_PORT_OVCR_U3_SEL;
		linkport &= ~LINKPORT_HOST_PORT_OVCR_U2_SEL;
	} else {
		linkport |= LINKPORT_HOST_PORT_OVCR_U3_SEL;
		linkport |= LINKPORT_HOST_PORT_OVCR_U2_SEL;
	}
	writel(linkport, regs_base + EXYNOS_USBCON_LINKPORT);

	if ((EXYNOS_USBCON_VER_02_0_0 <= version)
				&& (version <= EXYNOS_USBCON_VER_02_MAX)) {

		u32 hsphyctrl;

		hsphyctrl = readl(regs_base + EXYNOS_USBCON_HSPHYCTRL);
		hsphyctrl |= HSPHYCTRL_PHYSWRST;
		writel(hsphyctrl, regs_base + EXYNOS_USBCON_HSPHYCTRL);
		udelay(20);
		hsphyctrl = readl(regs_base + EXYNOS_USBCON_HSPHYCTRL);
		hsphyctrl &= ~HSPHYCTRL_PHYSWRST;
		writel(hsphyctrl, regs_base + EXYNOS_USBCON_HSPHYCTRL);
	}
}

void samsung_exynos_cal_usb3phy_late_enable(
	struct exynos_usbphy_info *usbphy_info)
{
	u32 version = usbphy_info->version;
	struct exynos_usbphy_ss_tune *tune = usbphy_info->ss_tune;

	if (EXYNOS_USBCON_VER_01_0_0 <= version
			&& version <= EXYNOS_USBCON_VER_01_MAX) {
		/* Set RXDET_MEAS_TIME[11:4] each reference clock */
		samsung_exynos_cal_cr_write(usbphy_info, 0x1010, 0x80);
		if (tune) {
#if defined(USB_SS_TX_TUNE_PCS)
			samsung_exynos_cal_cr_write(usbphy_info,
					0x1002, 0x4000 |
					(tune->tx_deemphasis_3p5db << 7) |
					tune->tx_swing_full);
#else
			samsung_exynos_cal_cr_write(usbphy_info, 0x1002, 0x0);
#endif
			/* Set RX_SCOPE_LFPS_EN */
			if (tune->rx_decode_mode) {
				samsung_exynos_cal_cr_write(usbphy_info,
							    0x1026, 0x1);
			}
			if (tune->set_crport_level_en) {
				/* Enable override los_bias, los_level and
				 * tx_vboost_lvl, Set los_bias to 0x5 and
				 * los_level to 0x9 */
				samsung_exynos_cal_cr_write(usbphy_info,
							    0x15, 0xA409);
				/* Set TX_VBOOST_LEVLE to tune->tx_boost_level */
				samsung_exynos_cal_cr_write(usbphy_info,
					0x12, tune->tx_boost_level<<13);
			}
			/* to set the charge pump proportional current */
			if (tune->set_crport_mpll_charge_pump) {
				samsung_exynos_cal_cr_write(usbphy_info,
							    0x30, 0xC0);
			}
			if (tune->enable_fixed_rxeq_mode) {
				samsung_exynos_cal_usb3phy_tune_fix_rxeq(
						usbphy_info);
			}
		}

		/* when reverse connection as DP Alt mode, early set ss_disable for power reduction. */
		if (usbphy_info->used_phy_port == 1)
			samsung_exynos_cal_usb3phy_dp_altmode_set_ss_disable(usbphy_info, 0);

	}
}

void samsung_exynos_cal_usb3phy_disable(struct exynos_usbphy_info *usbphy_info)
{
	void __iomem *regs_base = usbphy_info->regs_base;
	u32 version = usbphy_info->version;
	u32 phyutmi;
	u32 phyclkrst;

	phyclkrst = readl(regs_base + EXYNOS_USBCON_PHYCLKRST);
	phyclkrst |= PHYCLKRST_EN_UTMISUSPEND;
	phyclkrst |= PHYCLKRST_COMMONONN;
	phyclkrst |= PHYCLKRST_RETENABLEN;
	phyclkrst &= ~PHYCLKRST_REF_SSP_EN;
	phyclkrst &= ~PHYCLKRST_SSC_EN;
	/* Select Reference clock source path */
	phyclkrst &= ~PHYCLKRST_REFCLKSEL_MASK;
	phyclkrst |= PHYCLKRST_REFCLKSEL(usbphy_info->refsel);

	/* Select ref clk */
	phyclkrst &= ~PHYCLKRST_FSEL_MASK;
	phyclkrst |= PHYCLKRST_FSEL(usbphy_info->refclk & 0x3f);
	writel(phyclkrst, regs_base + EXYNOS_USBCON_PHYCLKRST);

	phyutmi = readl(regs_base + EXYNOS_USBCON_PHYUTMI);
	phyutmi &= ~PHYUTMI_IDPULLUP;
	phyutmi &= ~PHYUTMI_DRVVBUS;
	phyutmi |= PHYUTMI_FORCESUSPEND;
	phyutmi |= PHYUTMI_FORCESLEEP;
	if (usbphy_info->not_used_vbus_pad) {
		phyutmi &= ~PHYUTMI_VBUSVLDEXTSEL;
		phyutmi &= ~PHYUTMI_VBUSVLDEXT;
	}
	writel(phyutmi, regs_base + EXYNOS_USBCON_PHYUTMI);

	if ((EXYNOS_USBCON_VER_01_0_0 <= version)
			&& (version <= EXYNOS_USBCON_VER_01_MAX)) {
		exynos_cal_ss_power_down(usbphy_info, 0);
	} else if (version >= EXYNOS_USBCON_VER_02_0_0
			&& version <= EXYNOS_USBCON_VER_02_MAX) {
		exynos_cal_hs_power_down(usbphy_info, 0);
	}

	/* Clear VBUSVALID signal if VBUS pad is not used */
	if (usbphy_info->not_used_vbus_pad) {
		u32 linksystem;

		linksystem = readl(regs_base + EXYNOS_USBCON_LINKSYSTEM);
		linksystem &= ~LINKSYSTEM_FORCE_BVALID;
		linksystem &= ~LINKSYSTEM_FORCE_VBUSVALID;
		writel(linksystem, regs_base + EXYNOS_USBCON_LINKSYSTEM);
	}

	/* Set force q-channel */
	if ((version & 0xf) >= 0x01)
		exynos_cal_usbphy_q_ch(regs_base, 0);
}

void samsung_exynos_cal_usb3phy_config_host_mode(
		struct exynos_usbphy_info *usbphy_info)
{
	void __iomem *regs_base = usbphy_info->regs_base;
	u32 phyutmi;

	phyutmi = readl(regs_base + EXYNOS_USBCON_PHYUTMI);
	phyutmi |= PHYUTMI_DMPULLDOWN;
	phyutmi |= PHYUTMI_DPPULLDOWN;
	phyutmi &= ~PHYUTMI_VBUSVLDEXTSEL;
	phyutmi &= ~PHYUTMI_VBUSVLDEXT;
	writel(phyutmi, regs_base + EXYNOS_USBCON_PHYUTMI);
}

void samsung_exynos_cal_usb3phy_enable_dp_pullup(
		struct exynos_usbphy_info *usbphy_info)
{
	void __iomem *regs_base = usbphy_info->regs_base;
	u32 phyutmi;

	phyutmi = readl(regs_base + EXYNOS_USBCON_PHYUTMI);
	phyutmi |= PHYUTMI_VBUSVLDEXT;
	writel(phyutmi, regs_base + EXYNOS_USBCON_PHYUTMI);
}

void samsung_exynos_cal_usb3phy_disable_dp_pullup(
		struct exynos_usbphy_info *usbphy_info)
{
	void __iomem *regs_base = usbphy_info->regs_base;
	u32 phyutmi;

	phyutmi = readl(regs_base + EXYNOS_USBCON_PHYUTMI);
	phyutmi &= ~PHYUTMI_VBUSVLDEXT;
	writel(phyutmi, regs_base + EXYNOS_USBCON_PHYUTMI);
}

void samsung_exynos_cal_usb3phy_tune_each(
	struct exynos_usbphy_info *usbphy_info,
	enum exynos_usbphy_tune_para para,
	int val)
{
	void __iomem *regs_base = usbphy_info->regs_base;
	u32 reg;

#if defined(USB_MUX_UTMI_ENABLE)
	/* UE_TASK: for utmi 2nd port test : will be removed */
	if (usbphy_info->regs_base_2nd && usbphy_info->used_phy_port == 1)
		regs_base = usbphy_info->regs_base_2nd;
#endif

	if (para < 0x10000) {
		struct exynos_usbphy_hs_tune *tune = usbphy_info->hs_tune;

		reg = readl(regs_base + EXYNOS_USBCON_PHYPARAM0);
		switch ((int) para) {
		case USBPHY_TUNE_HS_COMPDIS:
			reg &= ~PHYPARAM0_COMPDISTUNE_MASK;
			reg |= PHYPARAM0_COMPDISTUNE(val);
			if (tune)
				tune->compdis = val;
			break;
		case USBPHY_TUNE_HS_OTG:
			reg &= ~PHYPARAM0_OTGTUNE_MASK;
			reg |= PHYPARAM0_OTGTUNE(val);
			if (tune)
				tune->otg = val;
			break;
		case USBPHY_TUNE_HS_SQRX:
			reg &= ~PHYPARAM0_SQRXTUNE_MASK;
			reg |= PHYPARAM0_SQRXTUNE(val);
			if (tune)
				tune->rx_sqrx = val;
			break;
		case USBPHY_TUNE_HS_TXFSLS:
			reg &= ~PHYPARAM0_TXFSLSTUNE_MASK;
			reg |= PHYPARAM0_TXFSLSTUNE(val);
			if (tune)
				tune->tx_fsls = val;
			break;
		case USBPHY_TUNE_HS_TXHSXV:
			reg &= ~PHYPARAM0_TXHSXVTUNE_MASK;
			reg |= PHYPARAM0_TXHSXVTUNE(val);
			if (tune)
				tune->tx_hsxv = val;
			break;
		case USBPHY_TUNE_HS_TXPREEMP:
			reg &= ~PHYPARAM0_TXPREEMPAMPTUNE_MASK;
			reg |= PHYPARAM0_TXPREEMPAMPTUNE(val);
			if (tune)
				tune->tx_pre_emp = val;
			break;
		case USBPHY_TUNE_HS_TXPREEMP_PLUS:
			if (val)
				reg |= PHYPARAM0_TXPREEMPPULSETUNE;
			else
				reg &= ~PHYPARAM0_TXPREEMPPULSETUNE;
			if (tune)
				tune->tx_pre_emp_puls = val & 0x1;
			break;
		case USBPHY_TUNE_HS_TXRES:
			reg &= ~PHYPARAM0_TXRESTUNE_MASK;
			reg |= PHYPARAM0_TXRESTUNE(val);
			if (tune)
				tune->tx_res = val;
			break;
		case USBPHY_TUNE_HS_TXRISE:
			reg &= ~PHYPARAM0_TXRISETUNE_MASK;
			reg |= PHYPARAM0_TXRISETUNE(val);
			if (tune)
				tune->tx_rise = val;
			break;
		case USBPHY_TUNE_HS_TXVREF:
			reg &= ~PHYPARAM0_TXVREFTUNE_MASK;
			reg |= PHYPARAM0_TXVREFTUNE(val);
			if (tune)
				tune->tx_vref = val;
			break;
		}
		writel(reg, regs_base + EXYNOS_USBCON_PHYPARAM0);
	} else {
		struct exynos_usbphy_ss_tune *tune = usbphy_info->ss_tune;

		if (usbphy_info->used_phy_port != -1) {
			if (usbphy_info->used_phy_port == 0)
				regs_base = usbphy_info->regs_base;
			else
				regs_base = usbphy_info->regs_base_2nd;
		}

		switch ((int) para) {
		case USBPHY_TUNE_SS_FIX_EQ:
			samsung_exynos_cal_usb3phy_tune_adaptive_eq(usbphy_info,
								    val);
			if (tune)
				tune->enable_fixed_rxeq_mode = val & 0x1;
			break;
		case USBPHY_TUNE_SS_RX_EQ:
			samsung_exynos_cal_usb3phy_tune_chg_rxeq(usbphy_info,
								 val);
			if (tune)
				tune->fix_rxeq_value = val & 0xf;
			break;
		case USBPHY_TUNE_SS_TX_BOOST:
			reg = readl(regs_base + EXYNOS_USBCON_PHYPARAM2);
			reg &= ~PHYPARAM2_TX_VBOOST_LVL_MASK;
			reg |= PHYPARAM2_TX_VBOOST_LVL(val);
			writel(reg, regs_base + EXYNOS_USBCON_PHYPARAM2);
			if (tune)
				tune->tx_boost_level = val;
			break;
		case USBPHY_TUNE_SS_TX_SWING:
#if !defined(USB_SS_TX_TUNE_PCS)
			reg = readl(regs_base + EXYNOS_USBCON_PHYPARAM1);
			reg &= ~PHYPARAM1_PCS_TXSWING_FULL_MASK;
			reg |= PHYPARAM1_PCS_TXSWING_FULL(val);
			writel(reg, regs_base + EXYNOS_USBCON_PHYPARAM1);
#else
			reg = samsung_exynos_cal_cr_read(usbphy_info, 0x1002);
			reg &= ~0x7f;
			reg |= val;
			samsung_exynos_cal_cr_write(usbphy_info,
							 0x1002, 0x4000 | reg);
#endif
			if (tune)
				tune->tx_swing_full = val;
			break;
		case USBPHY_TUNE_SS_TX_DEEMPHASIS:
#if !defined(USB_SS_TX_TUNE_PCS)
			reg = readl(regs_base + EXYNOS_USBCON_PHYPARAM1);
			reg &= ~PHYPARAM1_PCS_TXDEEMPH_3P5DB_MASK;
			reg |= PHYPARAM1_PCS_TXDEEMPH_3P5DB(val);
			writel(reg, regs_base + EXYNOS_USBCON_PHYPARAM1);
#else
			reg = samsung_exynos_cal_cr_read(usbphy_info, 0x1002);
			reg &= ~(0x3f << 7);
			reg |= (val & 0x3f) << 7;
			samsung_exynos_cal_cr_write(usbphy_info,
							 0x1002, 0x4000 | reg);
#endif
			if (tune)
				tune->tx_deemphasis_3p5db = val;
			break;
		}

	}
}

void samsung_exynos_cal_usb3phy_hs_tune_extract(
	struct exynos_usbphy_info *usbphy_info)
{
	struct exynos_usbphy_hs_tune *hs_tune;
	u32 reg;

	hs_tune = usbphy_info->hs_tune;

	if (!hs_tune)
		return;

	reg = readl(usbphy_info->regs_base + EXYNOS_USBCON_PHYPARAM0);

	hs_tune->tx_vref = PHYPARAM0_TXVREFTUNE_EXT(reg);
	hs_tune->tx_rise = PHYPARAM0_TXRISETUNE_EXT(reg);
	hs_tune->tx_res = PHYPARAM0_TXRESTUNE_EXT(reg);
	hs_tune->tx_pre_emp_puls = PHYPARAM0_TXPREEMPPULSETUNE_EXT(reg);
	hs_tune->tx_pre_emp = PHYPARAM0_TXPREEMPAMPTUNE_EXT(reg);
	hs_tune->tx_hsxv = PHYPARAM0_TXHSXVTUNE_EXT(reg);
	hs_tune->tx_fsls = PHYPARAM0_TXFSLSTUNE_EXT(reg);
	hs_tune->rx_sqrx = PHYPARAM0_SQRXTUNE_EXT(reg);
	hs_tune->otg = PHYPARAM0_OTGTUNE_EXT(reg);
	hs_tune->compdis = PHYPARAM0_COMPDISTUNE_EXT(reg);
}

void samsung_exynos_cal_usb3phy_tune_dev(struct exynos_usbphy_info *usbphy_info)
{
	void __iomem *regs_base = usbphy_info->regs_base;
	u32 reg;

#if defined(USB_MUX_UTMI_ENABLE)
	/* UE_TASK: for utmi 2nd port test : will be removed */
	if (usbphy_info->regs_base_2nd && usbphy_info->used_phy_port == 1)
		regs_base = usbphy_info->regs_base_2nd;
#endif

	/* Set the LINK Version Control and Frame Adjust Value */
	reg = readl(regs_base + EXYNOS_USBCON_LINKSYSTEM);
	reg &= ~LINKSYSTEM_FLADJ_MASK;
	reg |= LINKSYSTEM_FLADJ(0x20);
	reg |= LINKSYSTEM_XHCI_VERSION_CONTROL;
	writel(reg, regs_base + EXYNOS_USBCON_LINKSYSTEM);

	/* Tuning the HS Block of phy */
	if (usbphy_info->hs_tune) {
		struct exynos_usbphy_hs_tune *tune = usbphy_info->hs_tune;

		/* Set tune value for 2.0(HS/FS) function */
		reg = readl(regs_base + EXYNOS_USBCON_PHYPARAM0);
		/* TX VREF TUNE */
		reg &= ~PHYPARAM0_TXVREFTUNE_MASK;
		reg |= PHYPARAM0_TXVREFTUNE(tune->tx_vref);
		/* TX RISE TUNE */
		reg &= ~PHYPARAM0_TXRISETUNE_MASK;
		reg |= PHYPARAM0_TXRISETUNE(tune->tx_rise);
		/* TX RES TUNE */
		reg &= ~PHYPARAM0_TXRESTUNE_MASK;
		reg |= PHYPARAM0_TXRESTUNE(tune->tx_res);
		/* TX PRE EMPHASIS PULS */
		if (tune->tx_pre_emp_puls)
			reg |= PHYPARAM0_TXPREEMPPULSETUNE;
		else
			reg &= ~PHYPARAM0_TXPREEMPPULSETUNE;
		/* TX PRE EMPHASIS */
		reg &= ~PHYPARAM0_TXPREEMPAMPTUNE_MASK;
		reg |= PHYPARAM0_TXPREEMPAMPTUNE(tune->tx_pre_emp);
		/* TX HS XV TUNE */
		reg &= ~PHYPARAM0_TXHSXVTUNE_MASK;
		reg |= PHYPARAM0_TXHSXVTUNE(tune->tx_hsxv);
		/* TX FSLS TUNE */
		reg &= ~PHYPARAM0_TXFSLSTUNE_MASK;
		reg |= PHYPARAM0_TXFSLSTUNE(tune->tx_fsls);
		/* RX SQ TUNE */
		reg &= ~PHYPARAM0_SQRXTUNE_MASK;
		reg |= PHYPARAM0_SQRXTUNE(tune->rx_sqrx);
		/* OTG TUNE */
		reg &= ~PHYPARAM0_OTGTUNE_MASK;
		reg |= PHYPARAM0_OTGTUNE(tune->otg);
		/* COM DIS TUNE */
		reg &= ~PHYPARAM0_COMPDISTUNE_MASK;
		reg |= PHYPARAM0_COMPDISTUNE(tune->compdis);

		writel(reg, regs_base + EXYNOS_USBCON_PHYPARAM0);
	}

	/* Tuning the SS Block of phy */
	if (usbphy_info->ss_tune) {
		struct exynos_usbphy_ss_tune *tune = usbphy_info->ss_tune;

		if (usbphy_info->used_phy_port != -1) {
			if (usbphy_info->used_phy_port == 0)
				regs_base = usbphy_info->regs_base;
			else
				regs_base = usbphy_info->regs_base_2nd;
		}
#if !defined(USB_SS_TX_TUNE_PCS)
		/* Set the PHY Signal Quality Tuning Value */
		reg = readl(regs_base + EXYNOS_USBCON_PHYPARAM1);
		/* TX SWING FULL */
		reg &= ~PHYPARAM1_PCS_TXSWING_FULL_MASK;
		reg |= PHYPARAM1_PCS_TXSWING_FULL(tune->tx_swing_full);
		/* TX DE EMPHASIS 3.5 dB */
		reg &= ~PHYPARAM1_PCS_TXDEEMPH_3P5DB_MASK;
		reg |= PHYPARAM1_PCS_TXDEEMPH_3P5DB(
				tune->tx_deemphasis_3p5db);
		writel(reg, regs_base + EXYNOS_USBCON_PHYPARAM1);
#endif

		/* Set vboost value for eye diagram */
		reg = readl(regs_base + EXYNOS_USBCON_PHYPARAM2);
		/* TX VBOOST Value */
		reg &= ~PHYPARAM2_TX_VBOOST_LVL_MASK;
		reg |= PHYPARAM2_TX_VBOOST_LVL(tune->tx_boost_level);
		/* LOS BIAS */
		reg &= ~PHYPARAM2_LOS_BIAS_MASK;
		reg |= PHYPARAM2_LOS_BIAS(tune->los_bias);
		writel(reg, regs_base + EXYNOS_USBCON_PHYPARAM2);

		/*
		 * Set pcs_rx_los_mask_val for 14nm PHY to mask the abnormal
		 * LFPS and glitches
		 */
		reg = readl(regs_base + EXYNOS_USBCON_PHYPCSVAL);
		reg &= ~PHYPCSVAL_PCS_RX_LOS_MASK_VAL_MASK;
		reg |= PHYPCSVAL_PCS_RX_LOS_MASK_VAL(tune->los_mask_val);
		writel(reg, regs_base + EXYNOS_USBCON_PHYPCSVAL);
	}
}

void samsung_exynos_cal_usb3phy_tune_host(
		struct exynos_usbphy_info *usbphy_info)
{
	void __iomem *regs_base = usbphy_info->regs_base;
	u32 reg;

#if defined(USB_MUX_UTMI_ENABLE)
	/* UE_TASK: for utmi 2nd port test : will be removed */
	if (usbphy_info->regs_base_2nd && usbphy_info->used_phy_port == 1)
		regs_base = usbphy_info->regs_base_2nd;
#endif

	/* Set the LINK Version Control and Frame Adjust Value */
	reg = readl(regs_base + EXYNOS_USBCON_LINKSYSTEM);
	reg &= ~LINKSYSTEM_FLADJ_MASK;
	reg |= LINKSYSTEM_FLADJ(0x20);
	reg |= LINKSYSTEM_XHCI_VERSION_CONTROL;
	writel(reg, regs_base + EXYNOS_USBCON_LINKSYSTEM);

	/* Tuning the HS Block of phy */
	if (usbphy_info->hs_tune) {
		struct exynos_usbphy_hs_tune *tune = usbphy_info->hs_tune;

		/* Set tune value for 2.0(HS/FS) function */
		reg = readl(regs_base + EXYNOS_USBCON_PHYPARAM0);
		/* TX VREF TUNE */
		reg &= ~PHYPARAM0_TXVREFTUNE_MASK;
		reg |= PHYPARAM0_TXVREFTUNE(tune->tx_vref);
		/* TX RISE TUNE */
		reg &= ~PHYPARAM0_TXRISETUNE_MASK;
		reg |= PHYPARAM0_TXRISETUNE(tune->tx_rise);
		/* TX RES TUNE */
		reg &= ~PHYPARAM0_TXRESTUNE_MASK;
		reg |= PHYPARAM0_TXRESTUNE(tune->tx_res);
		/* TX PRE EMPHASIS PULS */
		if (tune->tx_pre_emp_puls)
			reg |= PHYPARAM0_TXPREEMPPULSETUNE;
		else
			reg &= ~PHYPARAM0_TXPREEMPPULSETUNE;
		/* TX PRE EMPHASIS */
		reg &= ~PHYPARAM0_TXPREEMPAMPTUNE_MASK;
		reg |= PHYPARAM0_TXPREEMPAMPTUNE(tune->tx_pre_emp);
		/* TX HS XV TUNE */
		reg &= ~PHYPARAM0_TXHSXVTUNE_MASK;
		reg |= PHYPARAM0_TXHSXVTUNE(tune->tx_hsxv);
		/* TX FSLS TUNE */
		reg &= ~PHYPARAM0_TXFSLSTUNE_MASK;
		reg |= PHYPARAM0_TXFSLSTUNE(tune->tx_fsls);
		/* RX SQ TUNE */
		reg &= ~PHYPARAM0_SQRXTUNE_MASK;
		reg |= PHYPARAM0_SQRXTUNE(tune->rx_sqrx);
		/* OTG TUNE */
		reg &= ~PHYPARAM0_OTGTUNE_MASK;
		reg |= PHYPARAM0_OTGTUNE(tune->otg);
		/* COM DIS TUNE */
		reg &= ~PHYPARAM0_COMPDISTUNE_MASK;
		reg |= PHYPARAM0_COMPDISTUNE(tune->compdis);

		writel(reg, regs_base + EXYNOS_USBCON_PHYPARAM0);
	}

	/* Tuning the SS Block of phy */
	if (usbphy_info->ss_tune) {
		struct exynos_usbphy_ss_tune *tune = usbphy_info->ss_tune;

		if (usbphy_info->used_phy_port != -1) {
			if (usbphy_info->used_phy_port == 0)
				regs_base = usbphy_info->regs_base;
			else
				regs_base = usbphy_info->regs_base_2nd;
		}
#if !defined(USB_SS_TX_TUNE_PCS)
		/* Set the PHY Signal Quality Tuning Value */
		reg = readl(regs_base + EXYNOS_USBCON_PHYPARAM1);
		/* TX SWING FULL */
		reg &= ~PHYPARAM1_PCS_TXSWING_FULL_MASK;
		reg |= PHYPARAM1_PCS_TXSWING_FULL(tune->tx_swing_full);
		/* TX DE EMPHASIS 3.5 dB */
		reg &= ~PHYPARAM1_PCS_TXDEEMPH_3P5DB_MASK;
		reg |= PHYPARAM1_PCS_TXDEEMPH_3P5DB(
				tune->tx_deemphasis_3p5db);
		writel(reg, regs_base + EXYNOS_USBCON_PHYPARAM1);
#endif

		/* Set vboost value for eye diagram */
		reg = readl(regs_base + EXYNOS_USBCON_PHYPARAM2);
		/* TX VBOOST Value */
		reg &= ~PHYPARAM2_TX_VBOOST_LVL_MASK;
		reg |= PHYPARAM2_TX_VBOOST_LVL(tune->tx_boost_level);
		/* LOS BIAS */
		reg &= ~PHYPARAM2_LOS_BIAS_MASK;
		reg |= PHYPARAM2_LOS_BIAS(tune->los_bias);
		writel(reg, regs_base + EXYNOS_USBCON_PHYPARAM2);

		/*
		 * Set pcs_rx_los_mask_val for 14nm PHY to mask the abnormal
		 * LFPS and glitches
		 */
		reg = readl(regs_base + EXYNOS_USBCON_PHYPCSVAL);
		reg &= ~PHYPCSVAL_PCS_RX_LOS_MASK_VAL_MASK;
		reg |= PHYPCSVAL_PCS_RX_LOS_MASK_VAL(tune->los_mask_val);
		writel(reg, regs_base + EXYNOS_USBCON_PHYPCSVAL);
	}
}