summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack Pham <jackp@codeaurora.org>2015-04-09 18:43:23 -0700
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-22 11:06:11 -0700
commit56eae7e2c24577b811387727c3078ee1cedaaac5 (patch)
tree5f9832ae34988154a1b21b845170171dde1815c9
parentcb69b6cb90b53c8ddac492e87a6d54a1f3f4327b (diff)
usb: phy: add snapshot of phy-msm drivers
This is a snapshot of phy-msm-{hsusb,ssusb,ssusb-qmp,qusb}.c taken as of msm-3.18 commit 9da4ddc18727 (Merge "clk: msm: clock-gcc: Associate gfx rail voting with gfx3d branch") Also replaced ARCH_MSM dependency with ARCH_QCOM in the Kconfig. Signed-off-by: Jack Pham <jackp@codeaurora.org>
-rw-r--r--Documentation/devicetree/bindings/usb/msm-phy.txt200
-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.c1177
-rw-r--r--drivers/usb/phy/phy-msm-ssusb-qmp.c1112
-rw-r--r--drivers/usb/phy/phy-msm-ssusb.c595
7 files changed, 3986 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/usb/msm-phy.txt b/Documentation/devicetree/bindings/usb/msm-phy.txt
new file mode 100644
index 000000000000..2372ae4361c5
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/msm-phy.txt
@@ -0,0 +1,200 @@
+MSM USB PHY transceivers
+
+HSUSB PHY
+
+Required properties:
+ - compatible: Should be "qcom,usb-hsphy"
+ - reg: Address and length of the register set for the device
+ Required regs are:
+ "core" : the QSCRATCH base register set
+ - <supply-name>-supply: phandle to the regulator device tree node
+ Required "supply-name" examples are:
+ "vdd" : vdd supply for HSPHY digital circuit operation
+ "vdda18" : 1.8v supply for HSPHY
+ "vdda33" : 3.3v supply for HSPHY
+ - qcom,vdd-voltage-level: This property must be a list of three integer
+ values (no, min, max) where each value represents either a voltage in
+ microvolts or a value corresponding to voltage corner
+
+Optional properties:
+ - reg: Additional registers
+ "tcsr" : top-level CSR register to be written during power-on reset
+ initialize the internal MUX that controls whether this PHY is
+ used with the USB3 or the USB2 controller.
+
+ - qcom,hsphy-init: Init value used to override HSPHY parameters into
+ QSCRATCH register. This 32-bit value represents parameters as follows:
+ bits 0-5 PARAMETER_OVERRIDE_A
+ bits 6-12 PARAMETER_OVERRIDE_B
+ bits 13-19 PARAMETER_OVERRIDE_C
+ bits 20-25 PARAMETER_OVERRIDE_D
+ - qcom,ext-vbus-id: If present, indicates that the PHY does not handle VBUS and ID changes.
+ - qcom,vbus-valid-override: If present, indicates VBUS pin is not connected to
+ the USB PHY and the controller must rely on external VBUS notification in
+ order to manually enable the D+ pull-up resistor.
+ - qcom,primary-phy: If present, indicates this is a secondary PHY and is
+ dependent on the primary PHY referenced by this phandle.
+ - qcom,set-pllbtune: If present, PLL tune is required in PHY initialization.
+ - qcom,num_ports: Indicates the number of ports that supported by the HS PHY.
+ If omitted, defaults to 1
+ - qcom,sleep-clk-reset: If present, the HSUSB PHY sleep clock supports the clk_reset
+ operation, and should be called during initialization.
+ - qcom,vdda-force-on: If present, HW requires a workaround that forces 1.8V and 3.1V
+ regulator supplies to be left on even when PHY enters into
+ low power mode.
+
+Example:
+ hsphy@f9200000 {
+ compatible = "qcom,usb-hsphy";
+ reg = <0xf9200000 0xfc000>;
+ qcom,hsphy-init = <0x00D191A4>;
+ vdd-supply = <&pm8841_s2_corner>;
+ vdda18-supply = <&pm8941_l6>;
+ vdda33-supply = <&pm8941_l24>;
+ qcom,num_of_ports = <3>;
+ qcom,vdd-voltage-level = <1 5 7>;
+ };
+
+SSUSB PHY
+
+Required properties:
+ - compatible: Should be "qcom,usb-ssphy" or "qcom,usb-ssphy-qmp-v2"
+ - reg: Address and length of the register set for the device
+ - <supply-name>-supply: phandle to the regulator device tree node
+ Required "supply-name" examples are:
+ "vdd" : vdd supply for SSPHY digital circuit operation
+ "vdda18" : 1.8v high-voltage analog supply for SSPHY
+ - qcom,vdd-voltage-level: This property must be a list of three integer
+ values (no, min, max) where each value represents either a voltage in
+ microvolts or a value corresponding to voltage corner
+
+Optional properties:
+ - qcom,vbus-valid-override: If present, indicates VBUS pin is not connected to
+ the USB PHY and the controller must rely on external VBUS notification in
+ order to manually relay the notification to the SSPHY.
+ - qcom,deemphasis-value: This property if present represents ss phy
+ deemphasis value to be used for overriding into SSPHY register.
+ - qcom,primary-phy: If present, indicates this is a secondary PHY and is
+ dependent on the primary PHY referenced by this phandle.
+ - qcom,override-pll-calibration: If present, program PHY register to overrride
+ the automatic PHY PLL calibration settings.
+ - qcom,qmp-misc-config: If present, program PHY miscellaneous device-specific
+ registers.
+
+Example:
+ ssphy@f9200000 {
+ compatible = "qcom,usb-ssphy";
+ reg = <0xf9200000 0xfc000>;
+ vdd-supply = <&pm8841_s2_corner>;
+ vdda18-supply = <&pm8941_l6>;
+ qcom,vdd-voltage-level = <1 5 7>;
+ qcom,deemphasis-value = <26>;
+ };
+
+SSUSB-QMP PHY
+
+Required properties:
+ - compatible: Should be "qcom,usb-ssphy-qmp", "qcom,usb-ssphy-qmp-v1" or
+ "qcom,usb-ssphy-qmp-v2"
+ - reg: Address and length of the register set for the device
+ Required regs are:
+ "qmp_phy_base" : QMP PHY Base register set.
+ - "vls_clamp_reg" : top-level CSR register to be written to enable phy vls
+ clamp which allows phy to detect autonomous mode.
+ - <supply-name>-supply: phandle to the regulator device tree node
+ Required "supply-name" examples are:
+ "vdd" : vdd supply for SSPHY digital circuit operation
+ "vdda18" : 1.8v high-voltage analog supply for SSPHY
+ - qcom,vdd-voltage-level: This property must be a list of three integer
+ values (no, min, max) where each value represents either a voltage in
+ microvolts or a value corresponding to voltage corner
+
+Optional properties:
+ - reg: Additional register set of address and length to control QMP PHY
+ "tcsr_phy_clk_scheme_sel": Read phy clk scheme single ended vs
+ differential to determine the value to write to QSERDES_COM_SYSCLK_EN_SEL.
+ - qcom,vbus-valid-override: If present, indicates VBUS pin is not connected to
+ the USB PHY and the controller must rely on external VBUS notification in
+ order to manually relay the notification to the SSPHY.
+ - qcom,emulation: Indicates that we are running on emulation platform.
+ - qcom,qmp-phy-init-seq: QMP PHY initialization sequence with reg, diff clk
+ value, single ended clk value, delay after register write.
+ - qcom,qmp-phy-reg-offset: If present stores phy register offsets in an order
+ defined in the phy driver.
+
+Example:
+ ssphy0: ssphy@f9b38000 {
+ compatible = "qcom,usb-ssphy-qmp";
+ reg = <0xf9b38000 0x16c>,
+ <0x01947244 0x4>;
+ reg-names = "qmp_phy_base",
+ "vls_clamp_reg";
+ vdd-supply = <&pmd9635_l4>;
+ vdda18-supply = <&pmd9635_l8>;
+ qcom,vdd-voltage-level = <0 900000 1050000>;
+ qcom,vbus-valid-override;
+ };
+
+QUSB2 High-Speed PHY
+
+Required properties:
+ - compatible: Should be "qcom,qusb2phy"
+ - reg: Address and length of the QUSB2 PHY register set
+ - reg-names: Should be "qusb_phy_base".
+ - <supply-name>-supply: phandle to the regulator device tree node
+ Required supplies are:
+ "vdd" : vdd supply for digital circuit operation
+ "vdda18" : 1.8v high-voltage analog supply
+ "vdda33" : 3.3v high-voltage analog supply
+ - qcom,vdd-voltage-level: This property must be a list of three integer
+ values (no, min, max) where each value represents either a voltage in
+ microvolts or a value corresponding to voltage corner
+ - clocks: a list of phandles to the PHY clocks. Use as per
+ Documentation/devicetree/bindings/clock/clock-bindings.txt
+ - clock-names: Names of the clocks in 1-1 correspondence with the "clocks"
+ property. Required clocks are "cfg_ahb_clk" and "phy_reset".
+ - phy_type: Should be one of "ulpi" or "utmi". ChipIdea core uses "ulpi" mode.
+
+Optional properties:
+ - reg: Address and length register set to control QUSB2 PHY
+ "qscratch_base" : QSCRATCH base register set.
+ "tune2_efuse_addr": EFUSE based register address to read TUNE2 parameter.
+ via the QSCRATCH interface.
+ "emu_phy_base" : phy base address used for programming emulation target phy.
+ "ref_clk_addr" : ref_clk bcr address used for on/off ref_clk before reset.
+ "tcsr_phy_clk_scheme_sel": address used to determine QUSB PHY clk source.
+ - reg-names: Should be "qscratch_base". The qscratch register bank
+ allows us to manipulate QUSB PHY bits eg. to enable D+ pull-up using s/w
+ control in device mode. The reg-names property is required if the
+ reg property is specified.
+ - qcom,qusb-phy-init-seq: QUSB PHY initialization sequence with value,reg pair.
+ - qcom,emu-init-seq : emulation initialization sequence with value,reg pair.
+ - qcom,phy-pll-reset-seq : emulation PLL reset sequence with value,reg pair.
+ - qcom,emu-dcm-reset-seq : emulation DCM reset sequence with value,reg pair.
+ - qcom,tune2-efuse-bit-pos: TUNE2 parameter related start bit position with EFUSE register
+ - qcom,tune2-efuse-num-bits: Number of bits based value to use for TUNE2 high nibble
+ - qcom,emulation: Indicates that we are running on emulation platform.
+ - qcom,hold-reset: Indicates that hold QUSB PHY into reset state.
+ - qcom,enable-dpdm-pulsing: enables dp and dm pulsing for PMIC driver to
+ perform charger detection.
+
+Example:
+ qusb_phy: qusb@f9b39000 {
+ compatible = "qcom,qusb2phy";
+ reg = <0x00079000 0x7000>,
+ <0x08af8800 0x400>;
+ reg-names = "qusb_phy_base",
+ "qscratch_base";
+ vdd-supply = <&pm8994_s2_corner>;
+ vdda18-supply = <&pm8994_l6>;
+ vdda33-supply = <&pm8994_l24>;
+ qcom,vdd-voltage-level = <1 5 7>;
+ qcom,tune2-efuse-bit-pos = <21>;
+ qcom,tune2-efuse-num-bits = <3>;
+
+ clocks = <&clock_rpm clk_ln_bb_clk>,
+ <&clock_gcc clk_gcc_rx2_usb1_clkref_clk>,
+ <&clock_gcc clk_gcc_usb_phy_cfg_ahb2phy_clk>,
+ <&clock_gcc clk_gcc_qusb2_phy_reset>;
+ clock-names = "ref_clk_src", "ref_clk", "cfg_ahb_clk", "phy_reset";
+ };
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig
index 795485eac7b0..732fa214f608 100644
--- a/drivers/usb/phy/Kconfig
+++ b/drivers/usb/phy/Kconfig
@@ -173,6 +173,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..5857f8ee8317 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
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..4782d18fa9cc
--- /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_optimum_mode(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_optimum_mode(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_optimum_mode(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_optimum_mode(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.c b/drivers/usb/phy/phy-msm-qusb.c
new file mode 100644
index 000000000000..c89af792383d
--- /dev/null
+++ b/drivers/usb/phy/phy-msm-qusb.c
@@ -0,0 +1,1177 @@
+/*
+ * Copyright (c) 2014-2015, 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/usb/phy.h>
+#include <linux/usb/msm_hsusb.h>
+
+/* TCSR_PHY_CLK_SCHEME_SEL bit mask */
+#define PHY_CLK_SCHEME_SEL BIT(0)
+
+#define QUSB2PHY_PLL_STATUS 0x38
+#define QUSB2PHY_PLL_LOCK BIT(5)
+
+#define QUSB2PHY_PORT_QC1 0x70
+#define VDM_SRC_EN BIT(4)
+#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_UTMI_CTRL1 0xC0
+#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
+
+/* In case Efuse register shows zero, use this value */
+#define TUNE2_DEFAULT_HIGH_NIBBLE 0xB
+#define TUNE2_DEFAULT_LOW_NIBBLE 0x3
+
+/* 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_UTMI_STATUS 0xF4
+#define LINESTATE_DP BIT(0)
+#define LINESTATE_DM BIT(1)
+
+#define HS_PHY_CTRL_REG 0x10
+#define UTMI_OTG_VBUS_VALID BIT(20)
+#define SW_SESSVLD_SEL BIT(28)
+
+#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 tune2;
+module_param(tune2, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(tune2, "QUSB PHY TUNE2");
+
+struct qusb_phy {
+ struct usb_phy phy;
+ void __iomem *base;
+ void __iomem *qscratch_base;
+ void __iomem *tune2_efuse_reg;
+ void __iomem *ref_clk_base;
+ void __iomem *tcsr_phy_clk_scheme_sel;
+
+ struct clk *ref_clk_src;
+ struct clk *ref_clk;
+ struct clk *cfg_ahb_clk;
+ struct clk *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 tune2_val;
+ int tune2_efuse_bit_pos;
+ int tune2_efuse_num_of_bits;
+
+ bool power_enabled;
+ bool clocks_enabled;
+ bool cable_connected;
+ bool suspended;
+ bool ulpi_mode;
+ bool rm_pulldown;
+ bool dpdm_pulsing_enabled;
+
+ /* 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;
+ spinlock_t pulse_lock;
+};
+
+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,
+ bool toggle_vdd)
+{
+ int ret = 0;
+
+ dev_dbg(qphy->phy.dev, "%s turn %s regulators. power_enabled:%d\n",
+ __func__, on ? "on" : "off", qphy->power_enabled);
+
+ if (toggle_vdd && qphy->power_enabled == on) {
+ dev_dbg(qphy->phy.dev, "PHYs' regulators are already ON.\n");
+ return 0;
+ }
+
+ if (!on)
+ goto disable_vdda33;
+
+ if (toggle_vdd) {
+ 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_optimum_mode(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_optimum_mode(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;
+ }
+
+ if (toggle_vdd)
+ 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_optimum_mode(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_optimum_mode(qphy->vdda18, 0);
+ if (ret < 0)
+ dev_err(qphy->phy.dev, "Unable to set LPM of vdda18\n");
+
+disable_vdd:
+ if (toggle_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:
+ if (toggle_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);
+ unsigned long flags;
+ int ret = 0;
+ u32 reg;
+
+ dev_dbg(phy->dev, "%s value:%d rm_pulldown:%d pulsing enabled %d\n",
+ __func__, value, qphy->rm_pulldown,
+ qphy->dpdm_pulsing_enabled);
+
+ 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, false);
+ if (ret >= 0) {
+ qphy->rm_pulldown = true;
+ dev_dbg(phy->dev, "DP_DM_F: rm_pulldown:%d\n",
+ qphy->rm_pulldown);
+ }
+ }
+
+ /* 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);
+ }
+ break;
+
+ case POWER_SUPPLY_DP_DM_DPR_DMR:
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DPR_DMR\n");
+ 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);
+ }
+
+ ret = qusb_phy_enable_power(qphy, false, false);
+ if (ret >= 0) {
+ qphy->rm_pulldown = false;
+ dev_dbg(phy->dev, "DP_DM_R: rm_pulldown:%d\n",
+ qphy->rm_pulldown);
+ }
+ }
+ 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 void qusb_phy_get_tune2_param(struct qusb_phy *qphy)
+{
+ u8 num_of_bits;
+ u32 bit_mask = 1;
+
+ 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 default value
+ * as 0xB as high nibble. 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);
+
+ if (!qphy->tune2_val)
+ qphy->tune2_val = TUNE2_DEFAULT_HIGH_NIBBLE;
+
+ /* Get TUNE2 byte value using high and low nibble value */
+ qphy->tune2_val = ((qphy->tune2_val << 0x4) |
+ TUNE2_DEFAULT_LOW_NIBBLE);
+}
+
+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;
+ bool is_se_clk = true;
+
+ dev_dbg(phy->dev, "%s\n", __func__);
+
+ ret = qusb_phy_enable_power(qphy, true, 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 */
+ clk_reset(qphy->phy_reset, CLK_RESET_ASSERT);
+ usleep_range(100, 150);
+ clk_reset(qphy->phy_reset, CLK_RESET_DEASSERT);
+
+ 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(CLAMP_N_EN | FREEZIO_N | POWER_DOWN,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+
+ /* 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 */
+ 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) {
+ 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 tune2 modparam set, override tune2 value */
+ if (tune2) {
+ pr_debug("%s(): (modparam) TUNE2 val:0x%02x\n",
+ __func__, tune2);
+ writel_relaxed(tune2,
+ qphy->base + QUSB2PHY_PORT_TUNE2);
+ }
+
+ /* ensure above writes are completed before re-enabling PHY */
+ wmb();
+
+ /* Enable the PHY */
+ writel_relaxed(CLAMP_N_EN | FREEZIO_N,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+
+ /* Ensure above write is completed before turning ON ref clk */
+ wmb();
+
+ /* Require to get phy pll lock successfully */
+ usleep_range(150, 160);
+
+ if (qphy->tcsr_phy_clk_scheme_sel) {
+ ret = readl_relaxed(qphy->tcsr_phy_clk_scheme_sel);
+ if (ret & PHY_CLK_SCHEME_SEL) {
+ pr_debug("%s:select single-ended clk src\n",
+ __func__);
+ is_se_clk = true;
+ } else {
+ pr_debug("%s:select differential clk src\n",
+ __func__);
+ is_se_clk = false;
+ }
+ }
+
+ if (!is_se_clk)
+ reset_val &= ~CLK_REF_SEL;
+ else
+ reset_val |= CLK_REF_SEL;
+
+ /* Turn on phy ref_clk if DIFF_CLK else select SE_CLK */
+ if (!is_se_clk && qphy->ref_clk_base)
+ writel_relaxed((readl_relaxed(qphy->ref_clk_base) |
+ QUSB2PHY_REFCLK_ENABLE),
+ qphy->ref_clk_base);
+ else
+ writel_relaxed(reset_val, qphy->base + QUSB2PHY_PLL_TEST);
+
+ /* Make sure that above write is completed to get PLL source clock */
+ wmb();
+
+ /* Required to get PHY PLL lock successfully */
+ usleep_range(100, 110);
+
+ if (!(readb_relaxed(qphy->base + QUSB2PHY_PLL_STATUS) &
+ QUSB2PHY_PLL_LOCK)) {
+ dev_err(phy->dev, "QUSB PHY PLL LOCK fails:%x\n",
+ readb_relaxed(qphy->base + QUSB2PHY_PLL_STATUS));
+ 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(CLAMP_N_EN | FREEZIO_N | POWER_DOWN,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+ wmb();
+
+ qusb_phy_enable_clocks(qphy, false);
+}
+/**
+ * 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);
+
+ qusb_phy_enable_clocks(qphy, false);
+ } else { /* Disconnect case */
+ /* Disable all interrupts */
+ writel_relaxed(0x00,
+ qphy->base + QUSB2PHY_PORT_INTR_CTRL);
+ /*
+ * 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);
+
+
+ qusb_phy_enable_clocks(qphy, false);
+ qusb_phy_enable_power(qphy, false, 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, true);
+ qusb_phy_enable_clocks(qphy, true);
+ }
+ qphy->suspended = false;
+ }
+
+ return 0;
+}
+
+static void qusb_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 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, " cable_connected=%d\n", qphy->cable_connected);
+
+ /* Set OTG VBUS Valid from HSPHY to controller */
+ qusb_write_readback(qphy->qscratch_base, HS_PHY_CTRL_REG,
+ UTMI_OTG_VBUS_VALID,
+ UTMI_OTG_VBUS_VALID);
+
+ /* Indicate value is driven by UTMI_OTG_VBUS_VALID bit */
+ qusb_write_readback(qphy->qscratch_base, HS_PHY_CTRL_REG,
+ SW_SESSVLD_SEL, SW_SESSVLD_SEL);
+
+ dev_dbg(phy->dev, "QUSB2 phy connect notification\n");
+ 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, " cable_connected=%d\n", qphy->cable_connected);
+
+ /* Set OTG VBUS Valid from HSPHY to controller */
+ qusb_write_readback(qphy->qscratch_base, HS_PHY_CTRL_REG,
+ UTMI_OTG_VBUS_VALID, 0);
+
+ /* Indicate value is driven by UTMI_OTG_VBUS_VALID bit */
+ qusb_write_readback(qphy->qscratch_base, HS_PHY_CTRL_REG,
+ SW_SESSVLD_SEL, 0);
+
+ dev_dbg(phy->dev, "QUSB2 phy disconnect notification\n");
+ 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;
+
+ qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL);
+ if (!qphy)
+ return -ENOMEM;
+
+ qphy->phy.dev = dev;
+ spin_lock_init(&qphy->pulse_lock);
+
+ 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,
+ "qscratch_base");
+ if (res) {
+ qphy->qscratch_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(qphy->qscratch_base)) {
+ dev_dbg(dev, "couldn't ioremap qscratch_base\n");
+ qphy->qscratch_base = NULL;
+ }
+ }
+
+ 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);
+ }
+
+ 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");
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "tcsr_phy_clk_scheme_sel");
+ if (res) {
+ qphy->tcsr_phy_clk_scheme_sel = devm_ioremap_nocache(dev,
+ res->start, resource_size(res));
+ if (IS_ERR(qphy->tcsr_phy_clk_scheme_sel))
+ dev_dbg(dev, "err reading tcsr_phy_clk_scheme_sel\n");
+ }
+
+ qphy->dpdm_pulsing_enabled = of_property_read_bool(dev->of_node,
+ "qcom,enable-dpdm-pulsing");
+
+ 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_clk_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,qemu-init-seq",
+ qphy->emu_init_seq,
+ qphy->emu_init_seq_len);
+ } else {
+ dev_dbg(dev, "error allocating memory for emu_init_seq\n");
+ }
+ }
+
+ 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");
+ }
+ }
+
+ 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");
+ }
+ }
+
+ 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");
+ 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);
+ }
+
+ 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.change_dpdm = qusb_phy_update_dpdm;
+ qphy->phy.type = USB_PHY_TYPE_USB2;
+
+ if (qphy->qscratch_base) {
+ qphy->phy.notify_connect = qusb_phy_notify_connect;
+ qphy->phy.notify_disconnect = qusb_phy_notify_disconnect;
+ }
+
+ /*
+ * 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)
+ clk_reset(qphy->phy_reset, CLK_RESET_ASSERT);
+
+ ret = usb_add_phy_dev(&qphy->phy);
+ 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, true);
+
+ 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..099beb8f9027
--- /dev/null
+++ b/drivers/usb/phy/phy-msm-ssusb-qmp.c
@@ -0,0 +1,1112 @@
+/*
+ * Copyright (c) 2013-2015, 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>
+
+#define INIT_MAX_TIME_USEC 1000
+
+#define USB_SSPHY_1P8_VOL_MIN 1800000 /* uV */
+#define USB_SSPHY_1P8_VOL_MAX 1800000 /* uV */
+#define USB_SSPHY_1P8_HPM_LOAD 23000 /* uA */
+
+
+/* USB3PHY_PCIE_USB3_PCS_PCS_STATUS bit */
+#define PHYSTATUS BIT(6)
+
+/* TCSR_PHY_CLK_SCHEME_SEL bit mask */
+#define PHY_CLK_SCHEME_SEL BIT(0)
+
+/* 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)
+
+enum qmp_phy_rev_reg {
+ USB3_REVISION_ID0,
+ USB3_REVISION_ID1,
+ USB3_REVISION_ID2,
+ USB3_REVISION_ID3,
+ 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_REG_MAX,
+};
+
+/* QMP PHY register offset for rev1 */
+unsigned int qmp_phy_rev1[] = {
+ [USB3_REVISION_ID0] = 0x730,
+ [USB3_REVISION_ID1] = 0x734,
+ [USB3_REVISION_ID2] = 0x738,
+ [USB3_REVISION_ID3] = 0x73c,
+ [USB3_PHY_PCS_STATUS] = 0x728,
+ [USB3_PHY_AUTONOMOUS_MODE_CTRL] = 0x6BC,
+ [USB3_PHY_LFPS_RXTERM_IRQ_CLEAR] = 0x6C0,
+ [USB3_PHY_POWER_DOWN_CONTROL] = 0x604,
+ [USB3_PHY_SW_RESET] = 0x600,
+ [USB3_PHY_START] = 0x608,
+};
+
+/* QMP PHY register offset for rev2 */
+unsigned int qmp_phy_rev2[] = {
+ [USB3_REVISION_ID0] = 0x788,
+ [USB3_REVISION_ID1] = 0x78C,
+ [USB3_REVISION_ID2] = 0x790,
+ [USB3_REVISION_ID3] = 0x794,
+ [USB3_PHY_PCS_STATUS] = 0x77C,
+ [USB3_PHY_AUTONOMOUS_MODE_CTRL] = 0x6D4,
+ [USB3_PHY_LFPS_RXTERM_IRQ_CLEAR] = 0x6D8,
+ [USB3_PHY_POWER_DOWN_CONTROL] = 0x604,
+ [USB3_PHY_SW_RESET] = 0x600,
+ [USB3_PHY_START] = 0x608,
+};
+
+/* reg values to write based on the phy clk scheme selected */
+struct qmp_reg_val {
+ u32 offset;
+ u32 diff_clk_sel_val;
+ u32 se_clk_sel_val;
+ u32 delay;
+};
+
+/* Use these offsets/values if PCIE_USB3_PHY_REVISION_ID0 == 0 */
+static const struct qmp_reg_val qmp_settings_rev0[] = {
+ {0x48, 0x08}, /* QSERDES_COM_SYSCLK_EN_SEL_TXBAND */
+ {0xA4, 0x82}, /* QSERDES_COM_DEC_START1 */
+ {0x104, 0x03}, /* QSERDES_COM_DEC_START2 */
+ {0xF8, 0xD5}, /* QSERDES_COM_DIV_FRAC_START1 */
+ {0xFC, 0xAA}, /* QSERDES_COM_DIV_FRAC_START2 */
+ {0x100, 0x4D}, /* QSERDES_COM_DIV_FRAC_START3 */
+ {0x94, 0x11}, /* QSERDES_COM_PLLLOCK_CMP_EN */
+ {0x88, 0x2B}, /* QSERDES_COM_PLLLOCK_CMP1 */
+ {0x8C, 0x68}, /* QSERDES_COM_PLLLOCK_CMP2 */
+ {0x10C, 0x7C}, /* QSERDES_COM_PLL_CRCTRL */
+ {0x34, 0x07}, /* QSERDES_COM_PLL_CP_SETI */
+ {0x38, 0x1F}, /* QSERDES_COM_PLL_IP_SETP */
+ {0x3C, 0x0F}, /* QSERDES_COM_PLL_CP_SETP */
+ {0x24, 0x01}, /* QSERDES_COM_PLL_IP_SETI */
+ {0x0C, 0x0F}, /* QSERDES_COM_IE_TRIM */
+ {0x10, 0x0F}, /* QSERDES_COM_IP_TRIM */
+ {0x14, 0x46}, /* QSERDES_COM_PLL_CNTRL */
+
+ /* CDR Settings */
+ {0x400, 0xDA}, /* QSERDES_RX_CDR_CONTROL1 */
+ {0x404, 0x42}, /* QSERDES_RX_CDR_CONTROL2 */
+ {0x41c, 0x75}, /* QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE */
+
+ /* Calibration Settings */
+ {0x4C, 0x90}, /* QSERDES_COM_RESETSM_CNTRL */
+ {0x50, 0x05}, /* QSERDES_COM_RESETSM_CNTRL2 */
+
+ {0xD8, 0x20}, /* QSERDES_COM_RES_CODE_START_SEG1 */
+ {0xE0, 0x77}, /* QSERDES_COM_RES_CODE_CAL_CSR */
+ {0xE8, 0x15}, /* QSERDES_COM_RES_TRIM_CONTROL */
+ {0x268, 0x02}, /* QSERDES_TX_RCV_DETECT_LVL */
+ {0x4F0, 0x67}, /* QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1 */
+ {0x4F4, 0x80}, /* QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2 */
+ {0x4BC, 0x06}, /* QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 */
+ {0x4C0, 0x6C}, /* QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3 */
+ {0x4C4, 0xA7}, /* QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4 */
+ {0x4F8, 0x40}, /* QSERDES_RX_SIGDET_ENABLES */
+ {0x500, 0x73}, /* QSERDES_RX_SIGDET_CNTRL */
+ {0x504, 0x06}, /* QSERDES_RX_SIGDET_DEGLITCH_CNTRL */
+
+ {0x64C, 0x48}, /* PCIE_USB3_PHY_RX_IDLE_DTCT_CNTRL */
+ {0xAC, 0x01}, /* QSERDES_COM_SSC_EN_CENTER */
+ {0xB0, 0x02}, /* QSERDES_COM_SSC_ADJ_PER1 */
+ {0xB8, 0x31}, /* QSERDES_COM_SSC_PER1 */
+ {0xBC, 0x01}, /* QSERDES_COM_SSC_PER2 */
+ {0xC0, 0x19}, /* QSERDES_COM_SSC_STEP_SIZE1 */
+ {0xC4, 0x19}, /* QSERDES_COM_SSC_STEP_SIZE2 */
+ {0x654, 0x08}, /* PCIE_USB3_PHY_POWER_STATE_CONFIG2 */
+ {0x65C, 0xE5}, /* PCIE_USB3_PHY_RCVR_DTCT_DLY_P1U2_L */
+ {0x660, 0x03}, /* PCIE_USB3_PHY_RCVR_DTCT_DLY_P1U2_H */
+ {0x6A0, 0x13}, /* PCIE_USB3_PHY_RXEQTRAINING_RUN_TIME */
+ {0x66C, 0xFF}, /* PCIE_USB3_PHY_LOCK_DETECT_CONFIG1 */
+ {0x674, 0x17}, /* PCIE_USB3_PHY_LOCK_DETECT_CONFIG3 */
+ {0x6AC, 0x05}, /* PCIE_USB3_PHY_FLL_CNTRL2 */
+
+ {-1, 0x00} /* terminating entry */
+};
+
+/*
+ * Use these offsets/values if PCIE_USB3_PHY_REVISION_ID0 == 1
+ * QSERDES_COM registers between 0x58 and 0x14C been moved (added) 8 bytes
+ */
+static const struct qmp_reg_val qmp_settings_rev1[] = {
+ {0x48, 0x08}, /* QSERDES_COM_SYSCLK_EN_SEL_TXBAND */
+ {0xAC, 0x82}, /* QSERDES_COM_DEC_START1 */
+ {0x10C, 0x03}, /* QSERDES_COM_DEC_START2 */
+ {0x100, 0xD5}, /* QSERDES_COM_DIV_FRAC_START1 */
+ {0x104, 0xAA}, /* QSERDES_COM_DIV_FRAC_START2 */
+ {0x108, 0x4D}, /* QSERDES_COM_DIV_FRAC_START3 */
+ {0x9C, 0x11}, /* QSERDES_COM_PLLLOCK_CMP_EN */
+ {0x90, 0x2B}, /* QSERDES_COM_PLLLOCK_CMP1 */
+ {0x94, 0x68}, /* QSERDES_COM_PLLLOCK_CMP2 */
+ {0x114, 0x7C}, /* QSERDES_COM_PLL_CRCTRL */
+ {0x34, 0x1F}, /* QSERDES_COM_PLL_CP_SETI */
+ {0x38, 0x12}, /* QSERDES_COM_PLL_IP_SETP */
+ {0x3C, 0x0F}, /* QSERDES_COM_PLL_CP_SETP */
+ {0x24, 0x01}, /* QSERDES_COM_PLL_IP_SETI */
+ {0x0C, 0x0F}, /* QSERDES_COM_IE_TRIM */
+ {0x10, 0x0F}, /* QSERDES_COM_IP_TRIM */
+ {0x14, 0x46}, /* QSERDES_COM_PLL_CNTRL */
+
+ /* CDR Settings */
+ {0x41C, 0x75}, /* QSERDEX_RX_UCDR_SO_SATURATION_AND_ENABLE */
+
+ /* Calibration Settings */
+ {0x4C, 0x90}, /* QSERDES_COM_RESETSM_CNTRL */
+ {0x50, 0x07}, /* QSERDES_COM_RESETSM_CNTRL2 */
+ {0x04, 0xE1}, /* QSERDES_COM_PLL_VCOTAIL_EN */
+
+ {0xE0, 0x24}, /* QSERDES_COM_RES_CODE_START_SEG1 */
+ {0xE8, 0x77}, /* QSERDES_COM_RES_CODE_CAL_CSR */
+ {0xF0, 0x15}, /* QSERDES_COM_RES_TRIM_CONTROL */
+ {0x268, 0x02}, /* QSERDES_TX_RCV_DETECT_LVL */
+ {0x4F0, 0x67}, /* QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1 */
+ {0x4F4, 0x80}, /* QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2 */
+ {0x4BC, 0x06}, /* QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 */
+ {0x4C0, 0x6C}, /* QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3 */
+ {0x4C4, 0xA7}, /* QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4 */
+ {0x4F8, 0x40}, /* QSERDES_RX_SIGDET_ENABLES */
+ {0x500, 0x73}, /* QSERDES_RX_SIGDET_CNTRL */
+ {0x504, 0x06}, /* QSERDES_RX_SIGDET_DEGLITCH_CNTRL */
+ {0x64C, 0x48}, /* PCIE_USB3_PHY_RX_IDLE_DTCT_CNTRL */
+ {0xB4, 0x01}, /* QSERDES_COM_SSC_EN_CENTER */
+ {0xB8, 0x02}, /* QSERDES_COM_SSC_ADJ_PER1 */
+ {0xC0, 0x31}, /* QSERDES_COM_SSC_PER1 */
+ {0xC4, 0x01}, /* QSERDES_COM_SSC_PER2 */
+ {0xC8, 0x19}, /* QSERDES_COM_SSC_STEP_SIZE1 */
+ {0xCC, 0x19}, /* QSERDES_COM_SSC_STEP_SIZE2 */
+ {0x654, 0x08}, /* PCIE_USB3_PHY_POWER_STATE_CONFIG2 */
+ {0x65C, 0xE5}, /* PCIE_USB3_PHY_RCVR_DTCT_DLY_P1U2_L */
+ {0x660, 0x03}, /* PCIE_USB3_PHY_RCVR_DTCT_DLY_P1U2_H */
+ {0x6A0, 0x13}, /* PCIE_USB3_PHY_RXEQTRAINING_RUN_TIME */
+ {0x66C, 0xFF}, /* PCIE_USB3_PHY_LOCK_DETECT_CONFIG1 */
+ {0x674, 0x17}, /* PCIE_USB3_PHY_LOCK_DETECT_CONFIG3 */
+ {0x6AC, 0x05}, /* PCIE_USB3_PHY_FLL_CNTRL2 */
+
+ {-1, 0x00} /* terminating entry */
+};
+
+/* USB3PHY_REVISION_ID3 = 0x20 where register offset is being changed. */
+static const struct qmp_reg_val qmp_settings_rev2[] = {
+ /* Common block settings */
+ {0xAC, 0x14}, /* QSERDES_COM_SYSCLK_EN_SEL */
+ {0x34, 0x08}, /* QSERDES_COM_BIAS_EN_CLKBUFLR_EN */
+ {0x174, 0x30}, /* QSERDES_COM_CLK_SELECT */
+ {0x194, 0x06}, /* QSERDES_COM_CMN_CONFIG */
+ {0x19C, 0x01}, /* QSERDES_COM_SVS_MODE_CLK_SEL */
+ {0x178, 0x00}, /* QSERDES_COM_HSCLK_SEL */
+ {0x70, 0x0F}, /* USB3PHY_QSERDES_COM_BG_TRIM */
+ {0x48, 0x0F}, /* USB3PHY_QSERDES_COM_PLL_IVCO */
+ {0x3C, 0x04}, /* QSERDES_COM_SYS_CLK_CTRL */
+
+ /* PLL and Loop filter settings */
+ {0xD0, 0x82}, /* QSERDES_COM_DEC_START_MODE0 */
+ {0xDC, 0x55}, /* QSERDES_COM_DIV_FRAC_START1_MODE0 */
+ {0xE0, 0x55}, /* QSERDES_COM_DIV_FRAC_START2_MODE0 */
+ {0xE4, 0x03}, /* QSERDES_COM_DIV_FRAC_START3_MODE0 */
+ {0x78, 0x0B}, /* QSERDES_COM_CP_CTRL_MODE0 */
+ {0x84, 0x16}, /* QSERDES_COM_PLL_RCTRL_MODE0 */
+ {0x90, 0x28}, /* QSERDES_COM_PLL_CCTRL_MODE0 */
+ {0x108, 0x80}, /*QSERDES_COM_INTEGLOOP_GAIN0_MODE0 */
+ {0x124, 0x00}, /* USB3PHY_QSERDES_COM_VCO_TUNE_CTRL */
+ {0x4C, 0x15}, /* QSERDES_COM_LOCK_CMP1_MODE0 */
+ {0x50, 0x34}, /* QSERDES_COM_LOCK_CMP2_MODE0 */
+ {0x54, 0x00}, /* QSERDES_COM_LOCK_CMP3_MODE0 */
+ {0x18C, 0x00}, /* QSERDES_COM_CORE_CLK_EN */
+ {0xCC, 0x00}, /* QSERDES_COM_LOCK_CMP_CFG */
+ {0x128, 0x00}, /* QSERDES_COM_VCO_TUNE_MAP */
+ {0x0C, 0x0A}, /* QSERDES_COM_BG_TIMER */
+
+ /* SSC settings */
+ {0x10, 0x01}, /* QSERDES_COM_SSC_EN_CENTER */
+ {0x1C, 0x31}, /* QSERDES_COM_SSC_PER1 */
+ {0x20, 0x01}, /* QSERDES_COM_SSC_PER2 */
+ {0x14, 0x00}, /* QSERDES_COM_SSC_ADJ_PER1 */
+ {0x18, 0x00}, /* QSERDES_COM_SSC_ADJ_PER2 */
+ {0x24, 0xDE}, /* QSERDES_COM_SSC_STEP_SIZE1 */
+ {0x28, 0x07}, /* QSERDES_COM_SSC_STEP_SIZE2 */
+
+ /* Rx settings */
+ {0x440, 0x0B}, /* QSERDES_RX_UCDR_FASTLOCK_FO_GAIN */
+ {0x41C, 0x04}, /* QSERDES_RX_UCDR_SO_GAIN */
+ {0x4D8, 0x02}, /* QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 */
+ {0x4DC, 0x4C}, /* QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3 */
+ {0x4E0, 0xBB}, /* QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4 */
+ {0x508, 0x77}, /* QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1 */
+ {0x50C, 0x80}, /* QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2 */
+ {0x514, 0x03}, /* QSERDES_RX_SIGDET_CNTRL */
+ {0x518, 0x18}, /* QSERDES_RX_SIGDET_LVL */
+ {0x51C, 0x16}, /* QSERDES_RX_SIGDET_DEGLITCH_CNTRL */
+
+ /* TX settings */
+ {0x268, 0x45}, /* QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN */
+ {0x2AC, 0x12}, /* QSERDES_TX_RCV_DETECT_LVL_2 */
+ {0x294, 0x06}, /* QSERDES_TX_LANE_MODE */
+
+ /* FLL settings */
+ {0x6C4, 0x03}, /* USB3_PHY_FLL_CNTRL2 */
+ {0x6C0, 0x02}, /* USB3_PHY_FLL_CNTRL1 */
+ {0x6C8, 0x09}, /* USB3_PHY_FLL_CNT_VAL_L */
+ {0x6CC, 0x42}, /* USB3_PHY_FLL_CNT_VAL_H_TOL */
+ {0x6D0, 0x85}, /* USB3_PHY_FLL_MAN_CODE */
+
+ /* Lock Det settings */
+ {0x680, 0xD1}, /* USB3_PHY_LOCK_DETECT_CONFIG1 */
+ {0x684, 0x1F}, /* USB3_PHY_LOCK_DETECT_CONFIG2 */
+ {0x688, 0x47}, /* USB3_PHY_LOCK_DETECT_CONFIG3 */
+ {0x664, 0x08}, /* USB3_PHY_POWER_STATE_CONFIG2 */
+
+ {-1, 0x00} /* terminating entry */
+};
+
+/* Override for QMP PHY revision 2 */
+static const struct qmp_reg_val qmp_settings_rev2_misc[] = {
+ {0x178, 0x01}, /* QSERDES_COM_HSCLK_SEL */
+
+ /* Rx settings */
+ {0x518, 0x1B}, /* QSERDES_RX_SIGDET_LVL */
+
+ /* Res_code settings */
+ {0xC4, 0x15}, /* USB3PHY_QSERDES_COM_RESCODE_DIV_NUM */
+ {0x1B8, 0x1F}, /* QSERDES_COM_CMN_MISC2 */
+ {-1, 0x00} /* terminating entry */
+};
+
+/* Override PLL Calibration */
+static const struct qmp_reg_val qmp_override_pll[] = {
+ {0x04, 0xE1}, /* QSERDES_COM_PLL_VCOTAIL_EN */
+ {0x50, 0x07}, /* QSERDES_COM_RESETSM_CNTRL2 */
+ {-1, 0x00} /* terminating entry */
+};
+
+/* Foundry specific settings */
+static const struct qmp_reg_val qmp_settings_rev0_misc[] = {
+ {0x10C, 0x37}, /* QSERDES_COM_PLL_CRCTRL */
+ {0x34, 0x04}, /* QSERDES_COM_PLL_CP_SETI */
+ {0x38, 0x32}, /* QSERDES_COM_PLL_IP_SETP */
+ {0x3C, 0x05}, /* QSERDES_COM_PLL_CP_SETP */
+ {0x500, 0xF7}, /* QSERDES_RX_SIGDET_CNTRL */
+ {0x4A8, 0xFF}, /* QSERDES_RX_RX_EQ_GAIN1_LSB */
+ {0x6B0, 0xF4}, /* PCIE_USB3_PHY_FLL_CNT_VAL_L */
+ {0x6B4, 0x41}, /* PCIE_USB3_PHY_FLL_CNT_VAL_H_TOL */
+ {-1, 0x00} /* terminating entry */
+};
+
+/* Vbg related settings */
+static const struct qmp_reg_val qmp_settings_rev1_misc[] = {
+ {0x0C, 0x03}, /* QSERDES_COM_IE_TRIM */
+ {0x10, 0x00}, /* QSERDES_COM_IP_TRIM */
+ {0xA0, 0xFF}, /* QSERDES_COM_BGTC */
+ {-1, 0x00} /* terminating entry */
+};
+
+struct msm_ssphy_qmp {
+ struct usb_phy phy;
+ void __iomem *base;
+ void __iomem *vls_clamp_reg;
+ void __iomem *tcsr_phy_clk_scheme_sel;
+
+ struct regulator *vdd;
+ struct regulator *vdda18;
+ int vdd_levels[3]; /* none, low, high */
+ struct clk *ref_clk_src;
+ struct clk *ref_clk;
+ struct clk *aux_clk;
+ struct clk *cfg_ahb_clk;
+ struct clk *pipe_clk;
+ struct clk *phy_reset;
+ struct clk *phy_phy_reset;
+ bool clk_enabled;
+ bool cable_connected;
+ bool in_suspend;
+ bool override_pll_cal;
+ bool emulation;
+ bool misc_config;
+ 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",
+ .data = qmp_phy_rev1,
+ },
+ {
+ .compatible = "qcom,usb-ssphy-qmp-v2",
+ .data = qmp_phy_rev2,
+ },
+ { },
+};
+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);
+ if (phy->phy.flags & DEVICE_IN_SS_MODE) {
+ val =
+ readb_relaxed(phy->base + autonomous_mode_offset);
+ val |= ARCVR_DTCT_EN;
+ 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_ssusb_qmp_config_vdd(struct msm_ssphy_qmp *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, "min_vol:%d max_vol:%d\n",
+ phy->vdd_levels[min], phy->vdd_levels[2]);
+ return ret;
+}
+
+static int msm_ssusb_qmp_ldo_enable(struct msm_ssphy_qmp *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_optimum_mode(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_optimum_mode(phy->vdda18, 0);
+ if (rc < 0)
+ dev_err(phy->phy.dev, "Unable to set LPM of vdda18\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);
+ u32 val;
+ bool diff_clk_sel = true;
+
+ if (!reg) {
+ dev_err(uphy->dev, "NULL PHY configuration\n");
+ return -EINVAL;
+ }
+
+ if (phy->tcsr_phy_clk_scheme_sel) {
+ val = readl_relaxed(phy->tcsr_phy_clk_scheme_sel);
+ if (val & PHY_CLK_SCHEME_SEL) {
+ pr_debug("%s:Single Ended clk scheme is selected\n",
+ __func__);
+ diff_clk_sel = false;
+ }
+ }
+
+ while (reg->offset != -1) {
+ writel_relaxed(diff_clk_sel ?
+ reg->diff_clk_sel_val : reg->se_clk_sel_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;
+ unsigned init_timeout_usec = INIT_MAX_TIME_USEC;
+ u32 revid;
+ const struct qmp_reg_val *reg = NULL, *misc = NULL, *pll = NULL;
+
+ dev_dbg(uphy->dev, "Initializing QMP phy\n");
+
+ if (phy->emulation)
+ return 0;
+
+ 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;
+ }
+
+ /* Rev ID is made up each of the LSBs of REVISION_ID[0-3] */
+ revid = (readl_relaxed(phy->base +
+ phy->phy_reg[USB3_REVISION_ID3]) & 0xFF) << 24;
+ revid |= (readl_relaxed(phy->base +
+ phy->phy_reg[USB3_REVISION_ID2]) & 0xFF) << 16;
+ revid |= (readl_relaxed(phy->base +
+ phy->phy_reg[USB3_REVISION_ID1]) & 0xFF) << 8;
+ revid |= readl_relaxed(phy->base +
+ phy->phy_reg[USB3_REVISION_ID0]) & 0xFF;
+
+ pll = qmp_override_pll;
+
+ switch (revid) {
+ case 0x10000000:
+ reg = qmp_settings_rev0;
+ misc = qmp_settings_rev0_misc;
+ break;
+ case 0x10000001:
+ reg = qmp_settings_rev1;
+ misc = qmp_settings_rev1_misc;
+ break;
+ case 0x20000000:
+ case 0x20000001:
+ reg = qmp_settings_rev2;
+ misc = qmp_settings_rev2_misc;
+ break;
+ default:
+ dev_err(uphy->dev, "Unknown revid 0x%x, cannot initialize PHY\n",
+ revid);
+ return -ENODEV;
+ }
+
+ if (phy->qmp_phy_init_seq)
+ reg = (struct qmp_reg_val *)phy->qmp_phy_init_seq;
+
+ 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();
+
+ /* Main configuration */
+ ret = configure_phy_regs(uphy, reg);
+ if (ret) {
+ dev_err(uphy->dev, "Failed the main PHY configuration\n");
+ return ret;
+ }
+
+ /* Feature specific configurations */
+ if (phy->override_pll_cal) {
+ ret = configure_phy_regs(uphy, pll);
+ if (ret) {
+ dev_err(uphy->dev,
+ "Failed the PHY PLL override configuration\n");
+ return ret;
+ }
+ }
+ if (phy->misc_config) {
+ ret = configure_phy_regs(uphy, misc);
+ if (ret) {
+ dev_err(uphy->dev, "Failed the misc PHY configuration\n");
+ return ret;
+ }
+ }
+
+ writel_relaxed(0x03, phy->base + phy->phy_reg[USB3_PHY_START]);
+ writel_relaxed(0x00, phy->base + phy->phy_reg[USB3_PHY_SW_RESET]);
+
+
+ /* 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 */
+ if (phy->phy_phy_reset) {
+ ret = clk_reset(phy->phy_phy_reset, CLK_RESET_ASSERT);
+ if (ret) {
+ dev_err(uphy->dev, "phy_phy reset assert failed\n");
+ goto exit;
+ }
+ } else {
+ ret = clk_reset(phy->pipe_clk, CLK_RESET_ASSERT);
+ if (ret) {
+ dev_err(uphy->dev, "pipe_clk reset assert failed\n");
+ goto exit;
+ }
+ }
+
+ /* Assert USB3 PHY CSR reset */
+ ret = clk_reset(phy->phy_reset, CLK_RESET_ASSERT);
+ if (ret) {
+ dev_err(uphy->dev, "phy_reset clk assert failed\n");
+ goto deassert_phy_phy_reset;
+ }
+
+ /* Deassert USB3 PHY CSR reset */
+ ret = clk_reset(phy->phy_reset, CLK_RESET_DEASSERT);
+ if (ret) {
+ dev_err(uphy->dev, "phy_reset clk deassert failed\n");
+ goto deassert_phy_phy_reset;
+ }
+
+ /* Deassert USB3 PHY reset */
+ if (phy->phy_phy_reset) {
+ ret = clk_reset(phy->phy_phy_reset, CLK_RESET_DEASSERT);
+ if (ret) {
+ dev_err(uphy->dev, "phy_phy reset deassert failed\n");
+ goto exit;
+ }
+ } else {
+ ret = clk_reset(phy->pipe_clk, CLK_RESET_DEASSERT);
+ if (ret) {
+ dev_err(uphy->dev, "pipe_clk reset deassert failed\n");
+ goto exit;
+ }
+ }
+
+ return 0;
+
+deassert_phy_phy_reset:
+ if (phy->phy_phy_reset)
+ clk_reset(phy->phy_phy_reset, CLK_RESET_DEASSERT);
+ else
+ clk_reset(phy->pipe_clk, CLK_RESET_DEASSERT);
+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 = regulator_enable(phy->vdd);
+ if (ret)
+ dev_err(phy->phy.dev,
+ "regulator_enable(phy->vdd) failed, ret=%d",
+ ret);
+
+ 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);
+
+ ret = regulator_disable(phy->vdd);
+ if (ret)
+ dev_err(phy->phy.dev, "regulator_disable(phy->vdd) failed, ret=%d",
+ 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)
+ writel_relaxed(0x00,
+ phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
+ else
+ msm_ssusb_qmp_enable_autonomous(phy, 1);
+
+ 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 {
+ msm_ssusb_qmp_enable_autonomous(phy, 0);
+ }
+ 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);
+
+ 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;
+ const struct of_device_id *phy_ver;
+
+ 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));
+
+ 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);
+ phy->cfg_ahb_clk = NULL;
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get cfg_ahb_clk\n");
+ 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;
+ }
+
+ if (of_property_match_string(pdev->dev.of_node,
+ "clock-names", "phy_reset") >= 0) {
+ phy->phy_reset = clk_get(&pdev->dev, "phy_reset");
+ if (IS_ERR(phy->phy_reset)) {
+ ret = PTR_ERR(phy->phy_reset);
+ phy->phy_reset = NULL;
+ dev_dbg(dev, "failed to get phy_reset\n");
+ goto err;
+ }
+ }
+
+ if (of_property_match_string(pdev->dev.of_node,
+ "clock-names", "phy_phy_reset") >= 0) {
+ phy->phy_phy_reset = clk_get(dev, "phy_phy_reset");
+ if (IS_ERR(phy->phy_phy_reset)) {
+ ret = PTR_ERR(phy->phy_phy_reset);
+ phy->phy_phy_reset = NULL;
+ dev_dbg(dev, "phy_phy_reset unavailable\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");
+ }
+ }
+
+ phy_ver = of_match_device(msm_usb_id_table, &pdev->dev);
+ if (phy_ver) {
+ dev_dbg(dev, "Found QMP PHY version as:%s.\n",
+ phy_ver->compatible);
+ if (phy_ver->data) {
+ phy->phy_reg = (unsigned int *)phy_ver->data;
+ } else if (phy->qmp_phy_reg_offset) {
+ phy->phy_reg = phy->qmp_phy_reg_offset;
+ } else {
+ dev_err(dev,
+ "QMP PHY version match but wrong data val.\n");
+ ret = -EINVAL;
+ }
+ } else {
+ dev_err(dev, "QMP PHY version mismatch.\n");
+ ret = -ENODEV;
+ }
+
+ if (ret)
+ goto err;
+
+ 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_err(dev, "failed getting vls_clamp_reg\n");
+ return -ENODEV;
+ }
+ 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_phy_clk_scheme_sel");
+ if (res) {
+ phy->tcsr_phy_clk_scheme_sel = devm_ioremap_nocache(dev,
+ res->start, resource_size(res));
+ if (IS_ERR(phy->tcsr_phy_clk_scheme_sel))
+ dev_dbg(dev, "err reading tcsr_phy_clk_scheme_sel\n");
+ }
+
+ 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");
+ }
+ }
+
+ phy->emulation = of_property_read_bool(dev->of_node,
+ "qcom,emulation");
+
+ 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;
+ }
+
+ 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->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 = msm_ssusb_qmp_config_vdd(phy, 1);
+ if (ret) {
+ dev_err(dev, "ssusb vdd_dig configuration failed\n");
+ goto err;
+ }
+
+ 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_qmp_ldo_enable(phy, 1);
+ if (ret) {
+ dev_err(dev, "ssusb vreg enable failed\n");
+ goto disable_ss_vdd;
+ }
+
+ 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->override_pll_cal = of_property_read_bool(dev->of_node,
+ "qcom,override-pll-calibration");
+ if (phy->override_pll_cal)
+ dev_dbg(dev, "Override PHY PLL calibration is enabled.\n");
+
+ phy->misc_config = of_property_read_bool(dev->of_node,
+ "qcom,qmp-misc-config");
+ if (phy->misc_config)
+ dev_dbg(dev, "Miscellaneous configurations are enabled.\n");
+
+ 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);
+ if (ret)
+ goto disable_ss_ldo;
+ return 0;
+
+disable_ss_ldo:
+ msm_ssusb_qmp_ldo_enable(phy, 0);
+disable_ss_vdd:
+ regulator_disable(phy->vdd);
+unconfig_ss_vdd:
+ msm_ssusb_qmp_config_vdd(phy, 0);
+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);
+ regulator_disable(phy->vdd);
+ msm_ssusb_qmp_config_vdd(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..8d728d64ef73
--- /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_optimum_mode(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_optimum_mode(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");