From 5bd60c1a18347fc5a39e726897a3e1a04f87791d Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Thu, 17 Oct 2019 15:48:38 -0700 Subject: [PATCH] usb: phy: Add snapshot of QTI USB PHY drivers This change adds USB PHY drivers for high speed and super speed/plus functionality on QTI MSM platforms. This snapshot is taken as of msm-4.19 commit <8197f81ffc14> ("Merge "msm: kgsl: Correct HFI acknowledgment failure condition""). Change-Id: Iede74fa0fd037a0741ee3269d78adb372600ea8d Signed-off-by: Mayank Rana --- drivers/usb/phy/Kconfig | 20 + drivers/usb/phy/Makefile | 2 + drivers/usb/phy/phy-msm-snps-hs.c | 847 ++++++++++++++++++ drivers/usb/phy/phy-msm-ssusb-qmp.c | 1237 +++++++++++++++++++++++++++ 4 files changed, 2106 insertions(+) create mode 100644 drivers/usb/phy/phy-msm-snps-hs.c create mode 100644 drivers/usb/phy/phy-msm-ssusb-qmp.c diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index d34c8450bc9b..ca981e8730b4 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -196,4 +196,24 @@ config USB_ULPI_VIEWPORT Provides read/write operations to the ULPI phy register set for controllers with a viewport register (e.g. Chipidea/ARC controllers). +config USB_MSM_SSPHY_QMP + tristate "MSM SSUSB QMP PHY Driver" + depends on ARCH_QCOM + 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 + 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. + endmenu diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index 4ae6228f1a87..7c9bad6e99c7 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -25,3 +25,5 @@ obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o 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_USB_MSM_SSPHY_QMP) += phy-msm-ssusb-qmp.o +obj-$(CONFIG_MSM_HSUSB_PHY) += phy-msm-snps-hs.o 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..6eacd0804457 --- /dev/null +++ b/drivers/usb/phy/phy-msm-snps-hs.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. + */ + +#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 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 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 */ + +struct msm_hsphy { + struct usb_phy phy; + void __iomem *base; + void __iomem *eud_enable_reg; + + 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; + + /* 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_config_vdd(struct msm_hsphy *phy, int high) +{ + int min, ret; + + min = high ? 1 : 0; /* low or none? */ + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[min], + phy->vdd_levels[2]); + if (ret) { + dev_err(phy->phy.dev, "unable to set voltage for hsusb vdd\n"); + return ret; + } + + dev_dbg(phy->phy.dev, "%s: min_vol:%d max_vol:%d\n", __func__, + phy->vdd_levels[min], phy->vdd_levels[2]); + + return ret; +} + +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 = msm_hsphy_config_vdd(phy, true); + if (ret) { + dev_err(phy->phy.dev, "Unable to config VDD:%d\n", + ret); + goto err_vdd; + } + + 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 = msm_hsphy_config_vdd(phy, false); + if (ret) + dev_err(phy->phy.dev, "Unable unconfig VDD:%d\n", + ret); +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)); + } +} + +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; + + dev_dbg(uphy->dev, "%s\n", __func__); + + if (phy->eud_enable_reg && readl_relaxed(phy->eud_enable_reg)) { + dev_err(phy->phy.dev, "eud is enabled\n"); + return 0; + } + + 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_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) { + dev_dbg(uphy->dev, "%s: USB PHY is already suspended\n", + __func__); + return 0; + } + + if (suspend) { /* Bus suspend */ + if (phy->cable_connected || + (phy->phy.flags & PHY_HOST_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); + if (!phy->dpdm_enable) { + 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 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 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); + if (PTR_ERR_OR_ZERO(phy->dpdm_rdev)) + return phy->dpdm_rdev; + + return 0; +} + +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_nocache(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); + } + } + + /* 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.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; + } + + msm_hsphy_create_debugfs(phy); + + 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; + + 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..24fad554cd3f --- /dev/null +++ b/drivers/usb/phy/phy-msm-ssusb-qmp.c @@ -0,0 +1,1237 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2019, The Linux Foundation. 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 */ + +/* USB3 Gen2 link training indicator */ +#define RX_EQUALIZATION_IN_PROGRESS BIT(3) + +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_DP_PHY_PD_CTL, + 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_DP_COM_SWI_CTRL, + USB3_PCS_MISC_CLAMP_ENABLE, + USB3_DP_PCS_PCS_STATUS2, + USB3_DP_PCS_INSIG_SW_CTRL3, + USB3_DP_PCS_INSIG_MX_CTRL3, + USB3_PHY_REG_MAX, +}; + +/* 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 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; + struct extcon_dev *extcon_dp; + struct notifier_block dp_nb; + 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; + struct hrtimer timer; +}; + +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 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 USB_PHY_TYPE_USB3_AND_DP: + writel_relaxed(!val, phy->base + + phy->phy_reg[USB3_PCS_MISC_CLAMP_ENABLE]); + break; + case USB_PHY_TYPE_USB3_OR_DP: + case USB_PHY_TYPE_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_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 = 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"); + return rc; + } + + 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"); + + 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 USB_PHY_TYPE_USB3_AND_DP: + /* override hardware control for reset of qmp phy */ + if (!(phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE)) + 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]); + } + + 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 USB_PHY_TYPE_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 USB_PHY_TYPE_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. + */ + + /* intentional fall-through */ + case USB_PHY_TYPE_USB3_OR_DP: + case USB_PHY_TYPE_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"); + + 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); + + /* 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"); + return ret; + } + + /* perform software reset of PHY common logic */ + if (phy->phy.type == USB_PHY_TYPE_USB3_AND_DP && + !(phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE)) + writel_relaxed(0x00, + phy->base + phy->phy_reg[USB3_DP_COM_SW_RESET]); + + /* 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])); + return -EBUSY; + } + + return 0; +} + +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 (uphy->type == USB_PHY_TYPE_USB3_AND_DP && + !(phy->phy.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(); + + hrtimer_cancel(&phy->timer); + 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 { + 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 enum hrtimer_restart timer_fn(struct hrtimer *timer) +{ + struct msm_ssphy_qmp *phy = + container_of(timer, struct msm_ssphy_qmp, timer); + u8 status2, status2_1, sw1, mx1, sw2, mx2; + int timeout = 15000; + + status2_1 = sw1 = sw2 = mx1 = mx2 = 0; + + status2 = readl_relaxed(phy->base + + phy->phy_reg[USB3_DP_PCS_PCS_STATUS2]); + if (status2 & RX_EQUALIZATION_IN_PROGRESS) { + while (timeout > 0) { + status2_1 = readl_relaxed(phy->base + + phy->phy_reg[USB3_DP_PCS_PCS_STATUS2]); + if (status2_1 & RX_EQUALIZATION_IN_PROGRESS) { + timeout -= 500; + udelay(500); + continue; + } + + writel_relaxed(0x08, phy->base + + phy->phy_reg[USB3_DP_PCS_INSIG_SW_CTRL3]); + writel_relaxed(0x08, phy->base + + phy->phy_reg[USB3_DP_PCS_INSIG_MX_CTRL3]); + sw1 = readl_relaxed(phy->base + + phy->phy_reg[USB3_DP_PCS_INSIG_SW_CTRL3]); + mx1 = readl_relaxed(phy->base + + phy->phy_reg[USB3_DP_PCS_INSIG_MX_CTRL3]); + udelay(1); + writel_relaxed(0x0, phy->base + + phy->phy_reg[USB3_DP_PCS_INSIG_SW_CTRL3]); + writel_relaxed(0x0, phy->base + + phy->phy_reg[USB3_DP_PCS_INSIG_MX_CTRL3]); + sw2 = readl_relaxed(phy->base + + phy->phy_reg[USB3_DP_PCS_INSIG_SW_CTRL3]); + mx2 = readl_relaxed(phy->base + + phy->phy_reg[USB3_DP_PCS_INSIG_MX_CTRL3]); + + break; + } + } + + dev_dbg(phy->phy.dev, + "st=%x st2=%x sw1=%x sw2=%x mx1=%x mx2=%x timeout=%d\n", + status2, status2_1, sw1, sw2, mx1, mx2, timeout); + + hrtimer_forward_now(timer, ms_to_ktime(1)); + + return HRTIMER_RESTART; +} + +static int msm_ssphy_qmp_link_training(struct usb_phy *uphy, bool start) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + if (start) { + hrtimer_start(&phy->timer, 0, HRTIMER_MODE_REL); + dev_dbg(uphy->dev, "link training start\n"); + } else { + hrtimer_cancel(&phy->timer); + dev_dbg(uphy->dev, "link training stop\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; + dev_dbg(uphy->dev, "cable_connected=%d\n", phy->cable_connected); + 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); + + 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]); + + hrtimer_cancel(&phy->timer); + 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_vbus_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + return 0; +} + +static int msm_ssphy_qmp_dp_notifier(struct notifier_block *nb, + unsigned long dp_lane, void *ptr) +{ + struct msm_ssphy_qmp *phy = container_of(nb, + struct msm_ssphy_qmp, dp_nb); + + if (dp_lane == 2 || dp_lane == 4) + phy->phy.flags |= PHY_USB_DP_CONCURRENT_MODE; + else + phy->phy.flags &= ~PHY_USB_DP_CONCURRENT_MODE; + + return 0; + +} + +static int msm_ssphy_qmp_extcon_register(struct msm_ssphy_qmp *phy, + struct device *dev) +{ + struct device_node *node = dev->of_node; + struct extcon_dev *edev; + int ret = 0; + + if (!of_property_read_bool(node, "extcon")) + return 0; + + edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(edev)) { + dev_err(dev, "failed to get phandle for msm_ssphy_qmp\n"); + return PTR_ERR(edev); + } + + phy->extcon_dp = edev; + phy->phy.vbus_nb.notifier_call = msm_ssphy_qmp_vbus_notifier; + phy->dp_nb.notifier_call = msm_ssphy_qmp_dp_notifier; + ret = extcon_register_blocking_notifier(edev, EXTCON_DISP_DP, + &phy->dp_nb); + if (ret < 0) { + dev_err(dev, "failed to register blocking notifier\n"); + return ret; + } + + 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 = USB_PHY_TYPE_USB3; + if (of_device_is_compatible(dev->of_node, + "qcom,usb-ssphy-qmp-dp-combo")) + phy->phy.type = USB_PHY_TYPE_USB3_AND_DP; + + if (of_device_is_compatible(dev->of_node, + "qcom,usb-ssphy-qmp-usb3-or-dp")) + phy->phy.type = USB_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 == USB_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_nocache(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; + } + + platform_set_drvdata(pdev, phy); + + if (of_property_read_bool(dev->of_node, "qcom,vbus-valid-override")) + phy->phy.flags |= PHY_VBUS_VALID_OVERRIDE; + + hrtimer_init(&phy->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + phy->timer.function = timer_fn; + + 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; + + if (of_property_read_bool(dev->of_node, "qcom,link-training-reset")) + phy->phy.link_training = msm_ssphy_qmp_link_training; + + + if (phy->phy.type == USB_PHY_TYPE_USB3_AND_DP) + phy->phy.reset = msm_ssphy_qmp_dp_combo_reset; + else + phy->phy.reset = msm_ssphy_qmp_reset; + + ret = msm_ssphy_qmp_extcon_register(phy, dev); + if (ret) + goto err; + + 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");