diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 52eebcb88c1f..1051e0a3a3b7 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -192,4 +192,47 @@ config JZ4770_PHY This driver provides PHY support for the USB controller found on the JZ-series and X-series SoCs from Ingenic. +config USB_QCOM_EMU_PHY + tristate "Qualcomm Technologies, Inc. emulation USB PHY driver" + depends on ARCH_QCOM || COMPILE_TEST + select USB_PHY + help + Enable this to support the USB transceiver used on + Qualcomm Technologies, Inc. emulation platforms. It simply performs + PHY initialization given a basic register write sequence. + + To compile this driver as a module, choose M here: the + module will be called phy-qcom-emu. + +config USB_MSM_SSPHY_QMP + tristate "MSM SSUSB QMP PHY Driver" + depends on ARCH_QCOM || COMPILE_TEST + select USB_PHY + help + Enable this to support the SuperSpeed USB transceiver on MSM chips. + This driver supports the PHY which uses the QSCRATCH-based register + set for its control sequences, normally paired with newer DWC3-based + SuperSpeed controllers. + +config MSM_HSUSB_PHY + tristate "MSM HSUSB PHY Driver" + depends on ARCH_QCOM || COMPILE_TEST + select USB_PHY + help + Enable this to support the HSUSB PHY on MSM chips. This driver supports + the high-speed PHY which is usually paired with either the ChipIdea or + Synopsys DWC3 USB IPs on MSM SOCs. This driver expects to configure the + PHY with a dedicated register I/O memory region. + +config USB_MSM_EUSB2_PHY + tristate "MSM EUSB2 PHY Driver" + depends on ARCH_QCOM || COMPILE_TEST + select USB_PHY + help + Enable this to support the USB EUSB2 PHY on MSM chips. This driver + supports reset and initialization sequence, and also perform required + set of operations with used repeater for USB HS/FS/LS functionality. + + To compile this driver as a module, choose M here. + endmenu diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index b352bdbe8712..3715d0ba0b65 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -25,3 +25,7 @@ obj-$(CONFIG_USB_ULPI) += phy-ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o obj-$(CONFIG_JZ4770_PHY) += phy-jz4770.o +obj-$(CONFIG_USB_QCOM_EMU_PHY) += phy-qcom-emu.o +obj-$(CONFIG_USB_MSM_SSPHY_QMP) += phy-msm-ssusb-qmp.o +obj-$(CONFIG_MSM_HSUSB_PHY) += phy-msm-snps-hs.o +obj-$(CONFIG_USB_MSM_EUSB2_PHY) += phy-msm-snps-eusb2.o diff --git a/drivers/usb/phy/phy-msm-snps-eusb2.c b/drivers/usb/phy/phy-msm-snps-eusb2.c new file mode 100644 index 000000000000..e5dc1e51c49e --- /dev/null +++ b/drivers/usb/phy/phy-msm-snps-eusb2.c @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "eusb2_phy: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_PHY_UTMI_CTRL0 (0x3c) +#define OPMODE_MASK (0x3 << 3) +#define OPMODE_NONDRIVING (0x1 << 3) +#define SLEEPM BIT(0) + +#define USB_PHY_UTMI_CTRL5 (0x50) +#define POR BIT(1) + +#define USB_PHY_HS_PHY_CTRL_COMMON0 (0x54) +#define PHY_ENABLE BIT(0) +#define SIDDQ_SEL BIT(1) +#define SIDDQ BIT(2) +#define RETENABLEN BIT(3) +#define FSEL (0x7 << 4) +#define FSEL_19_2_MHZ_VAL (0x0 << 4) +#define FSEL_38_4_MHZ_VAL (0x4 << 4) + +#define USB_PHY_CFG_CTRL_1 (0x58) +#define PHY_CFG_PLL_CPBIAS_CNTRL (0xfe) +#define PHY_CFG_PLL_CPBIAS_CNTRL_SHIFT (0x1) + +#define USB_PHY_CFG_CTRL_2 (0x5c) +#define PHY_CFG_PLL_FB_DIV_7_0 (0xff) +#define DIV_7_0_19_2_MHZ_VAL (0x90) +#define DIV_7_0_38_4_MHZ_VAL (0xc8) + +#define USB_PHY_CFG_CTRL_3 (0x60) +#define PHY_CFG_PLL_FB_DIV_11_8 (0xf) +#define DIV_11_8_19_2_MHZ_VAL (0x1) +#define DIV_11_8_38_4_MHZ_VAL (0x0) + +#define PHY_CFG_PLL_REF_DIV (0xf << 4) +#define PLL_REF_DIV_VAL (0x0) + +#define USB_PHY_HS_PHY_CTRL2 (0x64) +#define VBUSVLDEXT0 BIT(0) +#define USB2_SUSPEND_N BIT(2) +#define USB2_SUSPEND_N_SEL BIT(3) +#define VBUS_DET_EXT_SEL BIT(4) + +#define USB_PHY_CFG_CTRL_4 (0x68) +#define PHY_CFG_PLL_GMP_CNTRL (0x3) +#define PHY_CFG_PLL_GMP_CNTRL_SHIFT (0x0) +#define PHY_CFG_PLL_INT_CNTRL (0xfc) +#define PHY_CFG_PLL_INT_CNTRL_SHIFT (0x2) + +#define USB_PHY_CFG_CTRL_5 (0x6c) +#define PHY_CFG_PLL_PROP_CNTRL (0x1f) +#define PHY_CFG_PLL_PROP_CNTRL_SHIFT (0x0) +#define PHY_CFG_PLL_VREF_TUNE (0x3 << 6) +#define PHY_CFG_PLL_VREF_TUNE_SHIFT (6) + +#define USB_PHY_CFG_CTRL_6 (0x70) +#define PHY_CFG_PLL_VCO_CNTRL (0x7) +#define PHY_CFG_PLL_VCO_CNTRL_SHIFT (0x0) + +#define USB_PHY_CFG_CTRL_7 (0x74) + +#define USB_PHY_CFG_CTRL_8 (0x78) +#define PHY_CFG_TX_FSLS_VREF_TUNE (0x3) +#define PHY_CFG_TX_FSLS_VREG_BYPASS BIT(2) +#define PHY_CFG_TX_HS_VREF_TUNE (0x7 << 3) +#define PHY_CFG_TX_HS_VREF_TUNE_SHIFT (0x3) +#define PHY_CFG_TX_HS_XV_TUNE (0x3 << 6) +#define PHY_CFG_TX_HS_XV_TUNE_SHIFT (6) + +#define USB_PHY_CFG_CTRL_9 (0x7c) +#define PHY_CFG_TX_PREEMP_TUNE (0x7) +#define PHY_CFG_TX_PREEMP_TUNE_SHIFT (0x0) +#define PHY_CFG_TX_RES_TUNE (0x3 << 3) +#define PHY_CFG_TX_RES_TUNE_SHIFT (0x3) +#define PHY_CFG_TX_RISE_TUNE (0x3 << 5) +#define PHY_CFG_TX_RISE_TUNE_SHIFT (0x5) +#define PHY_CFG_RCAL_BYPASS BIT(7) +#define PHY_CFG_RCAL_BYPASS_SHIFT (0x7) + +#define USB_PHY_CFG_CTRL_10 (0x80) + +#define USB_PHY_CFG0 (0x94) +#define DATAPATH_CTRL_OVERRIDE_EN BIT(0) +#define CMN_CTRL_OVERRIDE_EN BIT(1) + +#define UTMI_PHY_CMN_CTRL0 (0x98) +#define TESTBURNIN BIT(6) + +#define USB_PHY_FSEL_SEL (0xb8) +#define FSEL_SEL BIT(0) + +#define USB_PHY_APB_ACCESS_CMD (0x130) +#define RW_ACCESS BIT(0) +#define APB_START_CMD BIT(1) +#define APB_LOGIC_RESET BIT(2) + +#define USB_PHY_APB_ACCESS_STATUS (0x134) +#define ACCESS_DONE BIT(0) +#define TIMED_OUT BIT(1) +#define ACCESS_ERROR BIT(2) +#define ACCESS_IN_PROGRESS BIT(3) + +#define USB_PHY_APB_ADDRESS (0x138) +#define APB_REG_ADDR (0xff) + +#define USB_PHY_APB_WRDATA_LSB (0x13c) +#define APB_REG_WRDATA_7_0 (0xf) + +#define USB_PHY_APB_WRDATA_MSB (0x140) +#define APB_REG_WRDATA_15_8 (0xf0) + +#define USB_PHY_APB_RDDATA_LSB (0x144) +#define APB_REG_RDDATA_7_0 (0xf) + +#define USB_PHY_APB_RDDATA_MSB (0x148) +#define APB_REG_RDDATA_15_8 (0xf0) + +/* EUD CSR field */ +#define EUD_EN2 BIT(0) + +/* VIOCTL_EUD_DETECT register based EUD_DETECT field */ +#define EUD_DETECT BIT(0) + +#define USB_HSPHY_1P2_VOL_MIN 1200000 /* uV */ +#define USB_HSPHY_1P2_VOL_MAX 1200000 /* uV */ +#define USB_HSPHY_1P2_HPM_LOAD 5905 /* uA */ +#define USB_HSPHY_VDD_HPM_LOAD 7757 /* uA */ + +struct msm_eusb2_phy { + struct usb_phy phy; + void __iomem *base; + + /* EUD related parameters */ + phys_addr_t eud_reg; + void __iomem *eud_enable_reg; + void __iomem *eud_detect_reg; + bool re_enable_eud; + + struct clk *ref_clk_src; + struct clk *ref_clk; + struct reset_control *phy_reset; + + struct regulator *vdd; + struct regulator *vdda12; + int vdd_levels[3]; /* none, low, high */ + + bool clocks_enabled; + bool power_enabled; + bool suspended; + bool cable_connected; + + struct power_supply *usb_psy; + unsigned int vbus_draw; + struct work_struct vbus_draw_work; + + int *param_override_seq; + int param_override_seq_cnt; + + /* debugfs entries */ + struct dentry *root; + u8 tx_pre_emphasis; + u8 tx_rise_fall_time; + u8 tx_src_impedence; + u8 tx_dc_vref; + u8 tx_xv; + + struct usb_repeater *ur; +}; + +static inline bool is_eud_debug_mode_active(struct msm_eusb2_phy *phy) +{ + if (phy->eud_enable_reg && + (readl_relaxed(phy->eud_enable_reg) & EUD_EN2)) + return true; + + return false; +} + +static void msm_eusb2_phy_clocks(struct msm_eusb2_phy *phy, bool on) +{ + dev_dbg(phy->phy.dev, "clocks_enabled:%d on:%d\n", + phy->clocks_enabled, on); + + if (phy->clocks_enabled == on) + return; + + if (on) { + clk_prepare_enable(phy->ref_clk_src); + clk_prepare_enable(phy->ref_clk); + } else { + clk_disable_unprepare(phy->ref_clk); + clk_disable_unprepare(phy->ref_clk_src); + } + + phy->clocks_enabled = on; +} + +static void msm_eusb2_phy_update_eud_detect(struct msm_eusb2_phy *phy, bool set) +{ + if (set) + writel_relaxed(EUD_DETECT, phy->eud_detect_reg); + else + writel_relaxed(readl_relaxed(phy->eud_detect_reg) & ~EUD_DETECT, + phy->eud_detect_reg); +} + +static int msm_eusb2_phy_power(struct msm_eusb2_phy *phy, bool on) +{ + int ret = 0; + + dev_dbg(phy->phy.dev, "turn %s regulators. power_enabled:%d\n", + on ? "on" : "off", phy->power_enabled); + + if (phy->power_enabled == on) + return 0; + + if (!on) + goto clear_eud_det; + + ret = regulator_set_load(phy->vdd, USB_HSPHY_VDD_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdd:%d\n", ret); + goto err_vdd; + } + + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[1], + phy->vdd_levels[2]); + if (ret) { + dev_err(phy->phy.dev, "Unable to set voltage for hsusb vdd\n"); + goto put_vdd_lpm; + } + + ret = regulator_enable(phy->vdd); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable VDD\n"); + goto unconfig_vdd; + } + + ret = regulator_set_load(phy->vdda12, USB_HSPHY_1P2_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdda12:%d\n", ret); + goto disable_vdd; + } + + ret = regulator_set_voltage(phy->vdda12, USB_HSPHY_1P2_VOL_MIN, + USB_HSPHY_1P2_VOL_MAX); + if (ret) { + dev_err(phy->phy.dev, + "Unable to set voltage for vdda12:%d\n", ret); + goto put_vdda12_lpm; + } + + ret = regulator_enable(phy->vdda12); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable vdda12:%d\n", ret); + goto unset_vdda12; + } + + /* Make sure all the writes are processed before setting EUD_DETECT */ + mb(); + /* Set eud_detect_reg after powering on eUSB PHY rails to bring EUD out of reset */ + msm_eusb2_phy_update_eud_detect(phy, true); + + phy->power_enabled = true; + pr_debug("eUSB2_PHY's regulators are turned ON.\n"); + return ret; + +clear_eud_det: + /* Clear eud_detect_reg to put EUD in reset */ + msm_eusb2_phy_update_eud_detect(phy, false); + + /* Make sure clearing EUD_DETECT is completed before turning off the regulators */ + mb(); + + ret = regulator_disable(phy->vdda12); + if (ret) + dev_err(phy->phy.dev, "Unable to disable vdda12:%d\n", ret); + +unset_vdda12: + ret = regulator_set_voltage(phy->vdda12, 0, USB_HSPHY_1P2_VOL_MAX); + if (ret) + dev_err(phy->phy.dev, + "Unable to set (0) voltage for vdda12:%d\n", ret); + +put_vdda12_lpm: + ret = regulator_set_load(phy->vdda12, 0); + if (ret < 0) + dev_err(phy->phy.dev, "Unable to set LPM of vdda12\n"); + +disable_vdd: + ret = regulator_disable(phy->vdd); + if (ret) + dev_err(phy->phy.dev, "Unable to disable vdd:%d\n", ret); + +unconfig_vdd: + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[0], + phy->vdd_levels[2]); + if (ret) + dev_err(phy->phy.dev, "unable to set voltage for hsusb vdd\n"); + +put_vdd_lpm: + ret = regulator_set_load(phy->vdd, 0); + if (ret < 0) + dev_err(phy->phy.dev, "Unable to set LPM of vdd\n"); + + /* case handling when regulator turning on failed */ + if (!phy->power_enabled) + return -EINVAL; + +err_vdd: + phy->power_enabled = false; + dev_dbg(phy->phy.dev, "eusb2_PHY's regulators are turned OFF.\n"); + return ret; +} + +static void msm_eusb2_write_readback(void __iomem *base, u32 offset, + const u32 mask, u32 val) +{ + u32 write_val, tmp = readl_relaxed(base + offset); + + tmp &= ~mask; + write_val = tmp | val; + + writel_relaxed(write_val, base + offset); + + /* Read back to see if val was written */ + tmp = readl_relaxed(base + offset); + tmp &= mask; + + if (tmp != val) + pr_err("write: %x to offset: %x FAILED\n", val, offset); +} + +static void eusb2_phy_reset_seq(struct msm_eusb2_phy *phy) +{ + writel(APB_LOGIC_RESET, phy->base + USB_PHY_APB_ACCESS_CMD); + writel(0x00, phy->base + USB_PHY_APB_ACCESS_CMD); +} + +#define APB_ACCESS_TIMEOUT 10 /* in us */ +#define APB_ACCESS_POLL_DELAY 1 /* in us */ +#define APB_READ_ACCESS_DONE 1 + +static int eusb2_phy_apb_cmd_wait(struct msm_eusb2_phy *phy) +{ + int ret; + u8 val; /* to read cmd status */ + + /* poll for cmd completion */ + ret = readl_poll_timeout(phy->base + USB_PHY_APB_ACCESS_STATUS, + val, APB_READ_ACCESS_DONE, + APB_ACCESS_POLL_DELAY, APB_ACCESS_TIMEOUT); + if (ret < 0) { + dev_err(phy->phy.dev, "APB_ACCESS_STAUS(%x) timeout\n", val); + eusb2_phy_reset_seq(phy); + return val; + } + + return 0; +} + +static void eusb2_phy_apb_reg_write(struct msm_eusb2_phy *phy, + u8 reg_index, u16 val) +{ + int ret; + + /* program register index to update requested register */ + writel(reg_index, phy->base + USB_PHY_APB_ADDRESS); + + /* value to be program */ + writel(((val >> 8) & 0xF), phy->base + USB_PHY_APB_WRDATA_MSB); + writel((val & 0xF), phy->base + USB_PHY_APB_WRDATA_LSB); + + /* send cmd to update reg_index with above programmed value */ + writel(RW_ACCESS | APB_START_CMD, phy->base + USB_PHY_APB_ACCESS_CMD); + + /* poll for cmd completion */ + ret = eusb2_phy_apb_cmd_wait(phy); + if (ret) { + dev_err(phy->phy.dev, "APB reg(%x) write failed\n", reg_index); + return; + } + + /* write access completed */ + writel(0x0, phy->base + USB_PHY_APB_ACCESS_CMD); + dev_info(phy->phy.dev, "APB reg(%x) updated with %x\n", reg_index, val); +} + +static void eusb2_phy_apb_reg_read(struct msm_eusb2_phy *phy, u8 reg_index) +{ + int ret; + u32 rddata_lsb; + u32 rddata_msb; + + /* program register which is required to read */ + writel(reg_index, phy->base + USB_PHY_APB_ADDRESS); + + /* send cmd to read reg_index based register value */ + writel(APB_START_CMD, phy->base + USB_PHY_APB_ACCESS_CMD); + + /* poll for cmd completion */ + ret = eusb2_phy_apb_cmd_wait(phy); + if (ret) { + dev_err(phy->phy.dev, "APB reg(%x) read failed\n", reg_index); + return; + } + + /* read data of reg_index register */ + rddata_lsb = readl(phy->base + USB_PHY_APB_RDDATA_LSB); + rddata_msb = readl(phy->base + USB_PHY_APB_RDDATA_MSB); + + /* read access completed */ + writel(0x0, phy->base + USB_PHY_APB_ACCESS_CMD); + dev_info(phy->phy.dev, "APB reg(%x) read success, val:%x\n", + reg_index, ((rddata_msb << 8) | rddata_lsb)); +} + +static int apb_reg_rw_open(struct inode *inode, struct file *file) +{ + return single_open(file, NULL, inode->i_private); +} + +static ssize_t apb_reg_rw_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct msm_eusb2_phy *phy = s->private; + char buf[32]; + bool read; + char *field, *p; + u8 reg_index; + u16 val; + + if (!phy->cable_connected) { + dev_err(phy->phy.dev, "eUSB2 PHY is not out of reset.\n"); + return -EINVAL; + } + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + /* + * For reading APB register: read e.g. read 0x33 + * For writing value into APB register: write + * e.g. write 0xff 0xd + * Valid range for offset: 0x00 - 0xFF + * Valid range for value: 0x0000 - 0xFFFF + */ + p = buf; + field = strsep(&p, " "); + if (!field || !*field) + goto err_out; + + if (strcmp(field, "read") == 0) + read = true; + else if (strcmp(field, "write") == 0) + read = false; + else + goto err_out; + + /* get register offset */ + field = strsep(&p, " "); + if (!field) { + dev_err(phy->phy.dev, "Provide register offset\n"); + goto err_out; + } + + if (kstrtou8(field, 16, ®_index)) { + dev_err(phy->phy.dev, "Invalid register offset\n"); + goto err_out; + } + + /* read apb register */ + if (read) { + eusb2_phy_apb_reg_read(phy, reg_index); + goto done; + } + + /* get value to update register for write */ + field = strsep(&p, " "); + if (!field) { + dev_err(phy->phy.dev, "Provide value to update register\n"); + goto err_out; + } + + if (kstrtou16(field, 16, &val)) { + dev_err(phy->phy.dev, "Invalid value\n"); + goto err_out; + } + + eusb2_phy_apb_reg_write(phy, reg_index, val); + goto done; + +err_out: + pr_info("Error: enter proper values to read OR write APB register.\n"); + pr_info("To read APB register: read \n"); + pr_info("To write APB register: write \n"); + pr_info("Valid range for offset: 0x00 - 0xFF\n"); + pr_info("Valid range for value: 0x0000 - 0xFFFF\n"); + +done: + return count; +} + +static const struct file_operations apb_reg_rw_fops = { + .open = apb_reg_rw_open, + .write = apb_reg_rw_write, + .release = single_release, +}; + +static void msm_eusb2_phy_reset(struct msm_eusb2_phy *phy) +{ + int ret; + + ret = reset_control_assert(phy->phy_reset); + if (ret) + dev_err(phy->phy.dev, "phy reset assert failed\n"); + + usleep_range(100, 150); + + ret = reset_control_deassert(phy->phy_reset); + if (ret) + dev_err(phy->phy.dev, "phy reset deassert failed\n"); +} + +static void eusb2_phy_write_seq(void __iomem *base, u32 *seq, int cnt) +{ + int i; + + pr_debug("Seq count:%d\n", cnt); + for (i = 0; i < cnt; i = i+2) { + pr_debug("write 0x%02x to 0x%02x\n", seq[i], seq[i+1]); + writel_relaxed(seq[i], base + seq[i+1]); + } +} + +static void msm_eusb2_parameter_override(struct msm_eusb2_phy *phy) +{ + /* default parameters: tx pre-emphasis */ + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_PREEMP_TUNE, (0 << PHY_CFG_TX_PREEMP_TUNE_SHIFT)); + + /* tx rise/fall time */ + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_RISE_TUNE, (0x2 << PHY_CFG_TX_RISE_TUNE_SHIFT)); + + /* source impedance adjustment */ + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_RES_TUNE, (0x1 << PHY_CFG_TX_RES_TUNE_SHIFT)); + + /* dc voltage level adjustement */ + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8, + PHY_CFG_TX_HS_VREF_TUNE, (0x3 << PHY_CFG_TX_HS_VREF_TUNE_SHIFT)); + + /* transmitter HS crossover adjustement */ + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8, + PHY_CFG_TX_HS_XV_TUNE, (0x0 << PHY_CFG_TX_HS_XV_TUNE_SHIFT)); + + /* override init sequence using devicetree based values */ + eusb2_phy_write_seq(phy->base, phy->param_override_seq, + phy->param_override_seq_cnt); + + /* override tune params using debugfs based values */ + if (phy->tx_pre_emphasis && phy->tx_pre_emphasis <= 7) + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_PREEMP_TUNE, + (phy->tx_pre_emphasis << PHY_CFG_TX_PREEMP_TUNE_SHIFT)); + + if (phy->tx_rise_fall_time && phy->tx_rise_fall_time <= 4) + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_RISE_TUNE, + (phy->tx_rise_fall_time << PHY_CFG_TX_RISE_TUNE_SHIFT)); + + if (phy->tx_src_impedence && phy->tx_src_impedence <= 4) + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_RES_TUNE, + (phy->tx_src_impedence << PHY_CFG_TX_RES_TUNE_SHIFT)); + + if (phy->tx_dc_vref && phy->tx_dc_vref <= 7) + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8, + PHY_CFG_TX_HS_VREF_TUNE, + (phy->tx_dc_vref << PHY_CFG_TX_HS_VREF_TUNE_SHIFT)); + + if (phy->tx_xv && phy->tx_xv <= 4) + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8, + PHY_CFG_TX_HS_XV_TUNE, + (phy->tx_xv << PHY_CFG_TX_HS_XV_TUNE_SHIFT)); +} + +static void msm_eusb2_ref_clk_init(struct usb_phy *uphy) +{ + unsigned long ref_clk_freq; + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + ref_clk_freq = clk_get_rate(phy->ref_clk_src); + switch (ref_clk_freq) { + case 19200000: + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0, + FSEL, FSEL_19_2_MHZ_VAL); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_2, + PHY_CFG_PLL_FB_DIV_7_0, DIV_7_0_19_2_MHZ_VAL); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_3, + PHY_CFG_PLL_FB_DIV_11_8, DIV_11_8_19_2_MHZ_VAL); + break; + case 38400000: + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0, + FSEL, FSEL_38_4_MHZ_VAL); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_2, + PHY_CFG_PLL_FB_DIV_7_0, DIV_7_0_38_4_MHZ_VAL); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_3, + PHY_CFG_PLL_FB_DIV_11_8, DIV_11_8_38_4_MHZ_VAL); + break; + default: + dev_err(uphy->dev, "unsupported ref_clk_freq:%lu\n", + ref_clk_freq); + } + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_3, + PHY_CFG_PLL_REF_DIV, PLL_REF_DIV_VAL); +} + +static int msm_eusb2_repeater_reset_and_init(struct msm_eusb2_phy *phy) +{ + int ret; + + if (phy->ur) + phy->ur->flags = phy->phy.flags; + + ret = usb_repeater_powerup(phy->ur); + if (ret) + dev_err(phy->phy.dev, "repeater powerup failed.\n"); + + ret = usb_repeater_reset(phy->ur, true); + if (ret) + dev_err(phy->phy.dev, "repeater reset failed.\n"); + + ret = usb_repeater_init(phy->ur); + if (ret) + dev_err(phy->phy.dev, "repeater init failed.\n"); + + return ret; +} + +static int msm_eusb2_phy_init(struct usb_phy *uphy) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + int ret; + + dev_dbg(uphy->dev, "phy_flags:%x\n", phy->phy.flags); + if (is_eud_debug_mode_active(phy)) { + /* if in host mode, disable EUD debug mode */ + if (phy->phy.flags & PHY_HOST_MODE) { + qcom_scm_io_writel(phy->eud_reg, 0x0); + phy->re_enable_eud = true; + } else { + msm_eusb2_phy_power(phy, true); + return msm_eusb2_repeater_reset_and_init(phy); + } + } + + ret = msm_eusb2_phy_power(phy, true); + if (ret) + return ret; + + /* Bring eUSB2 repeater out of reset and initialized before eUSB2 PHY */ + ret = msm_eusb2_repeater_reset_and_init(phy); + if (ret) + return ret; + + msm_eusb2_phy_clocks(phy, true); + + msm_eusb2_phy_reset(phy); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG0, + CMN_CTRL_OVERRIDE_EN, CMN_CTRL_OVERRIDE_EN); + + msm_eusb2_write_readback(phy->base, USB_PHY_UTMI_CTRL5, POR, POR); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0, + PHY_ENABLE | RETENABLEN, PHY_ENABLE | RETENABLEN); + + msm_eusb2_write_readback(phy->base, USB_PHY_APB_ACCESS_CMD, + APB_LOGIC_RESET, APB_LOGIC_RESET); + + msm_eusb2_write_readback(phy->base, UTMI_PHY_CMN_CTRL0, TESTBURNIN, 0); + + msm_eusb2_write_readback(phy->base, USB_PHY_FSEL_SEL, + FSEL_SEL, FSEL_SEL); + + /* update ref_clk related registers */ + msm_eusb2_ref_clk_init(uphy); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_1, + PHY_CFG_PLL_CPBIAS_CNTRL, + (0x1 << PHY_CFG_PLL_CPBIAS_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_4, + PHY_CFG_PLL_INT_CNTRL, + (0x8 << PHY_CFG_PLL_INT_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_4, + PHY_CFG_PLL_GMP_CNTRL, + (0x1 << PHY_CFG_PLL_GMP_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_5, + PHY_CFG_PLL_PROP_CNTRL, + (0x10 << PHY_CFG_PLL_PROP_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_6, + PHY_CFG_PLL_VCO_CNTRL, + (0x0 << PHY_CFG_PLL_VCO_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_5, + PHY_CFG_PLL_VREF_TUNE, + (0x1 << PHY_CFG_PLL_VREF_TUNE_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL2, + VBUS_DET_EXT_SEL, VBUS_DET_EXT_SEL); + + /* set parameter override if needed */ + msm_eusb2_parameter_override(phy); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N); + + msm_eusb2_write_readback(phy->base, USB_PHY_UTMI_CTRL0, SLEEPM, SLEEPM); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0, + SIDDQ_SEL, SIDDQ_SEL); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0, + SIDDQ, 0); + + msm_eusb2_write_readback(phy->base, USB_PHY_UTMI_CTRL5, POR, 0); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL, 0); + return 0; +} + +static int msm_eusb2_phy_set_suspend(struct usb_phy *uphy, int suspend) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + if (phy->suspended && suspend) { + dev_dbg(uphy->dev, "USB PHY is already suspended\n"); + return 0; + } + + dev_dbg(uphy->dev, "phy->flags:0x%x\n", phy->phy.flags); + if (suspend) { + /* Bus suspend handling */ + if (phy->cable_connected || + (phy->phy.flags & PHY_HOST_MODE)) { + msm_eusb2_phy_clocks(phy, false); + goto suspend_exit; + } + + /* Cable disconnect handling */ + if (phy->re_enable_eud) { + dev_dbg(uphy->dev, "re-enabling EUD\n"); + qcom_scm_io_writel(phy->eud_reg, 0x1); + phy->re_enable_eud = false; + } + + /* With EUD spoof disconnect, keep clk and ldos on */ + if (phy->phy.flags & EUD_SPOOF_DISCONNECT) + goto suspend_exit; + + msm_eusb2_phy_clocks(phy, false); + msm_eusb2_phy_power(phy, false); + + /* Hold repeater into reset after powering down PHY */ + usb_repeater_reset(phy->ur, false); + usb_repeater_powerdown(phy->ur); + } else { + /* Bus resume and cable connect handling */ + msm_eusb2_phy_clocks(phy, true); + } + +suspend_exit: + phy->suspended = !!suspend; + return 0; +} + +static int msm_eusb2_phy_notify_connect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + phy->cable_connected = true; + return 0; +} + +static int msm_eusb2_phy_notify_disconnect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + phy->cable_connected = false; + return 0; +} + +static void msm_eusb2_phy_vbus_draw_work(struct work_struct *w) +{ + struct msm_eusb2_phy *phy = container_of(w, struct msm_eusb2_phy, + vbus_draw_work); + union power_supply_propval val = {0}; + int ret; + + if (!phy->usb_psy) { + phy->usb_psy = power_supply_get_by_name("usb"); + if (!phy->usb_psy) { + dev_err(phy->phy.dev, "Could not get usb psy\n"); + return; + } + } + + dev_info(phy->phy.dev, "Avail curr from USB = %u\n", phy->vbus_draw); + /* Set max current limit in uA */ + val.intval = 1000 * phy->vbus_draw; + ret = power_supply_set_property(phy->usb_psy, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val); + if (ret) { + dev_dbg(phy->phy.dev, "Error setting ICL:(%d)\n", ret); + return; + } +} + +static int msm_eusb2_phy_set_power(struct usb_phy *uphy, unsigned int mA) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + phy->vbus_draw = mA; + schedule_work(&phy->vbus_draw_work); + + return 0; +} + +static void msm_eusb2_phy_create_debugfs(struct msm_eusb2_phy *phy) +{ + phy->root = debugfs_create_dir(dev_name(phy->phy.dev), NULL); + debugfs_create_x8("tx_pre_emphasis", 0644, phy->root, + &phy->tx_pre_emphasis); + debugfs_create_x8("tx_rise_fall_time", 0644, phy->root, + &phy->tx_rise_fall_time); + debugfs_create_x8("tx_src_imp", 0644, phy->root, + &phy->tx_src_impedence); + debugfs_create_x8("tx_dc_vref", 0644, phy->root, &phy->tx_dc_vref); + debugfs_create_x8("tx_xv", 0644, phy->root, &phy->tx_xv); + + debugfs_create_file("apb_reg_rw", 0200, phy->root, phy, + &apb_reg_rw_fops); +} + +static int msm_eusb2_phy_probe(struct platform_device *pdev) +{ + struct msm_eusb2_phy *phy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0; + struct usb_repeater *ur = NULL; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + ret = -ENOMEM; + goto err_ret; + } + + ur = devm_usb_get_repeater_by_phandle(dev, "usb-repeater", 0); + if (IS_ERR(ur)) { + ret = PTR_ERR(ur); + goto err_ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "eusb2_phy_base"); + if (!res) { + dev_err(dev, "missing eusb2phy memory resource\n"); + ret = -ENODEV; + goto err_ret; + } + + phy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->base)) { + dev_err(dev, "ioremap failed\n"); + ret = -ENODEV; + goto err_ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "eud_enable_reg"); + if (res) { + phy->eud_enable_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->eud_enable_reg)) { + ret = PTR_ERR(phy->eud_enable_reg); + dev_err(dev, "eud_enable_reg ioremap err:%d\n", ret); + goto err_ret; + } + phy->eud_reg = res->start; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "eud_detect_reg"); + if (!res) { + dev_err(dev, "missing eud_detect register address\n"); + ret = -ENODEV; + goto err_ret; + } + + phy->eud_detect_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->eud_detect_reg)) { + ret = PTR_ERR(phy->eud_detect_reg); + dev_err(dev, "eud_detect_reg ioremap err:%d\n", ret); + goto err_ret; + } + + phy->ref_clk_src = devm_clk_get(dev, "ref_clk_src"); + if (IS_ERR(phy->ref_clk_src)) { + dev_err(dev, "clk get failed for ref_clk_src\n"); + ret = PTR_ERR(phy->ref_clk_src); + goto err_ret; + } + + phy->ref_clk = devm_clk_get(dev, "ref_clk"); + if (IS_ERR(phy->ref_clk)) { + dev_err(dev, "clk get failed for ref_clk\n"); + ret = PTR_ERR(phy->ref_clk); + goto err_ret; + } + + phy->phy_reset = devm_reset_control_get(dev, "phy_reset"); + if (IS_ERR(phy->phy_reset)) { + ret = PTR_ERR(phy->phy_reset); + goto err_ret; + } + + ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", + (u32 *) phy->vdd_levels, + ARRAY_SIZE(phy->vdd_levels)); + if (ret) { + dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); + goto err_ret; + } + + phy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(phy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + ret = PTR_ERR(phy->vdd); + goto err_ret; + } + + phy->vdda12 = devm_regulator_get(dev, "vdda12"); + if (IS_ERR(phy->vdda12)) { + dev_err(dev, "unable to get vdda12 supply\n"); + ret = PTR_ERR(phy->vdda12); + goto err_ret; + } + + phy->param_override_seq_cnt = of_property_count_elems_of_size( + dev->of_node, "qcom,param-override-seq", + sizeof(*phy->param_override_seq)); + if (phy->param_override_seq_cnt % 2) { + dev_err(dev, "invalid param_override_seq_len\n"); + ret = -EINVAL; + goto err_ret; + } + + if (phy->param_override_seq_cnt > 0) { + phy->param_override_seq = devm_kcalloc(dev, + phy->param_override_seq_cnt, + sizeof(*phy->param_override_seq), + GFP_KERNEL); + if (!phy->param_override_seq) { + ret = -ENOMEM; + goto err_ret; + } + + ret = of_property_read_u32_array(dev->of_node, + "qcom,param-override-seq", + phy->param_override_seq, + phy->param_override_seq_cnt); + if (ret) { + dev_err(dev, "qcom,param-override-seq read failed %d\n", + ret); + goto err_ret; + } + } + + phy->ur = ur; + phy->phy.dev = dev; + platform_set_drvdata(pdev, phy); + + phy->phy.init = msm_eusb2_phy_init; + phy->phy.set_suspend = msm_eusb2_phy_set_suspend; + phy->phy.notify_connect = msm_eusb2_phy_notify_connect; + phy->phy.notify_disconnect = msm_eusb2_phy_notify_disconnect; + phy->phy.set_power = msm_eusb2_phy_set_power; + phy->phy.type = USB_PHY_TYPE_USB2; + + ret = usb_add_phy_dev(&phy->phy); + if (ret) + goto err_ret; + + INIT_WORK(&phy->vbus_draw_work, msm_eusb2_phy_vbus_draw_work); + msm_eusb2_phy_create_debugfs(phy); + + /* + * EUD may be enable in boot loader and to keep EUD session alive across + * kernel boot till USB phy driver is initialized based on cable status, + * keep LDOs on here. + */ + if (is_eud_debug_mode_active(phy)) + msm_eusb2_phy_power(phy, true); + + return 0; + +err_ret: + return ret; +} + +static int msm_eusb2_phy_remove(struct platform_device *pdev) +{ + struct msm_eusb2_phy *phy = platform_get_drvdata(pdev); + + if (!phy) + return 0; + + flush_work(&phy->vbus_draw_work); + if (phy->usb_psy) + power_supply_put(phy->usb_psy); + + debugfs_remove_recursive(phy->root); + usb_remove_phy(&phy->phy); + clk_disable_unprepare(phy->ref_clk); + clk_disable_unprepare(phy->ref_clk_src); + msm_eusb2_phy_clocks(phy, false); + msm_eusb2_phy_power(phy, false); + return 0; +} + +static const struct of_device_id msm_usb_id_table[] = { + { + .compatible = "qcom,usb-snps-eusb2-phy", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm_usb_id_table); + +static struct platform_driver msm_eusb2_phy_driver = { + .probe = msm_eusb2_phy_probe, + .remove = msm_eusb2_phy_remove, + .driver = { + .name = "msm_eusb2_phy", + .of_match_table = of_match_ptr(msm_usb_id_table), + }, +}; + +module_platform_driver(msm_eusb2_phy_driver); +MODULE_DESCRIPTION("MSM USB eUSB2 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-msm-snps-hs.c b/drivers/usb/phy/phy-msm-snps-hs.c new file mode 100644 index 000000000000..0a55035cd702 --- /dev/null +++ b/drivers/usb/phy/phy-msm-snps-hs.c @@ -0,0 +1,939 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB2_PHY_USB_PHY_UTMI_CTRL0 (0x3c) +#define OPMODE_MASK (0x3 << 3) +#define OPMODE_NONDRIVING (0x1 << 3) +#define SLEEPM BIT(0) + +#define USB2_PHY_USB_PHY_UTMI_CTRL5 (0x50) +#define POR BIT(1) + +#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0 (0x54) +#define SIDDQ BIT(2) +#define RETENABLEN BIT(3) +#define FSEL_MASK (0x7 << 4) +#define FSEL_DEFAULT (0x3 << 4) + +#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1 (0x58) +#define VBUSVLDEXTSEL0 BIT(4) +#define PLLBTUNE BIT(5) + +#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON2 (0x5c) +#define VREGBYPASS BIT(0) + +#define USB2_PHY_USB_PHY_HS_PHY_CTRL1 (0x60) +#define VBUSVLDEXT0 BIT(0) + +#define USB2_PHY_USB_PHY_HS_PHY_CTRL2 (0x64) +#define USB2_AUTO_RESUME BIT(0) +#define USB2_SUSPEND_N BIT(2) +#define USB2_SUSPEND_N_SEL BIT(3) + +#define USB2_PHY_USB_PHY_CFG0 (0x94) +#define UTMI_PHY_DATAPATH_CTRL_OVERRIDE_EN BIT(0) +#define UTMI_PHY_CMN_CTRL_OVERRIDE_EN BIT(1) + +#define USB2_PHY_USB_PHY_REFCLK_CTRL (0xa0) +#define REFCLK_SEL_MASK (0x3 << 0) +#define REFCLK_SEL_DEFAULT (0x2 << 0) + +#define USB2_PHY_USB_PHY_PWRDOWN_CTRL (0xa4) +#define PWRDOWN_B BIT(0) + +#define USB2PHY_USB_PHY_RTUNE_SEL (0xb4) +#define RTUNE_SEL BIT(0) + +#define TXPREEMPAMPTUNE0(x) (x << 6) +#define TXPREEMPAMPTUNE0_MASK (BIT(7) | BIT(6)) +#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X0 0x6c +#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1 0x70 +#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X2 0x74 +#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X3 0x78 +#define TXVREFTUNE0_MASK 0xF +#define PARAM_OVRD_MASK 0xFF + +#define USB_HSPHY_3P3_VOL_MIN 3050000 /* uV */ +#define USB_HSPHY_3P3_VOL_MAX 3300000 /* uV */ +#define USB_HSPHY_3P3_HPM_LOAD 16000 /* uA */ +#define USB_HSPHY_3P3_VOL_FSHOST 3150000 /* uV */ + +#define USB_HSPHY_1P8_VOL_MIN 1704000 /* uV */ +#define USB_HSPHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_HSPHY_1P8_HPM_LOAD 19000 /* uA */ + +#define USB_HSPHY_VDD_HPM_LOAD 30000 /* uA */ + +struct msm_hsphy { + struct usb_phy phy; + void __iomem *base; + phys_addr_t eud_reg; + void __iomem *eud_enable_reg; + bool re_enable_eud; + + struct clk *ref_clk_src; + struct clk *cfg_ahb_clk; + struct reset_control *phy_reset; + + struct regulator *vdd; + struct regulator *vdda33; + struct regulator *vdda18; + int vdd_levels[3]; /* none, low, high */ + + bool clocks_enabled; + bool power_enabled; + bool suspended; + bool cable_connected; + bool dpdm_enable; + + int *param_override_seq; + int param_override_seq_cnt; + + void __iomem *phy_rcal_reg; + u32 rcal_mask; + + struct mutex phy_lock; + struct regulator_desc dpdm_rdesc; + struct regulator_dev *dpdm_rdev; + + struct power_supply *usb_psy; + unsigned int vbus_draw; + struct work_struct vbus_draw_work; + + /* debugfs entries */ + struct dentry *root; + u8 txvref_tune0; + u8 pre_emphasis; + u8 param_ovrd0; + u8 param_ovrd1; + u8 param_ovrd2; + u8 param_ovrd3; +}; + +static void msm_hsphy_enable_clocks(struct msm_hsphy *phy, bool on) +{ + dev_dbg(phy->phy.dev, "%s(): clocks_enabled:%d on:%d\n", + __func__, phy->clocks_enabled, on); + + if (!phy->clocks_enabled && on) { + clk_prepare_enable(phy->ref_clk_src); + + if (phy->cfg_ahb_clk) + clk_prepare_enable(phy->cfg_ahb_clk); + + phy->clocks_enabled = true; + } + + if (phy->clocks_enabled && !on) { + if (phy->cfg_ahb_clk) + clk_disable_unprepare(phy->cfg_ahb_clk); + + clk_disable_unprepare(phy->ref_clk_src); + phy->clocks_enabled = false; + } + +} + +static int msm_hsphy_enable_power(struct msm_hsphy *phy, bool on) +{ + int ret = 0; + + dev_dbg(phy->phy.dev, "%s turn %s regulators. power_enabled:%d\n", + __func__, on ? "on" : "off", phy->power_enabled); + + if (phy->power_enabled == on) { + dev_dbg(phy->phy.dev, "PHYs' regulators are already ON.\n"); + return 0; + } + + if (!on) + goto disable_vdda33; + + ret = regulator_set_load(phy->vdd, USB_HSPHY_VDD_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdd:%d\n", ret); + goto err_vdd; + } + + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[1], + phy->vdd_levels[2]); + if (ret) { + dev_err(phy->phy.dev, "unable to set voltage for hsusb vdd\n"); + goto put_vdd_lpm; + } + + ret = regulator_enable(phy->vdd); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable VDD\n"); + goto unconfig_vdd; + } + + ret = regulator_set_load(phy->vdda18, USB_HSPHY_1P8_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdda18:%d\n", ret); + goto disable_vdd; + } + + ret = regulator_set_voltage(phy->vdda18, USB_HSPHY_1P8_VOL_MIN, + USB_HSPHY_1P8_VOL_MAX); + if (ret) { + dev_err(phy->phy.dev, + "Unable to set voltage for vdda18:%d\n", ret); + goto put_vdda18_lpm; + } + + ret = regulator_enable(phy->vdda18); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable vdda18:%d\n", ret); + goto unset_vdda18; + } + + ret = regulator_set_load(phy->vdda33, USB_HSPHY_3P3_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdda33:%d\n", ret); + goto disable_vdda18; + } + + ret = regulator_set_voltage(phy->vdda33, USB_HSPHY_3P3_VOL_MIN, + USB_HSPHY_3P3_VOL_MAX); + if (ret) { + dev_err(phy->phy.dev, + "Unable to set voltage for vdda33:%d\n", ret); + goto put_vdda33_lpm; + } + + ret = regulator_enable(phy->vdda33); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable vdda33:%d\n", ret); + goto unset_vdd33; + } + + phy->power_enabled = true; + + pr_debug("%s(): HSUSB PHY's regulators are turned ON.\n", __func__); + return ret; + +disable_vdda33: + ret = regulator_disable(phy->vdda33); + if (ret) + dev_err(phy->phy.dev, "Unable to disable vdda33:%d\n", ret); + +unset_vdd33: + ret = regulator_set_voltage(phy->vdda33, 0, USB_HSPHY_3P3_VOL_MAX); + if (ret) + dev_err(phy->phy.dev, + "Unable to set (0) voltage for vdda33:%d\n", ret); + +put_vdda33_lpm: + ret = regulator_set_load(phy->vdda33, 0); + if (ret < 0) + dev_err(phy->phy.dev, "Unable to set (0) HPM of vdda33\n"); + +disable_vdda18: + ret = regulator_disable(phy->vdda18); + if (ret) + dev_err(phy->phy.dev, "Unable to disable vdda18:%d\n", ret); + +unset_vdda18: + ret = regulator_set_voltage(phy->vdda18, 0, USB_HSPHY_1P8_VOL_MAX); + if (ret) + dev_err(phy->phy.dev, + "Unable to set (0) voltage for vdda18:%d\n", ret); + +put_vdda18_lpm: + ret = regulator_set_load(phy->vdda18, 0); + if (ret < 0) + dev_err(phy->phy.dev, "Unable to set LPM of vdda18\n"); + +disable_vdd: + ret = regulator_disable(phy->vdd); + if (ret) + dev_err(phy->phy.dev, "Unable to disable vdd:%d\n", ret); + +unconfig_vdd: + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[0], + phy->vdd_levels[2]); + if (ret) + dev_err(phy->phy.dev, "unable to set voltage for hsusb vdd\n"); + +put_vdd_lpm: + ret = regulator_set_load(phy->vdd, 0); + if (ret < 0) + dev_err(phy->phy.dev, "Unable to set LPM of vdd\n"); +err_vdd: + phy->power_enabled = false; + dev_dbg(phy->phy.dev, "HSUSB PHY's regulators are turned OFF.\n"); + return ret; +} + +static void msm_usb_write_readback(void __iomem *base, u32 offset, + const u32 mask, u32 val) +{ + u32 write_val, tmp = readl_relaxed(base + offset); + + tmp &= ~mask; /* retain other bits */ + write_val = tmp | val; + + writel_relaxed(write_val, base + offset); + + /* Read back to see if val was written */ + tmp = readl_relaxed(base + offset); + tmp &= mask; /* clear other bits */ + + if (tmp != val) + pr_err("%s: write: %x to QSCRATCH: %x FAILED\n", + __func__, val, offset); +} + +static void msm_hsphy_reset(struct msm_hsphy *phy) +{ + int ret; + + ret = reset_control_assert(phy->phy_reset); + if (ret) + dev_err(phy->phy.dev, "%s: phy_reset assert failed\n", + __func__); + usleep_range(100, 150); + + ret = reset_control_deassert(phy->phy_reset); + if (ret) + dev_err(phy->phy.dev, "%s: phy_reset deassert failed\n", + __func__); +} + +static void hsusb_phy_write_seq(void __iomem *base, u32 *seq, int cnt, + unsigned long delay) +{ + int i; + + pr_debug("Seq count:%d\n", cnt); + for (i = 0; i < cnt; i = i+2) { + pr_debug("write 0x%02x to 0x%02x\n", seq[i], seq[i+1]); + writel_relaxed(seq[i], base + seq[i+1]); + if (delay) + usleep_range(delay, (delay + 2000)); + } +} + +#define EUD_EN2 BIT(0) +static int msm_hsphy_init(struct usb_phy *uphy) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + int ret; + u32 rcal_code = 0, eud_csr_reg = 0; + + dev_dbg(uphy->dev, "%s phy_flags:0x%x\n", __func__, phy->phy.flags); + if (phy->eud_enable_reg) { + eud_csr_reg = readl_relaxed(phy->eud_enable_reg); + if (eud_csr_reg & EUD_EN2) { + dev_dbg(phy->phy.dev, "csr:0x%x eud is enabled\n", + eud_csr_reg); + /* if in host mode, disable EUD */ + if (phy->phy.flags & PHY_HOST_MODE) { + qcom_scm_io_writel(phy->eud_reg, 0x0); + phy->re_enable_eud = true; + } else { + ret = msm_hsphy_enable_power(phy, true); + return ret; + } + } + } + + ret = msm_hsphy_enable_power(phy, true); + if (ret) + return ret; + + msm_hsphy_enable_clocks(phy, true); + + msm_hsphy_reset(phy); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_CFG0, + UTMI_PHY_CMN_CTRL_OVERRIDE_EN, + UTMI_PHY_CMN_CTRL_OVERRIDE_EN); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL5, + POR, POR); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0, + FSEL_MASK, 0); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1, + PLLBTUNE, PLLBTUNE); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_REFCLK_CTRL, + REFCLK_SEL_MASK, REFCLK_SEL_DEFAULT); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1, + VBUSVLDEXTSEL0, VBUSVLDEXTSEL0); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL1, + VBUSVLDEXT0, VBUSVLDEXT0); + + /* set parameter ovrride if needed */ + if (phy->param_override_seq) + hsusb_phy_write_seq(phy->base, phy->param_override_seq, + phy->param_override_seq_cnt, 0); + + if (phy->pre_emphasis) { + u8 val = TXPREEMPAMPTUNE0(phy->pre_emphasis) & + TXPREEMPAMPTUNE0_MASK; + if (val) + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1, + TXPREEMPAMPTUNE0_MASK, val); + } + + if (phy->txvref_tune0) { + u8 val = phy->txvref_tune0 & TXVREFTUNE0_MASK; + + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1, + TXVREFTUNE0_MASK, val); + } + + if (phy->param_ovrd0) { + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X0, + PARAM_OVRD_MASK, phy->param_ovrd0); + } + + if (phy->param_ovrd1) { + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1, + PARAM_OVRD_MASK, phy->param_ovrd1); + } + + if (phy->param_ovrd2) { + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X2, + PARAM_OVRD_MASK, phy->param_ovrd2); + } + + if (phy->param_ovrd3) { + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X3, + PARAM_OVRD_MASK, phy->param_ovrd3); + } + + dev_dbg(uphy->dev, "x0:%08x x1:%08x x2:%08x x3:%08x\n", + readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X0), + readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1), + readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X2), + readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X3)); + + if (phy->phy_rcal_reg) { + rcal_code = readl_relaxed(phy->phy_rcal_reg) & phy->rcal_mask; + + dev_dbg(uphy->dev, "rcal_mask:%08x reg:%pK code:%08x\n", + phy->rcal_mask, phy->phy_rcal_reg, rcal_code); + } + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON2, + VREGBYPASS, VREGBYPASS); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL0, + SLEEPM, SLEEPM); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0, + SIDDQ, 0); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL5, + POR, 0); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL, 0); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_CFG0, + UTMI_PHY_CMN_CTRL_OVERRIDE_EN, 0); + + return 0; +} + +static int msm_hsphy_set_suspend(struct usb_phy *uphy, int suspend) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + + if (phy->suspended && suspend) { + if (phy->phy.flags & PHY_SUS_OVERRIDE) + goto suspend; + + dev_dbg(uphy->dev, "%s: USB PHY is already suspended\n", + __func__); + return 0; + } + +suspend: + if (suspend) { /* Bus suspend */ + if (phy->cable_connected) { + /* Enable auto-resume functionality during host mode + * bus suspend with some FS/HS peripheral connected. + */ + if ((phy->phy.flags & PHY_HOST_MODE) && + (phy->phy.flags & PHY_HSFS_MODE)) { + /* Enable auto-resume functionality by pulsing + * signal + */ + msm_usb_write_readback(phy->base, + USB2_PHY_USB_PHY_HS_PHY_CTRL2, + USB2_AUTO_RESUME, USB2_AUTO_RESUME); + usleep_range(500, 1000); + msm_usb_write_readback(phy->base, + USB2_PHY_USB_PHY_HS_PHY_CTRL2, + USB2_AUTO_RESUME, 0); + } + msm_hsphy_enable_clocks(phy, false); + } else {/* Cable disconnect */ + mutex_lock(&phy->phy_lock); + dev_dbg(uphy->dev, "phy->flags:0x%x\n", phy->phy.flags); + if (phy->re_enable_eud) { + dev_dbg(uphy->dev, "re-enabling EUD\n"); + qcom_scm_io_writel(phy->eud_reg, 0x1); + phy->re_enable_eud = false; + } + + if (!phy->dpdm_enable) { + if (!(phy->phy.flags & EUD_SPOOF_DISCONNECT)) { + dev_dbg(uphy->dev, "turning off clocks/ldo\n"); + msm_usb_write_readback(phy->base, + USB2_PHY_USB_PHY_PWRDOWN_CTRL, + PWRDOWN_B, 0); + msm_hsphy_enable_clocks(phy, false); + msm_hsphy_enable_power(phy, false); + } + } else { + dev_dbg(uphy->dev, "dpdm reg still active. Keep clocks/ldo ON\n"); + } + mutex_unlock(&phy->phy_lock); + } + phy->suspended = true; + } else { /* Bus resume and cable connect */ + msm_hsphy_enable_clocks(phy, true); + phy->suspended = false; + } + + return 0; +} + +static int msm_hsphy_notify_connect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + + phy->cable_connected = true; + + return 0; +} + +static int msm_hsphy_notify_disconnect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + + phy->cable_connected = false; + + return 0; +} + +static void msm_hsphy_vbus_draw_work(struct work_struct *w) +{ + struct msm_hsphy *phy = container_of(w, struct msm_hsphy, + vbus_draw_work); + union power_supply_propval val = {0}; + int ret; + + if (!phy->usb_psy) { + phy->usb_psy = power_supply_get_by_name("usb"); + if (!phy->usb_psy) { + dev_err(phy->phy.dev, "Could not get usb psy\n"); + return; + } + } + + dev_info(phy->phy.dev, "Avail curr from USB = %u\n", phy->vbus_draw); + + /* Set max current limit in uA */ + val.intval = 1000 * phy->vbus_draw; + ret = power_supply_set_property(phy->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val); + if (ret) { + dev_dbg(phy->phy.dev, "Error (%d) setting input current limit\n", ret); + return; + } +} + +static int msm_hsphy_set_power(struct usb_phy *uphy, unsigned int mA) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + + phy->vbus_draw = mA; + schedule_work(&phy->vbus_draw_work); + + return 0; +} + +static int msm_hsphy_dpdm_regulator_enable(struct regulator_dev *rdev) +{ + int ret = 0; + struct msm_hsphy *phy = rdev_get_drvdata(rdev); + + dev_dbg(phy->phy.dev, "%s dpdm_enable:%d\n", + __func__, phy->dpdm_enable); + + if (phy->eud_enable_reg && readl_relaxed(phy->eud_enable_reg)) { + dev_err(phy->phy.dev, "eud is enabled\n"); + return 0; + } + + mutex_lock(&phy->phy_lock); + if (!phy->dpdm_enable) { + ret = msm_hsphy_enable_power(phy, true); + if (ret) { + mutex_unlock(&phy->phy_lock); + return ret; + } + + msm_hsphy_enable_clocks(phy, true); + + msm_hsphy_reset(phy); + + /* + * For PMIC charger detection, place PHY in UTMI non-driving + * mode which leaves Dp and Dm lines in high-Z state. + */ + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N); + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL0, + OPMODE_MASK, OPMODE_NONDRIVING); + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_CFG0, + UTMI_PHY_DATAPATH_CTRL_OVERRIDE_EN, + UTMI_PHY_DATAPATH_CTRL_OVERRIDE_EN); + + phy->dpdm_enable = true; + } + mutex_unlock(&phy->phy_lock); + + return ret; +} + +static int msm_hsphy_dpdm_regulator_disable(struct regulator_dev *rdev) +{ + int ret = 0; + struct msm_hsphy *phy = rdev_get_drvdata(rdev); + + dev_dbg(phy->phy.dev, "%s dpdm_enable:%d\n", + __func__, phy->dpdm_enable); + + mutex_lock(&phy->phy_lock); + if (phy->dpdm_enable) { + if (!phy->cable_connected) { + msm_hsphy_enable_clocks(phy, false); + ret = msm_hsphy_enable_power(phy, false); + if (ret < 0) { + mutex_unlock(&phy->phy_lock); + return ret; + } + } + phy->dpdm_enable = false; + } + mutex_unlock(&phy->phy_lock); + + return ret; +} + +static int msm_hsphy_dpdm_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct msm_hsphy *phy = rdev_get_drvdata(rdev); + + dev_dbg(phy->phy.dev, "%s dpdm_enable:%d\n", + __func__, phy->dpdm_enable); + + return phy->dpdm_enable; +} + +static const struct regulator_ops msm_hsphy_dpdm_regulator_ops = { + .enable = msm_hsphy_dpdm_regulator_enable, + .disable = msm_hsphy_dpdm_regulator_disable, + .is_enabled = msm_hsphy_dpdm_regulator_is_enabled, +}; + +static int msm_hsphy_regulator_init(struct msm_hsphy *phy) +{ + struct device *dev = phy->phy.dev; + struct regulator_config cfg = {}; + struct regulator_init_data *init_data; + + init_data = devm_kzalloc(dev, sizeof(*init_data), GFP_KERNEL); + if (!init_data) + return -ENOMEM; + + init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_STATUS; + phy->dpdm_rdesc.owner = THIS_MODULE; + phy->dpdm_rdesc.type = REGULATOR_VOLTAGE; + phy->dpdm_rdesc.ops = &msm_hsphy_dpdm_regulator_ops; + phy->dpdm_rdesc.name = kbasename(dev->of_node->full_name); + + cfg.dev = dev; + cfg.init_data = init_data; + cfg.driver_data = phy; + cfg.of_node = dev->of_node; + + phy->dpdm_rdev = devm_regulator_register(dev, &phy->dpdm_rdesc, &cfg); + return PTR_ERR_OR_ZERO(phy->dpdm_rdev); +} + +static void msm_hsphy_create_debugfs(struct msm_hsphy *phy) +{ + phy->root = debugfs_create_dir(dev_name(phy->phy.dev), NULL); + debugfs_create_x8("pre_emphasis", 0644, phy->root, &phy->pre_emphasis); + debugfs_create_x8("txvref_tune0", 0644, phy->root, &phy->txvref_tune0); + debugfs_create_x8("param_ovrd0", 0644, phy->root, &phy->param_ovrd0); + debugfs_create_x8("param_ovrd1", 0644, phy->root, &phy->param_ovrd1); + debugfs_create_x8("param_ovrd2", 0644, phy->root, &phy->param_ovrd2); + debugfs_create_x8("param_ovrd3", 0644, phy->root, &phy->param_ovrd3); +} + +static int msm_hsphy_probe(struct platform_device *pdev) +{ + struct msm_hsphy *phy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + ret = -ENOMEM; + goto err_ret; + } + + phy->phy.dev = dev; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "hsusb_phy_base"); + if (!res) { + dev_err(dev, "missing memory base resource\n"); + ret = -ENODEV; + goto err_ret; + } + + phy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->base)) { + dev_err(dev, "ioremap failed\n"); + ret = -ENODEV; + goto err_ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "phy_rcal_reg"); + if (res) { + phy->phy_rcal_reg = devm_ioremap(dev, + res->start, resource_size(res)); + if (IS_ERR(phy->phy_rcal_reg)) { + dev_err(dev, "couldn't ioremap phy_rcal_reg\n"); + phy->phy_rcal_reg = NULL; + } + if (of_property_read_u32(dev->of_node, + "qcom,rcal-mask", &phy->rcal_mask)) { + dev_err(dev, "unable to read phy rcal mask\n"); + phy->phy_rcal_reg = NULL; + } + dev_dbg(dev, "rcal_mask:%08x reg:%pK\n", phy->rcal_mask, + phy->phy_rcal_reg); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "eud_enable_reg"); + if (res) { + phy->eud_enable_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->eud_enable_reg)) { + dev_err(dev, "err getting eud_enable_reg address\n"); + return PTR_ERR(phy->eud_enable_reg); + } + phy->eud_reg = res->start; + } + + /* ref_clk_src is needed irrespective of SE_CLK or DIFF_CLK usage */ + phy->ref_clk_src = devm_clk_get(dev, "ref_clk_src"); + if (IS_ERR(phy->ref_clk_src)) { + dev_dbg(dev, "clk get failed for ref_clk_src\n"); + ret = PTR_ERR(phy->ref_clk_src); + return ret; + } + + if (of_property_match_string(pdev->dev.of_node, + "clock-names", "cfg_ahb_clk") >= 0) { + phy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb_clk"); + if (IS_ERR(phy->cfg_ahb_clk)) { + ret = PTR_ERR(phy->cfg_ahb_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "clk get failed for cfg_ahb_clk ret %d\n", ret); + return ret; + } + } + + phy->phy_reset = devm_reset_control_get(dev, "phy_reset"); + if (IS_ERR(phy->phy_reset)) + return PTR_ERR(phy->phy_reset); + + phy->param_override_seq_cnt = of_property_count_elems_of_size( + dev->of_node, + "qcom,param-override-seq", + sizeof(*phy->param_override_seq)); + if (phy->param_override_seq_cnt > 0) { + phy->param_override_seq = devm_kcalloc(dev, + phy->param_override_seq_cnt, + sizeof(*phy->param_override_seq), + GFP_KERNEL); + if (!phy->param_override_seq) + return -ENOMEM; + + if (phy->param_override_seq_cnt % 2) { + dev_err(dev, "invalid param_override_seq_len\n"); + return -EINVAL; + } + + ret = of_property_read_u32_array(dev->of_node, + "qcom,param-override-seq", + phy->param_override_seq, + phy->param_override_seq_cnt); + if (ret) { + dev_err(dev, "qcom,param-override-seq read failed %d\n", + ret); + return ret; + } + } + + ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", + (u32 *) phy->vdd_levels, + ARRAY_SIZE(phy->vdd_levels)); + if (ret) { + dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); + goto err_ret; + } + + + phy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(phy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + ret = PTR_ERR(phy->vdd); + goto err_ret; + } + + phy->vdda33 = devm_regulator_get(dev, "vdda33"); + if (IS_ERR(phy->vdda33)) { + dev_err(dev, "unable to get vdda33 supply\n"); + ret = PTR_ERR(phy->vdda33); + goto err_ret; + } + + phy->vdda18 = devm_regulator_get(dev, "vdda18"); + if (IS_ERR(phy->vdda18)) { + dev_err(dev, "unable to get vdda18 supply\n"); + ret = PTR_ERR(phy->vdda18); + goto err_ret; + } + + mutex_init(&phy->phy_lock); + platform_set_drvdata(pdev, phy); + + phy->phy.init = msm_hsphy_init; + phy->phy.set_suspend = msm_hsphy_set_suspend; + phy->phy.notify_connect = msm_hsphy_notify_connect; + phy->phy.notify_disconnect = msm_hsphy_notify_disconnect; + phy->phy.set_power = msm_hsphy_set_power; + phy->phy.type = USB_PHY_TYPE_USB2; + + ret = usb_add_phy_dev(&phy->phy); + if (ret) + return ret; + + ret = msm_hsphy_regulator_init(phy); + if (ret) { + usb_remove_phy(&phy->phy); + return ret; + } + + INIT_WORK(&phy->vbus_draw_work, msm_hsphy_vbus_draw_work); + msm_hsphy_create_debugfs(phy); + + /* + * EUD may be enable in boot loader and to keep EUD session alive across + * kernel boot till USB phy driver is initialized based on cable status, + * keep LDOs on here. + */ + if (phy->eud_enable_reg && readl_relaxed(phy->eud_enable_reg)) + msm_hsphy_enable_power(phy, true); + return 0; + +err_ret: + return ret; +} + +static int msm_hsphy_remove(struct platform_device *pdev) +{ + struct msm_hsphy *phy = platform_get_drvdata(pdev); + + if (!phy) + return 0; + + if (phy->usb_psy) + power_supply_put(phy->usb_psy); + + debugfs_remove_recursive(phy->root); + + usb_remove_phy(&phy->phy); + clk_disable_unprepare(phy->ref_clk_src); + + msm_hsphy_enable_clocks(phy, false); + msm_hsphy_enable_power(phy, false); + return 0; +} + +static const struct of_device_id msm_usb_id_table[] = { + { + .compatible = "qcom,usb-hsphy-snps-femto", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm_usb_id_table); + +static struct platform_driver msm_hsphy_driver = { + .probe = msm_hsphy_probe, + .remove = msm_hsphy_remove, + .driver = { + .name = "msm-usb-hsphy", + .of_match_table = of_match_ptr(msm_usb_id_table), + }, +}; + +module_platform_driver(msm_hsphy_driver); + +MODULE_DESCRIPTION("MSM USB HS PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-msm-ssusb-qmp.c b/drivers/usb/phy/phy-msm-ssusb-qmp.c new file mode 100644 index 000000000000..da49d5cd8964 --- /dev/null +++ b/drivers/usb/phy/phy-msm-ssusb-qmp.c @@ -0,0 +1,1169 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum core_ldo_levels { + CORE_LEVEL_NONE = 0, + CORE_LEVEL_MIN, + CORE_LEVEL_MAX, +}; + +#define INIT_MAX_TIME_USEC 1000 + +/* default CORE votlage and load values */ +#define USB_SSPHY_1P2_VOL_MIN 1200000 /* uV */ +#define USB_SSPHY_1P2_VOL_MAX 1200000 /* uV */ +#define USB_SSPHY_HPM_LOAD 30000 /* uA */ + +/* USB3PHY_PCIE_USB3_PCS_PCS_STATUS bit */ +#define PHYSTATUS BIT(6) + +/* PCIE_USB3_PHY_AUTONOMOUS_MODE_CTRL bits */ +#define ARCVR_DTCT_EN BIT(0) +#define ALFPS_DTCT_EN BIT(1) +#define ARCVR_DTCT_EVENT_SEL BIT(4) + +/* + * register bits + * PCIE_USB3_PHY_PCS_MISC_TYPEC_CTRL - for QMP USB PHY + * USB3_DP_COM_PHY_MODE_CTRL - for QMP USB DP Combo PHY + */ + +/* 0 - selects Lane A. 1 - selects Lane B */ +#define SW_PORTSELECT BIT(0) +/* port select mux: 1 - sw control. 0 - HW control*/ +#define SW_PORTSELECT_MX BIT(1) + +/* USB3_DP_PHY_USB3_DP_COM_SWI_CTRL bits */ + +/* LANE related register read/write with USB3 */ +#define USB3_SWI_ACT_ACCESS_EN BIT(0) +/* LANE related register read/write with DP */ +#define DP_SWI_ACT_ACCESS_EN BIT(1) + +/* USB3_DP_COM_RESET_OVRD_CTRL bits */ + +/* DP PHY soft reset */ +#define SW_DPPHY_RESET BIT(0) +/* mux to select DP PHY reset control, 0:HW control, 1: software reset */ +#define SW_DPPHY_RESET_MUX BIT(1) +/* USB3 PHY soft reset */ +#define SW_USB3PHY_RESET BIT(2) +/* mux to select USB3 PHY reset control, 0:HW control, 1: software reset */ +#define SW_USB3PHY_RESET_MUX BIT(3) + +/* USB3_DP_COM_PHY_MODE_CTRL bits */ +#define USB3_MODE BIT(0) /* enables USB3 mode */ +#define DP_MODE BIT(1) /* enables DP mode */ +#define USB3_DP_COMBO_MODE (USB3_MODE | DP_MODE) /*enables combo mode */ + +enum qmp_phy_rev_reg { + USB3_PHY_PCS_STATUS, + USB3_PHY_AUTONOMOUS_MODE_CTRL, + USB3_PHY_LFPS_RXTERM_IRQ_CLEAR, + USB3_PHY_POWER_DOWN_CONTROL, + USB3_PHY_SW_RESET, + USB3_PHY_START, + + /* TypeC port select configuration (optional) */ + USB3_PHY_PCS_MISC_TYPEC_CTRL, + + /* USB DP Combo PHY related */ + USB3_DP_COM_POWER_DOWN_CTRL, + USB3_DP_COM_SW_RESET, + USB3_DP_COM_RESET_OVRD_CTRL, + USB3_DP_COM_PHY_MODE_CTRL, + USB3_DP_COM_TYPEC_CTRL, + USB3_PCS_MISC_CLAMP_ENABLE, + USB3_PHY_REG_MAX, +}; + +enum qmp_phy_type { + USB3, + USB3_OR_DP, + USB3_AND_DP, +}; + +/* reg values to write */ +struct qmp_reg_val { + u32 offset; + u32 val; + u32 delay; +}; + +struct msm_ssphy_qmp { + struct usb_phy phy; + void __iomem *base; + void __iomem *vls_clamp_reg; + void __iomem *pcs_clamp_enable_reg; + void __iomem *tcsr_usb3_dp_phymode; + + struct regulator *vdd; + int vdd_levels[3]; /* none, low, high */ + int vdd_max_uA; + struct regulator *core_ldo; + int core_voltage_levels[3]; + int core_max_uA; + struct regulator *usb3_dp_phy_gdsc; + struct clk *ref_clk_src; + struct clk *ref_clk; + struct clk *aux_clk; + struct clk *com_aux_clk; + struct clk *cfg_ahb_clk; + struct clk *pipe_clk; + struct clk *pipe_clk_mux; + struct clk *pipe_clk_ext_src; + struct reset_control *phy_reset; + struct reset_control *phy_phy_reset; + struct reset_control *global_phy_reset; + bool power_enabled; + bool clk_enabled; + bool cable_connected; + bool in_suspend; + u32 *phy_reg; /* revision based offset */ + int reg_offset_cnt; + u32 *qmp_phy_init_seq; + int init_seq_len; + enum qmp_phy_type phy_type; +}; + +static const struct of_device_id msm_usb_id_table[] = { + { + .compatible = "qcom,usb-ssphy-qmp", + }, + { + .compatible = "qcom,usb-ssphy-qmp-v1", + }, + { + .compatible = "qcom,usb-ssphy-qmp-v2", + }, + { + .compatible = "qcom,usb-ssphy-qmp-dp-combo", + }, + { + .compatible = "qcom,usb-ssphy-qmp-usb3-or-dp", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm_usb_id_table); + +static void usb_qmp_powerup_phy(struct msm_ssphy_qmp *phy); +static void msm_ssphy_qmp_enable_clks(struct msm_ssphy_qmp *phy, bool on); +static int msm_ssphy_qmp_reset(struct usb_phy *uphy); +static int msm_ssphy_qmp_dp_combo_reset(struct usb_phy *uphy); + +static inline char *get_cable_status_str(struct msm_ssphy_qmp *phy) +{ + return phy->cable_connected ? "connected" : "disconnected"; +} + +static void msm_ssusb_qmp_clr_lfps_rxterm_int(struct msm_ssphy_qmp *phy) +{ + writel_relaxed(1, phy->base + + phy->phy_reg[USB3_PHY_LFPS_RXTERM_IRQ_CLEAR]); + /* flush the previous write before next write */ + wmb(); + writel_relaxed(0, phy->base + + phy->phy_reg[USB3_PHY_LFPS_RXTERM_IRQ_CLEAR]); +} + +static void msm_ssusb_qmp_clamp_enable(struct msm_ssphy_qmp *phy, bool val) +{ + switch (phy->phy_type) { + case USB3_AND_DP: + writel_relaxed(!val, phy->base + + phy->phy_reg[USB3_PCS_MISC_CLAMP_ENABLE]); + break; + case USB3_OR_DP: + case USB3: + if (phy->vls_clamp_reg) + writel_relaxed(!!val, phy->vls_clamp_reg); + if (phy->pcs_clamp_enable_reg) + writel_relaxed(!val, phy->pcs_clamp_enable_reg); + break; + default: + break; + } +} + +static void msm_ssusb_qmp_enable_autonomous(struct msm_ssphy_qmp *phy, + int enable) +{ + u32 val; + unsigned int autonomous_mode_offset = + phy->phy_reg[USB3_PHY_AUTONOMOUS_MODE_CTRL]; + + dev_dbg(phy->phy.dev, "enabling QMP autonomous mode with cable %s\n", + get_cable_status_str(phy)); + + if (enable) { + msm_ssusb_qmp_clr_lfps_rxterm_int(phy); + val = readl_relaxed(phy->base + autonomous_mode_offset); + val |= ARCVR_DTCT_EN; + if (phy->phy.flags & DEVICE_IN_SS_MODE) { + val |= ALFPS_DTCT_EN; + val &= ~ARCVR_DTCT_EVENT_SEL; + } else { + val &= ~ALFPS_DTCT_EN; + val |= ARCVR_DTCT_EVENT_SEL; + } + writel_relaxed(val, phy->base + autonomous_mode_offset); + msm_ssusb_qmp_clamp_enable(phy, true); + } else { + msm_ssusb_qmp_clamp_enable(phy, false); + writel_relaxed(0, phy->base + autonomous_mode_offset); + msm_ssusb_qmp_clr_lfps_rxterm_int(phy); + } +} + +static int msm_ssusb_qmp_gdsc(struct msm_ssphy_qmp *phy, bool on) +{ + int ret; + + if (IS_ERR_OR_NULL(phy->usb3_dp_phy_gdsc)) + return 0; + + if (on) + ret = regulator_enable(phy->usb3_dp_phy_gdsc); + else + ret = regulator_disable(phy->usb3_dp_phy_gdsc); + + if (ret) + dev_err(phy->phy.dev, "err:%d fail to %s usb3_dp_phy_gdsc\n", + ret, on ? "enable" : "disable"); + return ret; +} + +static int msm_ssusb_qmp_ldo_enable(struct msm_ssphy_qmp *phy, int on) +{ + int min, rc = 0; + + dev_dbg(phy->phy.dev, "reg (%s)\n", on ? "HPM" : "LPM"); + + if (phy->power_enabled == on) { + dev_dbg(phy->phy.dev, "PHYs' regulators status %d\n", + phy->power_enabled); + return 0; + } + + phy->power_enabled = on; + + min = on ? 1 : 0; /* low or none? */ + + if (!on) + goto disable_regulators; + + rc = msm_ssusb_qmp_gdsc(phy, true); + if (rc < 0) + return rc; + + rc = regulator_set_load(phy->vdd, phy->vdd_max_uA); + if (rc < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of %s\n", "vdd"); + goto put_gdsc; + } + + rc = regulator_set_voltage(phy->vdd, phy->vdd_levels[min], + phy->vdd_levels[2]); + if (rc) { + dev_err(phy->phy.dev, "Unable to set voltage for %s\n", "vdd"); + goto put_vdd_lpm; + } + + dev_dbg(phy->phy.dev, "min_vol:%d max_vol:%d\n", + phy->vdd_levels[min], phy->vdd_levels[2]); + + rc = regulator_enable(phy->vdd); + if (rc) { + dev_err(phy->phy.dev, "Unable to enable %s\n", "vdd"); + goto unconfig_vdd; + } + + rc = regulator_set_load(phy->core_ldo, phy->core_max_uA); + if (rc < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of %s\n", "core_ldo"); + goto disable_vdd; + } + + rc = regulator_set_voltage(phy->core_ldo, + phy->core_voltage_levels[CORE_LEVEL_MIN], + phy->core_voltage_levels[CORE_LEVEL_MAX]); + if (rc) { + dev_err(phy->phy.dev, "Unable to set voltage for %s\n", + "core_ldo"); + goto put_core_ldo_lpm; + } + + rc = regulator_enable(phy->core_ldo); + if (rc) { + dev_err(phy->phy.dev, "Unable to enable %s\n", "core_ldo"); + goto unset_core_ldo; + } + + return 0; + +disable_regulators: + rc = regulator_disable(phy->core_ldo); + if (rc) + dev_err(phy->phy.dev, "Unable to disable %s\n", "core_ldo"); + +unset_core_ldo: + rc = regulator_set_voltage(phy->core_ldo, + phy->core_voltage_levels[CORE_LEVEL_NONE], + phy->core_voltage_levels[CORE_LEVEL_MAX]); + if (rc) + dev_err(phy->phy.dev, "Unable to set voltage for %s\n", + "core_ldo"); + +put_core_ldo_lpm: + rc = regulator_set_load(phy->core_ldo, 0); + if (rc < 0) + dev_err(phy->phy.dev, "Unable to set LPM of %s\n", "core_ldo"); + +disable_vdd: + rc = regulator_disable(phy->vdd); + if (rc) + dev_err(phy->phy.dev, "Unable to disable %s\n", "vdd"); + +unconfig_vdd: + rc = regulator_set_voltage(phy->vdd, phy->vdd_levels[min], + phy->vdd_levels[2]); + if (rc) + dev_err(phy->phy.dev, "Unable to set voltage for %s\n", "vdd"); + +put_vdd_lpm: + rc = regulator_set_load(phy->vdd, 0); + if (rc < 0) + dev_err(phy->phy.dev, "Unable to set LPM of %s\n", "vdd"); + +put_gdsc: + rc = msm_ssusb_qmp_gdsc(phy, false); + return rc < 0 ? rc : 0; +} + +static int configure_phy_regs(struct usb_phy *uphy, + const struct qmp_reg_val *reg) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + if (!reg) { + dev_err(uphy->dev, "NULL PHY configuration\n"); + return -EINVAL; + } + + while (reg->offset != -1) { + writel_relaxed(reg->val, phy->base + reg->offset); + if (reg->delay) + usleep_range(reg->delay, reg->delay + 10); + reg++; + } + return 0; +} + +static void msm_ssphy_qmp_setmode(struct msm_ssphy_qmp *phy, u32 mode) +{ + mode = mode & USB3_DP_COMBO_MODE; + + writel_relaxed(mode, + phy->base + phy->phy_reg[USB3_DP_COM_PHY_MODE_CTRL]); + + /* flush the write by reading it */ + readl_relaxed(phy->base + phy->phy_reg[USB3_DP_COM_PHY_MODE_CTRL]); +} + +static void usb_qmp_update_portselect_phymode(struct msm_ssphy_qmp *phy) +{ + int val; + + /* perform lane selection */ + val = -EINVAL; + if (phy->phy.flags & PHY_LANE_A) + val = SW_PORTSELECT_MX; + else if (phy->phy.flags & PHY_LANE_B) + val = SW_PORTSELECT | SW_PORTSELECT_MX; + + /* PHY must be powered up before updating portselect and phymode. */ + usb_qmp_powerup_phy(phy); + + switch (phy->phy_type) { + case USB3_AND_DP: + writel_relaxed(0x01, + phy->base + phy->phy_reg[USB3_DP_COM_SW_RESET]); + writel_relaxed(0x00, + phy->base + phy->phy_reg[USB3_DP_COM_SW_RESET]); + + if (!(phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE)) + /* override hardware control for reset of qmp phy */ + writel_relaxed(SW_DPPHY_RESET_MUX | SW_DPPHY_RESET | + SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET, + phy->base + phy->phy_reg[USB3_DP_COM_RESET_OVRD_CTRL]); + + /* update port select */ + if (val > 0) { + dev_err(phy->phy.dev, + "USB DP QMP PHY: Update TYPEC CTRL(%d)\n", val); + writel_relaxed(val, phy->base + + phy->phy_reg[USB3_DP_COM_TYPEC_CTRL]); + } + +#ifdef CONFIG_PINCTRL + /* + * if there is no default pinctrl state for orientation, + * it need external module provide SW orientation info, + * report an error if there is no such info. + */ + if (val < 0 && !phy->phy.dev->pins) + dev_err(phy->phy.dev, + "USB DP QMP PHY: NO SW PORTSELECT\n"); +#endif + + if (!(phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE)) { + msm_ssphy_qmp_setmode(phy, USB3_DP_COMBO_MODE); + + /* bring both USB and DP PHYs PCS block out of reset */ + writel_relaxed(0x00, phy->base + + phy->phy_reg[USB3_DP_COM_RESET_OVRD_CTRL]); + } + break; + case USB3_OR_DP: + if (val > 0) { + dev_err(phy->phy.dev, + "USB QMP PHY: Update TYPEC CTRL(%d)\n", val); + writel_relaxed(val, phy->base + + phy->phy_reg[USB3_PHY_PCS_MISC_TYPEC_CTRL]); + } + break; + default: + dev_dbg(phy->phy.dev, "no portselect for phy type %d\n", + phy->phy_type); + break; + } + + /* Make sure above selection and reset sequence is gone through */ + mb(); +} + +static void usb_qmp_powerup_phy(struct msm_ssphy_qmp *phy) +{ + switch (phy->phy_type) { + case USB3_AND_DP: + /* power up USB3 and DP common logic block */ + writel_relaxed(0x01, + phy->base + phy->phy_reg[USB3_DP_COM_POWER_DOWN_CTRL]); + + /* + * Don't write 0x0 to DP_COM_SW_RESET here as portselect and + * phymode operation needs DP_COM_SW_RESET as 0x1. + * msm_ssphy_qmp_init() writes 0x0 to DP_COM_SW_RESET before + * initializing PHY. + */ + fallthrough; + case USB3_OR_DP: + case USB3: + /* power up USB3 PHY */ + writel_relaxed(0x01, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + break; + default: + dev_err(phy->phy.dev, "phy_powerup: Unknown USB QMP PHY type\n"); + break; + } + + /* Make sure that above write completed to power up PHY */ + mb(); +} + +/* SSPHY Initialization */ +static int msm_ssphy_qmp_init(struct usb_phy *uphy) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + int ret; + unsigned int init_timeout_usec = INIT_MAX_TIME_USEC; + const struct qmp_reg_val *reg = NULL; + + dev_dbg(uphy->dev, "Initializing QMP phy\n"); + + if (uphy->flags & PHY_DP_MODE) { + dev_info(uphy->dev, "QMP PHY currently in DP mode\n"); + return -EBUSY; + } + + ret = msm_ssusb_qmp_ldo_enable(phy, 1); + if (ret) { + dev_err(phy->phy.dev, + "msm_ssusb_qmp_ldo_enable(1) failed, ret=%d\n", + ret); + return ret; + } + + msm_ssphy_qmp_enable_clks(phy, true); + + if (phy->phy_type == USB3_AND_DP) + ret = msm_ssphy_qmp_dp_combo_reset(&phy->phy); + else + ret = msm_ssphy_qmp_reset(&phy->phy); + + /* select appropriate port select and PHY mode if applicable */ + usb_qmp_update_portselect_phymode(phy); + + /* power up PHY */ + usb_qmp_powerup_phy(phy); + + reg = (struct qmp_reg_val *)phy->qmp_phy_init_seq; + + /* Main configuration */ + ret = configure_phy_regs(uphy, reg); + if (ret) { + dev_err(uphy->dev, "Failed the main PHY configuration\n"); + goto fail; + } + + /* perform software reset of PCS/Serdes */ + writel_relaxed(0x00, phy->base + phy->phy_reg[USB3_PHY_SW_RESET]); + /* start PCS/Serdes to operation mode */ + writel_relaxed(0x03, phy->base + phy->phy_reg[USB3_PHY_START]); + + /* Make sure above write completed to bring PHY out of reset */ + mb(); + + /* Wait for PHY initialization to be done */ + do { + if (readl_relaxed(phy->base + + phy->phy_reg[USB3_PHY_PCS_STATUS]) & PHYSTATUS) + usleep_range(1, 2); + else + break; + } while (--init_timeout_usec); + + if (!init_timeout_usec) { + dev_err(uphy->dev, "QMP PHY initialization timeout\n"); + dev_err(uphy->dev, "USB3_PHY_PCS_STATUS:%x\n", + readl_relaxed(phy->base + + phy->phy_reg[USB3_PHY_PCS_STATUS])); + ret = -EBUSY; + goto fail; + } + + return ret; +fail: + phy->in_suspend = true; + writel_relaxed(0x00, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + msm_ssphy_qmp_enable_clks(phy, false); + msm_ssusb_qmp_ldo_enable(phy, 0); + + return ret; +} + +static int msm_ssphy_qmp_dp_combo_reset(struct usb_phy *uphy) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + int ret = 0; + + if (phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE) { + dev_dbg(uphy->dev, "Resetting USB part of QMP phy\n"); + + /* Assert USB3 PHY CSR reset */ + ret = reset_control_assert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset assert failed\n"); + goto exit; + } + + /* Deassert USB3 PHY CSR reset */ + ret = reset_control_deassert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset deassert failed\n"); + goto exit; + } + return 0; + } + + dev_dbg(uphy->dev, "Global reset of QMP DP combo phy\n"); + /* Assert global PHY reset */ + ret = reset_control_assert(phy->global_phy_reset); + if (ret) { + dev_err(uphy->dev, "global_phy_reset assert failed\n"); + goto exit; + } + + /* Assert QMP USB PHY reset */ + ret = reset_control_assert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset assert failed\n"); + goto exit; + } + + /* De-Assert QMP USB PHY reset */ + ret = reset_control_deassert(phy->phy_reset); + if (ret) + dev_err(uphy->dev, "phy_reset deassert failed\n"); + + /* De-Assert global PHY reset */ + ret = reset_control_deassert(phy->global_phy_reset); + if (ret) + dev_err(uphy->dev, "global_phy_reset deassert failed\n"); + +exit: + return ret; +} + +static int msm_ssphy_qmp_reset(struct usb_phy *uphy) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + int ret; + + dev_dbg(uphy->dev, "Resetting QMP phy\n"); + + /* Assert USB3 PHY reset */ + ret = reset_control_assert(phy->phy_phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_phy_reset assert failed\n"); + goto exit; + } + + /* Assert USB3 PHY CSR reset */ + ret = reset_control_assert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset assert failed\n"); + goto deassert_phy_phy_reset; + } + + /* select usb3 phy mode */ + if (phy->tcsr_usb3_dp_phymode) + writel_relaxed(0x0, phy->tcsr_usb3_dp_phymode); + + /* Deassert USB3 PHY CSR reset */ + ret = reset_control_deassert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset deassert failed\n"); + goto deassert_phy_phy_reset; + } + + /* Deassert USB3 PHY reset */ + ret = reset_control_deassert(phy->phy_phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_phy_reset deassert failed\n"); + goto exit; + } + + return 0; + +deassert_phy_phy_reset: + ret = reset_control_deassert(phy->phy_phy_reset); + if (ret) + dev_err(uphy->dev, "phy_phy_reset deassert failed\n"); +exit: + phy->in_suspend = false; + + return ret; +} + +static int msm_ssphy_power_enable(struct msm_ssphy_qmp *phy, bool on) +{ + bool host = phy->phy.flags & PHY_HOST_MODE; + int ret = 0; + + /* + * Turn off the phy's LDOs when cable is disconnected for device mode + * with external vbus_id indication. + */ + if (!host && !phy->cable_connected) { + if (on) { + ret = msm_ssusb_qmp_ldo_enable(phy, 1); + if (ret) + dev_err(phy->phy.dev, + "msm_ssusb_qmp_ldo_enable(1) failed, ret=%d\n", + ret); + } else { + ret = msm_ssusb_qmp_ldo_enable(phy, 0); + if (ret) + dev_err(phy->phy.dev, + "msm_ssusb_qmp_ldo_enable(0) failed, ret=%d\n", + ret); + } + } + + return ret; +} + +/** + * Performs QMP PHY suspend/resume functionality. + * + * @uphy - usb phy pointer. + * @suspend - to enable suspend or not. 1 - suspend, 0 - resume + * + */ +static int msm_ssphy_qmp_set_suspend(struct usb_phy *uphy, int suspend) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + dev_dbg(uphy->dev, "QMP PHY set_suspend for %s called with cable %s\n", + (suspend ? "suspend" : "resume"), + get_cable_status_str(phy)); + + if (phy->in_suspend == suspend) { + dev_dbg(uphy->dev, "%s: USB PHY is already %s.\n", + __func__, (suspend ? "suspended" : "resumed")); + return 0; + } + + if (suspend) { + if (phy->cable_connected) { + msm_ssusb_qmp_enable_autonomous(phy, 1); + } else { + /* Reset phy mode to USB only if DP not connected */ + if (phy->phy_type == USB3_AND_DP && + !((uphy->flags & PHY_DP_MODE) || + (uphy->flags & PHY_USB_DP_CONCURRENT_MODE))) + msm_ssphy_qmp_setmode(phy, USB3_MODE); + writel_relaxed(0x00, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + } + + /* Make sure above write completed with PHY */ + wmb(); + + msm_ssphy_qmp_enable_clks(phy, false); + phy->in_suspend = true; + msm_ssphy_power_enable(phy, 0); + dev_dbg(uphy->dev, "QMP PHY is suspend\n"); + } else { + if (uphy->flags & PHY_DP_MODE) { + dev_info(uphy->dev, "QMP PHY currently in DP mode\n"); + return -EBUSY; + } + + msm_ssphy_power_enable(phy, 1); + msm_ssphy_qmp_enable_clks(phy, true); + if (!phy->cable_connected) { + writel_relaxed(0x01, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + } else { + msm_ssusb_qmp_enable_autonomous(phy, 0); + } + + /* Make sure that above write completed with PHY */ + wmb(); + + phy->in_suspend = false; + dev_dbg(uphy->dev, "QMP PHY is resumed\n"); + } + + return 0; +} + +static int msm_ssphy_qmp_notify_connect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + dev_dbg(uphy->dev, "QMP phy connect notification\n"); + phy->cable_connected = true; + atomic_notifier_call_chain(&uphy->notifier, 1, uphy); + + return 0; +} + +static int msm_ssphy_qmp_notify_disconnect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + atomic_notifier_call_chain(&uphy->notifier, 0, uphy); + if (phy->phy.flags & PHY_HOST_MODE) { + writel_relaxed(0x00, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + readl_relaxed(phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + } + + dev_dbg(uphy->dev, "QMP phy disconnect notification\n"); + dev_dbg(uphy->dev, " cable_connected=%d\n", phy->cable_connected); + phy->cable_connected = false; + + return 0; +} + +static int msm_ssphy_qmp_get_clks(struct msm_ssphy_qmp *phy, struct device *dev) +{ + int ret = 0; + + phy->aux_clk = devm_clk_get(dev, "aux_clk"); + if (IS_ERR(phy->aux_clk)) { + ret = PTR_ERR(phy->aux_clk); + phy->aux_clk = NULL; + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get aux_clk\n"); + goto err; + } + clk_set_rate(phy->aux_clk, clk_round_rate(phy->aux_clk, ULONG_MAX)); + + if (of_property_match_string(dev->of_node, + "clock-names", "cfg_ahb_clk") >= 0) { + phy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb_clk"); + if (IS_ERR(phy->cfg_ahb_clk)) { + ret = PTR_ERR(phy->cfg_ahb_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "failed to get cfg_ahb_clk ret %d\n", ret); + goto err; + } + } + + phy->pipe_clk = devm_clk_get(dev, "pipe_clk"); + if (IS_ERR(phy->pipe_clk)) { + ret = PTR_ERR(phy->pipe_clk); + phy->pipe_clk = NULL; + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get pipe_clk\n"); + goto err; + } + + phy->pipe_clk_mux = devm_clk_get(dev, "pipe_clk_mux"); + if (IS_ERR(phy->pipe_clk_mux)) + phy->pipe_clk_mux = NULL; + + phy->pipe_clk_ext_src = devm_clk_get(dev, "pipe_clk_ext_src"); + if (IS_ERR(phy->pipe_clk_ext_src)) + phy->pipe_clk_ext_src = NULL; + + phy->ref_clk_src = devm_clk_get(dev, "ref_clk_src"); + if (IS_ERR(phy->ref_clk_src)) + phy->ref_clk_src = NULL; + + phy->ref_clk = devm_clk_get(dev, "ref_clk"); + if (IS_ERR(phy->ref_clk)) + phy->ref_clk = NULL; + + if (of_property_match_string(dev->of_node, + "clock-names", "com_aux_clk") >= 0) { + phy->com_aux_clk = devm_clk_get(dev, "com_aux_clk"); + if (IS_ERR(phy->com_aux_clk)) { + ret = PTR_ERR(phy->com_aux_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "failed to get com_aux_clk ret %d\n", ret); + goto err; + } + } + +err: + return ret; +} + +static void msm_ssphy_qmp_enable_clks(struct msm_ssphy_qmp *phy, bool on) +{ + dev_dbg(phy->phy.dev, "%s(): clk_enabled:%d on:%d\n", __func__, + phy->clk_enabled, on); + + if (!phy->clk_enabled && on) { + if (phy->ref_clk_src) + clk_prepare_enable(phy->ref_clk_src); + + if (phy->ref_clk) + clk_prepare_enable(phy->ref_clk); + + if (phy->com_aux_clk) + clk_prepare_enable(phy->com_aux_clk); + + clk_prepare_enable(phy->aux_clk); + if (phy->cfg_ahb_clk) + clk_prepare_enable(phy->cfg_ahb_clk); + + //select PHY pipe clock + clk_set_parent(phy->pipe_clk_mux, phy->pipe_clk_ext_src); + clk_prepare_enable(phy->pipe_clk); + phy->clk_enabled = true; + } + + if (phy->clk_enabled && !on) { + clk_disable_unprepare(phy->pipe_clk); + + //select XO instead of PHY pipe clock + clk_set_parent(phy->pipe_clk_mux, phy->ref_clk_src); + + if (phy->cfg_ahb_clk) + clk_disable_unprepare(phy->cfg_ahb_clk); + + clk_disable_unprepare(phy->aux_clk); + if (phy->com_aux_clk) + clk_disable_unprepare(phy->com_aux_clk); + + if (phy->ref_clk) + clk_disable_unprepare(phy->ref_clk); + + if (phy->ref_clk_src) + clk_disable_unprepare(phy->ref_clk_src); + + phy->clk_enabled = false; + } +} + +static int msm_ssphy_qmp_probe(struct platform_device *pdev) +{ + struct msm_ssphy_qmp *phy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0, size = 0, len; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->phy_type = USB3; + if (of_device_is_compatible(dev->of_node, + "qcom,usb-ssphy-qmp-dp-combo")) + phy->phy_type = USB3_AND_DP; + + if (of_device_is_compatible(dev->of_node, + "qcom,usb-ssphy-qmp-usb3-or-dp")) + phy->phy_type = USB3_OR_DP; + + ret = msm_ssphy_qmp_get_clks(phy, dev); + if (ret) + goto err; + + phy->phy_reset = devm_reset_control_get(dev, "phy_reset"); + if (IS_ERR(phy->phy_reset)) { + ret = PTR_ERR(phy->phy_reset); + dev_dbg(dev, "failed to get phy_reset\n"); + goto err; + } + + if (phy->phy_type == USB3_AND_DP) { + phy->global_phy_reset = devm_reset_control_get(dev, + "global_phy_reset"); + if (IS_ERR(phy->global_phy_reset)) { + ret = PTR_ERR(phy->global_phy_reset); + dev_dbg(dev, "failed to get global_phy_reset\n"); + goto err; + } + } else { + phy->phy_phy_reset = devm_reset_control_get(dev, + "phy_phy_reset"); + if (IS_ERR(phy->phy_phy_reset)) { + ret = PTR_ERR(phy->phy_phy_reset); + dev_dbg(dev, "failed to get phy_phy_reset\n"); + goto err; + } + } + + of_get_property(dev->of_node, "qcom,qmp-phy-reg-offset", &size); + if (size) { + phy->phy_reg = devm_kzalloc(dev, size, GFP_KERNEL); + if (phy->phy_reg) { + phy->reg_offset_cnt = (size / sizeof(*phy->phy_reg)); + if (phy->reg_offset_cnt > USB3_PHY_REG_MAX) { + dev_err(dev, "invalid reg offset count\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,qmp-phy-reg-offset", + phy->phy_reg, phy->reg_offset_cnt); + } else { + dev_err(dev, "err mem alloc for qmp_phy_reg_offset\n"); + return -ENOMEM; + } + } else { + dev_err(dev, "err provide qcom,qmp-phy-reg-offset\n"); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "qmp_phy_base"); + if (!res) { + dev_err(dev, "failed getting qmp_phy_base\n"); + return -ENODEV; + } + + /* + * For USB QMP DP combo PHY, common set of registers shall be accessed + * by DP driver as well. + */ + phy->base = devm_ioremap(dev, res->start, resource_size(res)); + if (IS_ERR_OR_NULL(phy->base)) { + ret = PTR_ERR(phy->base); + goto err; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "vls_clamp_reg"); + if (res) { + phy->vls_clamp_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->vls_clamp_reg)) { + dev_err(dev, "err getting vls_clamp_reg address\n"); + return PTR_ERR(phy->vls_clamp_reg); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "pcs_clamp_enable_reg"); + if (res) { + phy->pcs_clamp_enable_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->pcs_clamp_enable_reg)) { + dev_err(dev, "err getting pcs_clamp_enable_reg address.\n"); + return PTR_ERR(phy->pcs_clamp_enable_reg); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "tcsr_usb3_dp_phymode"); + if (res) { + phy->tcsr_usb3_dp_phymode = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->tcsr_usb3_dp_phymode)) { + dev_err(dev, "err getting tcsr_usb3_dp_phymode addr\n"); + return PTR_ERR(phy->tcsr_usb3_dp_phymode); + } + } + + of_get_property(dev->of_node, "qcom,qmp-phy-init-seq", &size); + if (size) { + if (size % sizeof(*phy->qmp_phy_init_seq)) { + dev_err(dev, "invalid init_seq_len\n"); + return -EINVAL; + } + + phy->qmp_phy_init_seq = devm_kzalloc(dev, size, GFP_KERNEL); + if (!phy->qmp_phy_init_seq) + return -ENOMEM; + + phy->init_seq_len = (size / sizeof(*phy->qmp_phy_init_seq)); + of_property_read_u32_array(dev->of_node, + "qcom,qmp-phy-init-seq", + phy->qmp_phy_init_seq, + phy->init_seq_len); + } else { + dev_err(dev, "error need qmp-phy-init-seq\n"); + return -EINVAL; + } + + /* Set default core voltage values */ + phy->core_voltage_levels[CORE_LEVEL_NONE] = 0; + phy->core_voltage_levels[CORE_LEVEL_MIN] = USB_SSPHY_1P2_VOL_MIN; + phy->core_voltage_levels[CORE_LEVEL_MAX] = USB_SSPHY_1P2_VOL_MAX; + + if (of_get_property(dev->of_node, "qcom,core-voltage-level", &len) && + len == sizeof(phy->core_voltage_levels)) { + ret = of_property_read_u32_array(dev->of_node, + "qcom,core-voltage-level", + (u32 *)phy->core_voltage_levels, + len / sizeof(u32)); + if (ret) { + dev_err(dev, "err qcom,core-voltage-level property\n"); + goto err; + } + } + + if (of_property_read_s32(dev->of_node, "qcom,core-max-load-uA", + &phy->core_max_uA) || !phy->core_max_uA) + phy->core_max_uA = USB_SSPHY_HPM_LOAD; + + if (of_get_property(dev->of_node, "qcom,vdd-voltage-level", &len) && + len == sizeof(phy->vdd_levels)) { + ret = of_property_read_u32_array(dev->of_node, + "qcom,vdd-voltage-level", + (u32 *) phy->vdd_levels, + len / sizeof(u32)); + if (ret) { + dev_err(dev, "err qcom,vdd-voltage-level property\n"); + goto err; + } + } else { + ret = -EINVAL; + dev_err(dev, "error invalid inputs for vdd-voltage-level\n"); + goto err; + } + + if (of_property_read_s32(dev->of_node, "qcom,vdd-max-load-uA", + &phy->vdd_max_uA) || !phy->vdd_max_uA) + phy->vdd_max_uA = USB_SSPHY_HPM_LOAD; + + phy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(phy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + ret = PTR_ERR(phy->vdd); + goto err; + } + + phy->core_ldo = devm_regulator_get(dev, "core"); + if (IS_ERR(phy->core_ldo)) { + dev_err(dev, "unable to get core ldo supply\n"); + ret = PTR_ERR(phy->core_ldo); + goto err; + } + + phy->usb3_dp_phy_gdsc = devm_regulator_get(dev, "usb3_dp_phy_gdsc"); + if (IS_ERR(phy->usb3_dp_phy_gdsc)) { + ret = PTR_ERR(phy->usb3_dp_phy_gdsc); + if (ret != -ENODEV) { + dev_err(dev, "fail to get usb3_dp_phy_gdsc(%d)\n", ret); + return ret; + } + dev_err(dev, "usb3_dp_phy_gdsc optional regulator missing\n"); + } + + platform_set_drvdata(pdev, phy); + + phy->phy.dev = dev; + phy->phy.init = msm_ssphy_qmp_init; + phy->phy.set_suspend = msm_ssphy_qmp_set_suspend; + phy->phy.notify_connect = msm_ssphy_qmp_notify_connect; + phy->phy.notify_disconnect = msm_ssphy_qmp_notify_disconnect; + + ret = usb_add_phy_dev(&phy->phy); + +err: + return ret; +} + +static int msm_ssphy_qmp_remove(struct platform_device *pdev) +{ + struct msm_ssphy_qmp *phy = platform_get_drvdata(pdev); + + if (!phy) + return 0; + + usb_remove_phy(&phy->phy); + msm_ssphy_qmp_enable_clks(phy, false); + msm_ssusb_qmp_ldo_enable(phy, 0); + return 0; +} + +static struct platform_driver msm_ssphy_qmp_driver = { + .probe = msm_ssphy_qmp_probe, + .remove = msm_ssphy_qmp_remove, + .driver = { + .name = "msm-usb-ssphy-qmp", + .of_match_table = of_match_ptr(msm_usb_id_table), + }, +}; + +module_platform_driver(msm_ssphy_qmp_driver); + +MODULE_DESCRIPTION("MSM USB SS QMP PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-qcom-emu.c b/drivers/usb/phy/phy-qcom-emu.c new file mode 100644 index 000000000000..9c270ffea03d --- /dev/null +++ b/drivers/usb/phy/phy-qcom-emu.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2018, 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* QSCRATCH registers */ +#define HS_PHY_CTRL_REG 0x10 +#define SW_SESSVLD_SEL BIT(28) + +/* HSPHY registers */ +#define HS2_LOCAL_RESET_REG_ADDR 0x04 +#define HS2_CLK_STATUS_ADDR 0x10 +#define HS2_CLK_STATUS_SEL_ADDR 0x14 +#define HS2_USB30_CTRL_ADDR 0x34 +#define HS2_USB30_PHY_POWER_OFF BIT(25) + +struct qcusb_emu_phy { + struct usb_phy phy; + struct device *dev; + void __iomem *base; + void __iomem *qscratch_base; + int *emu_init_seq; + int emu_init_seq_len; +}; + +static int qcusb_emu_phy_init(struct usb_phy *phy) +{ + struct qcusb_emu_phy *qphy = container_of(phy, + struct qcusb_emu_phy, phy); + u32 tmp; + int i; + + /* reset everything */ + writel_relaxed(0xffffffff, qphy->base + HS2_LOCAL_RESET_REG_ADDR); + usleep_range(10000, 12000); + + /* power down HS phy */ + tmp = readl_relaxed(qphy->base + HS2_USB30_CTRL_ADDR) | + HS2_USB30_PHY_POWER_OFF; + writel_relaxed(tmp, qphy->base + HS2_USB30_CTRL_ADDR); + usleep_range(10000, 12000); + + /* power up HS phy */ + tmp = readl_relaxed(qphy->base + HS2_USB30_CTRL_ADDR) & + (~HS2_USB30_PHY_POWER_OFF); + writel_relaxed(tmp, qphy->base + HS2_USB30_CTRL_ADDR); + usleep_range(10000, 12000); + + writel_relaxed(0xfffffff3, qphy->base + HS2_LOCAL_RESET_REG_ADDR); + usleep_range(10000, 12000); + + /* put phy out of reset */ + writel_relaxed(0xfffffff0, qphy->base + HS2_LOCAL_RESET_REG_ADDR); + usleep_range(10000, 12000); + + /* selection of HS phy clock MMCM value */ + for (i = 0; i < qphy->emu_init_seq_len; i = i+2) { + dev_dbg(phy->dev, "write 0x%02x to 0x%02x\n", + qphy->emu_init_seq[i], qphy->emu_init_seq[i+1]); + writel_relaxed(qphy->emu_init_seq[i], + qphy->base + qphy->emu_init_seq[i+1]); + /* 10ms to ensure write propagates across bus */ + usleep_range(10000, 12000); + } + + /* clear other reset */ + writel_relaxed(0x0, qphy->base + HS2_LOCAL_RESET_REG_ADDR); + usleep_range(10000, 12000); + + /* clock select to read UTMI/ULPI clock */ + writel_relaxed(0x9, qphy->base + HS2_CLK_STATUS_SEL_ADDR); + usleep_range(10000, 12000); + dev_info(phy->dev, "PHY UTMI/ULPI CLK frequency:%d MHz\n", + (readl_relaxed(qphy->base + HS2_CLK_STATUS_ADDR) / 1000)); + + if (qphy->qscratch_base) { + /* Use UTMI VBUS signal from HW */ + tmp = readl_relaxed(qphy->qscratch_base + HS_PHY_CTRL_REG); + tmp &= ~SW_SESSVLD_SEL; + writel_relaxed(tmp, qphy->qscratch_base + HS_PHY_CTRL_REG); + } + + return 0; +} + +static int qcusb_emu_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct qcusb_emu_phy *qphy; + struct resource *res; + int ret, size; + + qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); + if (!qphy) + return -ENOMEM; + + qphy->phy.dev = dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + qphy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(qphy->base)) + return PTR_ERR(qphy->base); + + of_get_property(dev->of_node, "qcom,emu-init-seq", &size); + if (!size) { + dev_err(dev, "emu-init-seq not specified\n"); + return -EINVAL; + } + + qphy->emu_init_seq = devm_kzalloc(dev, size, GFP_KERNEL); + if (!qphy->emu_init_seq) + return -ENOMEM; + + qphy->emu_init_seq_len = (size / sizeof(*qphy->emu_init_seq)); + if (qphy->emu_init_seq_len % 2) { + dev_err(dev, "invalid emu_init_seq_len, must be in pairs\n"); + return -EINVAL; + } + + ret = of_property_read_u32_array(dev->of_node, "qcom,emu-init-seq", + qphy->emu_init_seq, qphy->emu_init_seq_len); + if (ret) { + dev_err(dev, "could not read emu-init-seq, returned %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, qphy); + + qphy->phy.label = "qcom-usb-emu-phy"; + qphy->phy.init = qcusb_emu_phy_init; + qphy->phy.type = USB_PHY_TYPE_USB2; + + ret = usb_add_phy_dev(&qphy->phy); + if (ret) + return ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "qscratch_base"); + if (res) { + qphy->qscratch_base = devm_ioremap(dev, res->start, + resource_size(res)); + if (IS_ERR(qphy->qscratch_base)) { + dev_dbg(dev, "error mapping qscratch\n"); + qphy->qscratch_base = NULL; + } + } + + return 0; +} + +static int qcusb_emu_phy_remove(struct platform_device *pdev) +{ + struct qcusb_emu_phy *qcphy = platform_get_drvdata(pdev); + + usb_remove_phy(&qcphy->phy); + + return 0; +} + +static const struct of_device_id emu_phy_dt_ids[] = { + { .compatible = "qcom,usb-emu-phy" }, + { } +}; + +MODULE_DEVICE_TABLE(of, emu_phy_dt_ids); + +static struct platform_driver qcusb_emu_phy_driver = { + .probe = qcusb_emu_phy_probe, + .remove = qcusb_emu_phy_remove, + .driver = { + .name = "usb_emu_phy", + .of_match_table = emu_phy_dt_ids, + }, +}; + +module_platform_driver(qcusb_emu_phy_driver); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. USB Emulation PHY driver"); +MODULE_LICENSE("GPL v2");