diff options
author | Jack Pham <jackp@codeaurora.org> | 2015-04-09 18:43:23 -0700 |
---|---|---|
committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-22 11:06:11 -0700 |
commit | 56eae7e2c24577b811387727c3078ee1cedaaac5 (patch) | |
tree | 5f9832ae34988154a1b21b845170171dde1815c9 | |
parent | cb69b6cb90b53c8ddac492e87a6d54a1f3f4327b (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.txt | 200 | ||||
-rw-r--r-- | drivers/usb/phy/Kconfig | 40 | ||||
-rw-r--r-- | drivers/usb/phy/Makefile | 4 | ||||
-rw-r--r-- | drivers/usb/phy/phy-msm-hsusb.c | 858 | ||||
-rw-r--r-- | drivers/usb/phy/phy-msm-qusb.c | 1177 | ||||
-rw-r--r-- | drivers/usb/phy/phy-msm-ssusb-qmp.c | 1112 | ||||
-rw-r--r-- | drivers/usb/phy/phy-msm-ssusb.c | 595 |
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"); |