summaryrefslogtreecommitdiff
path: root/drivers/usb/phy
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/phy')
-rw-r--r--drivers/usb/phy/Kconfig40
-rw-r--r--drivers/usb/phy/Makefile4
-rw-r--r--drivers/usb/phy/phy-msm-hsusb.c858
-rw-r--r--drivers/usb/phy/phy-msm-qusb-v2.c1141
-rw-r--r--drivers/usb/phy/phy-msm-qusb.c1543
-rw-r--r--drivers/usb/phy/phy-msm-ssusb-qmp.c842
-rw-r--r--drivers/usb/phy/phy-msm-ssusb.c595
-rw-r--r--drivers/usb/phy/phy-msm-usb.c4
8 files changed, 5025 insertions, 2 deletions
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig
index bdb9578cc296..e358fc8086f7 100644
--- a/drivers/usb/phy/Kconfig
+++ b/drivers/usb/phy/Kconfig
@@ -174,6 +174,46 @@ config USB_QCOM_8X16_PHY
To compile this driver as a module, choose M here: the
module will be called phy-qcom-8x16-usb.
+config USB_MSM_HSPHY
+ tristate "MSM HSUSB PHY Driver"
+ depends on ARCH_QCOM
+ select USB_PHY
+ help
+ Enable this to support the High-speed 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 USB_MSM_SSPHY
+ tristate "MSM SSUSB 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 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_QUSB_PHY
+ tristate "MSM QUSB2 PHY Driver"
+ depends on ARCH_QCOM
+ select USB_PHY
+ help
+ Enable this to support the QUSB2 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_MV_OTG
tristate "Marvell USB OTG support"
depends on USB_EHCI_MV && USB_MV_UDC && PM && USB_OTG
diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile
index f7543f3b9943..170f3ce3e881 100644
--- a/drivers/usb/phy/Makefile
+++ b/drivers/usb/phy/Makefile
@@ -23,6 +23,10 @@ obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o
obj-$(CONFIG_USB_ISP1301) += phy-isp1301.o
obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o
obj-$(CONFIG_USB_QCOM_8X16_PHY) += phy-qcom-8x16-usb.o
+obj-$(CONFIG_USB_MSM_HSPHY) += phy-msm-hsusb.o
+obj-$(CONFIG_USB_MSM_SSPHY) += phy-msm-ssusb.o
+obj-$(CONFIG_USB_MSM_SSPHY_QMP) += phy-msm-ssusb-qmp.o
+obj-$(CONFIG_MSM_QUSB_PHY) += phy-msm-qusb.o phy-msm-qusb-v2.o
obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o
obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o
obj-$(CONFIG_USB_RCAR_PHY) += phy-rcar-usb.o
diff --git a/drivers/usb/phy/phy-msm-hsusb.c b/drivers/usb/phy/phy-msm-hsusb.c
new file mode 100644
index 000000000000..56832adf8716
--- /dev/null
+++ b/drivers/usb/phy/phy-msm-hsusb.c
@@ -0,0 +1,858 @@
+/*
+ * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/clk/msm-clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/phy.h>
+#include <linux/usb/msm_hsusb.h>
+
+static int override_phy_init;
+module_param(override_phy_init, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(override_phy_init, "Override HSPHY Init Seq");
+
+
+#define PORT_OFFSET(i) ((i == 0) ? 0x0 : ((i == 1) ? 0x6c : 0x88))
+
+/* QSCRATCH register settings differ based on MSM core ver */
+#define MSM_CORE_VER_120 0x10020061
+#define MSM_CORE_VER_160 0x10060000
+#define MSM_CORE_VER_161 0x10060001
+
+/* QSCRATCH register offsets */
+#define GENERAL_CFG_REG (0x08)
+#define HS_PHY_CTRL_REG(i) (0x10 + PORT_OFFSET(i))
+#define PARAMETER_OVERRIDE_X_REG(i) (0x14 + PORT_OFFSET(i))
+#define ALT_INTERRUPT_EN_REG(i) (0x20 + PORT_OFFSET(i))
+#define HS_PHY_IRQ_STAT_REG(i) (0x24 + PORT_OFFSET(i))
+#define HS_PHY_CTRL_COMMON_REG (0xEC) /* ver >= MSM_CORE_VER_120 */
+
+/* GENERAL_CFG_REG bits */
+#define SEC_UTMI_FREE_CLK_GFM_SEL1 (0x80)
+
+/* HS_PHY_CTRL_REG bits */
+#define RETENABLEN BIT(1)
+#define FSEL_MASK (0x7 << 4)
+#define FSEL_DEFAULT (0x3 << 4)
+#define CLAMP_EN_N BIT(7)
+#define OTGSESSVLD_HV_CLAMP_EN_N BIT(8)
+#define ID_HV_CLAMP_EN_N BIT(9)
+#define COMMONONN BIT(11)
+#define OTGDISABLE0 BIT(12)
+#define VBUSVLDEXT0 BIT(13)
+#define VBUSVLDEXTSEL0 BIT(14)
+#define OTGSESSVLDHV_INTEN BIT(15)
+#define IDHV_INTEN BIT(16)
+#define DPSEHV_CLAMP_EN_N BIT(17)
+#define UTMI_OTG_VBUS_VALID BIT(20)
+#define USB2_UTMI_CLK_EN BIT(21)
+#define USB2_SUSPEND_N BIT(22)
+#define USB2_SUSPEND_N_SEL BIT(23)
+#define DMSEHV_CLAMP_EN_N BIT(24)
+#define CLAMP_MPM_DPSE_DMSE_EN_N BIT(26)
+/* Following exist only when core_ver >= MSM_CORE_VER_120 */
+#define FREECLK_DIS_WHEN_SUSP BIT(27)
+#define SW_SESSVLD_SEL BIT(28)
+#define FREECLOCK_SEL BIT(29)
+
+/* HS_PHY_CTRL_COMMON_REG bits used when core_ver >= MSM_CORE_VER_120 */
+#define COMMON_PLLITUNE_1 BIT(18)
+#define COMMON_PLLBTUNE BIT(15)
+#define COMMON_CLKCORE BIT(14)
+#define COMMON_VBUSVLDEXTSEL0 BIT(12)
+#define COMMON_OTGDISABLE0 BIT(11)
+#define COMMON_OTGTUNE0_MASK (0x7 << 8)
+#define COMMON_OTGTUNE0_DEFAULT (0x4 << 8)
+#define COMMON_COMMONONN BIT(7)
+#define COMMON_FSEL (0x7 << 4)
+#define COMMON_RETENABLEN BIT(3)
+
+/* ALT_INTERRUPT_EN/HS_PHY_IRQ_STAT bits */
+#define ACAINTEN BIT(0)
+#define DMINTEN BIT(1)
+#define DCDINTEN BIT(1)
+#define DPINTEN BIT(3)
+#define CHGDETINTEN BIT(4)
+#define RIDFLOATNINTEN BIT(5)
+#define DPSEHV_INTEN BIT(6)
+#define DMSEHV_INTEN BIT(7)
+#define DPSEHV_HI_INTEN BIT(8)
+#define DPSEHV_LO_INTEN BIT(9)
+#define DMSEHV_HI_INTEN BIT(10)
+#define DMSEHV_LO_INTEN BIT(11)
+#define LINESTATE_INTEN BIT(12)
+#define DPDMHV_INT_MASK (0xFC0)
+#define ALT_INTERRUPT_MASK (0x1FFF)
+
+#define TCSR_USB30_CONTROL BIT(8)
+#define TCSR_HSPHY_ARES BIT(11)
+
+#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 1800000 /* 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 *tcsr;
+ int hsphy_init_seq;
+ bool set_pllbtune;
+ u32 core_ver;
+
+ struct clk *sleep_clk;
+ bool sleep_clk_reset;
+
+ struct regulator *vdd;
+ struct regulator *vdda33;
+ struct regulator *vdda18;
+ int vdd_levels[3]; /* none, low, high */
+ u32 lpm_flags;
+ bool suspended;
+ bool vdda_force_on;
+
+ /* Using external VBUS/ID notification */
+ bool ext_vbus_id;
+ int num_ports;
+ bool cable_connected;
+};
+
+/* global reference counter between all HSPHY instances */
+static atomic_t hsphy_active_count;
+
+static int msm_hsusb_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_hsusb_ldo_enable(struct msm_hsphy *phy, int on)
+{
+ int rc = 0;
+
+ dev_dbg(phy->phy.dev, "reg (%s)\n", on ? "HPM" : "LPM");
+
+ if (!on)
+ goto disable_regulators;
+
+
+ rc = regulator_set_load(phy->vdda18, USB_HSPHY_1P8_HPM_LOAD);
+ if (rc < 0) {
+ dev_err(phy->phy.dev, "Unable to set HPM of vdda18\n");
+ return rc;
+ }
+
+ rc = regulator_set_voltage(phy->vdda18, USB_HSPHY_1P8_VOL_MIN,
+ USB_HSPHY_1P8_VOL_MAX);
+ if (rc) {
+ dev_err(phy->phy.dev, "unable to set voltage for vdda18\n");
+ goto put_vdda18_lpm;
+ }
+
+ rc = regulator_enable(phy->vdda18);
+ if (rc) {
+ dev_err(phy->phy.dev, "Unable to enable vdda18\n");
+ goto unset_vdda18;
+ }
+
+ rc = regulator_set_load(phy->vdda33, USB_HSPHY_3P3_HPM_LOAD);
+ if (rc < 0) {
+ dev_err(phy->phy.dev, "Unable to set HPM of vdda33\n");
+ goto disable_vdda18;
+ }
+
+ rc = regulator_set_voltage(phy->vdda33, USB_HSPHY_3P3_VOL_MIN,
+ USB_HSPHY_3P3_VOL_MAX);
+ if (rc) {
+ dev_err(phy->phy.dev, "unable to set voltage for vdda33\n");
+ goto put_vdda33_lpm;
+ }
+
+ rc = regulator_enable(phy->vdda33);
+ if (rc) {
+ dev_err(phy->phy.dev, "Unable to enable vdda33\n");
+ goto unset_vdda33;
+ }
+
+ return 0;
+
+disable_regulators:
+ rc = regulator_disable(phy->vdda33);
+ if (rc)
+ dev_err(phy->phy.dev, "Unable to disable vdda33\n");
+
+unset_vdda33:
+ rc = regulator_set_voltage(phy->vdda33, 0, USB_HSPHY_3P3_VOL_MAX);
+ if (rc)
+ dev_err(phy->phy.dev, "unable to set voltage for vdda33\n");
+
+put_vdda33_lpm:
+ rc = regulator_set_load(phy->vdda33, 0);
+ if (rc < 0)
+ dev_err(phy->phy.dev, "Unable to set LPM of vdda33\n");
+
+disable_vdda18:
+ rc = regulator_disable(phy->vdda18);
+ if (rc)
+ dev_err(phy->phy.dev, "Unable to disable vdda18\n");
+
+unset_vdda18:
+ rc = regulator_set_voltage(phy->vdda18, 0, USB_HSPHY_1P8_VOL_MAX);
+ if (rc)
+ dev_err(phy->phy.dev, "unable to set voltage for vdda18\n");
+
+put_vdda18_lpm:
+ rc = regulator_set_load(phy->vdda18, 0);
+ if (rc < 0)
+ dev_err(phy->phy.dev, "Unable to set LPM of vdda18\n");
+
+ return rc < 0 ? rc : 0;
+}
+
+static void msm_usb_write_readback(void *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 int msm_hsphy_reset(struct usb_phy *uphy)
+{
+ struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy);
+ u32 val;
+ int ret;
+
+ /* skip reset if there are other active PHY instances */
+ ret = atomic_read(&hsphy_active_count);
+ if (ret > 1) {
+ dev_dbg(uphy->dev, "skipping reset, inuse count=%d\n", ret);
+ return 0;
+ }
+
+ if (phy->tcsr) {
+ val = readl_relaxed(phy->tcsr);
+
+ /* Assert/deassert TCSR Reset */
+ writel_relaxed((val | TCSR_HSPHY_ARES), phy->tcsr);
+ usleep_range(1000, 1200);
+ writel_relaxed((val & ~TCSR_HSPHY_ARES), phy->tcsr);
+ } else if (phy->sleep_clk_reset) {
+ /* Reset PHY using sleep clock */
+ ret = clk_reset(phy->sleep_clk, CLK_RESET_ASSERT);
+ if (ret) {
+ dev_err(uphy->dev, "hsphy_sleep_clk assert failed\n");
+ return ret;
+ }
+
+ usleep_range(1000, 1200);
+ ret = clk_reset(phy->sleep_clk, CLK_RESET_DEASSERT);
+ if (ret) {
+ dev_err(uphy->dev, "hsphy_sleep_clk reset deassert failed\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int msm_hsphy_init(struct usb_phy *uphy)
+{
+ struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy);
+ u32 val;
+
+ msm_hsphy_reset(uphy);
+
+ /* different sequences based on core version */
+ phy->core_ver = readl_relaxed(phy->base);
+
+ /*
+ * HSPHY Initialization: Enable UTMI clock and clamp enable HVINTs,
+ * and disable RETENTION (power-on default is ENABLED)
+ */
+ val = readl_relaxed(phy->base + HS_PHY_CTRL_REG(0));
+ val |= (USB2_UTMI_CLK_EN | CLAMP_MPM_DPSE_DMSE_EN_N | RETENABLEN);
+
+ if (uphy->flags & ENABLE_SECONDARY_PHY) {
+ val &= ~(USB2_UTMI_CLK_EN | FREECLOCK_SEL);
+ val |= FREECLK_DIS_WHEN_SUSP;
+ }
+
+ writel_relaxed(val, phy->base + HS_PHY_CTRL_REG(0));
+ usleep_range(2000, 2200);
+
+ if (uphy->flags & ENABLE_SECONDARY_PHY)
+ msm_usb_write_readback(phy->base, GENERAL_CFG_REG,
+ SEC_UTMI_FREE_CLK_GFM_SEL1,
+ SEC_UTMI_FREE_CLK_GFM_SEL1);
+
+ if (phy->core_ver >= MSM_CORE_VER_120) {
+ if (phy->set_pllbtune) {
+ val = readl_relaxed(phy->base + HS_PHY_CTRL_COMMON_REG);
+ val |= COMMON_PLLBTUNE | COMMON_CLKCORE;
+ val &= ~COMMON_FSEL;
+ writel_relaxed(val, phy->base + HS_PHY_CTRL_COMMON_REG);
+ } else {
+ writel_relaxed(COMMON_OTGDISABLE0 |
+ COMMON_OTGTUNE0_DEFAULT |
+ COMMON_COMMONONN | FSEL_DEFAULT |
+ COMMON_RETENABLEN,
+ phy->base + HS_PHY_CTRL_COMMON_REG);
+ }
+ }
+
+ /*
+ * write HSPHY init value to QSCRATCH reg to set HSPHY parameters like
+ * VBUS valid threshold, disconnect valid threshold, DC voltage level,
+ * preempasis and rise/fall time.
+ */
+ if (override_phy_init)
+ phy->hsphy_init_seq = override_phy_init;
+ if (phy->hsphy_init_seq)
+ msm_usb_write_readback(phy->base,
+ PARAMETER_OVERRIDE_X_REG(0), 0x03FFFFFF,
+ phy->hsphy_init_seq & 0x03FFFFFF);
+
+ 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);
+ bool host = uphy->flags & PHY_HOST_MODE;
+ bool chg_connected = uphy->flags & PHY_CHARGER_CONNECTED;
+ int i, count;
+
+ if (!!suspend == phy->suspended) {
+ dev_dbg(uphy->dev, "%s\n", suspend ? "already suspended"
+ : "already resumed");
+ return 0;
+ }
+
+ if (suspend) {
+ for (i = 0; i < phy->num_ports; i++) {
+ /* Clear interrupt latch register */
+ writel_relaxed(ALT_INTERRUPT_MASK,
+ phy->base + HS_PHY_IRQ_STAT_REG(i));
+
+ if (host) {
+ /* Enable DP and DM HV interrupts */
+ if (phy->core_ver >= MSM_CORE_VER_120)
+ msm_usb_write_readback(phy->base,
+ ALT_INTERRUPT_EN_REG(i),
+ (LINESTATE_INTEN |
+ DPINTEN | DMINTEN),
+ (LINESTATE_INTEN |
+ DPINTEN | DMINTEN));
+ else
+ msm_usb_write_readback(phy->base,
+ ALT_INTERRUPT_EN_REG(i),
+ DPDMHV_INT_MASK,
+ DPDMHV_INT_MASK);
+
+ udelay(5);
+ } else {
+ /* set the following:
+ * OTGDISABLE0=1
+ * USB2_SUSPEND_N_SEL=1, USB2_SUSPEND_N=0
+ */
+ if (phy->core_ver >= MSM_CORE_VER_120)
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_COMMON_REG,
+ COMMON_OTGDISABLE0,
+ COMMON_OTGDISABLE0);
+ else
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_REG(i),
+ OTGDISABLE0, OTGDISABLE0);
+
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_REG(i),
+ (USB2_SUSPEND_N_SEL | USB2_SUSPEND_N),
+ USB2_SUSPEND_N_SEL);
+ /*
+ * Enable PHY retention
+ * RETENABLEN bit is not available on few platforms.
+ */
+ if (!chg_connected) {
+ if (phy->set_pllbtune)
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_COMMON_REG,
+ COMMON_PLLITUNE_1,
+ COMMON_PLLITUNE_1);
+ else
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_REG(i),
+ RETENABLEN, 0);
+ phy->lpm_flags |= PHY_RETENTIONED;
+ }
+ }
+
+ if (!phy->ext_vbus_id)
+ /* Enable PHY-based IDHV and
+ *OTGSESSVLD HV interrupts
+ */
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_REG(i),
+ (OTGSESSVLDHV_INTEN | IDHV_INTEN),
+ (OTGSESSVLDHV_INTEN | IDHV_INTEN));
+ }
+ /* can turn off regulators if disconnected in device mode */
+ if (phy->lpm_flags & PHY_RETENTIONED && !phy->cable_connected) {
+ if (phy->ext_vbus_id) {
+ msm_hsusb_ldo_enable(phy, 0);
+ phy->lpm_flags |= PHY_PWR_COLLAPSED;
+ }
+ msm_hsusb_config_vdd(phy, 0);
+ }
+
+ count = atomic_dec_return(&hsphy_active_count);
+ if (count < 0) {
+ dev_WARN(uphy->dev, "hsphy_active_count=%d, something wrong?\n",
+ count);
+ atomic_set(&hsphy_active_count, 0);
+ }
+ } else {
+ atomic_inc(&hsphy_active_count);
+ if (phy->lpm_flags & PHY_RETENTIONED && !phy->cable_connected) {
+ msm_hsusb_config_vdd(phy, 1);
+ if (phy->ext_vbus_id) {
+ msm_hsusb_ldo_enable(phy, 1);
+ phy->lpm_flags &= ~PHY_PWR_COLLAPSED;
+ }
+ phy->lpm_flags &= ~PHY_RETENTIONED;
+ }
+
+ if (phy->core_ver >= MSM_CORE_VER_120) {
+ if (phy->set_pllbtune) {
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_COMMON_REG,
+ FSEL_MASK, 0);
+ } else {
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_COMMON_REG,
+ FSEL_MASK, FSEL_DEFAULT);
+ }
+ }
+ for (i = 0; i < phy->num_ports; i++) {
+ if (!phy->ext_vbus_id)
+ /* Disable HV interrupts */
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_REG(i),
+ (OTGSESSVLDHV_INTEN | IDHV_INTEN),
+ 0);
+ if (host) {
+ /* Clear interrupt latch register */
+ writel_relaxed(ALT_INTERRUPT_MASK,
+ phy->base + HS_PHY_IRQ_STAT_REG(i));
+ /* Disable DP and DM HV interrupt */
+ if (phy->core_ver >= MSM_CORE_VER_120)
+ msm_usb_write_readback(phy->base,
+ ALT_INTERRUPT_EN_REG(i),
+ LINESTATE_INTEN, 0);
+ else
+ msm_usb_write_readback(phy->base,
+ ALT_INTERRUPT_EN_REG(i),
+ DPDMHV_INT_MASK, 0);
+ } else {
+ /* Disable PHY retention */
+ if (phy->set_pllbtune)
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_COMMON_REG,
+ COMMON_PLLITUNE_1, 0);
+ else
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_REG(i),
+ RETENABLEN, RETENABLEN);
+
+ /* Bring PHY out of suspend */
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_REG(i),
+ USB2_SUSPEND_N_SEL, 0);
+
+ if (phy->core_ver >= MSM_CORE_VER_120)
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_COMMON_REG,
+ COMMON_OTGDISABLE0,
+ 0);
+ else
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_REG(i),
+ OTGDISABLE0, 0);
+ }
+ }
+ /*
+ * write HSPHY init value to QSCRATCH reg to set HSPHY
+ * parameters like VBUS valid threshold, disconnect valid
+ * threshold, DC voltage level,preempasis and rise/fall time
+ */
+ if (override_phy_init)
+ phy->hsphy_init_seq = override_phy_init;
+ if (phy->hsphy_init_seq)
+ msm_usb_write_readback(phy->base,
+ PARAMETER_OVERRIDE_X_REG(0),
+ 0x03FFFFFF,
+ phy->hsphy_init_seq & 0x03FFFFFF);
+ }
+
+ phy->suspended = !!suspend; /* double-NOT coerces to bool value */
+ return 0;
+}
+
+static int msm_hsphy_notify_connect(struct usb_phy *uphy,
+ enum usb_device_speed speed)
+{
+ int rc = 0;
+ struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy);
+
+ phy->cable_connected = true;
+
+ if (uphy->flags & PHY_HOST_MODE) {
+ if (phy->core_ver == MSM_CORE_VER_160 ||
+ phy->core_ver == MSM_CORE_VER_161) {
+ /* Some snps usb2 picophy revisions require 3.15 V to
+ * operate correctly during full speed host mode at
+ * sub zero temperature.
+ */
+ rc = regulator_set_voltage(phy->vdda33,
+ USB_HSPHY_3P3_VOL_FSHOST,
+ USB_HSPHY_3P3_VOL_MAX);
+ if (rc)
+ dev_err(phy->phy.dev,
+ "unable to set voltage for vdda33\n");
+ }
+ return 0;
+ }
+
+ if (!(uphy->flags & PHY_VBUS_VALID_OVERRIDE))
+ return 0;
+
+ /* Set External VBUS Valid Select. Set once, can be left on */
+ if (phy->core_ver >= MSM_CORE_VER_120) {
+ msm_usb_write_readback(phy->base, HS_PHY_CTRL_COMMON_REG,
+ COMMON_VBUSVLDEXTSEL0,
+ COMMON_VBUSVLDEXTSEL0);
+ } else {
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_REG(0),
+ VBUSVLDEXTSEL0, VBUSVLDEXTSEL0);
+ }
+
+ /* Enable D+ pull-up resistor */
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_REG(0),
+ VBUSVLDEXT0, VBUSVLDEXT0);
+
+ /* Set OTG VBUS Valid from HSPHY to controller */
+ msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(0),
+ UTMI_OTG_VBUS_VALID,
+ UTMI_OTG_VBUS_VALID);
+
+ /* Indicate value is driven by UTMI_OTG_VBUS_VALID bit */
+ if (phy->core_ver >= MSM_CORE_VER_120)
+ msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(0),
+ SW_SESSVLD_SEL, SW_SESSVLD_SEL);
+
+ return 0;
+}
+
+static int msm_hsphy_notify_disconnect(struct usb_phy *uphy,
+ enum usb_device_speed speed)
+{
+ int rc = 0;
+ struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy);
+
+ phy->cable_connected = false;
+
+ if (uphy->flags & PHY_HOST_MODE) {
+ if (phy->core_ver == MSM_CORE_VER_160 ||
+ phy->core_ver == MSM_CORE_VER_161) {
+ rc = regulator_set_voltage(phy->vdda33,
+ USB_HSPHY_3P3_VOL_MIN,
+ USB_HSPHY_3P3_VOL_MAX);
+ if (rc)
+ dev_err(phy->phy.dev,
+ "unable to set voltage for vdda33\n");
+ }
+ return 0;
+ }
+
+ if (!(uphy->flags & PHY_VBUS_VALID_OVERRIDE))
+ return 0;
+
+ /* Clear OTG VBUS Valid to Controller */
+ msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(0),
+ UTMI_OTG_VBUS_VALID, 0);
+
+ /* Disable D+ pull-up resistor */
+ msm_usb_write_readback(phy->base,
+ HS_PHY_CTRL_REG(0), VBUSVLDEXT0, 0);
+
+ /* Indicate value is no longer driven by UTMI_OTG_VBUS_VALID bit */
+ if (phy->core_ver >= MSM_CORE_VER_120)
+ msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(0),
+ SW_SESSVLD_SEL, 0);
+
+ return 0;
+}
+
+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;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core");
+ if (!res) {
+ dev_err(dev, "missing memory base resource\n");
+ ret = -ENODEV;
+ goto err_ret;
+ }
+
+ phy->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+ if (!phy->base) {
+ dev_err(dev, "ioremap failed\n");
+ ret = -ENODEV;
+ goto err_ret;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tcsr");
+ if (res) {
+ phy->tcsr = devm_ioremap_nocache(dev, res->start,
+ resource_size(res));
+ if (!phy->tcsr) {
+ dev_err(dev, "tcsr ioremap failed\n");
+ return -ENODEV;
+ }
+
+ /* switch MUX to let SNPS controller use the primary HSPHY */
+ writel_relaxed(readl_relaxed(phy->tcsr) | TCSR_USB30_CONTROL,
+ phy->tcsr);
+ }
+
+ if (of_get_property(dev->of_node, "qcom,primary-phy", NULL)) {
+ dev_dbg(dev, "secondary HSPHY\n");
+ phy->phy.flags |= ENABLE_SECONDARY_PHY;
+ }
+
+ 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->ext_vbus_id = of_property_read_bool(dev->of_node,
+ "qcom,ext-vbus-id");
+ phy->phy.dev = dev;
+
+ 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;
+ }
+
+ ret = msm_hsusb_config_vdd(phy, 1);
+ if (ret) {
+ dev_err(dev, "hsusb vdd_dig configuration failed\n");
+ goto err_ret;
+ }
+
+ ret = regulator_enable(phy->vdd);
+ if (ret) {
+ dev_err(dev, "unable to enable the hsusb vdd_dig\n");
+ goto unconfig_hs_vdd;
+ }
+
+ ret = msm_hsusb_ldo_enable(phy, 1);
+ if (ret) {
+ dev_err(dev, "hsusb vreg enable failed\n");
+ goto disable_hs_vdd;
+ }
+
+ phy->sleep_clk = devm_clk_get(&pdev->dev, "phy_sleep_clk");
+ if (IS_ERR(phy->sleep_clk)) {
+ dev_err(&pdev->dev, "failed to get phy_sleep_clk\n");
+ ret = PTR_ERR(phy->sleep_clk);
+ goto disable_hs_ldo;
+ }
+ clk_prepare_enable(phy->sleep_clk);
+ phy->sleep_clk_reset = of_property_read_bool(dev->of_node,
+ "qcom,sleep-clk-reset");
+
+ if (of_property_read_u32(dev->of_node, "qcom,hsphy-init",
+ &phy->hsphy_init_seq))
+ dev_dbg(dev, "unable to read hsphy init seq\n");
+ else if (!phy->hsphy_init_seq)
+ dev_warn(dev, "hsphy init seq cannot be 0. Using POR value\n");
+
+ if (of_property_read_u32(dev->of_node, "qcom,num-ports",
+ &phy->num_ports))
+ phy->num_ports = 1;
+ else if (phy->num_ports > 3) {
+ dev_err(dev, " number of ports more that 3 is not supported\n");
+ goto disable_clk;
+ }
+
+ phy->set_pllbtune = of_property_read_bool(dev->of_node,
+ "qcom,set-pllbtune");
+
+ /*
+ * If this workaround flag is enabled, the HW requires the 1.8 and 3.x
+ * regulators to be kept ON when entering suspend. The easiest way to
+ * do that is to call regulator_enable() an additional time here,
+ * since it will keep the regulators' reference counts nonzero.
+ */
+ phy->vdda_force_on = of_property_read_bool(dev->of_node,
+ "qcom,vdda-force-on");
+ if (phy->vdda_force_on) {
+ ret = msm_hsusb_ldo_enable(phy, 1);
+ if (ret)
+ goto disable_clk;
+ }
+
+ platform_set_drvdata(pdev, phy);
+
+ if (of_property_read_bool(dev->of_node, "qcom,vbus-valid-override"))
+ phy->phy.flags |= PHY_VBUS_VALID_OVERRIDE;
+
+ 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.reset = msm_hsphy_reset;
+ /*FIXME: this conflicts with dwc3_otg */
+ /*phy->phy.type = USB_PHY_TYPE_USB2; */
+
+ ret = usb_add_phy_dev(&phy->phy);
+ if (ret)
+ goto disable_clk;
+
+ atomic_inc(&hsphy_active_count);
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(phy->sleep_clk);
+disable_hs_ldo:
+ msm_hsusb_ldo_enable(phy, 0);
+disable_hs_vdd:
+ regulator_disable(phy->vdd);
+unconfig_hs_vdd:
+ msm_hsusb_config_vdd(phy, 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;
+
+ usb_remove_phy(&phy->phy);
+ clk_disable_unprepare(phy->sleep_clk);
+
+ /* Undo the additional regulator enable */
+ if (phy->vdda_force_on)
+ msm_hsusb_ldo_enable(phy, 0);
+ msm_hsusb_ldo_enable(phy, 0);
+ regulator_disable(phy->vdd);
+ msm_hsusb_config_vdd(phy, 0);
+ if (!phy->suspended)
+ atomic_dec(&hsphy_active_count);
+ kfree(phy);
+
+ return 0;
+}
+
+static const struct of_device_id msm_usb_id_table[] = {
+ {
+ .compatible = "qcom,usb-hsphy",
+ },
+ { },
+};
+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-qusb-v2.c b/drivers/usb/phy/phy-msm-qusb-v2.c
new file mode 100644
index 000000000000..e5f38e42e165
--- /dev/null
+++ b/drivers/usb/phy/phy-msm-qusb-v2.c
@@ -0,0 +1,1141 @@
+/*
+ * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/clk/msm-clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/usb/phy.h>
+#include <linux/usb/msm_hsusb.h>
+#include <linux/reset.h>
+
+#define QUSB2PHY_PWR_CTRL1 0x210
+#define PWR_CTRL1_POWR_DOWN BIT(0)
+
+#define QUSB2PHY_PLL_COMMON_STATUS_ONE 0x1A0
+#define CORE_READY_STATUS BIT(0)
+
+/* Get TUNE value from efuse bit-mask */
+#define TUNE_VAL_MASK(val, pos, mask) ((val >> pos) & mask)
+
+#define QUSB2PHY_INTR_CTRL 0x22C
+#define DMSE_INTR_HIGH_SEL BIT(4)
+#define DPSE_INTR_HIGH_SEL BIT(3)
+#define CHG_DET_INTR_EN BIT(2)
+#define DMSE_INTR_EN BIT(1)
+#define DPSE_INTR_EN BIT(0)
+
+#define QUSB2PHY_INTR_STAT 0x230
+#define DMSE_INTERRUPT BIT(1)
+#define DPSE_INTERRUPT BIT(0)
+
+#define QUSB2PHY_PORT_TUNE1 0x23c
+#define QUSB2PHY_TEST1 0x24C
+
+#define QUSB2PHY_1P2_VOL_MIN 1200000 /* uV */
+#define QUSB2PHY_1P2_VOL_MAX 1200000 /* uV */
+#define QUSB2PHY_1P2_HPM_LOAD 23000
+
+#define QUSB2PHY_1P8_VOL_MIN 1800000 /* uV */
+#define QUSB2PHY_1P8_VOL_MAX 1800000 /* uV */
+#define QUSB2PHY_1P8_HPM_LOAD 30000 /* uA */
+
+#define QUSB2PHY_3P3_VOL_MIN 3075000 /* uV */
+#define QUSB2PHY_3P3_VOL_MAX 3200000 /* uV */
+#define QUSB2PHY_3P3_HPM_LOAD 30000 /* uA */
+
+#define LINESTATE_DP BIT(0)
+#define LINESTATE_DM BIT(1)
+
+#define QUSB2PHY_PLL_ANALOG_CONTROLS_ONE 0x0
+#define QUSB2PHY_PLL_ANALOG_CONTROLS_TWO 0x4
+
+unsigned int phy_tune1;
+module_param(phy_tune1, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(phy_tune1, "QUSB PHY v2 TUNE1");
+
+struct qusb_phy {
+ struct usb_phy phy;
+ struct mutex lock;
+ void __iomem *base;
+ void __iomem *efuse_reg;
+ void __iomem *tcsr_clamp_dig_n;
+
+ struct clk *ref_clk_src;
+ struct clk *ref_clk;
+ struct clk *cfg_ahb_clk;
+ struct reset_control *phy_reset;
+
+ struct regulator *vdd;
+ struct regulator *vdda33;
+ struct regulator *vdda18;
+ struct regulator *vdda12;
+ int vdd_levels[3]; /* none, low, high */
+ int vdda33_levels[3];
+ int init_seq_len;
+ int *qusb_phy_init_seq;
+ int host_init_seq_len;
+ int *qusb_phy_host_init_seq;
+
+ u32 tune_val;
+ int efuse_bit_pos;
+ int efuse_num_of_bits;
+
+ int power_enabled_ref;
+ bool clocks_enabled;
+ bool cable_connected;
+ bool suspended;
+ bool rm_pulldown;
+
+ struct regulator_desc dpdm_rdesc;
+ struct regulator_dev *dpdm_rdev;
+
+ /* emulation targets specific */
+ void __iomem *emu_phy_base;
+ bool emulation;
+ int *emu_init_seq;
+ int emu_init_seq_len;
+ int *phy_pll_reset_seq;
+ int phy_pll_reset_seq_len;
+ int *emu_dcm_reset_seq;
+ int emu_dcm_reset_seq_len;
+};
+
+static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on)
+{
+ dev_dbg(qphy->phy.dev, "%s(): clocks_enabled:%d on:%d\n",
+ __func__, qphy->clocks_enabled, on);
+
+ if (!qphy->clocks_enabled && on) {
+ clk_prepare_enable(qphy->ref_clk_src);
+ clk_prepare_enable(qphy->ref_clk);
+ clk_prepare_enable(qphy->cfg_ahb_clk);
+ qphy->clocks_enabled = true;
+ }
+
+ if (qphy->clocks_enabled && !on) {
+ clk_disable_unprepare(qphy->ref_clk);
+ clk_disable_unprepare(qphy->ref_clk_src);
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ qphy->clocks_enabled = false;
+ }
+
+ dev_dbg(qphy->phy.dev, "%s(): clocks_enabled:%d\n", __func__,
+ qphy->clocks_enabled);
+}
+
+static int qusb_phy_config_vdd(struct qusb_phy *qphy, int high)
+{
+ int min, ret;
+
+ min = high ? 1 : 0; /* low or none? */
+ ret = regulator_set_voltage(qphy->vdd, qphy->vdd_levels[min],
+ qphy->vdd_levels[2]);
+ if (ret) {
+ dev_err(qphy->phy.dev, "unable to set voltage for qusb vdd\n");
+ return ret;
+ }
+
+ dev_dbg(qphy->phy.dev, "min_vol:%d max_vol:%d\n",
+ qphy->vdd_levels[min], qphy->vdd_levels[2]);
+ return ret;
+}
+
+static int qusb_phy_enable_power(struct qusb_phy *qphy, bool on)
+{
+ int ret = 0;
+
+ mutex_lock(&qphy->lock);
+
+ dev_dbg(qphy->phy.dev,
+ "%s:req to turn %s regulators. power_enabled_ref:%d\n",
+ __func__, on ? "on" : "off", qphy->power_enabled_ref);
+
+ if (on && ++qphy->power_enabled_ref > 1) {
+ dev_dbg(qphy->phy.dev, "PHYs' regulators are already on\n");
+ goto done;
+ }
+
+ if (!on) {
+ if (on == qphy->power_enabled_ref) {
+ dev_dbg(qphy->phy.dev,
+ "PHYs' regulators are already off\n");
+ goto done;
+ }
+
+ qphy->power_enabled_ref--;
+ if (!qphy->power_enabled_ref)
+ goto disable_vdda33;
+
+ dev_dbg(qphy->phy.dev, "Skip turning off PHYs' regulators\n");
+ goto done;
+ }
+
+ ret = qusb_phy_config_vdd(qphy, true);
+ if (ret) {
+ dev_err(qphy->phy.dev, "Unable to config VDD:%d\n",
+ ret);
+ goto err_vdd;
+ }
+
+ ret = regulator_enable(qphy->vdd);
+ if (ret) {
+ dev_err(qphy->phy.dev, "Unable to enable VDD\n");
+ goto unconfig_vdd;
+ }
+
+ ret = regulator_set_load(qphy->vdda12, QUSB2PHY_1P2_HPM_LOAD);
+ if (ret < 0) {
+ dev_err(qphy->phy.dev, "Unable to set HPM of vdda12:%d\n", ret);
+ goto disable_vdd;
+ }
+
+ ret = regulator_set_voltage(qphy->vdda12, QUSB2PHY_1P2_VOL_MIN,
+ QUSB2PHY_1P2_VOL_MAX);
+ if (ret) {
+ dev_err(qphy->phy.dev,
+ "Unable to set voltage for vdda12:%d\n", ret);
+ goto put_vdda12_lpm;
+ }
+
+ ret = regulator_enable(qphy->vdda12);
+ if (ret) {
+ dev_err(qphy->phy.dev, "Unable to enable vdda12:%d\n", ret);
+ goto unset_vdda12;
+ }
+
+ ret = regulator_set_load(qphy->vdda18, QUSB2PHY_1P8_HPM_LOAD);
+ if (ret < 0) {
+ dev_err(qphy->phy.dev, "Unable to set HPM of vdda18:%d\n", ret);
+ goto disable_vdda12;
+ }
+
+ ret = regulator_set_voltage(qphy->vdda18, QUSB2PHY_1P8_VOL_MIN,
+ QUSB2PHY_1P8_VOL_MAX);
+ if (ret) {
+ dev_err(qphy->phy.dev,
+ "Unable to set voltage for vdda18:%d\n", ret);
+ goto put_vdda18_lpm;
+ }
+
+ ret = regulator_enable(qphy->vdda18);
+ if (ret) {
+ dev_err(qphy->phy.dev, "Unable to enable vdda18:%d\n", ret);
+ goto unset_vdda18;
+ }
+
+ ret = regulator_set_load(qphy->vdda33, QUSB2PHY_3P3_HPM_LOAD);
+ if (ret < 0) {
+ dev_err(qphy->phy.dev, "Unable to set HPM of vdda33:%d\n", ret);
+ goto disable_vdda18;
+ }
+
+ ret = regulator_set_voltage(qphy->vdda33, qphy->vdda33_levels[0],
+ qphy->vdda33_levels[2]);
+ if (ret) {
+ dev_err(qphy->phy.dev,
+ "Unable to set voltage for vdda33:%d\n", ret);
+ goto put_vdda33_lpm;
+ }
+
+ ret = regulator_enable(qphy->vdda33);
+ if (ret) {
+ dev_err(qphy->phy.dev, "Unable to enable vdda33:%d\n", ret);
+ goto unset_vdd33;
+ }
+
+ pr_debug("%s(): QUSB PHY's regulators are turned ON.\n", __func__);
+
+ mutex_unlock(&qphy->lock);
+ return ret;
+
+disable_vdda33:
+ ret = regulator_disable(qphy->vdda33);
+ if (ret)
+ dev_err(qphy->phy.dev, "Unable to disable vdda33:%d\n", ret);
+
+unset_vdd33:
+ ret = regulator_set_voltage(qphy->vdda33, 0, qphy->vdda33_levels[2]);
+ if (ret)
+ dev_err(qphy->phy.dev,
+ "Unable to set (0) voltage for vdda33:%d\n", ret);
+
+put_vdda33_lpm:
+ ret = regulator_set_load(qphy->vdda33, 0);
+ if (ret < 0)
+ dev_err(qphy->phy.dev, "Unable to set (0) HPM of vdda33\n");
+
+disable_vdda18:
+ ret = regulator_disable(qphy->vdda18);
+ if (ret)
+ dev_err(qphy->phy.dev, "Unable to disable vdda18:%d\n", ret);
+
+unset_vdda18:
+ ret = regulator_set_voltage(qphy->vdda18, 0, QUSB2PHY_1P8_VOL_MAX);
+ if (ret)
+ dev_err(qphy->phy.dev,
+ "Unable to set (0) voltage for vdda18:%d\n", ret);
+
+put_vdda18_lpm:
+ ret = regulator_set_load(qphy->vdda18, 0);
+ if (ret < 0)
+ dev_err(qphy->phy.dev, "Unable to set LPM of vdda18\n");
+
+disable_vdda12:
+ ret = regulator_disable(qphy->vdda12);
+ if (ret)
+ dev_err(qphy->phy.dev, "Unable to disable vdda12:%d\n", ret);
+unset_vdda12:
+ ret = regulator_set_voltage(qphy->vdda12, 0, QUSB2PHY_1P2_VOL_MAX);
+ if (ret)
+ dev_err(qphy->phy.dev,
+ "Unable to set (0) voltage for vdda12:%d\n", ret);
+put_vdda12_lpm:
+ ret = regulator_set_load(qphy->vdda12, 0);
+ if (ret < 0)
+ dev_err(qphy->phy.dev, "Unable to set LPM of vdda12\n");
+
+disable_vdd:
+ ret = regulator_disable(qphy->vdd);
+ if (ret)
+ dev_err(qphy->phy.dev, "Unable to disable vdd:%d\n",
+ ret);
+
+unconfig_vdd:
+ ret = qusb_phy_config_vdd(qphy, false);
+ if (ret)
+ dev_err(qphy->phy.dev, "Unable unconfig VDD:%d\n",
+ ret);
+err_vdd:
+ dev_dbg(qphy->phy.dev, "QUSB PHY's regulators are turned OFF.\n");
+
+ /* in case of error in turning on regulators */
+ if (qphy->power_enabled_ref)
+ qphy->power_enabled_ref--;
+done:
+ mutex_unlock(&qphy->lock);
+ return ret;
+}
+
+static int qusb_phy_update_dpdm(struct usb_phy *phy, int value)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+ int ret = 0;
+
+ dev_dbg(phy->dev, "%s value:%d rm_pulldown:%d\n",
+ __func__, value, qphy->rm_pulldown);
+
+ switch (value) {
+ case POWER_SUPPLY_DP_DM_DPF_DMF:
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DPF_DMF\n");
+ if (!qphy->rm_pulldown) {
+ ret = qusb_phy_enable_power(qphy, true);
+ if (ret >= 0) {
+ qphy->rm_pulldown = true;
+ dev_dbg(phy->dev, "DP_DM_F: rm_pulldown:%d\n",
+ qphy->rm_pulldown);
+ }
+ }
+
+ break;
+
+ case POWER_SUPPLY_DP_DM_DPR_DMR:
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DPR_DMR\n");
+ if (qphy->rm_pulldown) {
+ ret = qusb_phy_enable_power(qphy, false);
+ if (ret >= 0) {
+ qphy->rm_pulldown = false;
+ dev_dbg(phy->dev, "DP_DM_R: rm_pulldown:%d\n",
+ qphy->rm_pulldown);
+ }
+ }
+ break;
+
+ default:
+ ret = -EINVAL;
+ dev_err(phy->dev, "Invalid power supply property(%d)\n", value);
+ break;
+ }
+
+ return ret;
+}
+
+static void qusb_phy_get_tune1_param(struct qusb_phy *qphy)
+{
+ u8 reg;
+ u32 bit_mask = 1;
+
+ pr_debug("%s(): num_of_bits:%d bit_pos:%d\n", __func__,
+ qphy->efuse_num_of_bits,
+ qphy->efuse_bit_pos);
+
+ /* get bit mask based on number of bits to use with efuse reg */
+ bit_mask = (bit_mask << qphy->efuse_num_of_bits) - 1;
+
+ /*
+ * if efuse reg is updated (i.e non-zero) then use it to program
+ * tune parameters
+ */
+ qphy->tune_val = readl_relaxed(qphy->efuse_reg);
+ pr_debug("%s(): bit_mask:%d efuse based tune1 value:%d\n",
+ __func__, bit_mask, qphy->tune_val);
+
+ qphy->tune_val = TUNE_VAL_MASK(qphy->tune_val,
+ qphy->efuse_bit_pos, bit_mask);
+ reg = readb_relaxed(qphy->base + QUSB2PHY_PORT_TUNE1);
+ if (qphy->tune_val) {
+ reg = reg & 0x0f;
+ reg |= (qphy->tune_val << 4);
+ }
+ qphy->tune_val = reg;
+}
+
+static void qusb_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 void qusb_phy_host_init(struct usb_phy *phy)
+{
+ u8 reg;
+ int ret;
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+
+ dev_dbg(phy->dev, "%s\n", __func__);
+
+ /* Perform phy reset */
+ ret = reset_control_assert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "%s: phy_reset assert failed\n", __func__);
+ usleep_range(100, 150);
+ ret = reset_control_deassert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "%s: phy_reset deassert failed\n", __func__);
+
+ qusb_phy_write_seq(qphy->base, qphy->qusb_phy_host_init_seq,
+ qphy->host_init_seq_len, 0);
+
+ /* Ensure above write is completed before turning ON ref clk */
+ wmb();
+
+ /* Require to get phy pll lock successfully */
+ usleep_range(150, 160);
+
+ reg = readb_relaxed(qphy->base + QUSB2PHY_PLL_COMMON_STATUS_ONE);
+ dev_dbg(phy->dev, "QUSB2PHY_PLL_COMMON_STATUS_ONE:%x\n", reg);
+ if (!(reg & CORE_READY_STATUS)) {
+ dev_err(phy->dev, "QUSB PHY PLL LOCK fails:%x\n", reg);
+ WARN_ON(1);
+ }
+}
+
+static int qusb_phy_init(struct usb_phy *phy)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+ int ret;
+ u8 reg;
+
+ dev_dbg(phy->dev, "%s\n", __func__);
+
+ /* bump up vdda33 voltage to operating level*/
+ ret = regulator_set_voltage(qphy->vdda33, qphy->vdda33_levels[1],
+ qphy->vdda33_levels[2]);
+ if (ret) {
+ dev_err(qphy->phy.dev,
+ "Unable to set voltage for vdda33:%d\n", ret);
+ return ret;
+ }
+
+ qusb_phy_enable_clocks(qphy, true);
+
+ /* Perform phy reset */
+ ret = reset_control_assert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "%s: phy_reset assert failed\n", __func__);
+ usleep_range(100, 150);
+ ret = reset_control_deassert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "%s: phy_reset deassert failed\n", __func__);
+
+ if (qphy->emulation) {
+ if (qphy->emu_init_seq)
+ qusb_phy_write_seq(qphy->emu_phy_base,
+ qphy->emu_init_seq, qphy->emu_init_seq_len, 0);
+
+ if (qphy->qusb_phy_init_seq)
+ qusb_phy_write_seq(qphy->base, qphy->qusb_phy_init_seq,
+ qphy->init_seq_len, 0);
+
+ /* Wait for 5ms as per QUSB2 RUMI sequence */
+ usleep_range(5000, 7000);
+
+ if (qphy->phy_pll_reset_seq)
+ qusb_phy_write_seq(qphy->base, qphy->phy_pll_reset_seq,
+ qphy->phy_pll_reset_seq_len, 10000);
+
+ if (qphy->emu_dcm_reset_seq)
+ qusb_phy_write_seq(qphy->emu_phy_base,
+ qphy->emu_dcm_reset_seq,
+ qphy->emu_dcm_reset_seq_len, 10000);
+
+ return 0;
+ }
+
+ /* Disable the PHY */
+ writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) |
+ PWR_CTRL1_POWR_DOWN,
+ qphy->base + QUSB2PHY_PWR_CTRL1);
+
+ if (qphy->qusb_phy_init_seq)
+ qusb_phy_write_seq(qphy->base, qphy->qusb_phy_init_seq,
+ qphy->init_seq_len, 0);
+ if (qphy->efuse_reg) {
+ if (!qphy->tune_val)
+ qusb_phy_get_tune1_param(qphy);
+
+ pr_debug("%s(): Programming TUNE1 parameter as:%x\n", __func__,
+ qphy->tune_val);
+ writel_relaxed(qphy->tune_val,
+ qphy->base + QUSB2PHY_PORT_TUNE1);
+ }
+
+ /* If phy_tune1 modparam set, override tune1 value */
+ if (phy_tune1) {
+ pr_debug("%s(): (modparam) TUNE1 val:0x%02x\n",
+ __func__, phy_tune1);
+ writel_relaxed(phy_tune1,
+ qphy->base + QUSB2PHY_PORT_TUNE1);
+ }
+
+ /* ensure above writes are completed before re-enabling PHY */
+ wmb();
+
+ /* Enable the PHY */
+ writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) &
+ ~PWR_CTRL1_POWR_DOWN,
+ qphy->base + QUSB2PHY_PWR_CTRL1);
+
+ /* Ensure above write is completed before turning ON ref clk */
+ wmb();
+
+ /* Require to get phy pll lock successfully */
+ usleep_range(150, 160);
+
+ reg = readb_relaxed(qphy->base + QUSB2PHY_PLL_COMMON_STATUS_ONE);
+ dev_dbg(phy->dev, "QUSB2PHY_PLL_COMMON_STATUS_ONE:%x\n", reg);
+ if (!(reg & CORE_READY_STATUS)) {
+ dev_err(phy->dev, "QUSB PHY PLL LOCK fails:%x\n", reg);
+ WARN_ON(1);
+ }
+ return 0;
+}
+
+static void qusb_phy_shutdown(struct usb_phy *phy)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+
+ dev_dbg(phy->dev, "%s\n", __func__);
+
+ qusb_phy_enable_clocks(qphy, true);
+
+ /* Disable the PHY */
+ writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) |
+ PWR_CTRL1_POWR_DOWN,
+ qphy->base + QUSB2PHY_PWR_CTRL1);
+
+ /* Makes sure that above write goes through */
+ wmb();
+
+ qusb_phy_enable_clocks(qphy, false);
+}
+
+static u32 qusb_phy_get_linestate(struct qusb_phy *qphy)
+{
+ u32 linestate = 0;
+
+ if (qphy->cable_connected) {
+ if (qphy->phy.flags & PHY_HSFS_MODE)
+ linestate |= LINESTATE_DP;
+ else if (qphy->phy.flags & PHY_LS_MODE)
+ linestate |= LINESTATE_DM;
+ }
+ return linestate;
+}
+
+/**
+ * Performs QUSB2 PHY suspend/resume functionality.
+ *
+ * @uphy - usb phy pointer.
+ * @suspend - to enable suspend or not. 1 - suspend, 0 - resume
+ *
+ */
+static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+ u32 linestate = 0, intr_mask = 0;
+ static u8 analog_ctrl_two;
+ int ret;
+
+ if (qphy->suspended && suspend) {
+ dev_dbg(phy->dev, "%s: USB PHY is already suspended\n",
+ __func__);
+ return 0;
+ }
+
+ if (suspend) {
+ /* Bus suspend case */
+ if (qphy->cable_connected ||
+ (qphy->phy.flags & PHY_HOST_MODE)) {
+
+ /* store clock settings like cmos/cml */
+ analog_ctrl_two =
+ readl_relaxed(qphy->base +
+ QUSB2PHY_PLL_ANALOG_CONTROLS_TWO);
+
+ /* use CSR & switch to SE clk */
+ writel_relaxed(0xb,
+ qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_TWO);
+
+ /* enable clock bypass */
+ writel_relaxed(0x90,
+ qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_ONE);
+
+ /* Disable all interrupts */
+ writel_relaxed(0x00,
+ qphy->base + QUSB2PHY_INTR_CTRL);
+
+ linestate = qusb_phy_get_linestate(qphy);
+ /*
+ * D+/D- interrupts are level-triggered, but we are
+ * only interested if the line state changes, so enable
+ * the high/low trigger based on current state. In
+ * other words, enable the triggers _opposite_ of what
+ * the current D+/D- levels are.
+ * e.g. if currently D+ high, D- low (HS 'J'/Suspend),
+ * configure the mask to trigger on D+ low OR D- high
+ */
+ intr_mask = DMSE_INTERRUPT | DPSE_INTERRUPT;
+ if (!(linestate & LINESTATE_DP)) /* D+ low */
+ intr_mask |= DPSE_INTR_HIGH_SEL;
+ if (!(linestate & LINESTATE_DM)) /* D- low */
+ intr_mask |= DMSE_INTR_HIGH_SEL;
+
+ writel_relaxed(intr_mask,
+ qphy->base + QUSB2PHY_INTR_CTRL);
+
+ if (linestate & (LINESTATE_DP | LINESTATE_DM)) {
+
+ /* enable phy auto-resume */
+ writel_relaxed(0x91,
+ qphy->base + QUSB2PHY_TEST1);
+ /* flush the previous write before next write */
+ wmb();
+ writel_relaxed(0x90,
+ qphy->base + QUSB2PHY_TEST1);
+ }
+
+ dev_dbg(phy->dev, "%s: intr_mask = %x\n",
+ __func__, intr_mask);
+
+ /* Makes sure that above write goes through */
+ wmb();
+ qusb_phy_enable_clocks(qphy, false);
+ } else { /* Cable disconnect case */
+
+ ret = reset_control_assert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "%s: phy_reset assert failed\n",
+ __func__);
+ usleep_range(100, 150);
+ ret = reset_control_deassert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "%s: phy_reset deassert failed\n",
+ __func__);
+
+ writel_relaxed(0x1b,
+ qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_TWO);
+
+ /* enable clock bypass */
+ writel_relaxed(0x90,
+ qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_ONE);
+
+ writel_relaxed(0x0, qphy->tcsr_clamp_dig_n);
+ /*
+ * clamp needs asserted before
+ * power/clocks can be turned off
+ */
+ wmb();
+
+ qusb_phy_enable_clocks(qphy, false);
+ qusb_phy_enable_power(qphy, false);
+ }
+ qphy->suspended = true;
+ } else {
+ /* Bus resume case */
+ if (qphy->cable_connected ||
+ (qphy->phy.flags & PHY_HOST_MODE)) {
+ qusb_phy_enable_clocks(qphy, true);
+
+ /* restore the default clock settings */
+ writel_relaxed(analog_ctrl_two,
+ qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_TWO);
+
+ /* disable clock bypass */
+ writel_relaxed(0x80,
+ qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_ONE);
+
+ /* Clear all interrupts on resume */
+ writel_relaxed(0x00,
+ qphy->base + QUSB2PHY_INTR_CTRL);
+
+ /* Makes sure that above write goes through */
+ wmb();
+ } else { /* Cable connect case */
+ writel_relaxed(0x1, qphy->tcsr_clamp_dig_n);
+
+ /*
+ * clamp needs de-asserted before
+ * power/clocks can be turned on
+ */
+ wmb();
+
+ qusb_phy_enable_power(qphy, true);
+ ret = reset_control_assert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "%s: phy_reset assert failed\n",
+ __func__);
+ usleep_range(100, 150);
+ ret = reset_control_deassert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "%s: phy_reset deassert failed\n",
+ __func__);
+
+ qusb_phy_enable_clocks(qphy, true);
+ }
+ qphy->suspended = false;
+ }
+
+ return 0;
+}
+
+static int qusb_phy_notify_connect(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+
+ qphy->cable_connected = true;
+
+ if (qphy->qusb_phy_host_init_seq && qphy->phy.flags & PHY_HOST_MODE)
+ qusb_phy_host_init(phy);
+
+ dev_dbg(phy->dev, "QUSB PHY: connect notification cable_connected=%d\n",
+ qphy->cable_connected);
+ return 0;
+}
+
+static int qusb_phy_notify_disconnect(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+
+ qphy->cable_connected = false;
+
+ dev_dbg(phy->dev, "QUSB PHY: connect notification cable_connected=%d\n",
+ qphy->cable_connected);
+ return 0;
+}
+
+static int qusb_phy_dpdm_regulator_enable(struct regulator_dev *rdev)
+{
+ struct qusb_phy *qphy = rdev_get_drvdata(rdev);
+
+ dev_dbg(qphy->phy.dev, "%s\n", __func__);
+ return qusb_phy_update_dpdm(&qphy->phy, POWER_SUPPLY_DP_DM_DPF_DMF);
+}
+
+static int qusb_phy_dpdm_regulator_disable(struct regulator_dev *rdev)
+{
+ struct qusb_phy *qphy = rdev_get_drvdata(rdev);
+
+ dev_dbg(qphy->phy.dev, "%s\n", __func__);
+ return qusb_phy_update_dpdm(&qphy->phy, POWER_SUPPLY_DP_DM_DPR_DMR);
+}
+
+static int qusb_phy_dpdm_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct qusb_phy *qphy = rdev_get_drvdata(rdev);
+
+ dev_dbg(qphy->phy.dev, "%s qphy->rm_pulldown = %d\n", __func__,
+ qphy->rm_pulldown);
+ return qphy->rm_pulldown;
+}
+
+static struct regulator_ops qusb_phy_dpdm_regulator_ops = {
+ .enable = qusb_phy_dpdm_regulator_enable,
+ .disable = qusb_phy_dpdm_regulator_disable,
+ .is_enabled = qusb_phy_dpdm_regulator_is_enabled,
+};
+
+static int qusb_phy_regulator_init(struct qusb_phy *qphy)
+{
+ struct device *dev = qphy->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;
+ qphy->dpdm_rdesc.owner = THIS_MODULE;
+ qphy->dpdm_rdesc.type = REGULATOR_VOLTAGE;
+ qphy->dpdm_rdesc.ops = &qusb_phy_dpdm_regulator_ops;
+ qphy->dpdm_rdesc.name = kbasename(dev->of_node->full_name);
+
+ cfg.dev = dev;
+ cfg.init_data = init_data;
+ cfg.driver_data = qphy;
+ cfg.of_node = dev->of_node;
+
+ qphy->dpdm_rdev = devm_regulator_register(dev, &qphy->dpdm_rdesc, &cfg);
+ if (IS_ERR(qphy->dpdm_rdev))
+ return PTR_ERR(qphy->dpdm_rdev);
+
+ return 0;
+}
+
+static int qusb_phy_probe(struct platform_device *pdev)
+{
+ struct qusb_phy *qphy;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ int ret = 0, size = 0;
+
+ qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL);
+ if (!qphy)
+ return -ENOMEM;
+
+ qphy->phy.dev = dev;
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "qusb_phy_base");
+ qphy->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(qphy->base))
+ return PTR_ERR(qphy->base);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "emu_phy_base");
+ if (res) {
+ qphy->emu_phy_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(qphy->emu_phy_base)) {
+ dev_dbg(dev, "couldn't ioremap emu_phy_base\n");
+ qphy->emu_phy_base = NULL;
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "tcsr_clamp_dig_n_1p8");
+ if (res) {
+ qphy->tcsr_clamp_dig_n = devm_ioremap_resource(dev, res);
+ if (IS_ERR(qphy->tcsr_clamp_dig_n)) {
+ dev_dbg(dev, "couldn't ioremap tcsr_clamp_dig_n\n");
+ return PTR_ERR(qphy->tcsr_clamp_dig_n);
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "efuse_addr");
+ if (res) {
+ qphy->efuse_reg = devm_ioremap_nocache(dev, res->start,
+ resource_size(res));
+ if (!IS_ERR_OR_NULL(qphy->efuse_reg)) {
+ ret = of_property_read_u32(dev->of_node,
+ "qcom,efuse-bit-pos",
+ &qphy->efuse_bit_pos);
+ if (!ret) {
+ ret = of_property_read_u32(dev->of_node,
+ "qcom,efuse-num-bits",
+ &qphy->efuse_num_of_bits);
+ }
+
+ if (ret) {
+ dev_err(dev,
+ "DT Value for efuse is invalid.\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ qphy->ref_clk_src = devm_clk_get(dev, "ref_clk_src");
+ if (IS_ERR(qphy->ref_clk_src))
+ dev_dbg(dev, "clk get failed for ref_clk_src\n");
+
+ qphy->ref_clk = devm_clk_get(dev, "ref_clk");
+ if (IS_ERR(qphy->ref_clk))
+ dev_dbg(dev, "clk get failed for ref_clk\n");
+ else
+ clk_set_rate(qphy->ref_clk, 19200000);
+
+ if (of_property_match_string(pdev->dev.of_node,
+ "clock-names", "cfg_ahb_clk") >= 0) {
+ qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb_clk");
+ if (IS_ERR(qphy->cfg_ahb_clk)) {
+ ret = PTR_ERR(qphy->cfg_ahb_clk);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev,
+ "clk get failed for cfg_ahb_clk ret %d\n", ret);
+ return ret;
+ }
+ }
+
+ qphy->phy_reset = devm_reset_control_get(dev, "phy_reset");
+ if (IS_ERR(qphy->phy_reset))
+ return PTR_ERR(qphy->phy_reset);
+
+ qphy->emulation = of_property_read_bool(dev->of_node,
+ "qcom,emulation");
+
+ of_get_property(dev->of_node, "qcom,emu-init-seq", &size);
+ if (size) {
+ qphy->emu_init_seq = devm_kzalloc(dev,
+ size, GFP_KERNEL);
+ if (qphy->emu_init_seq) {
+ 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\n");
+ return -EINVAL;
+ }
+
+ of_property_read_u32_array(dev->of_node,
+ "qcom,emu-init-seq",
+ qphy->emu_init_seq,
+ qphy->emu_init_seq_len);
+ } else {
+ dev_dbg(dev,
+ "error allocating memory for emu_init_seq\n");
+ }
+ }
+
+ size = 0;
+ of_get_property(dev->of_node, "qcom,phy-pll-reset-seq", &size);
+ if (size) {
+ qphy->phy_pll_reset_seq = devm_kzalloc(dev,
+ size, GFP_KERNEL);
+ if (qphy->phy_pll_reset_seq) {
+ qphy->phy_pll_reset_seq_len =
+ (size / sizeof(*qphy->phy_pll_reset_seq));
+ if (qphy->phy_pll_reset_seq_len % 2) {
+ dev_err(dev, "invalid phy_pll_reset_seq_len\n");
+ return -EINVAL;
+ }
+
+ of_property_read_u32_array(dev->of_node,
+ "qcom,phy-pll-reset-seq",
+ qphy->phy_pll_reset_seq,
+ qphy->phy_pll_reset_seq_len);
+ } else {
+ dev_dbg(dev,
+ "error allocating memory for phy_pll_reset_seq\n");
+ }
+ }
+
+ size = 0;
+ of_get_property(dev->of_node, "qcom,emu-dcm-reset-seq", &size);
+ if (size) {
+ qphy->emu_dcm_reset_seq = devm_kzalloc(dev,
+ size, GFP_KERNEL);
+ if (qphy->emu_dcm_reset_seq) {
+ qphy->emu_dcm_reset_seq_len =
+ (size / sizeof(*qphy->emu_dcm_reset_seq));
+ if (qphy->emu_dcm_reset_seq_len % 2) {
+ dev_err(dev, "invalid emu_dcm_reset_seq_len\n");
+ return -EINVAL;
+ }
+
+ of_property_read_u32_array(dev->of_node,
+ "qcom,emu-dcm-reset-seq",
+ qphy->emu_dcm_reset_seq,
+ qphy->emu_dcm_reset_seq_len);
+ } else {
+ dev_dbg(dev,
+ "error allocating memory for emu_dcm_reset_seq\n");
+ }
+ }
+
+ size = 0;
+ of_get_property(dev->of_node, "qcom,qusb-phy-init-seq", &size);
+ if (size) {
+ qphy->qusb_phy_init_seq = devm_kzalloc(dev,
+ size, GFP_KERNEL);
+ if (qphy->qusb_phy_init_seq) {
+ qphy->init_seq_len =
+ (size / sizeof(*qphy->qusb_phy_init_seq));
+ if (qphy->init_seq_len % 2) {
+ dev_err(dev, "invalid init_seq_len\n");
+ return -EINVAL;
+ }
+
+ of_property_read_u32_array(dev->of_node,
+ "qcom,qusb-phy-init-seq",
+ qphy->qusb_phy_init_seq,
+ qphy->init_seq_len);
+ } else {
+ dev_err(dev,
+ "error allocating memory for phy_init_seq\n");
+ }
+ }
+
+ qphy->host_init_seq_len = of_property_count_elems_of_size(dev->of_node,
+ "qcom,qusb-phy-host-init-seq",
+ sizeof(*qphy->qusb_phy_host_init_seq));
+ if (qphy->host_init_seq_len > 0) {
+ qphy->qusb_phy_host_init_seq = devm_kcalloc(dev,
+ qphy->host_init_seq_len,
+ sizeof(*qphy->qusb_phy_host_init_seq),
+ GFP_KERNEL);
+ if (qphy->qusb_phy_host_init_seq)
+ of_property_read_u32_array(dev->of_node,
+ "qcom,qusb-phy-host-init-seq",
+ qphy->qusb_phy_host_init_seq,
+ qphy->host_init_seq_len);
+ else
+ return -ENOMEM;
+ }
+
+ ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level",
+ (u32 *) qphy->vdd_levels,
+ ARRAY_SIZE(qphy->vdd_levels));
+ if (ret) {
+ dev_err(dev, "error reading qcom,vdd-voltage-level property\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32_array(dev->of_node,
+ "qcom,vdda33-voltage-level",
+ (u32 *) qphy->vdda33_levels,
+ ARRAY_SIZE(qphy->vdda33_levels));
+ if (ret == -EINVAL) {
+ qphy->vdda33_levels[0] = QUSB2PHY_3P3_VOL_MIN;
+ qphy->vdda33_levels[1] = QUSB2PHY_3P3_VOL_MIN;
+ qphy->vdda33_levels[2] = QUSB2PHY_3P3_VOL_MAX;
+ } else if (ret) {
+ dev_err(dev, "error reading qcom,vdda33-voltage-level property\n");
+ return ret;
+ }
+
+ qphy->vdd = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(qphy->vdd)) {
+ dev_err(dev, "unable to get vdd supply\n");
+ return PTR_ERR(qphy->vdd);
+ }
+
+ qphy->vdda33 = devm_regulator_get(dev, "vdda33");
+ if (IS_ERR(qphy->vdda33)) {
+ dev_err(dev, "unable to get vdda33 supply\n");
+ return PTR_ERR(qphy->vdda33);
+ }
+
+ qphy->vdda18 = devm_regulator_get(dev, "vdda18");
+ if (IS_ERR(qphy->vdda18)) {
+ dev_err(dev, "unable to get vdda18 supply\n");
+ return PTR_ERR(qphy->vdda18);
+ }
+
+ qphy->vdda12 = devm_regulator_get(dev, "vdda12");
+ if (IS_ERR(qphy->vdda12)) {
+ dev_err(dev, "unable to get vdda12 supply\n");
+ return PTR_ERR(qphy->vdda12);
+ }
+
+ mutex_init(&qphy->lock);
+
+ platform_set_drvdata(pdev, qphy);
+
+ qphy->phy.label = "msm-qusb-phy-v2";
+ qphy->phy.init = qusb_phy_init;
+ qphy->phy.set_suspend = qusb_phy_set_suspend;
+ qphy->phy.shutdown = qusb_phy_shutdown;
+ qphy->phy.type = USB_PHY_TYPE_USB2;
+ qphy->phy.notify_connect = qusb_phy_notify_connect;
+ qphy->phy.notify_disconnect = qusb_phy_notify_disconnect;
+
+ ret = usb_add_phy_dev(&qphy->phy);
+ if (ret)
+ return ret;
+
+ ret = qusb_phy_regulator_init(qphy);
+ if (ret)
+ usb_remove_phy(&qphy->phy);
+
+ /* de-asseert clamp dig n to reduce leakage on 1p8 upon boot up */
+ writel_relaxed(0x0, qphy->tcsr_clamp_dig_n);
+
+ return ret;
+}
+
+static int qusb_phy_remove(struct platform_device *pdev)
+{
+ struct qusb_phy *qphy = platform_get_drvdata(pdev);
+
+ usb_remove_phy(&qphy->phy);
+
+ if (qphy->clocks_enabled) {
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ clk_disable_unprepare(qphy->ref_clk);
+ clk_disable_unprepare(qphy->ref_clk_src);
+ qphy->clocks_enabled = false;
+ }
+
+ qusb_phy_enable_power(qphy, false);
+
+ return 0;
+}
+
+static const struct of_device_id qusb_phy_id_table[] = {
+ { .compatible = "qcom,qusb2phy-v2", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, qusb_phy_id_table);
+
+static struct platform_driver qusb_phy_driver = {
+ .probe = qusb_phy_probe,
+ .remove = qusb_phy_remove,
+ .driver = {
+ .name = "msm-qusb-phy-v2",
+ .of_match_table = of_match_ptr(qusb_phy_id_table),
+ },
+};
+
+module_platform_driver(qusb_phy_driver);
+
+MODULE_DESCRIPTION("MSM QUSB2 PHY v2 driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/phy/phy-msm-qusb.c b/drivers/usb/phy/phy-msm-qusb.c
new file mode 100644
index 000000000000..1b09c028d098
--- /dev/null
+++ b/drivers/usb/phy/phy-msm-qusb.c
@@ -0,0 +1,1543 @@
+/*
+ * Copyright (c) 2014-2017,2019 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/clk/msm-clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/usb/phy.h>
+#include <linux/usb/msm_hsusb.h>
+#include <linux/reset.h>
+
+#define QUSB2PHY_PLL_PWR_CTL 0x18
+#define REF_BUF_EN BIT(0)
+#define REXT_EN BIT(1)
+#define PLL_BYPASSNL BIT(2)
+#define REXT_TRIM_0 BIT(4)
+
+#define QUSB2PHY_PLL_AUTOPGM_CTL1 0x1C
+#define PLL_RESET_N_CNT_5 0x5
+#define PLL_RESET_N BIT(4)
+#define PLL_AUTOPGM_EN BIT(7)
+
+#define QUSB2PHY_PLL_STATUS 0x38
+#define QUSB2PHY_PLL_LOCK BIT(5)
+
+#define QUSB2PHY_PORT_QC1 0x70
+#define VDM_SRC_EN BIT(4)
+#define IDP_SRC_EN BIT(3)
+#define VDP_SRC_EN BIT(2)
+
+#define QUSB2PHY_PORT_QC2 0x74
+#define RDM_UP_EN BIT(1)
+#define RDP_UP_EN BIT(3)
+#define RPUM_LOW_EN BIT(4)
+#define RPUP_LOW_EN BIT(5)
+
+#define QUSB2PHY_PORT_POWERDOWN 0xB4
+#define CLAMP_N_EN BIT(5)
+#define FREEZIO_N BIT(1)
+#define POWER_DOWN BIT(0)
+
+#define QUSB2PHY_PORT_TEST_CTRL 0xB8
+
+#define QUSB2PHY_PWR_CTRL1 0x210
+#define PWR_CTRL1_CLAMP_N_EN BIT(1)
+#define PWR_CTRL1_POWR_DOWN BIT(0)
+
+#define QUSB2PHY_PLL_COMMON_STATUS_ONE 0x1A0
+#define CORE_READY_STATUS BIT(0)
+
+#define QUSB2PHY_PORT_UTMI_CTRL1 0xC0
+#define SUSPEND_N BIT(5)
+#define TERM_SELECT BIT(4)
+#define XCVR_SELECT_FS BIT(2)
+#define OP_MODE_NON_DRIVE BIT(0)
+
+#define QUSB2PHY_PORT_UTMI_CTRL2 0xC4
+#define UTMI_ULPI_SEL BIT(7)
+#define UTMI_TEST_MUX_SEL BIT(6)
+
+#define QUSB2PHY_PLL_TEST 0x04
+#define CLK_REF_SEL BIT(7)
+
+#define QUSB2PHY_PORT_TUNE1 0x80
+#define QUSB2PHY_PORT_TUNE2 0x84
+#define QUSB2PHY_PORT_TUNE3 0x88
+#define QUSB2PHY_PORT_TUNE4 0x8C
+#define QUSB2PHY_PORT_TUNE5 0x90
+
+/* Get TUNE2's high nibble value read from efuse */
+#define TUNE2_HIGH_NIBBLE_VAL(val, pos, mask) ((val >> pos) & mask)
+
+#define QUSB2PHY_PORT_INTR_CTRL 0xBC
+#define CHG_DET_INTR_EN BIT(4)
+#define DMSE_INTR_HIGH_SEL BIT(3)
+#define DMSE_INTR_EN BIT(2)
+#define DPSE_INTR_HIGH_SEL BIT(1)
+#define DPSE_INTR_EN BIT(0)
+
+#define QUSB2PHY_PORT_INT_STATUS 0xF0
+#define QUSB2PHY_PORT_UTMI_STATUS 0xF4
+#define LINESTATE_DP BIT(0)
+#define LINESTATE_DM BIT(1)
+
+
+#define QUSB2PHY_1P8_VOL_MIN 1800000 /* uV */
+#define QUSB2PHY_1P8_VOL_MAX 1800000 /* uV */
+#define QUSB2PHY_1P8_HPM_LOAD 30000 /* uA */
+
+#define QUSB2PHY_3P3_VOL_MIN 3075000 /* uV */
+#define QUSB2PHY_3P3_VOL_MAX 3200000 /* uV */
+#define QUSB2PHY_3P3_HPM_LOAD 30000 /* uA */
+
+#define QUSB2PHY_REFCLK_ENABLE BIT(0)
+
+unsigned int tune1;
+module_param(tune1, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(tune1, "QUSB PHY TUNE1");
+
+unsigned int tune2;
+module_param(tune2, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(tune2, "QUSB PHY TUNE2");
+
+unsigned int tune3;
+module_param(tune3, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(tune3, "QUSB PHY TUNE3");
+
+unsigned int tune4;
+module_param(tune4, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(tune4, "QUSB PHY TUNE4");
+
+unsigned int tune5;
+module_param(tune5, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(tune5, "QUSB PHY TUNE5");
+
+
+struct qusb_phy {
+ struct usb_phy phy;
+ void __iomem *base;
+ void __iomem *tune2_efuse_reg;
+ void __iomem *ref_clk_base;
+ void __iomem *tcsr_clamp_dig_n;
+
+ struct clk *ref_clk_src;
+ struct clk *ref_clk;
+ 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 */
+ int init_seq_len;
+ int *qusb_phy_init_seq;
+ u32 major_rev;
+
+ u32 tune2_val;
+ int tune2_efuse_bit_pos;
+ int tune2_efuse_num_of_bits;
+ int tune2_efuse_correction;
+
+ bool power_enabled;
+ bool clocks_enabled;
+ bool cable_connected;
+ bool suspended;
+ bool ulpi_mode;
+ bool rm_pulldown;
+ bool is_se_clk;
+
+ struct regulator_desc dpdm_rdesc;
+ struct regulator_dev *dpdm_rdev;
+
+ bool dpdm_pulsing_enabled;
+ struct power_supply *dpdm_psy;
+ struct power_supply_desc dpdm_psy_desc;
+
+ /* emulation targets specific */
+ void __iomem *emu_phy_base;
+ bool emulation;
+ int *emu_init_seq;
+ int emu_init_seq_len;
+ int *phy_pll_reset_seq;
+ int phy_pll_reset_seq_len;
+ int *emu_dcm_reset_seq;
+ int emu_dcm_reset_seq_len;
+ bool put_into_high_z_state;
+ struct mutex phy_lock;
+ spinlock_t pulse_lock;
+};
+
+static enum power_supply_property dpdm_props[] = {
+ POWER_SUPPLY_PROP_DP_DM,
+};
+
+static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on)
+{
+ dev_dbg(qphy->phy.dev, "%s(): clocks_enabled:%d on:%d\n",
+ __func__, qphy->clocks_enabled, on);
+
+ if (!qphy->clocks_enabled && on) {
+ clk_prepare_enable(qphy->ref_clk_src);
+ clk_prepare_enable(qphy->ref_clk);
+ clk_prepare_enable(qphy->cfg_ahb_clk);
+ qphy->clocks_enabled = true;
+ }
+
+ if (qphy->clocks_enabled && !on) {
+ clk_disable_unprepare(qphy->ref_clk);
+ clk_disable_unprepare(qphy->ref_clk_src);
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ qphy->clocks_enabled = false;
+ }
+
+ dev_dbg(qphy->phy.dev, "%s(): clocks_enabled:%d\n", __func__,
+ qphy->clocks_enabled);
+}
+
+static int qusb_phy_config_vdd(struct qusb_phy *qphy, int high)
+{
+ int min, ret;
+
+ min = high ? 1 : 0; /* low or none? */
+ ret = regulator_set_voltage(qphy->vdd, qphy->vdd_levels[min],
+ qphy->vdd_levels[2]);
+ if (ret) {
+ dev_err(qphy->phy.dev, "unable to set voltage for qusb vdd\n");
+ return ret;
+ }
+
+ dev_dbg(qphy->phy.dev, "min_vol:%d max_vol:%d\n",
+ qphy->vdd_levels[min], qphy->vdd_levels[2]);
+ return ret;
+}
+
+static int qusb_phy_enable_power(struct qusb_phy *qphy, bool on)
+{
+ int ret = 0;
+
+ dev_dbg(qphy->phy.dev, "%s turn %s regulators. power_enabled:%d\n",
+ __func__, on ? "on" : "off", qphy->power_enabled);
+
+ if (qphy->power_enabled == on) {
+ dev_dbg(qphy->phy.dev, "PHYs' regulators are already ON.\n");
+ return 0;
+ }
+
+ if (!on)
+ goto disable_vdda33;
+
+ ret = qusb_phy_config_vdd(qphy, true);
+ if (ret) {
+ dev_err(qphy->phy.dev, "Unable to config VDD:%d\n",
+ ret);
+ goto err_vdd;
+ }
+
+ ret = regulator_enable(qphy->vdd);
+ if (ret) {
+ dev_err(qphy->phy.dev, "Unable to enable VDD\n");
+ goto unconfig_vdd;
+ }
+
+ ret = regulator_set_load(qphy->vdda18, QUSB2PHY_1P8_HPM_LOAD);
+ if (ret < 0) {
+ dev_err(qphy->phy.dev, "Unable to set HPM of vdda18:%d\n", ret);
+ goto disable_vdd;
+ }
+
+ ret = regulator_set_voltage(qphy->vdda18, QUSB2PHY_1P8_VOL_MIN,
+ QUSB2PHY_1P8_VOL_MAX);
+ if (ret) {
+ dev_err(qphy->phy.dev,
+ "Unable to set voltage for vdda18:%d\n", ret);
+ goto put_vdda18_lpm;
+ }
+
+ ret = regulator_enable(qphy->vdda18);
+ if (ret) {
+ dev_err(qphy->phy.dev, "Unable to enable vdda18:%d\n", ret);
+ goto unset_vdda18;
+ }
+
+ ret = regulator_set_load(qphy->vdda33, QUSB2PHY_3P3_HPM_LOAD);
+ if (ret < 0) {
+ dev_err(qphy->phy.dev, "Unable to set HPM of vdda33:%d\n", ret);
+ goto disable_vdda18;
+ }
+
+ ret = regulator_set_voltage(qphy->vdda33, QUSB2PHY_3P3_VOL_MIN,
+ QUSB2PHY_3P3_VOL_MAX);
+ if (ret) {
+ dev_err(qphy->phy.dev,
+ "Unable to set voltage for vdda33:%d\n", ret);
+ goto put_vdda33_lpm;
+ }
+
+ ret = regulator_enable(qphy->vdda33);
+ if (ret) {
+ dev_err(qphy->phy.dev, "Unable to enable vdda33:%d\n", ret);
+ goto unset_vdd33;
+ }
+
+ qphy->power_enabled = true;
+
+ pr_debug("%s(): QUSB PHY's regulators are turned ON.\n", __func__);
+ return ret;
+
+disable_vdda33:
+ ret = regulator_disable(qphy->vdda33);
+ if (ret)
+ dev_err(qphy->phy.dev, "Unable to disable vdda33:%d\n", ret);
+
+unset_vdd33:
+ ret = regulator_set_voltage(qphy->vdda33, 0, QUSB2PHY_3P3_VOL_MAX);
+ if (ret)
+ dev_err(qphy->phy.dev,
+ "Unable to set (0) voltage for vdda33:%d\n", ret);
+
+put_vdda33_lpm:
+ ret = regulator_set_load(qphy->vdda33, 0);
+ if (ret < 0)
+ dev_err(qphy->phy.dev, "Unable to set (0) HPM of vdda33\n");
+
+disable_vdda18:
+ ret = regulator_disable(qphy->vdda18);
+ if (ret)
+ dev_err(qphy->phy.dev, "Unable to disable vdda18:%d\n", ret);
+
+unset_vdda18:
+ ret = regulator_set_voltage(qphy->vdda18, 0, QUSB2PHY_1P8_VOL_MAX);
+ if (ret)
+ dev_err(qphy->phy.dev,
+ "Unable to set (0) voltage for vdda18:%d\n", ret);
+
+put_vdda18_lpm:
+ ret = regulator_set_load(qphy->vdda18, 0);
+ if (ret < 0)
+ dev_err(qphy->phy.dev, "Unable to set LPM of vdda18\n");
+
+disable_vdd:
+ ret = regulator_disable(qphy->vdd);
+ if (ret)
+ dev_err(qphy->phy.dev, "Unable to disable vdd:%d\n",
+ ret);
+
+unconfig_vdd:
+ ret = qusb_phy_config_vdd(qphy, false);
+ if (ret)
+ dev_err(qphy->phy.dev, "Unable unconfig VDD:%d\n",
+ ret);
+err_vdd:
+ qphy->power_enabled = false;
+ dev_dbg(qphy->phy.dev, "QUSB PHY's regulators are turned OFF.\n");
+ return ret;
+}
+
+#define PHY_PULSE_TIME_USEC 250
+static int qusb_phy_update_dpdm(struct usb_phy *phy, int value)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+ int ret = 0;
+ unsigned long flags;
+ u32 reg;
+
+ dev_dbg(phy->dev, "%s value:%d rm_pulldown:%d\n",
+ __func__, value, qphy->rm_pulldown);
+
+ switch (value) {
+ case POWER_SUPPLY_DP_DM_DPF_DMF:
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DPF_DMF\n");
+ mutex_lock(&qphy->phy_lock);
+ if (!qphy->rm_pulldown) {
+ ret = qusb_phy_enable_power(qphy, true);
+ if (ret >= 0) {
+ qphy->rm_pulldown = true;
+ dev_dbg(phy->dev, "DP_DM_F: rm_pulldown:%d\n",
+ qphy->rm_pulldown);
+ }
+
+ if (qphy->put_into_high_z_state) {
+ if (qphy->tcsr_clamp_dig_n)
+ writel_relaxed(0x1,
+ qphy->tcsr_clamp_dig_n);
+
+ qusb_phy_enable_clocks(qphy, true);
+
+ dev_dbg(phy->dev, "RESET QUSB PHY\n");
+ ret = reset_control_assert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "phyassert failed\n");
+ usleep_range(100, 150);
+ ret = reset_control_deassert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "deassert failed\n");
+
+ /*
+ * Phy in non-driving mode leaves Dp and Dm
+ * lines in high-Z state. Controller power
+ * collapse is not switching phy to non-driving
+ * mode causing charger detection failure. Bring
+ * phy to non-driving mode by overriding
+ * controller output via UTMI interface.
+ */
+ writel_relaxed(TERM_SELECT | XCVR_SELECT_FS |
+ OP_MODE_NON_DRIVE,
+ qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
+ writel_relaxed(UTMI_ULPI_SEL |
+ UTMI_TEST_MUX_SEL,
+ qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);
+
+ /* Disable PHY */
+ writel_relaxed(CLAMP_N_EN | FREEZIO_N |
+ POWER_DOWN,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+ /* Make sure that above write is completed */
+ wmb();
+
+ if (qphy->suspended)
+ qusb_phy_enable_clocks(qphy, false);
+ }
+ }
+
+ /* Clear QC1 and QC2 registers when rm_pulldown = 1 */
+ if (qphy->dpdm_pulsing_enabled && qphy->rm_pulldown) {
+ dev_dbg(phy->dev, "clearing qc1 and qc2 registers.\n");
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ /* Clear qc1 and qc2 registers */
+ writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC1);
+ writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC2);
+ /* to make sure above write goes through */
+ mb();
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ }
+ mutex_unlock(&qphy->phy_lock);
+
+ break;
+
+ case POWER_SUPPLY_DP_DM_DPR_DMR:
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DPR_DMR\n");
+ mutex_lock(&qphy->phy_lock);
+ if (qphy->rm_pulldown) {
+ dev_dbg(phy->dev, "clearing qc1 and qc2 registers.\n");
+ if (qphy->dpdm_pulsing_enabled) {
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ /* Clear qc1 and qc2 registers */
+ writel_relaxed(0x00,
+ qphy->base + QUSB2PHY_PORT_QC1);
+ writel_relaxed(0x00,
+ qphy->base + QUSB2PHY_PORT_QC2);
+ /* to make sure above write goes through */
+ mb();
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ }
+
+ if (!qphy->cable_connected) {
+ if (qphy->tcsr_clamp_dig_n)
+ writel_relaxed(0x0,
+ qphy->tcsr_clamp_dig_n);
+ dev_dbg(phy->dev, "turn off for HVDCP case\n");
+ ret = qusb_phy_enable_power(qphy, false);
+ }
+ if (ret >= 0) {
+ qphy->rm_pulldown = false;
+ dev_dbg(phy->dev, "DP_DM_R: rm_pulldown:%d\n",
+ qphy->rm_pulldown);
+ }
+ }
+ mutex_unlock(&qphy->phy_lock);
+ break;
+
+ case POWER_SUPPLY_DP_DM_DP0P6_DMF:
+ if (!qphy->dpdm_pulsing_enabled)
+ break;
+
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DP0P6_DMF\n");
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ /* Set DP to 0.6v and DM to High Z state */
+ writel_relaxed(VDP_SRC_EN, qphy->base + QUSB2PHY_PORT_QC1);
+ /* complete above write */
+ mb();
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ break;
+
+ case POWER_SUPPLY_DP_DM_DP0P6_DM3P3:
+ if (!qphy->dpdm_pulsing_enabled)
+ break;
+
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DP0PHVDCP_36_DM3P3\n");
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ /* Set DP to 0.6v */
+ writel_relaxed(VDP_SRC_EN, qphy->base + QUSB2PHY_PORT_QC1);
+ /* Set DM to 3.075v */
+ writel_relaxed(RPUM_LOW_EN | RDM_UP_EN,
+ qphy->base + QUSB2PHY_PORT_QC2);
+ /* complete above write */
+ mb();
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ break;
+
+ case POWER_SUPPLY_DP_DM_DP_PULSE:
+ if (!qphy->dpdm_pulsing_enabled)
+ break;
+
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DP_PULSE\n");
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ spin_lock_irqsave(&qphy->pulse_lock, flags);
+ /*Set DP to 3.075v, sleep for .25 ms */
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC2);
+ reg |= (RDP_UP_EN | RPUP_LOW_EN);
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC2);
+
+ /* complete above write */
+ mb();
+
+ /*
+ * It is recommended to wait here to get voltage change on
+ * DP/DM line.
+ */
+ udelay(PHY_PULSE_TIME_USEC);
+
+ /* Set DP to 0.6v, sleep 2-3ms */
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC1);
+ reg |= VDP_SRC_EN;
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC1);
+
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC2);
+ reg &= ~(RDP_UP_EN | RPUP_LOW_EN);
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC2);
+ /* complete above write */
+ mb();
+ spin_unlock_irqrestore(&qphy->pulse_lock, flags);
+ /*
+ * It is recommended to wait here to get voltage change on
+ * DP/DM line.
+ */
+ usleep_range(2000, 3000);
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ break;
+
+ case POWER_SUPPLY_DP_DM_DM_PULSE:
+ if (!qphy->dpdm_pulsing_enabled)
+ break;
+
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DM_PULSE\n");
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ spin_lock_irqsave(&qphy->pulse_lock, flags);
+ /* Set DM to 0.6v, sleep .25 ms */
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC1);
+ reg |= VDM_SRC_EN;
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC1);
+
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC2);
+ reg &= ~(RDM_UP_EN | RPUM_LOW_EN);
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC2);
+
+ /* complete above write */
+ mb();
+
+ /*
+ * It is recommended to wait here to get voltage change on
+ * DP/DM line.
+ */
+ udelay(PHY_PULSE_TIME_USEC);
+
+ /* DM to 3.075v, sleep 2-3ms */
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC2);
+ reg |= (RPUM_LOW_EN | RDM_UP_EN);
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC2);
+
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC1);
+ reg &= ~VDM_SRC_EN;
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC1);
+
+ /* complete above write */
+ mb();
+ spin_unlock_irqrestore(&qphy->pulse_lock, flags);
+
+ /*
+ * It is recommended to wait here to get voltage change on
+ * DP/DM line.
+ */
+ usleep_range(2000, 3000);
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ break;
+ default:
+ ret = -EINVAL;
+ dev_err(phy->dev, "Invalid power supply property(%d)\n", value);
+ break;
+ }
+
+clk_error:
+ return ret;
+}
+
+static int qusb_phy_get_property_usb(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ return -EINVAL;
+}
+
+static int qusb_phy_set_property_usb(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct qusb_phy *qphy = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_DP_DM:
+ ret = qusb_phy_update_dpdm(&qphy->phy, val->intval);
+ if (ret) {
+ dev_dbg(qphy->phy.dev, "error in dpdm update: %d\n",
+ ret);
+ return ret;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void qusb_phy_get_tune2_param(struct qusb_phy *qphy)
+{
+ u8 num_of_bits;
+ u32 bit_mask = 1;
+ u8 reg_val;
+
+ pr_debug("%s(): num_of_bits:%d bit_pos:%d\n", __func__,
+ qphy->tune2_efuse_num_of_bits,
+ qphy->tune2_efuse_bit_pos);
+
+ /* get bit mask based on number of bits to use with efuse reg */
+ if (qphy->tune2_efuse_num_of_bits) {
+ num_of_bits = qphy->tune2_efuse_num_of_bits;
+ bit_mask = (bit_mask << num_of_bits) - 1;
+ }
+
+ /*
+ * Read EFUSE register having TUNE2 parameter's high nibble.
+ * If efuse register shows value as 0x0, then use previous value
+ * as it is. Otherwise use efuse register based value for this purpose.
+ */
+ qphy->tune2_val = readl_relaxed(qphy->tune2_efuse_reg);
+ pr_debug("%s(): bit_mask:%d efuse based tune2 value:%d\n",
+ __func__, bit_mask, qphy->tune2_val);
+
+ qphy->tune2_val = TUNE2_HIGH_NIBBLE_VAL(qphy->tune2_val,
+ qphy->tune2_efuse_bit_pos, bit_mask);
+
+ /* Update higher nibble of TUNE2 value for better rise/fall times */
+ if (qphy->tune2_efuse_correction && qphy->tune2_val) {
+ if (qphy->tune2_efuse_correction > 5 ||
+ qphy->tune2_efuse_correction < -10)
+ pr_warn("Correction value is out of range : %d\n",
+ qphy->tune2_efuse_correction);
+ else
+ qphy->tune2_val = qphy->tune2_val +
+ qphy->tune2_efuse_correction;
+ }
+
+ reg_val = readb_relaxed(qphy->base + QUSB2PHY_PORT_TUNE2);
+ if (qphy->tune2_val) {
+ reg_val &= 0x0f;
+ reg_val |= (qphy->tune2_val << 4);
+ }
+
+ qphy->tune2_val = reg_val;
+}
+
+static void qusb_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 qusb_phy_init(struct usb_phy *phy)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+ int ret, reset_val = 0;
+ u8 reg;
+ bool pll_lock_fail = false;
+
+ dev_dbg(phy->dev, "%s\n", __func__);
+
+ ret = qusb_phy_enable_power(qphy, true);
+ if (ret)
+ return ret;
+
+ qusb_phy_enable_clocks(qphy, true);
+
+ /*
+ * ref clock is enabled by default after power on reset. Linux clock
+ * driver will disable this clock as part of late init if peripheral
+ * driver(s) does not explicitly votes for it. Linux clock driver also
+ * does not disable the clock until late init even if peripheral
+ * driver explicitly requests it and cannot defer the probe until late
+ * init. Hence, Explicitly disable the clock using register write to
+ * allow QUSB PHY PLL to lock properly.
+ */
+ if (qphy->ref_clk_base) {
+ writel_relaxed((readl_relaxed(qphy->ref_clk_base) &
+ ~QUSB2PHY_REFCLK_ENABLE),
+ qphy->ref_clk_base);
+ /* Make sure that above write complete to get ref clk OFF */
+ wmb();
+ }
+
+ /* Perform phy reset */
+ ret = reset_control_assert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "%s: phy_reset assert failed\n", __func__);
+ usleep_range(100, 150);
+ ret = reset_control_deassert(qphy->phy_reset);
+ if (ret)
+ dev_err(phy->dev, "%s: phy_reset deassert failed\n", __func__);
+
+ if (qphy->emulation) {
+ if (qphy->emu_init_seq)
+ qusb_phy_write_seq(qphy->emu_phy_base,
+ qphy->emu_init_seq, qphy->emu_init_seq_len, 0);
+
+ if (qphy->qusb_phy_init_seq)
+ qusb_phy_write_seq(qphy->base, qphy->qusb_phy_init_seq,
+ qphy->init_seq_len, 0);
+
+ /* Wait for 5ms as per QUSB2 RUMI sequence */
+ usleep_range(5000, 7000);
+
+ if (qphy->phy_pll_reset_seq)
+ qusb_phy_write_seq(qphy->base, qphy->phy_pll_reset_seq,
+ qphy->phy_pll_reset_seq_len, 10000);
+
+ if (qphy->emu_dcm_reset_seq)
+ qusb_phy_write_seq(qphy->emu_phy_base,
+ qphy->emu_dcm_reset_seq,
+ qphy->emu_dcm_reset_seq_len, 10000);
+
+ return 0;
+ }
+
+ /* Disable the PHY */
+ if (qphy->major_rev < 2)
+ writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+ else
+ writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) |
+ PWR_CTRL1_POWR_DOWN,
+ qphy->base + QUSB2PHY_PWR_CTRL1);
+
+ /* configure for ULPI mode if requested */
+ if (qphy->ulpi_mode)
+ writel_relaxed(0x0, qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);
+
+ /* save reset value to override based on clk scheme */
+ if (qphy->ref_clk_base)
+ reset_val = readl_relaxed(qphy->base + QUSB2PHY_PLL_TEST);
+
+ if (qphy->qusb_phy_init_seq)
+ qusb_phy_write_seq(qphy->base, qphy->qusb_phy_init_seq,
+ qphy->init_seq_len, 0);
+
+ /*
+ * Check for EFUSE value only if tune2_efuse_reg is available
+ * and try to read EFUSE value only once i.e. not every USB
+ * cable connect case.
+ */
+ if (qphy->tune2_efuse_reg && !tune2) {
+ if (!qphy->tune2_val)
+ qusb_phy_get_tune2_param(qphy);
+
+ pr_debug("%s(): Programming TUNE2 parameter as:%x\n", __func__,
+ qphy->tune2_val);
+ writel_relaxed(qphy->tune2_val,
+ qphy->base + QUSB2PHY_PORT_TUNE2);
+ }
+
+ /* If tune modparam set, override tune value */
+
+ pr_debug("%s():userspecified modparams TUNEX val:0x%x %x %x %x %x\n",
+ __func__, tune1, tune2, tune3, tune4, tune5);
+ if (tune1)
+ writel_relaxed(tune1,
+ qphy->base + QUSB2PHY_PORT_TUNE1);
+
+ if (tune2)
+ writel_relaxed(tune2,
+ qphy->base + QUSB2PHY_PORT_TUNE2);
+
+ if (tune3)
+ writel_relaxed(tune3,
+ qphy->base + QUSB2PHY_PORT_TUNE3);
+
+ if (tune4)
+ writel_relaxed(tune4,
+ qphy->base + QUSB2PHY_PORT_TUNE4);
+
+ if (tune5)
+ writel_relaxed(tune5,
+ qphy->base + QUSB2PHY_PORT_TUNE5);
+
+ /* ensure above writes are completed before re-enabling PHY */
+ wmb();
+
+ /* Enable the PHY */
+ if (qphy->major_rev < 2)
+ writel_relaxed(CLAMP_N_EN | FREEZIO_N,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+ else
+ writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) &
+ ~PWR_CTRL1_POWR_DOWN,
+ qphy->base + QUSB2PHY_PWR_CTRL1);
+
+ /* Ensure above write is completed before turning ON ref clk */
+ wmb();
+
+ /* Require to get phy pll lock successfully */
+ usleep_range(150, 160);
+
+ /* Turn on phy ref_clk if DIFF_CLK else select SE_CLK */
+ if (qphy->ref_clk_base) {
+ if (!qphy->is_se_clk) {
+ reset_val &= ~CLK_REF_SEL;
+ writel_relaxed((readl_relaxed(qphy->ref_clk_base) |
+ QUSB2PHY_REFCLK_ENABLE),
+ qphy->ref_clk_base);
+ } else {
+ reset_val |= CLK_REF_SEL;
+ writel_relaxed(reset_val,
+ qphy->base + QUSB2PHY_PLL_TEST);
+ }
+
+ /* Make sure above write is completed to get PLL source clock */
+ wmb();
+
+ /* Required to get PHY PLL lock successfully */
+ usleep_range(50000, 51000);
+ }
+
+ if (qphy->major_rev < 2) {
+ reg = readb_relaxed(qphy->base + QUSB2PHY_PLL_STATUS);
+ dev_dbg(phy->dev, "QUSB2PHY_PLL_STATUS:%x\n", reg);
+ if (!(reg & QUSB2PHY_PLL_LOCK))
+ pll_lock_fail = true;
+ } else {
+ reg = readb_relaxed(qphy->base +
+ QUSB2PHY_PLL_COMMON_STATUS_ONE);
+ dev_dbg(phy->dev, "QUSB2PHY_PLL_COMMON_STATUS_ONE:%x\n", reg);
+ if (!(reg & CORE_READY_STATUS))
+ pll_lock_fail = true;
+ }
+
+ if (pll_lock_fail) {
+ dev_err(phy->dev, "QUSB PHY PLL LOCK fails:%x\n", reg);
+ WARN_ON(1);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static void qusb_phy_shutdown(struct usb_phy *phy)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+
+ dev_dbg(phy->dev, "%s\n", __func__);
+
+ qusb_phy_enable_clocks(qphy, true);
+
+ /* Disable the PHY */
+ if (qphy->major_rev < 2)
+ writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+ else
+ writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) |
+ PWR_CTRL1_POWR_DOWN,
+ qphy->base + QUSB2PHY_PWR_CTRL1);
+ wmb();
+
+ qusb_phy_enable_clocks(qphy, false);
+}
+
+/**
+ * Returns DP/DM linestate with Idp_src enabled to detect if lines are floating
+ *
+ * @uphy - usb phy pointer.
+ *
+ */
+static int qusb_phy_linestate_with_idp_src(struct usb_phy *phy)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+ u8 int_status, ret;
+
+ /* Disable/powerdown the PHY */
+ writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+
+ /* Put PHY in non-driving mode */
+ writel_relaxed(TERM_SELECT | XCVR_SELECT_FS | OP_MODE_NON_DRIVE |
+ SUSPEND_N, qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
+
+ /* Switch PHY to utmi register mode */
+ writel_relaxed(UTMI_ULPI_SEL | UTMI_TEST_MUX_SEL,
+ qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);
+
+ writel_relaxed(PLL_RESET_N_CNT_5,
+ qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
+
+ /* Enable PHY */
+ writel_relaxed(CLAMP_N_EN | FREEZIO_N,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+
+ writel_relaxed(REF_BUF_EN | REXT_EN | PLL_BYPASSNL | REXT_TRIM_0,
+ qphy->base + QUSB2PHY_PLL_PWR_CTL);
+
+ usleep_range(5, 1000);
+
+ writel_relaxed(PLL_RESET_N | PLL_RESET_N_CNT_5,
+ qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
+ usleep_range(50, 1000);
+
+ writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC1);
+ writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC2);
+
+ /* Enable all chg_det events from PHY */
+ writel_relaxed(0x1F, qphy->base + QUSB2PHY_PORT_INTR_CTRL);
+ /* Enable Idp_src */
+ writel_relaxed(IDP_SRC_EN, qphy->base + QUSB2PHY_PORT_QC1);
+
+ usleep_range(1000, 2000);
+ int_status = readl_relaxed(qphy->base + QUSB2PHY_PORT_INT_STATUS);
+
+ /* Exit chg_det mode, set PHY regs to default values */
+ writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN); /* 23 */
+
+ writel_relaxed(PLL_AUTOPGM_EN | PLL_RESET_N | PLL_RESET_N_CNT_5,
+ qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
+
+ writel_relaxed(UTMI_ULPI_SEL, qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);
+
+ writel_relaxed(TERM_SELECT, qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
+
+ writel_relaxed(CLAMP_N_EN | FREEZIO_N,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+
+ int_status = int_status & 0x5;
+
+ /*
+ * int_status's Bit(0) is DP and Bit(2) is DM.
+ * Caller expects bit(1) as DP and bit(0) DM i.e. usual linestate format
+ */
+ ret = (int_status >> 2) | ((int_status & 0x1) << 1);
+ pr_debug("%s: int_status:%x, dpdm:%x\n", __func__, int_status, ret);
+
+ return ret;
+}
+
+/**
+ * Performs QUSB2 PHY suspend/resume functionality.
+ *
+ * @uphy - usb phy pointer.
+ * @suspend - to enable suspend or not. 1 - suspend, 0 - resume
+ *
+ */
+static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+ u32 linestate = 0, intr_mask = 0;
+
+ if (qphy->suspended && suspend) {
+ dev_dbg(phy->dev, "%s: USB PHY is already suspended\n",
+ __func__);
+ return 0;
+ }
+
+ if (suspend) {
+ /* Bus suspend case */
+ if (qphy->cable_connected ||
+ (qphy->phy.flags & PHY_HOST_MODE)) {
+ /* Clear all interrupts */
+ writel_relaxed(0x00,
+ qphy->base + QUSB2PHY_PORT_INTR_CTRL);
+
+ linestate = readl_relaxed(qphy->base +
+ QUSB2PHY_PORT_UTMI_STATUS);
+
+ /*
+ * D+/D- interrupts are level-triggered, but we are
+ * only interested if the line state changes, so enable
+ * the high/low trigger based on current state. In
+ * other words, enable the triggers _opposite_ of what
+ * the current D+/D- levels are.
+ * e.g. if currently D+ high, D- low (HS 'J'/Suspend),
+ * configure the mask to trigger on D+ low OR D- high
+ */
+ intr_mask = DPSE_INTR_EN | DMSE_INTR_EN;
+ if (!(linestate & LINESTATE_DP)) /* D+ low */
+ intr_mask |= DPSE_INTR_HIGH_SEL;
+ if (!(linestate & LINESTATE_DM)) /* D- low */
+ intr_mask |= DMSE_INTR_HIGH_SEL;
+
+ writel_relaxed(intr_mask,
+ qphy->base + QUSB2PHY_PORT_INTR_CTRL);
+
+ if (linestate & (LINESTATE_DP | LINESTATE_DM)) {
+ /* enable phy auto-resume */
+ writel_relaxed(0x0C,
+ qphy->base + QUSB2PHY_PORT_TEST_CTRL);
+ /* flush the previous write before next write */
+ wmb();
+ writel_relaxed(0x04,
+ qphy->base + QUSB2PHY_PORT_TEST_CTRL);
+ }
+
+
+ dev_dbg(phy->dev, "%s: intr_mask = %x\n",
+ __func__, intr_mask);
+
+ /* Makes sure that above write goes through */
+ wmb();
+
+ qusb_phy_enable_clocks(qphy, false);
+ } else { /* Disconnect case */
+ mutex_lock(&qphy->phy_lock);
+ /* Disable all interrupts */
+ writel_relaxed(0x00,
+ qphy->base + QUSB2PHY_PORT_INTR_CTRL);
+
+ /* Disable PHY */
+ writel_relaxed(POWER_DOWN,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+ /* Make sure that above write is completed */
+ wmb();
+
+ qusb_phy_enable_clocks(qphy, false);
+ if (qphy->tcsr_clamp_dig_n)
+ writel_relaxed(0x0,
+ qphy->tcsr_clamp_dig_n);
+ /* Do not disable power rails if there is vote for it */
+ if (!qphy->rm_pulldown)
+ qusb_phy_enable_power(qphy, false);
+ else
+ dev_dbg(phy->dev, "race with rm_pulldown. Keep ldo ON\n");
+ mutex_unlock(&qphy->phy_lock);
+
+ /*
+ * Set put_into_high_z_state to true so next USB
+ * cable connect, DPF_DMF request performs PHY
+ * reset and put it into high-z state. For bootup
+ * with or without USB cable, it doesn't require
+ * to put QUSB PHY into high-z state.
+ */
+ qphy->put_into_high_z_state = true;
+ }
+ qphy->suspended = true;
+ } else {
+ /* Bus suspend case */
+ if (qphy->cable_connected ||
+ (qphy->phy.flags & PHY_HOST_MODE)) {
+ qusb_phy_enable_clocks(qphy, true);
+ /* Clear all interrupts on resume */
+ writel_relaxed(0x00,
+ qphy->base + QUSB2PHY_PORT_INTR_CTRL);
+ } else {
+ qusb_phy_enable_power(qphy, true);
+ if (qphy->tcsr_clamp_dig_n)
+ writel_relaxed(0x1,
+ qphy->tcsr_clamp_dig_n);
+ qusb_phy_enable_clocks(qphy, true);
+ }
+ qphy->suspended = false;
+ }
+
+ return 0;
+}
+
+static int qusb_phy_notify_connect(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+
+ qphy->cable_connected = true;
+
+ dev_dbg(phy->dev, "QUSB PHY: connect notification cable_connected=%d\n",
+ qphy->cable_connected);
+ return 0;
+}
+
+static int qusb_phy_notify_disconnect(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
+
+ qphy->cable_connected = false;
+
+ dev_dbg(phy->dev, "QUSB PHY: connect notification cable_connected=%d\n",
+ qphy->cable_connected);
+ return 0;
+}
+
+static int qusb_phy_dpdm_regulator_enable(struct regulator_dev *rdev)
+{
+ struct qusb_phy *qphy = rdev_get_drvdata(rdev);
+
+ dev_dbg(qphy->phy.dev, "%s\n", __func__);
+ return qusb_phy_update_dpdm(&qphy->phy, POWER_SUPPLY_DP_DM_DPF_DMF);
+}
+
+static int qusb_phy_dpdm_regulator_disable(struct regulator_dev *rdev)
+{
+ struct qusb_phy *qphy = rdev_get_drvdata(rdev);
+
+ dev_dbg(qphy->phy.dev, "%s\n", __func__);
+ return qusb_phy_update_dpdm(&qphy->phy, POWER_SUPPLY_DP_DM_DPR_DMR);
+}
+
+static int qusb_phy_dpdm_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct qusb_phy *qphy = rdev_get_drvdata(rdev);
+
+ dev_dbg(qphy->phy.dev, "%s qphy->rm_pulldown = %d\n", __func__,
+ qphy->rm_pulldown);
+ return qphy->rm_pulldown;
+}
+
+static struct regulator_ops qusb_phy_dpdm_regulator_ops = {
+ .enable = qusb_phy_dpdm_regulator_enable,
+ .disable = qusb_phy_dpdm_regulator_disable,
+ .is_enabled = qusb_phy_dpdm_regulator_is_enabled,
+};
+
+static int qusb_phy_regulator_init(struct qusb_phy *qphy)
+{
+ struct device *dev = qphy->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;
+ qphy->dpdm_rdesc.owner = THIS_MODULE;
+ qphy->dpdm_rdesc.type = REGULATOR_VOLTAGE;
+ qphy->dpdm_rdesc.ops = &qusb_phy_dpdm_regulator_ops;
+ qphy->dpdm_rdesc.name = kbasename(dev->of_node->full_name);
+
+ cfg.dev = dev;
+ cfg.init_data = init_data;
+ cfg.driver_data = qphy;
+ cfg.of_node = dev->of_node;
+
+ qphy->dpdm_rdev = devm_regulator_register(dev, &qphy->dpdm_rdesc, &cfg);
+ if (IS_ERR(qphy->dpdm_rdev))
+ return PTR_ERR(qphy->dpdm_rdev);
+
+ return 0;
+}
+
+static int qusb_phy_probe(struct platform_device *pdev)
+{
+ struct qusb_phy *qphy;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ int ret = 0, size = 0;
+ const char *phy_type;
+ bool hold_phy_reset;
+ struct power_supply_config dpdm_cfg = {};
+
+ qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL);
+ if (!qphy)
+ return -ENOMEM;
+
+ qphy->phy.dev = dev;
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "qusb_phy_base");
+ qphy->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(qphy->base))
+ return PTR_ERR(qphy->base);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "emu_phy_base");
+ if (res) {
+ qphy->emu_phy_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(qphy->emu_phy_base)) {
+ dev_dbg(dev, "couldn't ioremap emu_phy_base\n");
+ qphy->emu_phy_base = NULL;
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "tune2_efuse_addr");
+ if (res) {
+ qphy->tune2_efuse_reg = devm_ioremap_nocache(dev, res->start,
+ resource_size(res));
+ if (!IS_ERR_OR_NULL(qphy->tune2_efuse_reg)) {
+ ret = of_property_read_u32(dev->of_node,
+ "qcom,tune2-efuse-bit-pos",
+ &qphy->tune2_efuse_bit_pos);
+ if (!ret) {
+ ret = of_property_read_u32(dev->of_node,
+ "qcom,tune2-efuse-num-bits",
+ &qphy->tune2_efuse_num_of_bits);
+ }
+ of_property_read_u32(dev->of_node,
+ "qcom,tune2-efuse-correction",
+ &qphy->tune2_efuse_correction);
+
+ if (ret) {
+ dev_err(dev, "DT Value for tune2 efuse is invalid.\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "ref_clk_addr");
+ if (res) {
+ qphy->ref_clk_base = devm_ioremap_nocache(dev,
+ res->start, resource_size(res));
+ if (IS_ERR(qphy->ref_clk_base)) {
+ dev_dbg(dev, "ref_clk_address is not available.\n");
+ return PTR_ERR(qphy->ref_clk_base);
+ }
+
+ ret = of_property_read_string(dev->of_node,
+ "qcom,phy-clk-scheme", &phy_type);
+ if (ret) {
+ dev_err(dev, "error need qsub_phy_clk_scheme.\n");
+ return ret;
+ }
+
+ if (!strcasecmp(phy_type, "cml")) {
+ qphy->is_se_clk = false;
+ } else if (!strcasecmp(phy_type, "cmos")) {
+ qphy->is_se_clk = true;
+ } else {
+ dev_err(dev, "erro invalid qusb_phy_clk_scheme\n");
+ return -EINVAL;
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "tcsr_clamp_dig_n_1p8");
+ if (res) {
+ qphy->tcsr_clamp_dig_n = devm_ioremap_nocache(dev,
+ res->start, resource_size(res));
+ if (IS_ERR(qphy->tcsr_clamp_dig_n)) {
+ dev_err(dev, "err reading tcsr_clamp_dig_n\n");
+ qphy->tcsr_clamp_dig_n = NULL;
+ }
+ }
+
+
+ qphy->ref_clk_src = devm_clk_get(dev, "ref_clk_src");
+ if (IS_ERR(qphy->ref_clk_src))
+ dev_dbg(dev, "clk get failed for ref_clk_src\n");
+
+ qphy->ref_clk = devm_clk_get(dev, "ref_clk");
+ if (IS_ERR(qphy->ref_clk))
+ dev_dbg(dev, "clk get failed for ref_clk\n");
+ else
+ clk_set_rate(qphy->ref_clk, 19200000);
+
+ qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb_clk");
+ if (IS_ERR(qphy->cfg_ahb_clk))
+ return PTR_ERR(qphy->cfg_ahb_clk);
+
+ qphy->phy_reset = devm_reset_control_get(dev, "phy_reset");
+ if (IS_ERR(qphy->phy_reset))
+ return PTR_ERR(qphy->phy_reset);
+
+ qphy->emulation = of_property_read_bool(dev->of_node,
+ "qcom,emulation");
+
+ of_get_property(dev->of_node, "qcom,emu-init-seq", &size);
+ if (size) {
+ qphy->emu_init_seq = devm_kzalloc(dev,
+ size, GFP_KERNEL);
+ if (qphy->emu_init_seq) {
+ 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\n");
+ return -EINVAL;
+ }
+
+ of_property_read_u32_array(dev->of_node,
+ "qcom,emu-init-seq",
+ qphy->emu_init_seq,
+ qphy->emu_init_seq_len);
+ } else {
+ dev_dbg(dev, "error allocating memory for emu_init_seq\n");
+ }
+ }
+
+ size = 0;
+ of_get_property(dev->of_node, "qcom,phy-pll-reset-seq", &size);
+ if (size) {
+ qphy->phy_pll_reset_seq = devm_kzalloc(dev,
+ size, GFP_KERNEL);
+ if (qphy->phy_pll_reset_seq) {
+ qphy->phy_pll_reset_seq_len =
+ (size / sizeof(*qphy->phy_pll_reset_seq));
+ if (qphy->phy_pll_reset_seq_len % 2) {
+ dev_err(dev, "invalid phy_pll_reset_seq_len\n");
+ return -EINVAL;
+ }
+
+ of_property_read_u32_array(dev->of_node,
+ "qcom,phy-pll-reset-seq",
+ qphy->phy_pll_reset_seq,
+ qphy->phy_pll_reset_seq_len);
+ } else {
+ dev_dbg(dev, "error allocating memory for phy_pll_reset_seq\n");
+ }
+ }
+
+ size = 0;
+ of_get_property(dev->of_node, "qcom,emu-dcm-reset-seq", &size);
+ if (size) {
+ qphy->emu_dcm_reset_seq = devm_kzalloc(dev,
+ size, GFP_KERNEL);
+ if (qphy->emu_dcm_reset_seq) {
+ qphy->emu_dcm_reset_seq_len =
+ (size / sizeof(*qphy->emu_dcm_reset_seq));
+ if (qphy->emu_dcm_reset_seq_len % 2) {
+ dev_err(dev, "invalid emu_dcm_reset_seq_len\n");
+ return -EINVAL;
+ }
+
+ of_property_read_u32_array(dev->of_node,
+ "qcom,emu-dcm-reset-seq",
+ qphy->emu_dcm_reset_seq,
+ qphy->emu_dcm_reset_seq_len);
+ } else {
+ dev_dbg(dev, "error allocating memory for emu_dcm_reset_seq\n");
+ }
+ }
+
+ size = 0;
+ of_get_property(dev->of_node, "qcom,qusb-phy-init-seq", &size);
+ if (size) {
+ qphy->qusb_phy_init_seq = devm_kzalloc(dev,
+ size, GFP_KERNEL);
+ if (qphy->qusb_phy_init_seq) {
+ qphy->init_seq_len =
+ (size / sizeof(*qphy->qusb_phy_init_seq));
+ if (qphy->init_seq_len % 2) {
+ dev_err(dev, "invalid init_seq_len\n");
+ return -EINVAL;
+ }
+
+ of_property_read_u32_array(dev->of_node,
+ "qcom,qusb-phy-init-seq",
+ qphy->qusb_phy_init_seq,
+ qphy->init_seq_len);
+ } else {
+ dev_err(dev, "error allocating memory for phy_init_seq\n");
+ }
+ }
+
+ qphy->ulpi_mode = false;
+ ret = of_property_read_string(dev->of_node, "phy_type", &phy_type);
+
+ if (!ret) {
+ if (!strcasecmp(phy_type, "ulpi"))
+ qphy->ulpi_mode = true;
+ } else {
+ dev_err(dev, "error reading phy_type property\n");
+ return ret;
+ }
+
+ hold_phy_reset = of_property_read_bool(dev->of_node, "qcom,hold-reset");
+
+ /* use default major revision as 2 */
+ qphy->major_rev = 2;
+ ret = of_property_read_u32(dev->of_node, "qcom,major-rev",
+ &qphy->major_rev);
+
+ ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level",
+ (u32 *) qphy->vdd_levels,
+ ARRAY_SIZE(qphy->vdd_levels));
+ if (ret) {
+ dev_err(dev, "error reading qcom,vdd-voltage-level property\n");
+ return ret;
+ }
+
+ qphy->vdd = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(qphy->vdd)) {
+ dev_err(dev, "unable to get vdd supply\n");
+ return PTR_ERR(qphy->vdd);
+ }
+
+ qphy->vdda33 = devm_regulator_get(dev, "vdda33");
+ if (IS_ERR(qphy->vdda33)) {
+ dev_err(dev, "unable to get vdda33 supply\n");
+ return PTR_ERR(qphy->vdda33);
+ }
+
+ qphy->vdda18 = devm_regulator_get(dev, "vdda18");
+ if (IS_ERR(qphy->vdda18)) {
+ dev_err(dev, "unable to get vdda18 supply\n");
+ return PTR_ERR(qphy->vdda18);
+ }
+
+ mutex_init(&qphy->phy_lock);
+ spin_lock_init(&qphy->pulse_lock);
+ platform_set_drvdata(pdev, qphy);
+
+ qphy->phy.label = "msm-qusb-phy";
+ qphy->phy.init = qusb_phy_init;
+ qphy->phy.set_suspend = qusb_phy_set_suspend;
+ qphy->phy.shutdown = qusb_phy_shutdown;
+ qphy->phy.type = USB_PHY_TYPE_USB2;
+ qphy->phy.notify_connect = qusb_phy_notify_connect;
+ qphy->phy.notify_disconnect = qusb_phy_notify_disconnect;
+ qphy->phy.dpdm_with_idp_src = qusb_phy_linestate_with_idp_src;
+
+ /*
+ * On some platforms multiple QUSB PHYs are available. If QUSB PHY is
+ * not used, there is leakage current seen with QUSB PHY related voltage
+ * rail. Hence keep QUSB PHY into reset state explicitly here.
+ */
+ if (hold_phy_reset) {
+ ret = reset_control_assert(qphy->phy_reset);
+ if (ret)
+ dev_err(dev, "%s:phy_reset assert failed\n", __func__);
+ }
+
+ qphy->dpdm_pulsing_enabled = of_property_read_bool(dev->of_node,
+ "qcom,enable-dpdm-pulsing");
+
+ if (qphy->dpdm_pulsing_enabled) {
+ qphy->dpdm_psy_desc.name = "dpdm";
+ qphy->dpdm_psy_desc.type = POWER_SUPPLY_TYPE_USB;
+ qphy->dpdm_psy_desc.properties = dpdm_props;
+ qphy->dpdm_psy_desc.num_properties = ARRAY_SIZE(dpdm_props);
+ qphy->dpdm_psy_desc.set_property = qusb_phy_set_property_usb;
+ qphy->dpdm_psy_desc.get_property = qusb_phy_get_property_usb;
+
+ dpdm_cfg.drv_data = qphy;
+ dpdm_cfg.of_node = dev->of_node;
+ qphy->dpdm_psy = power_supply_register(&pdev->dev,
+ &qphy->dpdm_psy_desc, &dpdm_cfg);
+ if (IS_ERR(qphy->dpdm_psy)) {
+ dev_err(&pdev->dev, "%s:dpdm power_supply_register failed\n",
+ __func__);
+ return PTR_ERR(qphy->dpdm_psy);
+ }
+ }
+
+ ret = usb_add_phy_dev(&qphy->phy);
+ if (ret)
+ goto unregister_psy;
+
+ ret = qusb_phy_regulator_init(qphy);
+ if (ret)
+ goto remove_phy;
+
+ /* de-assert clamp dig n to reduce leakage on 1p8 upon boot up */
+ if (qphy->tcsr_clamp_dig_n)
+ writel_relaxed(0x0, qphy->tcsr_clamp_dig_n);
+
+ return ret;
+
+remove_phy:
+ usb_remove_phy(&qphy->phy);
+unregister_psy:
+ if (qphy->dpdm_psy)
+ power_supply_unregister(qphy->dpdm_psy);
+
+ return ret;
+}
+
+static int qusb_phy_remove(struct platform_device *pdev)
+{
+ struct qusb_phy *qphy = platform_get_drvdata(pdev);
+
+ if (qphy->dpdm_psy)
+ power_supply_unregister(qphy->dpdm_psy);
+ usb_remove_phy(&qphy->phy);
+
+ if (qphy->clocks_enabled) {
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ clk_disable_unprepare(qphy->ref_clk);
+ clk_disable_unprepare(qphy->ref_clk_src);
+ qphy->clocks_enabled = false;
+ }
+
+ qusb_phy_enable_power(qphy, false);
+
+ return 0;
+}
+
+static const struct of_device_id qusb_phy_id_table[] = {
+ { .compatible = "qcom,qusb2phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, qusb_phy_id_table);
+
+static struct platform_driver qusb_phy_driver = {
+ .probe = qusb_phy_probe,
+ .remove = qusb_phy_remove,
+ .driver = {
+ .name = "msm-qusb-phy",
+ .of_match_table = of_match_ptr(qusb_phy_id_table),
+ },
+};
+
+module_platform_driver(qusb_phy_driver);
+
+MODULE_DESCRIPTION("MSM QUSB2 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..8cfbb1c100fe
--- /dev/null
+++ b/drivers/usb/phy/phy-msm-ssusb-qmp.c
@@ -0,0 +1,842 @@
+/*
+ * Copyright (c) 2013-2019, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/phy.h>
+#include <linux/usb/msm_hsusb.h>
+#include <linux/clk.h>
+#include <linux/clk/msm-clk.h>
+#include <linux/reset.h>
+
+enum ldo_levels {
+ VOLTAGE_LEVEL_NONE = 0,
+ VOLTAGE_LEVEL_MIN,
+ VOLTAGE_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 23000 /* uA */
+
+#define USB_SSPHY_LOAD_DEFAULT -1
+
+/* 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)
+
+/* PCIE_USB3_PHY_PCS_MISC_TYPEC_CTRL bits */
+
+/* 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)
+
+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,
+ USB3_PHY_PCS_MISC_TYPEC_CTRL,
+ 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 *tcsr_usb3_dp_phymode;
+
+ struct regulator *vdd;
+ int vdd_levels[3]; /* none, low, high */
+ struct regulator *core_ldo;
+ int core_voltage_levels[3];
+ struct regulator *fpc_redrive_ldo;
+ int redrive_voltage_levels[3];
+ int redrive_load;
+ struct clk *ref_clk_src;
+ struct clk *ref_clk;
+ struct clk *aux_clk;
+ struct clk *cfg_ahb_clk;
+ struct clk *pipe_clk;
+ bool power_enabled;
+ struct reset_control *phy_reset;
+ struct reset_control *phy_phy_reset;
+
+ bool clk_enabled;
+ bool cable_connected;
+ bool in_suspend;
+ bool emulation;
+ unsigned int *phy_reg; /* revision based offset */
+ unsigned int *qmp_phy_init_seq;
+ int init_seq_len;
+ unsigned int *qmp_phy_reg_offset;
+ int reg_offset_cnt;
+};
+
+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",
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, msm_usb_id_table);
+
+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)
+{
+ writeb_relaxed(1, phy->base +
+ phy->phy_reg[USB3_PHY_LFPS_RXTERM_IRQ_CLEAR]);
+ /* flush the previous write before next write */
+ wmb();
+ writeb_relaxed(0, phy->base +
+ phy->phy_reg[USB3_PHY_LFPS_RXTERM_IRQ_CLEAR]);
+}
+
+static void msm_ssusb_qmp_enable_autonomous(struct msm_ssphy_qmp *phy,
+ int enable)
+{
+ u8 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 = readb_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;
+ }
+
+ writeb_relaxed(val, phy->base + autonomous_mode_offset);
+ /* clamp phy level shifter to perform autonomous detection */
+ writel_relaxed(0x1, phy->vls_clamp_reg);
+ } else {
+ writel_relaxed(0x0, phy->vls_clamp_reg);
+ writeb_relaxed(0, phy->base + autonomous_mode_offset);
+ msm_ssusb_qmp_clr_lfps_rxterm_int(phy);
+ }
+}
+
+static int msm_ldo_enable(struct msm_ssphy_qmp *phy,
+ struct regulator *ldo, int *voltage_levels, int load)
+{
+ int ret = 0;
+
+ dev_dbg(phy->phy.dev,
+ "ldo: min_vol:%duV max_vol:%duV\n",
+ voltage_levels[VOLTAGE_LEVEL_MIN],
+ voltage_levels[VOLTAGE_LEVEL_MAX]);
+
+ if (load > 0) {
+ ret = regulator_set_load(ldo, load);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = regulator_set_voltage(ldo,
+ voltage_levels[VOLTAGE_LEVEL_MIN],
+ voltage_levels[VOLTAGE_LEVEL_MAX]);
+ if (ret)
+ return ret;
+
+ ret = regulator_enable(ldo);
+
+ 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;
+
+ if (phy->fpc_redrive_ldo) {
+ rc = msm_ldo_enable(phy, phy->fpc_redrive_ldo,
+ phy->redrive_voltage_levels,
+ phy->redrive_load);
+ if (rc < 0) {
+ dev_err(phy->phy.dev,
+ "enable phy->fpc_redrive_ldo failed\n");
+ return rc;
+ }
+ }
+
+ rc = msm_ldo_enable(phy, phy->vdd, phy->vdd_levels,
+ USB_SSPHY_LOAD_DEFAULT);
+ if (rc < 0) {
+ dev_err(phy->phy.dev, "enable phy->vdd failed\n");
+ goto disable_fpc_redrive;
+ }
+
+ rc = msm_ldo_enable(phy, phy->core_ldo, phy->core_voltage_levels,
+ USB_SSPHY_HPM_LOAD);
+ if (rc < 0) {
+ dev_err(phy->phy.dev, "enable phy->core_ldo failed\n");
+ goto disable_vdd;
+ }
+
+ return 0;
+
+disable_regulators:
+ rc = regulator_disable(phy->core_ldo);
+ if (rc)
+ dev_err(phy->phy.dev, "disable phy->core_ldo failed\n");
+
+disable_vdd:
+ rc = regulator_disable(phy->vdd);
+ if (rc)
+ dev_err(phy->phy.dev, "disable phy->vdd failed\n");
+
+disable_fpc_redrive:
+ if (phy->fpc_redrive_ldo) {
+ rc = regulator_disable(phy->fpc_redrive_ldo);
+ if (rc)
+ dev_err(phy->phy.dev,
+ "disable phy->fpc_redrive_ldo failed\n");
+ }
+
+ 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;
+}
+
+/* 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, val;
+ unsigned init_timeout_usec = INIT_MAX_TIME_USEC;
+ const struct qmp_reg_val *reg = NULL;
+
+ dev_dbg(uphy->dev, "Initializing QMP phy\n");
+
+ if (phy->emulation)
+ return 0;
+
+ 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;
+ }
+
+ if (!phy->clk_enabled) {
+ if (phy->ref_clk_src)
+ clk_prepare_enable(phy->ref_clk_src);
+ if (phy->ref_clk)
+ clk_prepare_enable(phy->ref_clk);
+ clk_prepare_enable(phy->aux_clk);
+ clk_prepare_enable(phy->cfg_ahb_clk);
+ clk_set_rate(phy->pipe_clk, 125000000);
+ clk_prepare_enable(phy->pipe_clk);
+ phy->clk_enabled = true;
+ }
+
+ writel_relaxed(0x01,
+ phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
+
+ /* Make sure that above write completed to get PHY into POWER DOWN */
+ mb();
+
+ 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 lane selection */
+ val = -EINVAL;
+ if (phy->phy.flags & PHY_LANE_A)
+ val = SW_PORTSELECT_MX;
+
+ if (phy->phy.flags & PHY_LANE_B)
+ val = SW_PORTSELECT | SW_PORTSELECT_MX;
+
+ if (val > 0)
+ writel_relaxed(val,
+ phy->base + phy->phy_reg[USB3_PHY_PCS_MISC_TYPEC_CTRL]);
+
+ writel_relaxed(0x03, phy->base + phy->phy_reg[USB3_PHY_START]);
+ writel_relaxed(0x00, phy->base + phy->phy_reg[USB3_PHY_SW_RESET]);
+
+ /* 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_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) {
+ if (phy->vls_clamp_reg)
+ msm_ssusb_qmp_enable_autonomous(phy, 1);
+ } else {
+ writel_relaxed(0x00,
+ phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
+ }
+
+ /* Make sure above write completed with PHY */
+ wmb();
+
+ if (phy->clk_enabled) {
+ clk_disable_unprepare(phy->cfg_ahb_clk);
+ clk_disable_unprepare(phy->aux_clk);
+ clk_disable_unprepare(phy->pipe_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;
+ }
+ 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);
+ clk_prepare_enable(phy->pipe_clk);
+ if (!phy->clk_enabled) {
+ if (phy->ref_clk_src)
+ clk_prepare_enable(phy->ref_clk_src);
+ if (phy->ref_clk)
+ clk_prepare_enable(phy->ref_clk);
+ clk_prepare_enable(phy->aux_clk);
+ clk_prepare_enable(phy->cfg_ahb_clk);
+ phy->clk_enabled = true;
+ }
+ if (!phy->cable_connected) {
+ writel_relaxed(0x01,
+ phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
+ } else {
+ if (phy->vls_clamp_reg)
+ 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;
+ 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]);
+
+ 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_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->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(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,
+ "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->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;
+ }
+
+ 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->qmp_phy_reg_offset = devm_kzalloc(dev,
+ size, GFP_KERNEL);
+ if (phy->qmp_phy_reg_offset) {
+ phy->reg_offset_cnt =
+ (size / sizeof(*phy->qmp_phy_reg_offset));
+ 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->qmp_phy_reg_offset,
+ phy->reg_offset_cnt);
+ } else {
+ dev_err(dev, "err mem alloc for qmp_phy_reg_offset\n");
+ return -ENOMEM;
+ }
+ phy->phy_reg = phy->qmp_phy_reg_offset;
+ } 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;
+ }
+ phy->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(phy->base)) {
+ ret = PTR_ERR(phy->base);
+ goto err;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "vls_clamp_reg");
+ if (!res) {
+ dev_dbg(dev, "vls_clamp_reg not passed\n");
+ } else {
+ phy->vls_clamp_reg = devm_ioremap_resource(dev, res);
+ if (IS_ERR(phy->vls_clamp_reg)) {
+ dev_err(dev, "couldn't find vls_clamp_reg address.\n");
+ return PTR_ERR(phy->vls_clamp_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);
+ }
+ }
+
+ phy->emulation = of_property_read_bool(dev->of_node,
+ "qcom,emulation");
+ if (!phy->emulation) {
+ 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) {
+ 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 allocating memory for phy_init_seq\n");
+ return -EINVAL;
+ }
+ } else {
+ dev_err(dev, "error need qmp-phy-init-seq\n");
+ return -EINVAL;
+ }
+ }
+
+ /* Set default core voltage values */
+ phy->core_voltage_levels[VOLTAGE_LEVEL_NONE] = 0;
+ phy->core_voltage_levels[VOLTAGE_LEVEL_MIN] = USB_SSPHY_1P2_VOL_MIN;
+ phy->core_voltage_levels[VOLTAGE_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_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;
+ }
+
+ 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->fpc_redrive_ldo = devm_regulator_get_optional(dev, "fpc-redrive");
+ if (IS_ERR(phy->fpc_redrive_ldo)) {
+ phy->fpc_redrive_ldo = NULL;
+ dev_dbg(dev, "no FPC re-drive ldo regulator\n");
+ } else {
+ if (of_get_property(dev->of_node,
+ "qcom,redrive-voltage-level", &len) &&
+ len == sizeof(phy->redrive_voltage_levels)) {
+ ret = of_property_read_u32_array(dev->of_node,
+ "qcom,redrive-voltage-level",
+ (u32 *) phy->redrive_voltage_levels,
+ len / sizeof(u32));
+ if (ret) {
+ dev_err(dev,
+ "err qcom,redrive-voltage-level\n");
+ goto err;
+ }
+ } else {
+ ret = -EINVAL;
+ dev_err(dev, "err inputs for redrive-voltage-level\n");
+ goto err;
+ }
+
+ ret = of_property_read_u32(dev->of_node, "qcom,redrive-load",
+ &phy->redrive_load);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to read redrive load\n");
+ goto err;
+ }
+
+ dev_dbg(dev, "Get FPC re-drive ldo regulator\n");
+ }
+
+ 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;
+
+ platform_set_drvdata(pdev, phy);
+
+ if (of_property_read_bool(dev->of_node, "qcom,vbus-valid-override"))
+ phy->phy.flags |= PHY_VBUS_VALID_OVERRIDE;
+
+ 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;
+ phy->phy.reset = msm_ssphy_qmp_reset;
+ phy->phy.type = USB_PHY_TYPE_USB3;
+
+ 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);
+ if (phy->ref_clk)
+ clk_disable_unprepare(phy->ref_clk);
+ if (phy->ref_clk_src)
+ clk_disable_unprepare(phy->ref_clk_src);
+ msm_ssusb_qmp_ldo_enable(phy, 0);
+ clk_disable_unprepare(phy->aux_clk);
+ clk_disable_unprepare(phy->cfg_ahb_clk);
+ clk_disable_unprepare(phy->pipe_clk);
+ kfree(phy);
+ 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-msm-ssusb.c b/drivers/usb/phy/phy-msm-ssusb.c
new file mode 100644
index 000000000000..5ea19deb8219
--- /dev/null
+++ b/drivers/usb/phy/phy-msm-ssusb.c
@@ -0,0 +1,595 @@
+/*
+ * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/clk/msm-clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/phy.h>
+#include <linux/usb/msm_hsusb.h>
+
+static int ss_phy_override_deemphasis;
+module_param(ss_phy_override_deemphasis, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(ss_phy_override_deemphasis, "Override SSPHY demphasis value");
+
+/* QSCRATCH SSPHY control registers */
+#define SS_PHY_CTRL_REG 0x30
+#define SS_PHY_PARAM_CTRL_1 0x34
+#define SS_PHY_PARAM_CTRL_2 0x38
+#define SS_CR_PROTOCOL_DATA_IN_REG 0x3C
+#define SS_CR_PROTOCOL_DATA_OUT_REG 0x40
+#define SS_CR_PROTOCOL_CAP_ADDR_REG 0x44
+#define SS_CR_PROTOCOL_CAP_DATA_REG 0x48
+#define SS_CR_PROTOCOL_READ_REG 0x4C
+#define SS_CR_PROTOCOL_WRITE_REG 0x50
+
+/* SS_PHY_CTRL_REG bits */
+#define SS_PHY_RESET BIT(7)
+#define REF_SS_PHY_EN BIT(8)
+#define LANE0_PWR_PRESENT BIT(24)
+#define TEST_POWERDOWN BIT(26)
+#define REF_USE_PAD BIT(28)
+
+#define USB_SSPHY_1P8_VOL_MIN 1800000 /* uV */
+#define USB_SSPHY_1P8_VOL_MAX 1800000 /* uV */
+#define USB_SSPHY_1P8_HPM_LOAD 23000 /* uA */
+
+struct msm_ssphy {
+ struct usb_phy phy;
+ void __iomem *base;
+ struct clk *core_clk; /* USB3 master clock */
+ struct clk *com_reset_clk; /* PHY common block reset */
+ struct clk *reset_clk; /* SS PHY reset */
+ struct regulator *vdd;
+ struct regulator *vdda18;
+ atomic_t active_count; /* num of active instances */
+ bool suspended;
+ int vdd_levels[3]; /* none, low, high */
+ int deemphasis_val;
+};
+
+static int msm_ssusb_config_vdd(struct msm_ssphy *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 ssusb 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_ssusb_ldo_enable(struct msm_ssphy *phy, int on)
+{
+ int rc = 0;
+
+ dev_dbg(phy->phy.dev, "reg (%s)\n", on ? "HPM" : "LPM");
+
+ if (!on)
+ goto disable_regulators;
+
+
+ rc = regulator_set_load(phy->vdda18, USB_SSPHY_1P8_HPM_LOAD);
+ if (rc < 0) {
+ dev_err(phy->phy.dev, "Unable to set HPM of vdda18\n");
+ return rc;
+ }
+
+ rc = regulator_set_voltage(phy->vdda18, USB_SSPHY_1P8_VOL_MIN,
+ USB_SSPHY_1P8_VOL_MAX);
+ if (rc) {
+ dev_err(phy->phy.dev, "unable to set voltage for vdda18\n");
+ goto put_vdda18_lpm;
+ }
+
+ rc = regulator_enable(phy->vdda18);
+ if (rc) {
+ dev_err(phy->phy.dev, "Unable to enable vdda18\n");
+ goto unset_vdda18;
+ }
+
+ return 0;
+
+disable_regulators:
+ rc = regulator_disable(phy->vdda18);
+ if (rc)
+ dev_err(phy->phy.dev, "Unable to disable vdda18\n");
+
+unset_vdda18:
+ rc = regulator_set_voltage(phy->vdda18, 0, USB_SSPHY_1P8_VOL_MAX);
+ if (rc)
+ dev_err(phy->phy.dev, "unable to set voltage for vdda18\n");
+
+put_vdda18_lpm:
+ rc = regulator_set_load(phy->vdda18, 0);
+ if (rc < 0)
+ dev_err(phy->phy.dev, "Unable to set LPM of vdda18\n");
+
+ return rc < 0 ? rc : 0;
+}
+
+static void msm_usb_write_readback(void *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);
+}
+
+/**
+ * Write SSPHY register with debug info.
+ *
+ * @base - base virtual address.
+ * @addr - SSPHY address to write.
+ * @val - value to write.
+ *
+ */
+static void msm_ssusb_write_phycreg(void *base, u32 addr, u32 val)
+{
+ writel_relaxed(addr, base + SS_CR_PROTOCOL_DATA_IN_REG);
+ writel_relaxed(0x1, base + SS_CR_PROTOCOL_CAP_ADDR_REG);
+ while (readl_relaxed(base + SS_CR_PROTOCOL_CAP_ADDR_REG))
+ cpu_relax();
+
+ writel_relaxed(val, base + SS_CR_PROTOCOL_DATA_IN_REG);
+ writel_relaxed(0x1, base + SS_CR_PROTOCOL_CAP_DATA_REG);
+ while (readl_relaxed(base + SS_CR_PROTOCOL_CAP_DATA_REG))
+ cpu_relax();
+
+ writel_relaxed(0x1, base + SS_CR_PROTOCOL_WRITE_REG);
+ while (readl_relaxed(base + SS_CR_PROTOCOL_WRITE_REG))
+ cpu_relax();
+}
+
+/**
+ * Read SSPHY register with debug info.
+ *
+ * @base - base virtual address.
+ * @addr - SSPHY address to read.
+ *
+ */
+static u32 msm_ssusb_read_phycreg(void *base, u32 addr)
+{
+ bool first_read = true;
+
+ writel_relaxed(addr, base + SS_CR_PROTOCOL_DATA_IN_REG);
+ writel_relaxed(0x1, base + SS_CR_PROTOCOL_CAP_ADDR_REG);
+ while (readl_relaxed(base + SS_CR_PROTOCOL_CAP_ADDR_REG))
+ cpu_relax();
+
+ /*
+ * Due to hardware bug, first read of SSPHY register might be
+ * incorrect. Hence as workaround, SW should perform SSPHY register
+ * read twice, but use only second read and ignore first read.
+ */
+retry:
+ writel_relaxed(0x1, base + SS_CR_PROTOCOL_READ_REG);
+ while (readl_relaxed(base + SS_CR_PROTOCOL_READ_REG))
+ cpu_relax();
+
+ if (first_read) {
+ readl_relaxed(base + SS_CR_PROTOCOL_DATA_OUT_REG);
+ first_read = false;
+ goto retry;
+ }
+
+ return readl_relaxed(base + SS_CR_PROTOCOL_DATA_OUT_REG);
+}
+
+static int msm_ssphy_set_params(struct usb_phy *uphy)
+{
+ struct msm_ssphy *phy = container_of(uphy, struct msm_ssphy, phy);
+ u32 data = 0;
+
+ /*
+ * WORKAROUND: There is SSPHY suspend bug due to which USB enumerates
+ * in HS mode instead of SS mode. Workaround it by asserting
+ * LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus mode
+ */
+ data = msm_ssusb_read_phycreg(phy->base, 0x102D);
+ data |= (1 << 7);
+ msm_ssusb_write_phycreg(phy->base, 0x102D, data);
+
+ data = msm_ssusb_read_phycreg(phy->base, 0x1010);
+ data &= ~0xFF0;
+ data |= 0x20;
+ msm_ssusb_write_phycreg(phy->base, 0x1010, data);
+
+ /*
+ * Fix RX Equalization setting as follows
+ * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0
+ * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1
+ * LANE0.RX_OVRD_IN_HI.RX_EQ set to 3
+ * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1
+ */
+ data = msm_ssusb_read_phycreg(phy->base, 0x1006);
+ data &= ~(1 << 6);
+ data |= (1 << 7);
+ data &= ~(0x7 << 8);
+ data |= (0x3 << 8);
+ data |= (0x1 << 11);
+ msm_ssusb_write_phycreg(phy->base, 0x1006, data);
+
+ /*
+ * Set EQ and TX launch amplitudes as follows
+ * LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22
+ * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127
+ * LANE0.TX_OVRD_DRV_LO.EN set to 1.
+ */
+ data = msm_ssusb_read_phycreg(phy->base, 0x1002);
+ data &= ~0x3F80;
+ if (ss_phy_override_deemphasis)
+ phy->deemphasis_val = ss_phy_override_deemphasis;
+ if (phy->deemphasis_val)
+ data |= (phy->deemphasis_val << 7);
+ else
+ data |= (0x16 << 7);
+ data &= ~0x7F;
+ data |= (0x7F | (1 << 14));
+ msm_ssusb_write_phycreg(phy->base, 0x1002, data);
+
+ /*
+ * Set the QSCRATCH SS_PHY_PARAM_CTRL1 parameters as follows
+ * TX_FULL_SWING [26:20] amplitude to 127
+ * TX_DEEMPH_3_5DB [13:8] to 22
+ * LOS_BIAS [2:0] to 0x5
+ */
+ msm_usb_write_readback(phy->base, SS_PHY_PARAM_CTRL_1,
+ 0x07f03f07, 0x07f01605);
+
+ return 0;
+}
+
+/* SSPHY Initialization */
+static int msm_ssphy_init(struct usb_phy *uphy)
+{
+ struct msm_ssphy *phy = container_of(uphy, struct msm_ssphy, phy);
+ u32 val;
+
+ /* Ensure clock is on before accessing QSCRATCH registers */
+ clk_prepare_enable(phy->core_clk);
+
+ /* read initial value */
+ val = readl_relaxed(phy->base + SS_PHY_CTRL_REG);
+
+ /* Use clk reset, if available; otherwise use SS_PHY_RESET bit */
+ if (phy->com_reset_clk) {
+ clk_reset(phy->com_reset_clk, CLK_RESET_ASSERT);
+ clk_reset(phy->reset_clk, CLK_RESET_ASSERT);
+ udelay(10); /* 10us required before de-asserting */
+ clk_reset(phy->com_reset_clk, CLK_RESET_DEASSERT);
+ clk_reset(phy->reset_clk, CLK_RESET_DEASSERT);
+ } else {
+ writel_relaxed(val | SS_PHY_RESET, phy->base + SS_PHY_CTRL_REG);
+ udelay(10); /* 10us required before de-asserting */
+ writel_relaxed(val, phy->base + SS_PHY_CTRL_REG);
+ }
+
+ /* Use ref_clk from pads and set its parameters */
+ val |= REF_USE_PAD;
+ writel_relaxed(val, phy->base + SS_PHY_CTRL_REG);
+ msleep(30);
+
+ /* Ref clock must be stable now, enable ref clock for HS mode */
+ val |= LANE0_PWR_PRESENT | REF_SS_PHY_EN;
+ writel_relaxed(val, phy->base + SS_PHY_CTRL_REG);
+ usleep_range(2000, 2200);
+
+ /*
+ * Reinitialize SSPHY parameters as SS_PHY RESET will reset
+ * the internal registers to default values.
+ */
+ msm_ssphy_set_params(uphy);
+
+ clk_disable_unprepare(phy->core_clk);
+
+ return 0;
+}
+
+static int msm_ssphy_set_suspend(struct usb_phy *uphy, int suspend)
+{
+ struct msm_ssphy *phy = container_of(uphy, struct msm_ssphy, phy);
+ void __iomem *base = phy->base;
+ int count;
+
+ /* Ensure clock is on before accessing QSCRATCH registers */
+ clk_prepare_enable(phy->core_clk);
+
+ if (suspend) {
+ count = atomic_dec_return(&phy->active_count);
+ if (count > 0 || phy->suspended) {
+ dev_dbg(uphy->dev, "Skipping suspend, active_count=%d phy->suspended=%d\n",
+ count, phy->suspended);
+ goto done;
+ }
+
+ if (count < 0) {
+ dev_WARN(uphy->dev, "Suspended too many times! active_count=%d\n",
+ count);
+ atomic_set(&phy->active_count, 0);
+ }
+
+ /* Clear REF_SS_PHY_EN */
+ msm_usb_write_readback(base, SS_PHY_CTRL_REG, REF_SS_PHY_EN, 0);
+ /* Clear REF_USE_PAD */
+ msm_usb_write_readback(base, SS_PHY_CTRL_REG, REF_USE_PAD, 0);
+ /* Set TEST_POWERDOWN (enables PHY retention) */
+ msm_usb_write_readback(base, SS_PHY_CTRL_REG, TEST_POWERDOWN,
+ TEST_POWERDOWN);
+ if (phy->com_reset_clk &&
+ !(phy->phy.flags & ENABLE_SECONDARY_PHY)) {
+ /* leave these asserted until resuming */
+ clk_reset(phy->com_reset_clk, CLK_RESET_ASSERT);
+ clk_reset(phy->reset_clk, CLK_RESET_ASSERT);
+ }
+
+ msm_ssusb_ldo_enable(phy, 0);
+ msm_ssusb_config_vdd(phy, 0);
+ phy->suspended = true;
+ } else {
+ count = atomic_inc_return(&phy->active_count);
+ if (count > 1 || !phy->suspended) {
+ dev_dbg(uphy->dev, "Skipping resume, active_count=%d phy->suspended=%d\n",
+ count, phy->suspended);
+ goto done;
+ }
+
+ phy->suspended = false;
+ msm_ssusb_config_vdd(phy, 1);
+ msm_ssusb_ldo_enable(phy, 1);
+
+ if (phy->phy.flags & ENABLE_SECONDARY_PHY) {
+ dev_err(uphy->dev, "secondary PHY, skipping reset\n");
+ goto done;
+ }
+
+ if (phy->com_reset_clk) {
+ clk_reset(phy->com_reset_clk, CLK_RESET_DEASSERT);
+ clk_reset(phy->reset_clk, CLK_RESET_DEASSERT);
+ } else {
+ /* Assert SS PHY RESET */
+ msm_usb_write_readback(base, SS_PHY_CTRL_REG,
+ SS_PHY_RESET, SS_PHY_RESET);
+ }
+
+ /* Set REF_USE_PAD */
+ msm_usb_write_readback(base, SS_PHY_CTRL_REG, REF_USE_PAD,
+ REF_USE_PAD);
+ /* Set REF_SS_PHY_EN */
+ msm_usb_write_readback(base, SS_PHY_CTRL_REG, REF_SS_PHY_EN,
+ REF_SS_PHY_EN);
+ /* Clear TEST_POWERDOWN */
+ msm_usb_write_readback(base, SS_PHY_CTRL_REG, TEST_POWERDOWN,
+ 0);
+ if (!phy->com_reset_clk) {
+ udelay(10); /* 10us required before de-asserting */
+ msm_usb_write_readback(base, SS_PHY_CTRL_REG,
+ SS_PHY_RESET, 0);
+ }
+ }
+
+done:
+ clk_disable_unprepare(phy->core_clk);
+ return 0;
+}
+
+static int msm_ssphy_notify_connect(struct usb_phy *uphy,
+ enum usb_device_speed speed)
+{
+ struct msm_ssphy *phy = container_of(uphy, struct msm_ssphy, phy);
+
+ if (uphy->flags & PHY_HOST_MODE)
+ return 0;
+
+ if (uphy->flags & PHY_VBUS_VALID_OVERRIDE)
+ /* Indicate power present to SS phy */
+ msm_usb_write_readback(phy->base, SS_PHY_CTRL_REG,
+ LANE0_PWR_PRESENT, LANE0_PWR_PRESENT);
+
+ return 0;
+}
+
+static int msm_ssphy_notify_disconnect(struct usb_phy *uphy,
+ enum usb_device_speed speed)
+{
+ struct msm_ssphy *phy = container_of(uphy, struct msm_ssphy, phy);
+
+ if (uphy->flags & PHY_HOST_MODE)
+ return 0;
+
+ if (uphy->flags & PHY_VBUS_VALID_OVERRIDE)
+ /* Clear power indication to SS phy */
+ msm_usb_write_readback(phy->base, SS_PHY_CTRL_REG,
+ LANE0_PWR_PRESENT, 0);
+
+ return 0;
+}
+
+static int msm_ssphy_probe(struct platform_device *pdev)
+{
+ struct msm_ssphy *phy;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ int ret = 0;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "missing memory base resource\n");
+ return -ENODEV;
+ }
+
+ phy->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+ if (!phy->base) {
+ dev_err(dev, "ioremap failed\n");
+ return -ENODEV;
+ }
+
+ phy->core_clk = devm_clk_get(dev, "core_clk");
+ if (IS_ERR(phy->core_clk)) {
+ dev_err(dev, "unable to get core_clk\n");
+ return PTR_ERR(phy->core_clk);
+ }
+
+ phy->com_reset_clk = devm_clk_get(dev, "com_reset_clk");
+ if (IS_ERR(phy->com_reset_clk)) {
+ dev_dbg(dev, "com_reset_clk unavailable\n");
+ phy->com_reset_clk = NULL;
+ }
+
+ phy->reset_clk = devm_clk_get(dev, "reset_clk");
+ if (IS_ERR(phy->reset_clk)) {
+ dev_dbg(dev, "reset_clk unavailable\n");
+ phy->reset_clk = NULL;
+ }
+
+ if (of_get_property(dev->of_node, "qcom,primary-phy", NULL)) {
+ dev_dbg(dev, "secondary HSPHY\n");
+ phy->phy.flags |= ENABLE_SECONDARY_PHY;
+ }
+
+ 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");
+ return ret;
+ }
+
+ phy->phy.dev = dev;
+ phy->vdd = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(phy->vdd)) {
+ dev_err(dev, "unable to get vdd supply\n");
+ return PTR_ERR(phy->vdd);
+ }
+
+ phy->vdda18 = devm_regulator_get(dev, "vdda18");
+ if (IS_ERR(phy->vdda18)) {
+ dev_err(dev, "unable to get vdda18 supply\n");
+ return PTR_ERR(phy->vdda18);
+ }
+
+ ret = msm_ssusb_config_vdd(phy, 1);
+ if (ret) {
+ dev_err(dev, "ssusb vdd_dig configuration failed\n");
+ return ret;
+ }
+
+ ret = regulator_enable(phy->vdd);
+ if (ret) {
+ dev_err(dev, "unable to enable the ssusb vdd_dig\n");
+ goto unconfig_ss_vdd;
+ }
+
+ ret = msm_ssusb_ldo_enable(phy, 1);
+ if (ret) {
+ dev_err(dev, "ssusb vreg enable failed\n");
+ goto disable_ss_vdd;
+ }
+
+ platform_set_drvdata(pdev, phy);
+
+ if (of_property_read_bool(dev->of_node, "qcom,vbus-valid-override"))
+ phy->phy.flags |= PHY_VBUS_VALID_OVERRIDE;
+
+ if (of_property_read_u32(dev->of_node, "qcom,deemphasis-value",
+ &phy->deemphasis_val))
+ dev_dbg(dev, "unable to read ssphy deemphasis value\n");
+
+ phy->phy.init = msm_ssphy_init;
+ phy->phy.set_suspend = msm_ssphy_set_suspend;
+ phy->phy.notify_connect = msm_ssphy_notify_connect;
+ phy->phy.notify_disconnect = msm_ssphy_notify_disconnect;
+ phy->phy.type = USB_PHY_TYPE_USB3;
+
+ ret = usb_add_phy_dev(&phy->phy);
+ if (ret)
+ goto disable_ss_ldo;
+
+ return 0;
+
+disable_ss_ldo:
+ msm_ssusb_ldo_enable(phy, 0);
+disable_ss_vdd:
+ regulator_disable(phy->vdd);
+unconfig_ss_vdd:
+ msm_ssusb_config_vdd(phy, 0);
+
+ return ret;
+}
+
+static int msm_ssphy_remove(struct platform_device *pdev)
+{
+ struct msm_ssphy *phy = platform_get_drvdata(pdev);
+
+ if (!phy)
+ return 0;
+
+ usb_remove_phy(&phy->phy);
+ msm_ssusb_ldo_enable(phy, 0);
+ regulator_disable(phy->vdd);
+ msm_ssusb_config_vdd(phy, 0);
+ kfree(phy);
+
+ return 0;
+}
+
+static const struct of_device_id msm_usb_id_table[] = {
+ {
+ .compatible = "qcom,usb-ssphy",
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, msm_usb_id_table);
+
+static struct platform_driver msm_ssphy_driver = {
+ .probe = msm_ssphy_probe,
+ .remove = msm_ssphy_remove,
+ .driver = {
+ .name = "msm-usb-ssphy",
+ .of_match_table = of_match_ptr(msm_usb_id_table),
+ },
+};
+
+module_platform_driver(msm_ssphy_driver);
+
+MODULE_DESCRIPTION("MSM USB SS PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/phy/phy-msm-usb.c b/drivers/usb/phy/phy-msm-usb.c
index 970a30e155cb..f1360f20ffe4 100644
--- a/drivers/usb/phy/phy-msm-usb.c
+++ b/drivers/usb/phy/phy-msm-usb.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved.
+/* Copyright (c) 2009-2011, 2017 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -1727,7 +1727,7 @@ static int msm_otg_probe(struct platform_device *pdev)
writel(0x1, phy_select);
}
- dev_info(&pdev->dev, "OTG regs = %p\n", motg->regs);
+ dev_info(&pdev->dev, "OTG regs = %pK\n", motg->regs);
motg->irq = platform_get_irq(pdev, 0);
if (motg->irq < 0) {