| /* |
| * 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; |
| } |