// SPDX-License-Identifier: GPL-2.0 /** * * Copyright (c) 2021 Samsung Electronics Co., Ltd. * http://www.samsung.com * * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 of * the License 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 #include #include #include #include "phy-samsung-usb-cal.h" #include "exynos-usb-blkcon-sfr.h" static void ready_rewa(struct exynos_usbphy_info *cal_info); void exynos_usbcon_init_link(struct exynos_usbphy_info *cal_info) { void __iomem *regs_base; u32 reg; regs_base = cal_info->regs_base; /* LINKCTRL * 1. Disable q-channel * 2. Bypass debounce filter for vbus, bvalid and id * 3. sel_sof for SOF tick when LPM L1 */ reg = readl(regs_base + USBCON_REG_LINKCTRL); ((USBCON_REG_LINKCTRL_p) (®))->b.dis_id0_qact = 1; ((USBCON_REG_LINKCTRL_p) (®))->b.dis_bvalid_qact = 1; ((USBCON_REG_LINKCTRL_p) (®))->b.dis_vbusvalid_qact = 1; ((USBCON_REG_LINKCTRL_p) (®))->b.dis_linkgate_qact = 1; ((USBCON_REG_LINKCTRL_p) (®))->b.force_qact = 0; udelay(500); writel(reg, regs_base + USBCON_REG_LINKCTRL); udelay(500); ((USBCON_REG_LINKCTRL_p) (®))->b.force_qact = 1; ((USBCON_REG_LINKCTRL_p) (®))->b.bus_filter_bypass = 0xf; ((USBCON_REG_LINKCTRL_p) (®))->b.sel_sof = 0x1; writel(reg, regs_base + USBCON_REG_LINKCTRL); pr_info("%s %d USBCON_REG_LINKCTRL: 0x %08x\n", __func__, __LINE__, readl(regs_base + USBCON_REG_LINKCTRL)); /* Reset Link */ reg = readl(regs_base + USBCON_REG_LINK_CLKRST); ((USBCON_REG_LINK_CLKRST_p) (®))->b.link_sw_rst = 1; writel(reg, regs_base + USBCON_REG_LINK_CLKRST); udelay(10); ((USBCON_REG_LINK_CLKRST_p) (®))->b.link_sw_rst = 0; writel(reg, regs_base + USBCON_REG_LINK_CLKRST); /* UTMI_CTRL * 1. Set high bvalid and vbus valid */ reg = readl(regs_base + USBCON_REG_UTMI_CTRL); ((USBCON_REG_UTMI_CTRL_p) (®))->b.force_bvalid = 1; ((USBCON_REG_UTMI_CTRL_p) (®))->b.force_vbusvalid = 1; writel(reg, regs_base + USBCON_REG_UTMI_CTRL); /* Initilze UTMI ReWA */ if (cal_info->hs_rewa) ready_rewa(cal_info); } void exynos_usbcon_detach_pipe3_phy(struct exynos_usbphy_info *cal_info) { void __iomem *regs_base; u32 reg; regs_base = cal_info->regs_base; /* force pipe3 signal for link */ reg = readl(regs_base + USBCON_REG_LINKCTRL); ((USBCON_REG_LINKCTRL_p) (®))->b.force_pipe_en = 1; ((USBCON_REG_LINKCTRL_p) (®))->b.force_phystatus = 0; ((USBCON_REG_LINKCTRL_p) (®))->b.force_rxelecidle = 1; writel(reg, regs_base + USBCON_REG_LINKCTRL); pr_info("%s %d USBCON_REG_LINKCTRL: 0x %08x\n", __func__, __LINE__, readl(regs_base + USBCON_REG_LINKCTRL)); /* pclk to suspend clock */ reg = readl(regs_base + USBCON_REG_LINK_CLKRST); ((USBCON_REG_LINK_CLKRST_p) (®))->b.link_pclk_sel = 0; writel(reg, regs_base + USBCON_REG_LINK_CLKRST); } void exynos_usbcon_disable_pipe3_phy(struct exynos_usbphy_info *cal_info) { void __iomem *regs_base; u32 reg; exynos_usbcon_detach_pipe3_phy(cal_info); regs_base = cal_info->regs_base; /* calibrate only eUSB Phy */ reg = readl(regs_base + USBCON_REG_HSP_MISC); ((USBCON_REG_HSP_MISC_p) (®))->b.sel_res_tune_mux = 2; ((USBCON_REG_HSP_MISC_p) (®))->b.set_req_in2 = 1; ((USBCON_REG_HSP_MISC_p) (®))->b.set_ack_in2 = 0; ((USBCON_REG_HSP_MISC_p) (®))->b.set_req_in1 = 0; ((USBCON_REG_HSP_MISC_p) (®))->b.set_ack_in1 = 0; writel(reg, regs_base + USBCON_REG_HSP_MISC); } void exynos_usbcon_ready_to_pipe3_phy(struct exynos_usbphy_info *cal_info) { void __iomem *regs_base; u32 reg; regs_base = cal_info->regs_base; /* Disable forcing pipe interface */ reg = readl(regs_base + USBCON_REG_LINKCTRL); ((USBCON_REG_LINKCTRL_p) (®))->b.force_pipe_en = 0; writel(reg, regs_base + USBCON_REG_LINKCTRL); /* calibrate Sequence: Dual Phy */ reg = readl(regs_base + USBCON_REG_HSP_MISC); ((USBCON_REG_HSP_MISC_p) (®))->b.sel_res_tune_mux = 1; ((USBCON_REG_HSP_MISC_p) (®))->b.set_req_in2 = 0; ((USBCON_REG_HSP_MISC_p) (®))->b.set_ack_in2 = 0; ((USBCON_REG_HSP_MISC_p) (®))->b.set_req_in1 = 0; ((USBCON_REG_HSP_MISC_p) (®))->b.set_ack_in1 = 0; writel(reg, regs_base + USBCON_REG_HSP_MISC); /* Pclk to pipe_clk */ reg = readl(regs_base + USBCON_REG_LINK_CLKRST); ((USBCON_REG_LINK_CLKRST_p) (®))->b.link_pclk_sel = 1; writel(reg, regs_base + USBCON_REG_LINK_CLKRST); } u64 exynos_usbcon_get_logic_trace(struct exynos_usbphy_info *cal_info) { void __iomem *regs_base; u64 ret; regs_base = cal_info->regs_base; ret = readl(regs_base + USBCON_REG_LINK_DEBUG_L); ret |= ((u64) readl(regs_base + USBCON_REG_LINK_DEBUG_H)) << 32; return ret; } void exynos_usbcon_set_fsv_out_en(struct exynos_usbphy_info *cal_info, u32 en) { void __iomem *regs_base = cal_info->regs_base; u32 reg; reg = readl(regs_base + USBCON_REG_HSP_MISC); if (en) { ((USBCON_REG_HSP_MISC_p) (®))->b.fsvp_out_en = 1; ((USBCON_REG_HSP_MISC_p) (®))->b.fsvm_out_en = 1; } else { ((USBCON_REG_HSP_MISC_p) (®))->b.fsvp_out_en = 0; ((USBCON_REG_HSP_MISC_p) (®))->b.fsvm_out_en = 0; } writel(reg, regs_base + USBCON_REG_HSP_MISC); } u8 exynos_usbcon_get_fs_vplus_vminus(struct exynos_usbphy_info *cal_info) { void __iomem *regs_base = cal_info->regs_base; USBCON_REG_HSP_MISC_o reg; reg.data = readl(regs_base + USBCON_REG_HSP_MISC); return ((reg.b.fsvminus & 0x1) << 1) | (reg.b.fsvplus & 0x1); } static void ready_rewa(struct exynos_usbphy_info *cal_info) { u32 reg; void __iomem *regs_base = cal_info->regs_base; /* Disable ReWA */ reg = readl(regs_base + USBCON_REG_REWA_CTL); ((USBCON_REG_REWA_CTL_p) (®))->b.hsrewa_en = 0; writel(reg, regs_base + USBCON_REG_REWA_CTL); /* Config ReWA Operation */ reg = readl(regs_base + USBCON_REG_HSREWA_CTL); /* Select line state check circuit * 0 : FSVPLUS/FSMINUS * 1 : LINE STATE * */ ((USBCON_REG_HSREWA_CTL_p) (®))->b.dpdm_mon_sel = 1; /* Select Drive K circuit * 0 : Auto Resume in the PHY * 1 : BYPASS mode by ReWA * */ ((USBCON_REG_HSREWA_CTL_p) (®))->b.dig_bypass_con_en = 0; writel(reg, regs_base + USBCON_REG_HSREWA_CTL); /* Set host K timeout from host K drive. */ reg = 0xFF00; // value 0xFF00 means 200ms (Nominal host K drive time is 20ms) writel(reg, regs_base + USBCON_REG_HSREWA_REFTO); /* Set host K delay from device K*/ reg = 0x1; // value 1 means 30.5us writel(reg, regs_base + USBCON_REG_HSREWA_HSTK); /* Mask wakeup_req and all inetrrupts */ reg = readl(regs_base + USBCON_REG_HSREWA_INTR); ((USBCON_REG_HSREWA_INTR_p) (®))->b.wakeup_req_mask = 1; ((USBCON_REG_HSREWA_INTR_p) (®))->b.timeout_intr_mask = 1; ((USBCON_REG_HSREWA_INTR_p) (®))->b.event_intr_mask = 1; ((USBCON_REG_HSREWA_INTR_p) (®))->b.wakeup_intr_mask = 1; writel(reg, regs_base + USBCON_REG_HSREWA_INTR); /* Mask all INT events */ reg = readl(regs_base + USBCON_REG_HSREWA_INT1_EVNT_MSK); ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.err_sus_mask = 1; ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.err_dev_k_mask = 1; ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.discon_mask = 1; ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.bypass_dis_mask = 1; ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.ret_dis_mask = 1; ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.ret_en_mask = 1; writel(reg, regs_base + USBCON_REG_HSREWA_INT1_EVNT_MSK); } int exynos_usbcon_enable_rewa(struct exynos_usbphy_info *cal_info) { void __iomem *regs_base = cal_info->regs_base; u32 reg; int cnt; /* Set the wakeup source mask */ reg = readl(regs_base + USBCON_REG_HSREWA_INTR); ((USBCON_REG_HSREWA_INTR_p) (®))->b.wakeup_req_mask = 1; ((USBCON_REG_HSREWA_INTR_p) (®))->b.timeout_intr_mask = 0; ((USBCON_REG_HSREWA_INTR_p) (®))->b.event_intr_mask = 0; ((USBCON_REG_HSREWA_INTR_p) (®))->b.wakeup_intr_mask = 0; writel(reg, regs_base + USBCON_REG_HSREWA_INTR); /* Clear the link_ready and sys_valid flag */ reg = readl(regs_base + USBCON_REG_HSREWA_CTL); ((USBCON_REG_HSREWA_CTL_p) (®))->b.hs_link_ready = 0; ((USBCON_REG_HSREWA_CTL_p) (®))->b.hs_sys_valid = 0; writel(reg, regs_base + USBCON_REG_HSREWA_CTL); /* Enable ReWA */ reg = readl(regs_base + USBCON_REG_REWA_CTL); ((USBCON_REG_REWA_CTL_p) (®))->b.hsrewa_en = 1; writel(reg, regs_base + USBCON_REG_REWA_CTL); /* Check Status : Wait ReWA Status is retention enabled */ for (cnt = 10000; cnt != 0; cnt--) { reg = readl(regs_base + USBCON_REG_HSREWA_INT1_EVNT); /* non suspend status*/ if (((USBCON_REG_HSREWA_INT1_EVNT_p) (®))->b.err_sus) { pr_info("enable_rewa-not_suspend\n"); return HS_REWA_EN_STS_NOT_SUSPEND; } /* Disconnect Status */ if (((USBCON_REG_HSREWA_INT1_EVNT_p) (®))->b.discon) { pr_info("enable_rewa-discon\n"); return HS_REWA_EN_STS_DISCONNECT; } /* Success ReWA Enable */ if (((USBCON_REG_HSREWA_INT1_EVNT_p) (®))->b.ret_en) break; udelay(30); } if (cnt == 0) { pr_info("enable_rewa-timeout\n"); return HS_REWA_EN_STS_NOT_SUSPEND; } /* Set the INT1 for detect K and Disconnect */ reg = readl(regs_base + USBCON_REG_HSREWA_INT1_EVNT_MSK); ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.err_sus_mask = 1; ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.err_dev_k_mask = 0; ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.discon_mask = 0; ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.bypass_dis_mask = 1; ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.ret_dis_mask = 1; ((USBCON_REG_HSREWA_INT1_EVNT_MSK_p) (®))->b.ret_en_mask = 1; writel(reg, regs_base + USBCON_REG_HSREWA_INT1_EVNT_MSK); /* Clear the wakeup source mask */ reg = readl(regs_base + USBCON_REG_HSREWA_INTR); ((USBCON_REG_HSREWA_INTR_p) (®))->b.wakeup_req_mask = 0; writel(reg, regs_base + USBCON_REG_HSREWA_INTR); udelay(100); pr_info("enable_rewa-done\n"); return HS_REWA_EN_STS_ENALBED; } int exynos_usbcon_rewa_req_sys_valid(struct exynos_usbphy_info *cal_info) { void __iomem *regs_base = cal_info->regs_base; u32 reg; int cnt; /* Set interrupt mask for prevent addtional interrupt */ reg = readl(regs_base + USBCON_REG_HSREWA_INTR); ((USBCON_REG_HSREWA_INTR_p) (®))->b.wakeup_req_mask = 1; ((USBCON_REG_HSREWA_INTR_p) (®))->b.timeout_intr_mask = 1; ((USBCON_REG_HSREWA_INTR_p) (®))->b.event_intr_mask = 1; ((USBCON_REG_HSREWA_INTR_p) (®))->b.wakeup_intr_mask = 1; writel(reg, regs_base + USBCON_REG_HSREWA_INTR); /* Set the system valid flag after all System clock resumed */ reg = readl(regs_base + USBCON_REG_HSREWA_CTL); ((USBCON_REG_HSREWA_CTL_p) (®))->b.hs_sys_valid = 1; writel(reg, regs_base + USBCON_REG_HSREWA_CTL); /* Check Status : Wait ReWA Status is retention disabled */ for (cnt = 10000; cnt != 0; cnt--) { reg = readl(regs_base + USBCON_REG_HSREWA_INT1_EVNT); /* Disconnect Status */ if (((USBCON_REG_HSREWA_INT1_EVNT_p) (®))->b.discon) { pr_info("sysvalid-discon\n"); return HS_REWA_EN_STS_DISCONNECT; } /* Success ReWA Enable */ if (((USBCON_REG_HSREWA_INT1_EVNT_p) (®))->b.ret_dis) break; udelay(30); } pr_info("sysvalid-done\n"); return HS_REWA_EN_STS_DISABLED; } int exynos_usbcon_rewa_disable(struct exynos_usbphy_info *cal_info) { void __iomem *regs_base = cal_info->regs_base; u32 reg; int cnt; /* Check ReWA already disable * If ReWA was disabled states, disabled sequence is already done */ reg = readl(regs_base + USBCON_REG_REWA_CTL); if (!(((USBCON_REG_REWA_CTL_p) (®))->b.hsrewa_en)) { pr_info("disable_rewa-already\n"); return 0; } /* Set the USB link_ready flag */ reg = readl(regs_base + USBCON_REG_HSREWA_CTL); ((USBCON_REG_HSREWA_CTL_p) (®))->b.hs_link_ready = 1; writel(reg, regs_base + USBCON_REG_HSREWA_CTL); /* Wait for Digital bypass control signals is disabled event */ for (cnt = 10000; cnt != 0; cnt--) { reg = readl(regs_base + USBCON_REG_HSREWA_INT1_EVNT); if (((USBCON_REG_HSREWA_INT1_EVNT_p) (®))->b.bypass_dis) break; udelay(30); } if (!cnt) { pr_info("disable_rewa-timeout_bypass_dis\n"); return -1; } /* Wait for HS-ReWA done */ for (cnt = 1000; cnt != 0; cnt--) { reg = readl(regs_base + USBCON_REG_HSREWA_CTL); if (((USBCON_REG_HSREWA_CTL_p) (®))->b.hs_rewa_done) break; udelay(30); } if (!cnt) { pr_info("disable_rewa-timeout_hs_rewa_done\n"); return -1; } reg = readl(regs_base + USBCON_REG_REWA_CTL); ((USBCON_REG_REWA_CTL_p) (®))->b.hsrewa_en = 0; writel(reg, regs_base + USBCON_REG_REWA_CTL); pr_info("disable_rewa-done\n"); return 0; } int exynos_usbcon_rewa_cancel(struct exynos_usbphy_info *cal_info) { void __iomem *regs_base = cal_info->regs_base; u32 reg; /* Check ReWA already disable * If ReWA was disabled states, disabled sequence is already done */ reg = readl(regs_base + USBCON_REG_REWA_CTL); if (!(((USBCON_REG_REWA_CTL_p) (®))->b.hsrewa_en)) { pr_info("cancel_rewa-already\n"); return 0; } reg = readl(regs_base + USBCON_REG_REWA_CTL); ((USBCON_REG_REWA_CTL_p) (®))->b.hsrewa_en = 0; writel(reg, regs_base + USBCON_REG_REWA_CTL); pr_info("cancel_rewa-done\n"); return 0; } void exynos_usbcon_u3_rewa_enable(struct exynos_usbphy_info *cal_info, int lfps_overlap_mode) { void __iomem *regs_base = cal_info->regs_base; u32 reg; u32 lfpsresp_limit_cnt = 0x39219; // 9ms reg = readl(regs_base + USBCON_REG_U3REWA_CTRL); ((USBCON_REG_U3REWA_CTRL_p) (®))->b.check_u3 = 0; ((USBCON_REG_U3REWA_CTRL_p) (®))->b.u3rewa_blk_en = 1; if (!lfps_overlap_mode) { /* Disable overlap_lfps */ ((USBCON_REG_U3REWA_CTRL_p) (®))->b.overlap_lfps = 0; } else { /* Enable overlap_lfps */ ((USBCON_REG_U3REWA_CTRL_p) (®))->b.overlap_lfps = 1; /* Set lfpsresp_limit_cnt */ writel(lfpsresp_limit_cnt, regs_base + USBCON_REG_U3REWA_LMT_CNT); } writel(reg, regs_base + USBCON_REG_U3REWA_CTRL); pr_info("enable_u3-rewa-done\n"); } void exynos_usbcon_u3_rewa_disable(struct exynos_usbphy_info *cal_info) { void __iomem *regs_base = cal_info->regs_base; u32 reg; reg = readl(regs_base + USBCON_REG_U3REWA_CTRL); ((USBCON_REG_U3REWA_CTRL_p) (®))->b.u3rewa_blk_en = 0; writel(reg, regs_base + USBCON_REG_U3REWA_CTRL); pr_info("disable_u3-rewa-done\n"); }