blob: 04ec128bd438e3a04cb934a1ef3a69568f748a08 [file] [log] [blame]
/*
* Copyright (c) 2017 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Sung-Hyun Na <sunghyun.na@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.
*/
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/io.h>
#include "phy-samsung-usb-cal.h"
#include "phy-exynos-usb3p1.h"
#include "phy-exynos-usb3p1-reg.h"
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_LINK_CTRL);
phy_resume |= LINKCTRL_DIS_QACT_ID0;
phy_resume |= LINKCTRL_DIS_QACT_VBUS_VALID;
phy_resume |= LINKCTRL_DIS_QACT_BVALID;
phy_resume |= LINKCTRL_DIS_QACT_LINKGATE;
phy_resume &= ~LINKCTRL_FORCE_QACT;
udelay(500);
writel(phy_resume, regs_base + EXYNOS_USBCON_LINK_CTRL);
udelay(500);
phy_resume = readl(regs_base + EXYNOS_USBCON_LINK_CTRL);
phy_resume |= LINKCTRL_FORCE_QACT;
udelay(500);
writel(phy_resume, regs_base + EXYNOS_USBCON_LINK_CTRL);
} else {
phy_resume = readl(regs_base + EXYNOS_USBCON_LINK_CTRL);
phy_resume &= ~LINKCTRL_FORCE_QACT;
phy_resume |= LINKCTRL_DIS_QACT_ID0;
phy_resume |= LINKCTRL_DIS_QACT_VBUS_VALID;
phy_resume |= LINKCTRL_DIS_QACT_BVALID;
phy_resume |= LINKCTRL_DIS_QACT_LINKGATE;
writel(phy_resume, regs_base + EXYNOS_USBCON_LINK_CTRL);
}
}
static void link_vbus_filter_en(struct exynos_usbphy_info *info,
u8 enable)
{
u32 phy_resume;
phy_resume = readl(info->regs_base + EXYNOS_USBCON_LINK_CTRL);
if (enable)
phy_resume &= ~LINKCTRL_BUS_FILTER_BYPASS_MASK;
else
phy_resume |= LINKCTRL_BUS_FILTER_BYPASS(0xf);
writel(phy_resume, info->regs_base + EXYNOS_USBCON_LINK_CTRL);
}
static void phy_power_en(struct exynos_usbphy_info *info, u8 en)
{
u32 reg;
int main_version;
main_version = info->version & EXYNOS_USBCON_VER_MAJOR_VER_MASK;
if (main_version == EXYNOS_USBCON_VER_05_0_0) {
void *__iomem reg_base;
if (info->used_phy_port == 1)
reg_base = info->regs_base_2nd;
else
reg_base = info->regs_base;
/* 3.0 PHY Power Down control */
reg = readl(reg_base + EXYNOS_USBCON_PWR);
if (en)
reg &= ~(PWR_TEST_POWERDOWN_SSP);
else
reg |= (PWR_TEST_POWERDOWN_SSP);
writel(reg, reg_base + EXYNOS_USBCON_PWR);
} else if (main_version == EXYNOS_USBCON_VER_03_0_0) {
/* 2.0 PHY Power Down Control */
reg = readl(info->regs_base + EXYNOS_USBCON_HSP_TEST);
if (en)
reg &= ~HSP_TEST_SIDDQ;
else
reg |= HSP_TEST_SIDDQ;
writel(reg, info->regs_base + EXYNOS_USBCON_HSP_TEST);
}
}
static void phy_sw_rst_high(struct exynos_usbphy_info *info)
{
void __iomem *regs_base = info->regs_base;
int main_version;
u32 clkrst;
main_version = info->version & EXYNOS_USBCON_VER_MAJOR_VER_MASK;
if ((main_version == EXYNOS_USBCON_VER_05_0_0) &&
(info->used_phy_port == 1))
regs_base = info->regs_base_2nd;
clkrst = readl(regs_base + EXYNOS_USBCON_CLKRST);
clkrst |= CLKRST_PHY_SW_RST;
clkrst |= CLKRST_PHY_RST_SEL;
writel(clkrst, regs_base + EXYNOS_USBCON_CLKRST);
}
static void phy_sw_rst_low(struct exynos_usbphy_info *info)
{
void __iomem *regs_base = info->regs_base;
int main_version;
u32 clkrst;
main_version = info->version & EXYNOS_USBCON_VER_MAJOR_VER_MASK;
if ((main_version == EXYNOS_USBCON_VER_05_0_0) &&
(info->used_phy_port == 1))
regs_base = info->regs_base_2nd;
clkrst = readl(regs_base + EXYNOS_USBCON_CLKRST);
clkrst |= CLKRST_PHY_RST_SEL;
clkrst &= ~CLKRST_PHY_SW_RST;
clkrst &= ~CLKRST_PORT_RST;
writel(clkrst, regs_base + EXYNOS_USBCON_CLKRST);
}
void phy_exynos_usb_v3p1_pma_ready(struct exynos_usbphy_info *info)
{
void __iomem *regs_base = info->regs_base;
u32 reg;
reg = readl(regs_base + EXYNOS_USBCON_COMBO_PMA_CTRL);
reg &= ~PMA_LOW_PWRN;
writel(reg, regs_base + EXYNOS_USBCON_COMBO_PMA_CTRL);
udelay(1);
reg |= PMA_APB_SW_RST;
reg |= PMA_INIT_SW_RST;
reg |= PMA_CMN_SW_RST;
reg |= PMA_TRSV_SW_RST;
writel(reg, regs_base + EXYNOS_USBCON_COMBO_PMA_CTRL);
udelay(1);
reg &= ~PMA_APB_SW_RST;
reg &= ~PMA_INIT_SW_RST;
reg &= ~PMA_PLL_REF_REQ_MASK;
reg &= ~PMA_REF_FREQ_MASK;
reg |= PMA_REF_FREQ_SET(0x1);
writel(reg, regs_base + EXYNOS_USBCON_COMBO_PMA_CTRL);
reg = readl(regs_base + EXYNOS_USBCON_LINK_CTRL);
reg &= ~LINKCTRL_PIPE3_FORCE_EN;
writel(reg, regs_base + EXYNOS_USBCON_LINK_CTRL);
}
void phy_exynos_usb_v3p1_pma_sw_rst_release(struct exynos_usbphy_info *info)
{
void __iomem *regs_base = info->regs_base;
u32 reg;
/* Reset Release for USB/DP PHY */
reg = readl(regs_base + EXYNOS_USBCON_COMBO_PMA_CTRL);
reg &= ~PMA_CMN_SW_RST;
reg &= ~PMA_TRSV_SW_RST;
writel(reg, regs_base + EXYNOS_USBCON_COMBO_PMA_CTRL);
udelay(200);
/* Change pipe pclk to pipe3 */
reg = readl(regs_base + EXYNOS_USBCON_CLKRST);
reg |= CLKRST_LINK_PCLK_SEL;
writel(reg, regs_base + EXYNOS_USBCON_CLKRST);
}
void phy_exynos_usb_v3p1_pipe_ready(struct exynos_usbphy_info *info)
{
void __iomem *regs_base = info->regs_base;
u32 reg;
reg = readl(regs_base + EXYNOS_USBCON_LINK_CTRL);
reg &= ~LINKCTRL_PIPE3_FORCE_EN;
writel(reg, regs_base + EXYNOS_USBCON_LINK_CTRL);
}
void phy_exynos_usb_v3p1_pipe_ovrd(struct exynos_usbphy_info *info)
{
void __iomem *regs_base = info->regs_base;
u32 reg;
/* PMA Disable and force pipe3 signal for link */
reg = readl(regs_base + EXYNOS_USBCON_LINK_CTRL);
reg |= LINKCTRL_PIPE3_FORCE_EN;
reg &= ~LINKCTRL_PIPE3_FORCE_PHY_STATUS;
reg |= LINKCTRL_PIPE3_FORCE_RX_ELEC_IDLE;
writel(reg, regs_base + EXYNOS_USBCON_LINK_CTRL);
/* PMA Disable */
reg = readl(regs_base + EXYNOS_USBCON_COMBO_PMA_CTRL);
reg |= PMA_LOW_PWRN;
writel(reg, regs_base + EXYNOS_USBCON_COMBO_PMA_CTRL);
}
void phy_exynos_usb_v3p1_enable(struct exynos_usbphy_info *info)
{
void __iomem *regs_base = info->regs_base;
u32 reg;
u32 reg_hsp;
bool ss_only_cap;
int main_version;
main_version = info->version & EXYNOS_USBCON_VER_MAJOR_VER_MASK;
ss_only_cap = (info->version & EXYNOS_USBCON_VER_SS_CAP) >> 4;
if (main_version == EXYNOS_USBCON_VER_03_0_0) {
#if !defined(CONFIG_BOARD_ZEBU)
/* Set force q-channel */
exynos_cal_usbphy_q_ch(regs_base, 1);
#endif
/* Link Reset */
if (main_version == EXYNOS_USBCON_VER_03_0_0) {
reg = readl(info->regs_base + EXYNOS_USBCON_CLKRST);
reg |= CLKRST_LINK_SW_RST;
writel(reg, regs_base + EXYNOS_USBCON_CLKRST);
udelay(10);
reg &= ~CLKRST_LINK_SW_RST;
writel(reg, regs_base + EXYNOS_USBCON_CLKRST);
}
}
/* Set PHY Reset High */
phy_sw_rst_high(info);
/* Enable PHY Power Mode */
phy_power_en(info, 1);
if (!ss_only_cap) {
reg = readl(regs_base + EXYNOS_USBCON_UTMI);
reg &= ~UTMI_FORCE_SUSPEND;
reg &= ~UTMI_FORCE_SLEEP;
reg &= ~UTMI_DP_PULLDOWN;
reg &= ~UTMI_DM_PULLDOWN;
writel(reg, regs_base + EXYNOS_USBCON_UTMI);
/* set phy clock & control HS phy */
reg = readl(regs_base + EXYNOS_USBCON_HSP);
if (info->common_block_disable) {
reg |= HSP_EN_UTMISUSPEND;
reg |= HSP_COMMONONN;
} else {
reg &= ~HSP_COMMONONN;
}
writel(reg, regs_base + EXYNOS_USBCON_HSP);
} else {
void *ss_reg_base;
if (info->used_phy_port == 1)
ss_reg_base = info->regs_base_2nd;
else
ss_reg_base = info->regs_base;
reg = readl(ss_reg_base + EXYNOS_USBCON_CLKRST);
reg |= CLKRST_LINK_PCLK_SEL;
writel(reg, ss_reg_base + EXYNOS_USBCON_CLKRST);
}
udelay(100);
/* Set PHY Reset Low */
phy_sw_rst_low(info);
if (ss_only_cap) {
phy_exynos_usb_v3p1_late_enable(info);
return;
}
/* Select PHY MUX */
if (info->dual_phy) {
u32 physel;
physel = readl(regs_base + EXYNOS_USBCON_DUALPHYSEL);
if (info->used_phy_port == 0) {
physel &= ~DUALPHYSEL_PHYSEL_CTRL;
physel &= ~DUALPHYSEL_PHYSEL_SSPHY;
physel &= ~DUALPHYSEL_PHYSEL_PIPECLK;
physel &= ~DUALPHYSEL_PHYSEL_PIPERST;
} else {
physel |= DUALPHYSEL_PHYSEL_CTRL;
physel |= DUALPHYSEL_PHYSEL_SSPHY;
physel |= DUALPHYSEL_PHYSEL_PIPECLK;
physel |= DUALPHYSEL_PHYSEL_PIPERST;
}
writel(physel, regs_base + EXYNOS_USBCON_DUALPHYSEL);
}
/* Follow setting sequence for USB Link */
/*
* 1. Set VBUS Valid and DP-Pull up control
* by VBUS pad usage
*/
reg = readl(regs_base + EXYNOS_USBCON_UTMI);
reg_hsp = readl(regs_base + EXYNOS_USBCON_HSP);
if (info->not_used_vbus_pad) {
link_vbus_filter_en(info, false);
reg |= UTMI_FORCE_BVALID;
reg |= UTMI_FORCE_VBUSVALID;
reg_hsp |= HSP_VBUSVLDEXTSEL;
reg_hsp |= HSP_VBUSVLDEXT;
} else {
link_vbus_filter_en(info, true);
reg &= ~UTMI_FORCE_BVALID;
reg &= ~UTMI_FORCE_VBUSVALID;
reg_hsp &= ~HSP_VBUSVLDEXTSEL;
reg_hsp &= ~HSP_VBUSVLDEXT;
}
writel(reg, regs_base + EXYNOS_USBCON_UTMI);
writel(reg_hsp, regs_base + EXYNOS_USBCON_HSP);
/* 2. OVC io usage */
reg = readl(regs_base + EXYNOS_USBCON_LINK_PORT);
if (info->use_io_for_ovc) {
reg &= ~LINKPORT_HUB_PORT_SEL_OCD_U3;
reg &= ~LINKPORT_HUB_PORT_SEL_OCD_U2;
} else {
reg |= LINKPORT_HUB_PORT_SEL_OCD_U3;
reg |= LINKPORT_HUB_PORT_SEL_OCD_U2;
}
writel(reg, regs_base + EXYNOS_USBCON_LINK_PORT);
/* Enable ReWA */
if (info->hs_rewa)
phy_exynos_usb3p1_rewa_ready(info);
}
enum exynos_usbcon_cr {
USBCON_CR_ADDR = 0,
USBCON_CR_DATA = 1,
USBCON_CR_READ = 18,
USBCON_CR_WRITE = 19,
};
static u16 phy_exynos_usb_v3p1_cr_access(struct exynos_usbphy_info *info,
enum exynos_usbcon_cr cr_bit, u16 data)
{
void __iomem *base;
u32 ssp_crctl0;
u32 ssp_crctl1;
u32 loop;
u32 loop_cnt;
if (info->used_phy_port != -1) {
if (info->used_phy_port == 0)
base = info->regs_base;
else
base = info->regs_base_2nd;
} else
base = info->regs_base;
/*Clear CR port register*/
ssp_crctl0 = readl(base + EXYNOS_USBCON_SSP_CRCTL0);
ssp_crctl0 &= ~(0xf);
ssp_crctl0 &= ~(0xffff << 16);
writel(ssp_crctl0, base + EXYNOS_USBCON_SSP_CRCTL0);
/*Set Data for cr port*/
ssp_crctl0 &= ~SSP_CCTRL0_CR_DATA_IN_MASK;
ssp_crctl0 |= SSP_CCTRL0_CR_DATA_IN(data);
writel(ssp_crctl0, base + EXYNOS_USBCON_SSP_CRCTL0);
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;
if (cr_bit == USBCON_CR_ADDR)
trigger_bit = SSP_CRCTRL0_CR_CAP_ADDR;
else {
if (loop_cnt == 0)
trigger_bit = SSP_CRCTRL0_CR_CAP_DATA;
else {
if (cr_bit == USBCON_CR_READ)
trigger_bit = SSP_CRCTRL0_CR_READ;
else
trigger_bit = SSP_CRCTRL0_CR_WRITE;
}
}
/* Handshake Procedure */
do {
u32 usec = 100;
if (handshake_cnt == 2)
ssp_crctl0 |= trigger_bit;
else
ssp_crctl0 &= ~trigger_bit;
writel(ssp_crctl0, base + EXYNOS_USBCON_SSP_CRCTL0);
/* Handshake */
do {
ssp_crctl1 = readl(base + EXYNOS_USBCON_SSP_CRCTL1);
if ((handshake_cnt == 2) && (ssp_crctl1 & SSP_CRCTL1_CR_ACK))
break;
else if ((handshake_cnt == 1) && !(ssp_crctl1 & SSP_CRCTL1_CR_ACK))
break;
udelay(1);
} while (usec-- > 0);
if (!usec)
pr_err("CRPORT handshake timeout1 (0x%08x)\n", ssp_crctl0);
udelay(5);
handshake_cnt--;
} while (handshake_cnt != 0);
udelay(50);
}
return (u16) ((ssp_crctl1 & SSP_CRCTL1_CR_DATA_OUT_MASK) >> 16);
}
void phy_exynos_usb_v3p1_cal_cr_write(struct exynos_usbphy_info *info, u16 addr, u16 data)
{
phy_exynos_usb_v3p1_cr_access(info, USBCON_CR_ADDR, addr);
phy_exynos_usb_v3p1_cr_access(info, USBCON_CR_WRITE, data);
}
u16 phy_exynos_usb_v3p1_cal_cr_read(struct exynos_usbphy_info *info, u16 addr)
{
phy_exynos_usb_v3p1_cr_access(info, USBCON_CR_ADDR, addr);
return phy_exynos_usb_v3p1_cr_access(info, USBCON_CR_WRITE, 0);
}
void phy_exynos_usb_v3p1_cal_usb3phy_tune_fix_rxeq(struct exynos_usbphy_info *info)
{
u16 reg;
struct exynos_usbphy_ss_tune *tune = info->ss_tune;
if (!tune)
return;
reg = phy_exynos_usb_v3p1_cal_cr_read(info, 0x1006);
reg &= ~(1 << 6);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x1006, reg);
udelay(10);
reg |= (1 << 7);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x1006, reg);
udelay(10);
reg &= ~(0x7 << 0x8);
reg |= (tune->fix_rxeq_value << 8);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x1006, reg);
udelay(10);
reg |= (1 << 11);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x1006, reg);
dev_dbg(info->dev, "Reg RX_OVRD_IN_HI : 0x%x\n",
phy_exynos_usb_v3p1_cal_cr_read(info, 0x1006));
dev_dbg(info->dev, "Reg RX_CDR_CDR_FSM_DEBUG : 0x%x\n",
phy_exynos_usb_v3p1_cal_cr_read(info, 0x101c));
}
static void set_ss_tx_impedance(struct exynos_usbphy_info *info)
{
u16 rtune_debug, tx_ovrd_in_hi;
u8 tx_imp;
/* obtain calibration code for 45Ohm impedance */
rtune_debug = phy_exynos_usb_v3p1_cal_cr_read(info, 0x3);
/* set SUP.DIG.RTUNE_DEBUG.TYPE = 2 */
rtune_debug &= ~(0x3 << 3);
rtune_debug |= (0x2 << 3);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x3, rtune_debug);
/* read SUP.DIG.RTUNE_STAT (0x0004[9:0]) */
tx_imp = phy_exynos_usb_v3p1_cal_cr_read(info, 0x4);
/* current_tx_cal_code[9:0] = SUP.DIG.RTUNE_STAT (0x0004[9:0]) */
tx_imp += 8;
/*
* tx_cal_code[9:0] = current_tx_cal_code[9:0] + 8(decimal)
* NOTE, max value is 63;
* i.e. if tx_cal_code[9:0] > 63, tx_cal_code[9:0]==63;
*/
if (tx_imp > 63)
tx_imp = 63;
/* set LANEX_DIG_TX_OVRD_IN_HI.TX_RESET_OVRD = 1 */
tx_ovrd_in_hi = phy_exynos_usb_v3p1_cal_cr_read(info, 0x1001);
tx_ovrd_in_hi |= (1 << 7);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x1001, tx_ovrd_in_hi);
/* SUP.DIG.RTUNE_DEBUG.MAN_TUNE = 0 */
rtune_debug &= ~(1 << 1);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x3, rtune_debug);
/* set SUP.DIG.RTUNE_DEBUG.VALUE = tx_cal_code[9:0] */
rtune_debug &= ~(0x1ff << 5);
rtune_debug |= (tx_imp << 5);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x3, rtune_debug);
/* set SUP.DIG.RTUNE_DEBUG.SET_VAL = 1 */
rtune_debug |= (1 << 2);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x3, rtune_debug);
/* set SUP.DIG.RTUNE_DEBUG.SET_VAL = 0 */
rtune_debug &= ~(1 << 2);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x3, rtune_debug);
/* set LANEX_DIG_TX_OVRD_IN_HI.TX_RESET = 1 */
tx_ovrd_in_hi |= (1 << 6);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x1001, tx_ovrd_in_hi);
/* set LANEX_DIG_TX_OVRD_IN_HI.TX_RESET = 0 */
tx_ovrd_in_hi &= ~(1 << 6);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x1001, tx_ovrd_in_hi);
}
void phy_exynos_usb_v3p1_cal_usb3phy_tune_adaptive_eq(
struct exynos_usbphy_info *info, u8 eq_fix)
{
u16 reg;
reg = phy_exynos_usb_v3p1_cal_cr_read(info, 0x1006);
if (eq_fix) {
reg |= (1 << 6);
reg &= ~(1 << 7);
} else {
reg &= ~(1 << 6);
reg |= (1 << 7);
}
phy_exynos_usb_v3p1_cal_cr_write(info, 0x1006, reg);
}
void phy_exynos_usb_v3p1_usb3phy_tune_chg_rxeq(
struct exynos_usbphy_info *info, u8 eq_val)
{
u16 reg;
reg = phy_exynos_usb_v3p1_cal_cr_read(info, 0x1006);
reg &= ~(0x7 << 0x8);
reg |= ((eq_val & 0x7) << 8);
reg |= (1 << 11);
phy_exynos_usb_v3p1_cal_cr_write(info, 0x1006, reg);
}
void phy_exynos_usb_v3p1_late_enable(struct exynos_usbphy_info *info)
{
u32 version = info->version;
if (version > EXYNOS_USBCON_VER_05_0_0) {
struct exynos_usbphy_ss_tune *tune = info->ss_tune;
/*Set RXDET_MEAS_TIME[11:4] each reference clock*/
phy_exynos_usb_v3p1_cal_cr_write(info, 0x1010, 0x80);
if (tune) {
phy_exynos_usb_v3p1_cal_cr_write(info, 0x1002, 0x4000 |
(tune->tx_deemphasis_3p5db << 7) |
tune->tx_swing_full);
if (tune->rx_decode_mode)
phy_exynos_usb_v3p1_cal_cr_write(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
*/
phy_exynos_usb_v3p1_cal_cr_write(info, 0x15, 0xA409);
/* Set TX_VBOOST_LEVLE to tune->tx_boost_level */
phy_exynos_usb_v3p1_cal_cr_write(info, 0x12, tune->tx_boost_level<<13);
}
/* to set the charge pump proportional current */
if (tune->set_crport_mpll_charge_pump)
phy_exynos_usb_v3p1_cal_cr_write(info, 0x30, 0xC0);
if (tune->enable_fixed_rxeq_mode)
phy_exynos_usb_v3p1_cal_usb3phy_tune_fix_rxeq(info);
if (tune->decrease_ss_tx_imp)
set_ss_tx_impedance(info);
}
}
}
void phy_exynos_usb_v3p1_disable(struct exynos_usbphy_info *info)
{
u32 reg;
void __iomem *regs_base = info->regs_base;
/* set phy clock & control HS phy */
reg = readl(regs_base + EXYNOS_USBCON_UTMI);
reg |= UTMI_FORCE_SUSPEND;
reg |= UTMI_FORCE_SLEEP;
writel(reg, regs_base + EXYNOS_USBCON_UTMI);
/* Disable PHY Power Mode */
phy_power_en(info, 0);
/* clear force q-channel */
exynos_cal_usbphy_q_ch(regs_base, 0);
}
void phy_exynos_usb_v3p1_config_host_mode(struct exynos_usbphy_info *info)
{
u32 reg;
void __iomem *regs_base = info->regs_base;
/* DP/DM Pull Down Control */
reg = readl(regs_base + EXYNOS_USBCON_UTMI);
reg |= UTMI_DP_PULLDOWN;
reg |= UTMI_DM_PULLDOWN;
reg &= ~UTMI_FORCE_BVALID;
reg &= ~UTMI_FORCE_VBUSVALID;
writel(reg, regs_base + EXYNOS_USBCON_UTMI);
/* Disable Pull-up Register */
reg = readl(regs_base + EXYNOS_USBCON_HSP);
reg &= ~HSP_VBUSVLDEXTSEL;
reg &= ~HSP_VBUSVLDEXT;
writel(reg, regs_base + EXYNOS_USBCON_HSP);
}
void phy_exynos_usb_v3p1_tune(struct exynos_usbphy_info *info)
{
u32 hsp_tune, ssp_tune0, ssp_tune1, cnt;
bool ss_only_cap;
ss_only_cap = (info->version & EXYNOS_USBCON_VER_SS_CAP) >> 4;
if (!info->tune_param)
return;
if (!ss_only_cap) { /* hsphy tuning */
void __iomem *regs_base = info->regs_base;
hsp_tune = readl(regs_base + EXYNOS_USBCON_HSP_TUNE);
cnt = 0;
for (; info->tune_param[cnt].value != EXYNOS_USB_TUNE_LAST; cnt++) {
char *para_name;
int val;
val = info->tune_param[cnt].value;
if (val == -1)
continue;
para_name = info->tune_param[cnt].name;
/* HSP PARACON : 0x135d_0000 + 0x58 */
if (!strcmp(para_name, "compdis")) {
hsp_tune &= ~HSP_TUNE_COMPDIS_MASK;
hsp_tune |= HSP_TUNE_COMPDIS_SET(val);
} else if (!strcmp(para_name, "otg")) {
hsp_tune &= ~HSP_TUNE_OTG_MASK;
hsp_tune |= HSP_TUNE_OTG_SET(val);
} else if (!strcmp(para_name, "rx_sqrx")) {
hsp_tune &= ~HSP_TUNE_SQRX_MASK;
hsp_tune |= HSP_TUNE_SQRX_SET(val);
} else if (!strcmp(para_name, "tx_fsls")) {
hsp_tune &= ~HSP_TUNE_TXFSLS_MASK;
hsp_tune |= HSP_TUNE_TXFSLS_SET(val);
} else if (!strcmp(para_name, "tx_hsxv")) {
hsp_tune &= ~HSP_TUNE_HSXV_MASK;
hsp_tune |= HSP_TUNE_HSXV_SET(val);
} else if (!strcmp(para_name, "tx_pre_emp")) {
hsp_tune &= ~HSP_TUNE_TXPREEMPA_MASK;
hsp_tune |= HSP_TUNE_TXPREEMPA_SET(val);
} else if (!strcmp(para_name, "tx_pre_emp_plus")) {
if (val)
hsp_tune |= HSP_TUNE_TXPREEMPA_PLUS;
else
hsp_tune &= ~HSP_TUNE_TXPREEMPA_PLUS;
} else if (!strcmp(para_name, "tx_res")) {
hsp_tune &= ~HSP_TUNE_TXRES_MASK;
hsp_tune |= HSP_TUNE_TXRES_SET(val);
} else if (!strcmp(para_name, "tx_rise")) {
hsp_tune &= ~HSP_TUNE_TXRISE_MASK;
hsp_tune |= HSP_TUNE_TXRISE_SET(val);
} else if (!strcmp(para_name, "tx_vref")) {
hsp_tune &= ~HSP_TUNE_TXVREF_MASK;
hsp_tune |= HSP_TUNE_TXVREF_SET(val);
}
}
writel(hsp_tune, regs_base + EXYNOS_USBCON_HSP_TUNE);
} else { /* ssphy tuning */
void __iomem *ss_reg_base;
if (info->used_phy_port == 1)
ss_reg_base = info->regs_base_2nd;
else
ss_reg_base = info->regs_base;
ssp_tune0 = readl(ss_reg_base + EXYNOS_USBCON_SSP_PARACON0);
ssp_tune1 = readl(ss_reg_base + EXYNOS_USBCON_SSP_PARACON1);
cnt = 0;
for (; info->tune_param[cnt].value != EXYNOS_USB_TUNE_LAST; cnt++) {
char *para_name;
int val;
val = info->tune_param[cnt].value;
if (val == -1)
continue;
para_name = info->tune_param[cnt].name;
/* SSP PARACON0 setting : 0x135e_0000 + 0x34 */
if (!strcmp(para_name, "tx0_term_offset")) {
ssp_tune0 &= ~SSP_PARACON0_TX0_TERM_OFFSET_MASK;
ssp_tune0 |= SSP_PARACON0_TX0_TERM_OFFSET(val);
} else if (!strcmp(para_name, "pcs_tx_swing_full")) {
ssp_tune0 &= ~SSP_PARACON0_PCS_TX_SWING_FULL_MASK;
ssp_tune0 |= SSP_PARACON0_PCS_TX_SWING_FULL(val);
} else if (!strcmp(para_name, "pcs_tx_deemph_6db")) {
ssp_tune0 &= ~SSP_PARACON0_PCS_TX_DEEMPH_6DB_MASK;
ssp_tune0 |= SSP_PARACON0_PCS_TX_DEEMPH_6DB(val);
} else if (!strcmp(para_name, "pcs_tx_deemph_3p5db")) {
ssp_tune0 &= ~SSP_PARACON0_PCS_TX_DEEMPH_3P5DB_MASK;
ssp_tune0 |= SSP_PARACON0_PCS_TX_DEEMPH_3P5DB(val);
/* SSP PARACON1 setting : 0x135e_0000 + 0x38 */
} else if (!strcmp(para_name, "tx_vboost_lvl")) {
ssp_tune1 &= ~SSP_PARACON1_TX_VBOOST_LVL_MASK;
ssp_tune1 |= SSP_PARACON1_TX_VBOOST_LVL(val);
} else if (!strcmp(para_name, "los_level")) {
ssp_tune1 &= ~SSP_PARACON1_LOS_LEVEL_MASK;
ssp_tune1 |= SSP_PARACON1_LOS_LEVEL(val);
} else if (!strcmp(para_name, "los_bias")) {
ssp_tune1 &= ~SSP_PARACON1_LOS_BIAS_MASK;
ssp_tune1 |= SSP_PARACON1_LOS_BIAS(val);
} else if (!strcmp(para_name, "pcs_rx_los_mask_val")) {
ssp_tune1 &= ~SSP_PARACON1_PCS_RX_LOS_MASK_VAL_MASK;
ssp_tune1 |= SSP_PARACON1_PCS_RX_LOS_MASK_VAL(val);
}
} /* for */
writel(ssp_tune0, ss_reg_base + EXYNOS_USBCON_SSP_PARACON0);
writel(ssp_tune1, ss_reg_base + EXYNOS_USBCON_SSP_PARACON1);
} /* else */
}
void phy_exynos_usb_v3p1_tune_each(struct exynos_usbphy_info *info,
char *para_name, int val)
{
u32 reg;
void __iomem *regs_base = info->regs_base;
if (val == -1)
return;
reg = readl(regs_base + EXYNOS_USBCON_HSP_TUNE);
if (!strcmp(para_name, "compdis")) {
reg &= ~HSP_TUNE_COMPDIS_MASK;
reg |= HSP_TUNE_COMPDIS_SET(val);
} else if (!strcmp(para_name, "otg")) {
reg &= ~HSP_TUNE_OTG_MASK;
reg |= HSP_TUNE_OTG_SET(val);
} else if (!strcmp(para_name, "rx_sqrx")) {
reg &= ~HSP_TUNE_SQRX_MASK;
reg |= HSP_TUNE_SQRX_SET(val);
} else if (!strcmp(para_name, "tx_fsls")) {
reg &= ~HSP_TUNE_TXFSLS_MASK;
reg |= HSP_TUNE_TXFSLS_SET(val);
} else if (!strcmp(para_name, "tx_hsxv")) {
reg &= ~HSP_TUNE_HSXV_MASK;
reg |= HSP_TUNE_HSXV_SET(val);
} else if (!strcmp(para_name, "tx_pre_emp")) {
reg &= ~HSP_TUNE_TXPREEMPA_MASK;
reg |= HSP_TUNE_TXPREEMPA_SET(val);
} else if (!strcmp(para_name, "tx_pre_emp_plus")) {
if (val)
reg |= HSP_TUNE_TXPREEMPA_PLUS;
else
reg &= ~HSP_TUNE_TXPREEMPA_PLUS;
} else if (!strcmp(para_name, "tx_res")) {
reg &= ~HSP_TUNE_TXRES_MASK;
reg |= HSP_TUNE_TXRES_SET(val);
} else if (!strcmp(para_name, "tx_rise")) {
reg &= ~HSP_TUNE_TXRISE_MASK;
reg |= HSP_TUNE_TXRISE_SET(val);
} else if (!strcmp(para_name, "tx_vref")) {
reg &= ~HSP_TUNE_TXVREF_MASK;
reg |= HSP_TUNE_TXVREF_SET(val);
}
writel(reg, regs_base + EXYNOS_USBCON_HSP_TUNE);
}
void phy_exynos_usb_v3p1_wr_tune_reg(struct exynos_usbphy_info *info, u32 val)
{
void __iomem *regs_base = info->regs_base;
writel(val, regs_base + EXYNOS_USBCON_HSP_TUNE);
}
void phy_exynos_usb_v3p1_rd_tune_reg(struct exynos_usbphy_info *info, u32 *val)
{
void __iomem *regs_base = info->regs_base;
if (!val)
return;
*val = readl(regs_base + EXYNOS_USBCON_HSP_TUNE);
}
void phy_exynos_usb3p1_rewa_ready(struct exynos_usbphy_info *info)
{
u32 reg;
void __iomem *regs_base = info->regs_base;
/* Disable ReWA */
reg = readl(regs_base + EXYNOS_USBCON_REWA_ENABLE);
reg &= ~REWA_ENABLE_HS_REWA_EN;
writel(reg, regs_base + EXYNOS_USBCON_REWA_ENABLE);
/* Config ReWA Operation */
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_CTRL);
/*
* Select line state check circuit
* 0 : FSVPLUS/FSMINUS
* 1 : LINE STATE
*/
reg &= ~HSREWA_CTRL_DPDM_MON_SEL;
/*
* Select Drive K circuit
* 0 : Auto Resume in the PHY
* 1 : BYPASS mode by ReWA
*/
reg |= HSREWA_CTRL_DIG_BYPASS_CON_EN;
writel(reg, regs_base + EXYNOS_USBCON_HSREWA_CTRL);
/* Set Driver K Time */
reg = 0x1;
writel(reg, regs_base + EXYNOS_USBCON_HSREWA_HSTK);
/* Set Timeout counter Driver K Time */
reg = 0xff00;
writel(reg, regs_base + EXYNOS_USBCON_HSREWA_REFTO);
/* Disable All events source for abnormal event generation */
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_INT1_MASK);
reg |= HSREWA_CTRL_HS_EVT_ERR_SUS |
HSREWA_CTRL_HS_EVT_ERR_DEV_K |
HSREWA_CTRL_HS_EVT_DISCON |
HSREWA_CTRL_HS_EVT_BYPASS_DIS |
HSREWA_CTRL_HS_EVT_RET_DIS |
HSREWA_CTRL_HS_EVT_RET_EN;
writel(reg, regs_base + EXYNOS_USBCON_HSREWA_INT1_MASK);
}
int phy_exynos_usb3p1_rewa_enable(struct exynos_usbphy_info *info)
{
int cnt;
u32 reg;
void __iomem *regs_base = info->regs_base;
/* Clear the system valid flag */
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_CTRL);
reg &= ~HSREWA_CTRL_HS_SYS_VALID;
writel(reg, regs_base + EXYNOS_USBCON_HSREWA_CTRL);
/* Enable ReWA */
reg = readl(regs_base + EXYNOS_USBCON_REWA_ENABLE);
reg |= REWA_ENABLE_HS_REWA_EN;
writel(reg, regs_base + EXYNOS_USBCON_REWA_ENABLE);
/* Check Status : Wait ReWA Status is retention enabled */
for (cnt = 10000; cnt != 0; cnt--) {
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_INT1_EVT);
/* non suspend status*/
if (reg & HSREWA_CTRL_HS_EVT_ERR_SUS)
return HS_REWA_EN_STS_NOT_SUSPEND;
/* Disconnect Status */
if (reg & HSREWA_CTRL_HS_EVT_DISCON)
return HS_REWA_EN_STS_DISCONNECT;
/* Success ReWA Enable */
if (reg & HSREWA_CTRL_HS_EVT_RET_EN)
break;
udelay(30);
}
if (!cnt)
return HS_REWA_EN_STS_NOT_SUSPEND;
/* Set the INT1 for detect K and Disconnect */
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_INT1_MASK);
reg &= ~HSREWA_CTRL_HS_EVT_DISCON &
~HSREWA_CTRL_HS_EVT_ERR_DEV_K;
reg |= HSREWA_CTRL_HS_EVT_ERR_SUS |
HSREWA_CTRL_HS_EVT_BYPASS_DIS |
HSREWA_CTRL_HS_EVT_RET_DIS |
HSREWA_CTRL_HS_EVT_RET_EN;
writel(reg, regs_base + EXYNOS_USBCON_HSREWA_INT1_MASK);
/* Enable All interrupt source and disnable Wake-up event */
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_INTR);
reg &= ~HSREWA_INTR_WAKEUP_REQ_MASK &
~HSREWA_INTR_EVT_MASK &
~HSREWA_INTR_WAKEUP_MASK &
~HSREWA_INTR_TIMEOUT_MASK;
writel(reg, regs_base + EXYNOS_USBCON_HSREWA_INTR);
udelay(100);
return HS_REWA_EN_STS_ENALBED;
}
int phy_exynos_usb3p1_rewa_req_sys_valid(struct exynos_usbphy_info *info)
{
int cnt;
u32 reg;
void __iomem *regs_base = info->regs_base;
/* Mask All Interrupt source for the INT1 */
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_INT1_MASK);
reg &= ~HSREWA_CTRL_HS_EVT_DISCON &
~HSREWA_CTRL_HS_EVT_ERR_DEV_K &
~HSREWA_CTRL_HS_EVT_ERR_SUS &
~HSREWA_CTRL_HS_EVT_BYPASS_DIS &
~HSREWA_CTRL_HS_EVT_RET_DIS &
~HSREWA_CTRL_HS_EVT_RET_EN;
writel(reg, regs_base + EXYNOS_USBCON_HSREWA_INT1_MASK);
/* Enable All interrupt source and disnable Wake-up event */
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_INTR);
reg |= HSREWA_INTR_WAKEUP_REQ_MASK;
reg |= HSREWA_INTR_TIMEOUT_MASK;
reg |= HSREWA_INTR_EVT_MASK;
reg |= HSREWA_INTR_WAKEUP_MASK;
writel(reg, regs_base + EXYNOS_USBCON_HSREWA_INTR);
/* Set the system valid flag */
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_CTRL);
reg |= HSREWA_CTRL_HS_SYS_VALID;
writel(reg, regs_base + EXYNOS_USBCON_HSREWA_CTRL);
for (cnt = 10000; cnt != 0; cnt--) {
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_INT1_EVT);
/* Disconnect Status */
if (reg & HSREWA_CTRL_HS_EVT_DISCON)
return HS_REWA_EN_STS_DISCONNECT;
/* Success ReWA Enable */
if (reg & HSREWA_CTRL_HS_EVT_RET_EN)
break;
udelay(30);
}
return HS_REWA_EN_STS_DISABLED;
}
int phy_exynos_usb3p1_rewa_disable(struct exynos_usbphy_info *info)
{
int cnt;
u32 reg;
void __iomem *regs_base = info->regs_base;
/*
* Check ReWA Already diabled
* If ReWA was disabled states, disabled sequence is already done
*/
reg = readl(regs_base + EXYNOS_USBCON_REWA_ENABLE);
if (!(reg & REWA_ENABLE_HS_REWA_EN))
return 0;
/* Set Link ready to notify ReWA */
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_CTRL);
reg |= HSREWA_CTRL_HS_LINK_READY;
writel(reg, regs_base + EXYNOS_USBCON_HSREWA_CTRL);
/* Wait Bypass Disable */
for (cnt = 10000; cnt != 0; cnt--) {
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_INT1_EVT);
/* Success ReWA Enable */
if (reg & HSREWA_CTRL_HS_EVT_BYPASS_DIS)
break;
udelay(30);
}
if (!cnt)
return -1;
/* Wait ReWA Done */
for (cnt = 1000; cnt != 0; cnt--) {
reg = readl(regs_base + EXYNOS_USBCON_HSREWA_CTRL);
/* Success ReWA Enable */
if (reg & HSREWA_CTRL_HS_REWA_DONE)
break;
udelay(30);
}
if (!cnt)
return -1;
/* Disable ReWA */
reg = readl(regs_base + EXYNOS_USBCON_REWA_ENABLE);
reg &= ~REWA_ENABLE_HS_REWA_EN;
writel(reg, regs_base + EXYNOS_USBCON_REWA_ENABLE);
return 0;
}
int phy_exynos_usb3p1_rewa_cancel(struct exynos_usbphy_info *info)
{
int ret;
u32 reg;
void __iomem *regs_base = info->regs_base;
ret = phy_exynos_usb3p1_rewa_req_sys_valid(info);
/* Disable ReWA */
reg = readl(regs_base + EXYNOS_USBCON_REWA_ENABLE);
reg &= ~REWA_ENABLE_HS_REWA_EN;
writel(reg, regs_base + EXYNOS_USBCON_REWA_ENABLE);
udelay(100);
return ret;
}